mirror of
https://github.com/XiaoMi/ha_xiaomi_home.git
synced 2026-01-18 08:00:42 +08:00
Merge 9c84425106 into 75e44f4f93
This commit is contained in:
commit
f5c0be19e3
@ -156,64 +156,56 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
|
|||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'unknown on property, %s', self.entity_id)
|
'unknown on property, %s', self.entity_id)
|
||||||
elif prop.name == 'mode':
|
elif prop.name == 'mode':
|
||||||
if (
|
if not prop.value_list:
|
||||||
not isinstance(prop.value_list, list)
|
|
||||||
or not prop.value_list
|
|
||||||
):
|
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'invalid mode value_list, %s', self.entity_id)
|
'invalid mode value_list, %s', self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._hvac_mode_map = {}
|
self._hvac_mode_map = {}
|
||||||
for item in prop.value_list:
|
for item in prop.value_list.items:
|
||||||
if item['name'].lower() in {'off', 'idle'}:
|
if item.name in {'off', 'idle'}:
|
||||||
self._hvac_mode_map[item['value']] = HVACMode.OFF
|
self._hvac_mode_map[item.value] = HVACMode.OFF
|
||||||
elif item['name'].lower() in {'auto'}:
|
elif item.name in {'auto'}:
|
||||||
self._hvac_mode_map[item['value']] = HVACMode.AUTO
|
self._hvac_mode_map[item.value] = HVACMode.AUTO
|
||||||
elif item['name'].lower() in {'cool'}:
|
elif item.name in {'cool'}:
|
||||||
self._hvac_mode_map[item['value']] = HVACMode.COOL
|
self._hvac_mode_map[item.value] = HVACMode.COOL
|
||||||
elif item['name'].lower() in {'heat'}:
|
elif item.name in {'heat'}:
|
||||||
self._hvac_mode_map[item['value']] = HVACMode.HEAT
|
self._hvac_mode_map[item.value] = HVACMode.HEAT
|
||||||
elif item['name'].lower() in {'dry'}:
|
elif item.name in {'dry'}:
|
||||||
self._hvac_mode_map[item['value']] = HVACMode.DRY
|
self._hvac_mode_map[item.value] = HVACMode.DRY
|
||||||
elif item['name'].lower() in {'fan'}:
|
elif item.name in {'fan'}:
|
||||||
self._hvac_mode_map[item['value']] = HVACMode.FAN_ONLY
|
self._hvac_mode_map[item.value] = HVACMode.FAN_ONLY
|
||||||
self._attr_hvac_modes = list(self._hvac_mode_map.values())
|
self._attr_hvac_modes = list(self._hvac_mode_map.values())
|
||||||
self._prop_mode = prop
|
self._prop_mode = prop
|
||||||
elif prop.name == 'target-temperature':
|
elif prop.name == 'target-temperature':
|
||||||
if not isinstance(prop.value_range, dict):
|
if not prop.value_range:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'invalid target-temperature value_range format, %s',
|
'invalid target-temperature value_range format, %s',
|
||||||
self.entity_id)
|
self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._attr_min_temp = prop.value_range['min']
|
self._attr_min_temp = prop.value_range.min_
|
||||||
self._attr_max_temp = prop.value_range['max']
|
self._attr_max_temp = prop.value_range.max_
|
||||||
self._attr_target_temperature_step = prop.value_range['step']
|
self._attr_target_temperature_step = prop.value_range.step
|
||||||
self._attr_temperature_unit = prop.external_unit
|
self._attr_temperature_unit = prop.external_unit
|
||||||
self._attr_supported_features |= (
|
self._attr_supported_features |= (
|
||||||
ClimateEntityFeature.TARGET_TEMPERATURE)
|
ClimateEntityFeature.TARGET_TEMPERATURE)
|
||||||
self._prop_target_temp = prop
|
self._prop_target_temp = prop
|
||||||
elif prop.name == 'target-humidity':
|
elif prop.name == 'target-humidity':
|
||||||
if not isinstance(prop.value_range, dict):
|
if not prop.value_range:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'invalid target-humidity value_range format, %s',
|
'invalid target-humidity value_range format, %s',
|
||||||
self.entity_id)
|
self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._attr_min_humidity = prop.value_range['min']
|
self._attr_min_humidity = prop.value_range.min_
|
||||||
self._attr_max_humidity = prop.value_range['max']
|
self._attr_max_humidity = prop.value_range.max_
|
||||||
self._attr_supported_features |= (
|
self._attr_supported_features |= (
|
||||||
ClimateEntityFeature.TARGET_HUMIDITY)
|
ClimateEntityFeature.TARGET_HUMIDITY)
|
||||||
self._prop_target_humi = prop
|
self._prop_target_humi = prop
|
||||||
elif prop.name == 'fan-level':
|
elif prop.name == 'fan-level':
|
||||||
if (
|
if not prop.value_list:
|
||||||
not isinstance(prop.value_list, list)
|
|
||||||
or not prop.value_list
|
|
||||||
):
|
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'invalid fan-level value_list, %s', self.entity_id)
|
'invalid fan-level value_list, %s', self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._fan_mode_map = {
|
self._fan_mode_map = prop.value_list.to_map()
|
||||||
item['value']: item['description']
|
|
||||||
for item in prop.value_list}
|
|
||||||
self._attr_fan_modes = list(self._fan_mode_map.values())
|
self._attr_fan_modes = list(self._fan_mode_map.values())
|
||||||
self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
|
self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
|
||||||
self._prop_fan_level = prop
|
self._prop_fan_level = prop
|
||||||
@ -269,8 +261,8 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
|
|||||||
elif self.get_prop_value(prop=self._prop_on) is False:
|
elif self.get_prop_value(prop=self._prop_on) is False:
|
||||||
await self.set_property_async(prop=self._prop_on, value=True)
|
await self.set_property_async(prop=self._prop_on, value=True)
|
||||||
# set mode
|
# set mode
|
||||||
mode_value = self.get_map_value(
|
mode_value = self.get_map_key(
|
||||||
map_=self._hvac_mode_map, description=hvac_mode)
|
map_=self._hvac_mode_map, value=hvac_mode)
|
||||||
if (
|
if (
|
||||||
mode_value is None or
|
mode_value is None or
|
||||||
not await self.set_property_async(
|
not await self.set_property_async(
|
||||||
@ -339,8 +331,8 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
|
|||||||
|
|
||||||
async def async_set_fan_mode(self, fan_mode):
|
async def async_set_fan_mode(self, fan_mode):
|
||||||
"""Set new target fan mode."""
|
"""Set new target fan mode."""
|
||||||
mode_value = self.get_map_value(
|
mode_value = self.get_map_key(
|
||||||
map_=self._fan_mode_map, description=fan_mode)
|
map_=self._fan_mode_map, value=fan_mode)
|
||||||
if mode_value is None or not await self.set_property_async(
|
if mode_value is None or not await self.set_property_async(
|
||||||
prop=self._prop_fan_level, value=mode_value):
|
prop=self._prop_fan_level, value=mode_value):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
@ -376,9 +368,9 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
|
|||||||
"""Return the hvac mode. e.g., heat, cool mode."""
|
"""Return the hvac mode. e.g., heat, cool mode."""
|
||||||
if self.get_prop_value(prop=self._prop_on) is False:
|
if self.get_prop_value(prop=self._prop_on) is False:
|
||||||
return HVACMode.OFF
|
return HVACMode.OFF
|
||||||
return self.get_map_description(
|
return self.get_map_key(
|
||||||
map_=self._hvac_mode_map,
|
map_=self._hvac_mode_map,
|
||||||
key=self.get_prop_value(prop=self._prop_mode))
|
value=self.get_prop_value(prop=self._prop_mode))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_mode(self) -> Optional[str]:
|
def fan_mode(self) -> Optional[str]:
|
||||||
@ -386,7 +378,7 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
|
|||||||
|
|
||||||
Requires ClimateEntityFeature.FAN_MODE.
|
Requires ClimateEntityFeature.FAN_MODE.
|
||||||
"""
|
"""
|
||||||
return self.get_map_description(
|
return self.get_map_value(
|
||||||
map_=self._fan_mode_map,
|
map_=self._fan_mode_map,
|
||||||
key=self.get_prop_value(prop=self._prop_fan_level))
|
key=self.get_prop_value(prop=self._prop_fan_level))
|
||||||
|
|
||||||
@ -446,8 +438,8 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
|
|||||||
}.get(v_ac_state['M'], None)
|
}.get(v_ac_state['M'], None)
|
||||||
if mode:
|
if mode:
|
||||||
self.set_prop_value(
|
self.set_prop_value(
|
||||||
prop=self._prop_mode, value=self.get_map_value(
|
prop=self._prop_mode, value=self.get_map_key(
|
||||||
map_=self._hvac_mode_map, description=mode))
|
map_=self._hvac_mode_map, value=mode))
|
||||||
# T: target temperature
|
# T: target temperature
|
||||||
if 'T' in v_ac_state and self._prop_target_temp:
|
if 'T' in v_ac_state and self._prop_target_temp:
|
||||||
self.set_prop_value(prop=self._prop_target_temp,
|
self.set_prop_value(prop=self._prop_target_temp,
|
||||||
@ -517,29 +509,24 @@ class Heater(MIoTServiceEntity, ClimateEntity):
|
|||||||
ClimateEntityFeature.TURN_OFF)
|
ClimateEntityFeature.TURN_OFF)
|
||||||
self._prop_on = prop
|
self._prop_on = prop
|
||||||
elif prop.name == 'target-temperature':
|
elif prop.name == 'target-temperature':
|
||||||
if not isinstance(prop.value_range, dict):
|
if not prop.value_range:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'invalid target-temperature value_range format, %s',
|
'invalid target-temperature value_range format, %s',
|
||||||
self.entity_id)
|
self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._attr_min_temp = prop.value_range['min']
|
self._attr_min_temp = prop.value_range.min_
|
||||||
self._attr_max_temp = prop.value_range['max']
|
self._attr_max_temp = prop.value_range.max_
|
||||||
self._attr_target_temperature_step = prop.value_range['step']
|
self._attr_target_temperature_step = prop.value_range.step
|
||||||
self._attr_temperature_unit = prop.external_unit
|
self._attr_temperature_unit = prop.external_unit
|
||||||
self._attr_supported_features |= (
|
self._attr_supported_features |= (
|
||||||
ClimateEntityFeature.TARGET_TEMPERATURE)
|
ClimateEntityFeature.TARGET_TEMPERATURE)
|
||||||
self._prop_target_temp = prop
|
self._prop_target_temp = prop
|
||||||
elif prop.name == 'heat-level':
|
elif prop.name == 'heat-level':
|
||||||
if (
|
if not prop.value_list:
|
||||||
not isinstance(prop.value_list, list)
|
|
||||||
or not prop.value_list
|
|
||||||
):
|
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'invalid heat-level value_list, %s', self.entity_id)
|
'invalid heat-level value_list, %s', self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._heat_level_map = {
|
self._heat_level_map = prop.value_list.to_map()
|
||||||
item['value']: item['description']
|
|
||||||
for item in prop.value_list}
|
|
||||||
self._attr_preset_modes = list(self._heat_level_map.values())
|
self._attr_preset_modes = list(self._heat_level_map.values())
|
||||||
self._attr_supported_features |= (
|
self._attr_supported_features |= (
|
||||||
ClimateEntityFeature.PRESET_MODE)
|
ClimateEntityFeature.PRESET_MODE)
|
||||||
@ -582,8 +569,8 @@ class Heater(MIoTServiceEntity, ClimateEntity):
|
|||||||
"""Set the preset mode."""
|
"""Set the preset mode."""
|
||||||
await self.set_property_async(
|
await self.set_property_async(
|
||||||
self._prop_heat_level,
|
self._prop_heat_level,
|
||||||
value=self.get_map_value(
|
value=self.get_map_key(
|
||||||
map_=self._heat_level_map, description=preset_mode))
|
map_=self._heat_level_map, value=preset_mode))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self) -> Optional[float]:
|
def target_temperature(self) -> Optional[float]:
|
||||||
@ -613,7 +600,7 @@ class Heater(MIoTServiceEntity, ClimateEntity):
|
|||||||
@property
|
@property
|
||||||
def preset_mode(self) -> Optional[str]:
|
def preset_mode(self) -> Optional[str]:
|
||||||
return (
|
return (
|
||||||
self.get_map_description(
|
self.get_map_value(
|
||||||
map_=self._heat_level_map,
|
map_=self._heat_level_map,
|
||||||
key=self.get_prop_value(prop=self._prop_heat_level))
|
key=self.get_prop_value(prop=self._prop_heat_level))
|
||||||
if self._prop_heat_level else None)
|
if self._prop_heat_level else None)
|
||||||
|
|||||||
@ -473,6 +473,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
await self._miot_oauth.deinit_async()
|
await self._miot_oauth.deinit_async()
|
||||||
self._miot_oauth = None
|
self._miot_oauth = None
|
||||||
return self.async_show_progress_done(next_step_id='homes_select')
|
return self.async_show_progress_done(next_step_id='homes_select')
|
||||||
|
# pylint: disable=unexpected-keyword-arg
|
||||||
return self.async_show_progress(
|
return self.async_show_progress(
|
||||||
step_id='oauth',
|
step_id='oauth',
|
||||||
progress_action='oauth',
|
progress_action='oauth',
|
||||||
@ -481,7 +482,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
f'<a href="{self._cc_oauth_auth_url}" target="_blank">',
|
f'<a href="{self._cc_oauth_auth_url}" target="_blank">',
|
||||||
'link_right': '</a>'
|
'link_right': '</a>'
|
||||||
},
|
},
|
||||||
progress_task=self._cc_task_oauth,
|
progress_task=self._cc_task_oauth, # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
async def __check_oauth_async(self) -> None:
|
async def __check_oauth_async(self) -> None:
|
||||||
@ -1196,7 +1197,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
err, traceback.format_exc())
|
err, traceback.format_exc())
|
||||||
self._cc_config_rc = str(err)
|
self._cc_config_rc = str(err)
|
||||||
return self.async_show_progress_done(next_step_id='oauth_error')
|
return self.async_show_progress_done(next_step_id='oauth_error')
|
||||||
|
# pylint: disable=unexpected-keyword-arg
|
||||||
return self.async_show_progress(
|
return self.async_show_progress(
|
||||||
step_id='oauth',
|
step_id='oauth',
|
||||||
progress_action='oauth',
|
progress_action='oauth',
|
||||||
@ -1205,7 +1206,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
f'<a href="{self._cc_oauth_auth_url}" target="_blank">',
|
f'<a href="{self._cc_oauth_auth_url}" target="_blank">',
|
||||||
'link_right': '</a>'
|
'link_right': '</a>'
|
||||||
},
|
},
|
||||||
progress_task=self._cc_task_oauth,
|
progress_task=self._cc_task_oauth, # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
async def __check_oauth_async(self) -> None:
|
async def __check_oauth_async(self) -> None:
|
||||||
|
|||||||
@ -132,53 +132,47 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
|||||||
# properties
|
# properties
|
||||||
for prop in entity_data.props:
|
for prop in entity_data.props:
|
||||||
if prop.name == 'motor-control':
|
if prop.name == 'motor-control':
|
||||||
if (
|
if not prop.value_list:
|
||||||
not isinstance(prop.value_list, list)
|
|
||||||
or not prop.value_list
|
|
||||||
):
|
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'motor-control value_list is None, %s', self.entity_id)
|
'motor-control value_list is None, %s', self.entity_id)
|
||||||
continue
|
continue
|
||||||
for item in prop.value_list:
|
for item in prop.value_list.items:
|
||||||
if item['name'].lower() in ['open']:
|
if item.name in {'open'}:
|
||||||
self._attr_supported_features |= (
|
self._attr_supported_features |= (
|
||||||
CoverEntityFeature.OPEN)
|
CoverEntityFeature.OPEN)
|
||||||
self._prop_motor_value_open = item['value']
|
self._prop_motor_value_open = item.value
|
||||||
elif item['name'].lower() in ['close']:
|
elif item.name in {'close'}:
|
||||||
self._attr_supported_features |= (
|
self._attr_supported_features |= (
|
||||||
CoverEntityFeature.CLOSE)
|
CoverEntityFeature.CLOSE)
|
||||||
self._prop_motor_value_close = item['value']
|
self._prop_motor_value_close = item.value
|
||||||
elif item['name'].lower() in ['pause']:
|
elif item.name in {'pause'}:
|
||||||
self._attr_supported_features |= (
|
self._attr_supported_features |= (
|
||||||
CoverEntityFeature.STOP)
|
CoverEntityFeature.STOP)
|
||||||
self._prop_motor_value_pause = item['value']
|
self._prop_motor_value_pause = item.value
|
||||||
self._prop_motor_control = prop
|
self._prop_motor_control = prop
|
||||||
elif prop.name == 'status':
|
elif prop.name == 'status':
|
||||||
if (
|
if not prop.value_list:
|
||||||
not isinstance(prop.value_list, list)
|
|
||||||
or not prop.value_list
|
|
||||||
):
|
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'status value_list is None, %s', self.entity_id)
|
'status value_list is None, %s', self.entity_id)
|
||||||
continue
|
continue
|
||||||
for item in prop.value_list:
|
for item in prop.value_list.items:
|
||||||
if item['name'].lower() in ['opening', 'open']:
|
if item.name in {'opening', 'open'}:
|
||||||
self._prop_status_opening = item['value']
|
self._prop_status_opening = item.value
|
||||||
elif item['name'].lower() in ['closing', 'close']:
|
elif item.name in {'closing', 'close'}:
|
||||||
self._prop_status_closing = item['value']
|
self._prop_status_closing = item.value
|
||||||
elif item['name'].lower() in ['stop', 'pause']:
|
elif item.name in {'stop', 'pause'}:
|
||||||
self._prop_status_stop = item['value']
|
self._prop_status_stop = item.value
|
||||||
self._prop_status = prop
|
self._prop_status = prop
|
||||||
elif prop.name == 'current-position':
|
elif prop.name == 'current-position':
|
||||||
self._prop_current_position = prop
|
self._prop_current_position = prop
|
||||||
elif prop.name == 'target-position':
|
elif prop.name == 'target-position':
|
||||||
if not isinstance(prop.value_range, dict):
|
if not prop.value_range:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'invalid target-position value_range format, %s',
|
'invalid target-position value_range format, %s',
|
||||||
self.entity_id)
|
self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._prop_position_value_min = prop.value_range['min']
|
self._prop_position_value_min = prop.value_range.min_
|
||||||
self._prop_position_value_max = prop.value_range['max']
|
self._prop_position_value_max = prop.value_range.max_
|
||||||
self._prop_position_value_range = (
|
self._prop_position_value_range = (
|
||||||
self._prop_position_value_max -
|
self._prop_position_value_max -
|
||||||
self._prop_position_value_min)
|
self._prop_position_value_min)
|
||||||
|
|||||||
@ -87,7 +87,7 @@ async def async_setup_entry(
|
|||||||
class Fan(MIoTServiceEntity, FanEntity):
|
class Fan(MIoTServiceEntity, FanEntity):
|
||||||
"""Fan entities for Xiaomi Home."""
|
"""Fan entities for Xiaomi Home."""
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
_prop_on: Optional[MIoTSpecProperty]
|
_prop_on: MIoTSpecProperty
|
||||||
_prop_fan_level: Optional[MIoTSpecProperty]
|
_prop_fan_level: Optional[MIoTSpecProperty]
|
||||||
_prop_mode: Optional[MIoTSpecProperty]
|
_prop_mode: Optional[MIoTSpecProperty]
|
||||||
_prop_horizontal_swing: Optional[MIoTSpecProperty]
|
_prop_horizontal_swing: Optional[MIoTSpecProperty]
|
||||||
@ -100,7 +100,7 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
_speed_step: int
|
_speed_step: int
|
||||||
_speed_names: Optional[list]
|
_speed_names: Optional[list]
|
||||||
_speed_name_map: Optional[dict[int, str]]
|
_speed_name_map: Optional[dict[int, str]]
|
||||||
_mode_list: Optional[dict[Any, Any]]
|
_mode_map: Optional[dict[Any, Any]]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
||||||
@ -111,7 +111,7 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
self._attr_current_direction = None
|
self._attr_current_direction = None
|
||||||
self._attr_supported_features = FanEntityFeature(0)
|
self._attr_supported_features = FanEntityFeature(0)
|
||||||
|
|
||||||
self._prop_on = None
|
# _prop_on is required
|
||||||
self._prop_fan_level = None
|
self._prop_fan_level = None
|
||||||
self._prop_mode = None
|
self._prop_mode = None
|
||||||
self._prop_horizontal_swing = None
|
self._prop_horizontal_swing = None
|
||||||
@ -124,7 +124,7 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
self._speed_names = []
|
self._speed_names = []
|
||||||
self._speed_name_map = {}
|
self._speed_name_map = {}
|
||||||
|
|
||||||
self._mode_list = None
|
self._mode_map = None
|
||||||
|
|
||||||
# properties
|
# properties
|
||||||
for prop in entity_data.props:
|
for prop in entity_data.props:
|
||||||
@ -133,42 +133,34 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
self._attr_supported_features |= FanEntityFeature.TURN_OFF
|
self._attr_supported_features |= FanEntityFeature.TURN_OFF
|
||||||
self._prop_on = prop
|
self._prop_on = prop
|
||||||
elif prop.name == 'fan-level':
|
elif prop.name == 'fan-level':
|
||||||
if isinstance(prop.value_range, dict):
|
if prop.value_range:
|
||||||
# Fan level with value-range
|
# Fan level with value-range
|
||||||
self._speed_min = prop.value_range['min']
|
self._speed_min = prop.value_range.min_
|
||||||
self._speed_max = prop.value_range['max']
|
self._speed_max = prop.value_range.max_
|
||||||
self._speed_step = prop.value_range['step']
|
self._speed_step = prop.value_range.step
|
||||||
self._attr_speed_count = int((
|
self._attr_speed_count = int((
|
||||||
self._speed_max - self._speed_min)/self._speed_step)+1
|
self._speed_max - self._speed_min)/self._speed_step)+1
|
||||||
self._attr_supported_features |= FanEntityFeature.SET_SPEED
|
self._attr_supported_features |= FanEntityFeature.SET_SPEED
|
||||||
self._prop_fan_level = prop
|
self._prop_fan_level = prop
|
||||||
elif (
|
elif (
|
||||||
self._prop_fan_level is None
|
self._prop_fan_level is None
|
||||||
and isinstance(prop.value_list, list)
|
|
||||||
and prop.value_list
|
and prop.value_list
|
||||||
):
|
):
|
||||||
# Fan level with value-list
|
# Fan level with value-list
|
||||||
# Fan level with value-range is prior to fan level with
|
# Fan level with value-range is prior to fan level with
|
||||||
# value-list when a fan has both fan level properties.
|
# value-list when a fan has both fan level properties.
|
||||||
self._speed_name_map = {
|
self._speed_name_map = prop.value_list.to_map()
|
||||||
item['value']: item['description']
|
|
||||||
for item in prop.value_list}
|
|
||||||
self._speed_names = list(self._speed_name_map.values())
|
self._speed_names = list(self._speed_name_map.values())
|
||||||
self._attr_speed_count = len(prop.value_list)
|
self._attr_speed_count = len(self._speed_names)
|
||||||
self._attr_supported_features |= FanEntityFeature.SET_SPEED
|
self._attr_supported_features |= FanEntityFeature.SET_SPEED
|
||||||
self._prop_fan_level = prop
|
self._prop_fan_level = prop
|
||||||
elif prop.name == 'mode':
|
elif prop.name == 'mode':
|
||||||
if (
|
if not prop.value_list:
|
||||||
not isinstance(prop.value_list, list)
|
|
||||||
or not prop.value_list
|
|
||||||
):
|
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'mode value_list is None, %s', self.entity_id)
|
'mode value_list is None, %s', self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._mode_list = {
|
self._mode_map = prop.value_list.to_map()
|
||||||
item['value']: item['description']
|
self._attr_preset_modes = list(self._mode_map.values())
|
||||||
for item in prop.value_list}
|
|
||||||
self._attr_preset_modes = list(self._mode_list.values())
|
|
||||||
self._attr_supported_features |= FanEntityFeature.PRESET_MODE
|
self._attr_supported_features |= FanEntityFeature.PRESET_MODE
|
||||||
self._prop_mode = prop
|
self._prop_mode = prop
|
||||||
elif prop.name == 'horizontal-swing':
|
elif prop.name == 'horizontal-swing':
|
||||||
@ -178,16 +170,11 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
if prop.format_ == 'bool':
|
if prop.format_ == 'bool':
|
||||||
self._prop_wind_reverse_forward = False
|
self._prop_wind_reverse_forward = False
|
||||||
self._prop_wind_reverse_reverse = True
|
self._prop_wind_reverse_reverse = True
|
||||||
elif (
|
elif prop.value_list:
|
||||||
isinstance(prop.value_list, list)
|
for item in prop.value_list.items:
|
||||||
and prop.value_list
|
if item.name in {'foreward'}:
|
||||||
):
|
self._prop_wind_reverse_forward = item.value
|
||||||
for item in prop.value_list:
|
self._prop_wind_reverse_reverse = item.value
|
||||||
if item['name'].lower() in {'foreward'}:
|
|
||||||
self._prop_wind_reverse_forward = item['value']
|
|
||||||
elif item['name'].lower() in {
|
|
||||||
'reversal', 'reverse'}:
|
|
||||||
self._prop_wind_reverse_reverse = item['value']
|
|
||||||
if (
|
if (
|
||||||
self._prop_wind_reverse_forward is None
|
self._prop_wind_reverse_forward is None
|
||||||
or self._prop_wind_reverse_reverse is None
|
or self._prop_wind_reverse_reverse is None
|
||||||
@ -199,21 +186,9 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
self._attr_supported_features |= FanEntityFeature.DIRECTION
|
self._attr_supported_features |= FanEntityFeature.DIRECTION
|
||||||
self._prop_wind_reverse = prop
|
self._prop_wind_reverse = prop
|
||||||
|
|
||||||
def __get_mode_description(self, key: int) -> Optional[str]:
|
|
||||||
if self._mode_list is None:
|
|
||||||
return None
|
|
||||||
return self._mode_list.get(key, None)
|
|
||||||
|
|
||||||
def __get_mode_value(self, description: str) -> Optional[int]:
|
|
||||||
if self._mode_list is None:
|
|
||||||
return None
|
|
||||||
for key, value in self._mode_list.items():
|
|
||||||
if value == description:
|
|
||||||
return key
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def async_turn_on(
|
async def async_turn_on(
|
||||||
self, percentage: int = None, preset_mode: str = None, **kwargs: Any
|
self, percentage: Optional[int] = None,
|
||||||
|
preset_mode: Optional[str] = None, **kwargs: Any
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Turn the fan on.
|
"""Turn the fan on.
|
||||||
|
|
||||||
@ -225,12 +200,12 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
# percentage
|
# percentage
|
||||||
if percentage:
|
if percentage:
|
||||||
if self._speed_names:
|
if self._speed_names:
|
||||||
speed = percentage_to_ordered_list_item(
|
|
||||||
self._speed_names, percentage)
|
|
||||||
speed_value = self.get_map_value(
|
|
||||||
map_=self._speed_name_map, description=speed)
|
|
||||||
await self.set_property_async(
|
await self.set_property_async(
|
||||||
prop=self._prop_fan_level, value=speed_value)
|
prop=self._prop_fan_level,
|
||||||
|
value=self.get_map_value(
|
||||||
|
map_=self._speed_name_map,
|
||||||
|
key=percentage_to_ordered_list_item(
|
||||||
|
self._speed_names, percentage)))
|
||||||
else:
|
else:
|
||||||
await self.set_property_async(
|
await self.set_property_async(
|
||||||
prop=self._prop_fan_level,
|
prop=self._prop_fan_level,
|
||||||
@ -241,7 +216,8 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
if preset_mode:
|
if preset_mode:
|
||||||
await self.set_property_async(
|
await self.set_property_async(
|
||||||
self._prop_mode,
|
self._prop_mode,
|
||||||
value=self.__get_mode_value(description=preset_mode))
|
value=self.get_map_key(
|
||||||
|
map_=self._mode_map, value=preset_mode))
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the fan off."""
|
"""Turn the fan off."""
|
||||||
@ -255,12 +231,12 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
"""Set the percentage of the fan speed."""
|
"""Set the percentage of the fan speed."""
|
||||||
if percentage > 0:
|
if percentage > 0:
|
||||||
if self._speed_names:
|
if self._speed_names:
|
||||||
speed = percentage_to_ordered_list_item(
|
|
||||||
self._speed_names, percentage)
|
|
||||||
speed_value = self.get_map_value(
|
|
||||||
map_=self._speed_name_map, description=speed)
|
|
||||||
await self.set_property_async(
|
await self.set_property_async(
|
||||||
prop=self._prop_fan_level, value=speed_value)
|
prop=self._prop_fan_level,
|
||||||
|
value=self.get_map_value(
|
||||||
|
map_=self._speed_name_map,
|
||||||
|
key=percentage_to_ordered_list_item(
|
||||||
|
self._speed_names, percentage)))
|
||||||
else:
|
else:
|
||||||
await self.set_property_async(
|
await self.set_property_async(
|
||||||
prop=self._prop_fan_level,
|
prop=self._prop_fan_level,
|
||||||
@ -277,7 +253,8 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
"""Set the preset mode."""
|
"""Set the preset mode."""
|
||||||
await self.set_property_async(
|
await self.set_property_async(
|
||||||
self._prop_mode,
|
self._prop_mode,
|
||||||
value=self.__get_mode_value(description=preset_mode))
|
value=self.get_map_key(
|
||||||
|
map_=self._mode_map, value=preset_mode))
|
||||||
|
|
||||||
async def async_set_direction(self, direction: str) -> None:
|
async def async_set_direction(self, direction: str) -> None:
|
||||||
"""Set the direction of the fan."""
|
"""Set the direction of the fan."""
|
||||||
@ -306,7 +283,8 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
"""Return the current preset mode,
|
"""Return the current preset mode,
|
||||||
e.g., auto, smart, eco, favorite."""
|
e.g., auto, smart, eco, favorite."""
|
||||||
return (
|
return (
|
||||||
self.__get_mode_description(
|
self.get_map_value(
|
||||||
|
map_=self._mode_map,
|
||||||
key=self.get_prop_value(prop=self._prop_mode))
|
key=self.get_prop_value(prop=self._prop_mode))
|
||||||
if self._prop_mode else None)
|
if self._prop_mode else None)
|
||||||
|
|
||||||
|
|||||||
@ -97,7 +97,7 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
|||||||
_prop_target_humidity: Optional[MIoTSpecProperty]
|
_prop_target_humidity: Optional[MIoTSpecProperty]
|
||||||
_prop_humidity: Optional[MIoTSpecProperty]
|
_prop_humidity: Optional[MIoTSpecProperty]
|
||||||
|
|
||||||
_mode_list: dict[Any, Any]
|
_mode_map: dict[Any, Any]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
||||||
@ -110,7 +110,7 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
|||||||
self._prop_mode = None
|
self._prop_mode = None
|
||||||
self._prop_target_humidity = None
|
self._prop_target_humidity = None
|
||||||
self._prop_humidity = None
|
self._prop_humidity = None
|
||||||
self._mode_list = None
|
self._mode_map = None
|
||||||
|
|
||||||
# properties
|
# properties
|
||||||
for prop in entity_data.props:
|
for prop in entity_data.props:
|
||||||
@ -119,28 +119,23 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
|||||||
self._prop_on = prop
|
self._prop_on = prop
|
||||||
# target-humidity
|
# target-humidity
|
||||||
elif prop.name == 'target-humidity':
|
elif prop.name == 'target-humidity':
|
||||||
if not isinstance(prop.value_range, dict):
|
if not prop.value_range:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'invalid target-humidity value_range format, %s',
|
'invalid target-humidity value_range format, %s',
|
||||||
self.entity_id)
|
self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._attr_min_humidity = prop.value_range['min']
|
self._attr_min_humidity = prop.value_range.min_
|
||||||
self._attr_max_humidity = prop.value_range['max']
|
self._attr_max_humidity = prop.value_range.max_
|
||||||
self._prop_target_humidity = prop
|
self._prop_target_humidity = prop
|
||||||
# mode
|
# mode
|
||||||
elif prop.name == 'mode':
|
elif prop.name == 'mode':
|
||||||
if (
|
if not prop.value_list:
|
||||||
not isinstance(prop.value_list, list)
|
|
||||||
or not prop.value_list
|
|
||||||
):
|
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'mode value_list is None, %s', self.entity_id)
|
'mode value_list is None, %s', self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._mode_list = {
|
self._mode_map = prop.value_list.to_map()
|
||||||
item['value']: item['description']
|
|
||||||
for item in prop.value_list}
|
|
||||||
self._attr_available_modes = list(
|
self._attr_available_modes = list(
|
||||||
self._mode_list.values())
|
self._mode_map.values())
|
||||||
self._attr_supported_features |= HumidifierEntityFeature.MODES
|
self._attr_supported_features |= HumidifierEntityFeature.MODES
|
||||||
self._prop_mode = prop
|
self._prop_mode = prop
|
||||||
# relative-humidity
|
# relative-humidity
|
||||||
@ -163,7 +158,8 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
|||||||
async def async_set_mode(self, mode: str) -> None:
|
async def async_set_mode(self, mode: str) -> None:
|
||||||
"""Set new target preset mode."""
|
"""Set new target preset mode."""
|
||||||
await self.set_property_async(
|
await self.set_property_async(
|
||||||
prop=self._prop_mode, value=self.__get_mode_value(description=mode))
|
prop=self._prop_mode,
|
||||||
|
value=self.get_map_key(map_=self._mode_map, value=mode))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> Optional[bool]:
|
def is_on(self) -> Optional[bool]:
|
||||||
@ -183,20 +179,6 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
|||||||
@property
|
@property
|
||||||
def mode(self) -> Optional[str]:
|
def mode(self) -> Optional[str]:
|
||||||
"""Return the current preset mode."""
|
"""Return the current preset mode."""
|
||||||
return self.__get_mode_description(
|
return self.get_map_value(
|
||||||
|
map_=self._mode_map,
|
||||||
key=self.get_prop_value(prop=self._prop_mode))
|
key=self.get_prop_value(prop=self._prop_mode))
|
||||||
|
|
||||||
def __get_mode_description(self, key: int) -> Optional[str]:
|
|
||||||
"""Convert mode value to description."""
|
|
||||||
if self._mode_list is None:
|
|
||||||
return None
|
|
||||||
return self._mode_list.get(key, None)
|
|
||||||
|
|
||||||
def __get_mode_value(self, description: str) -> Optional[int]:
|
|
||||||
"""Convert mode description to value."""
|
|
||||||
if self._mode_list is None:
|
|
||||||
return None
|
|
||||||
for key, value in self._mode_list.items():
|
|
||||||
if value == description:
|
|
||||||
return key
|
|
||||||
return None
|
|
||||||
|
|||||||
@ -96,14 +96,14 @@ class Light(MIoTServiceEntity, LightEntity):
|
|||||||
"""Light entities for Xiaomi Home."""
|
"""Light entities for Xiaomi Home."""
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
_VALUE_RANGE_MODE_COUNT_MAX = 30
|
_VALUE_RANGE_MODE_COUNT_MAX = 30
|
||||||
_prop_on: Optional[MIoTSpecProperty]
|
_prop_on: MIoTSpecProperty
|
||||||
_prop_brightness: Optional[MIoTSpecProperty]
|
_prop_brightness: Optional[MIoTSpecProperty]
|
||||||
_prop_color_temp: Optional[MIoTSpecProperty]
|
_prop_color_temp: Optional[MIoTSpecProperty]
|
||||||
_prop_color: Optional[MIoTSpecProperty]
|
_prop_color: Optional[MIoTSpecProperty]
|
||||||
_prop_mode: Optional[MIoTSpecProperty]
|
_prop_mode: Optional[MIoTSpecProperty]
|
||||||
|
|
||||||
_brightness_scale: Optional[tuple[int, int]]
|
_brightness_scale: Optional[tuple[int, int]]
|
||||||
_mode_list: Optional[dict[Any, Any]]
|
_mode_map: Optional[dict[Any, Any]]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
||||||
@ -122,7 +122,7 @@ class Light(MIoTServiceEntity, LightEntity):
|
|||||||
self._prop_color = None
|
self._prop_color = None
|
||||||
self._prop_mode = None
|
self._prop_mode = None
|
||||||
self._brightness_scale = None
|
self._brightness_scale = None
|
||||||
self._mode_list = None
|
self._mode_map = None
|
||||||
|
|
||||||
# properties
|
# properties
|
||||||
for prop in entity_data.props:
|
for prop in entity_data.props:
|
||||||
@ -131,20 +131,17 @@ class Light(MIoTServiceEntity, LightEntity):
|
|||||||
self._prop_on = prop
|
self._prop_on = prop
|
||||||
# brightness
|
# brightness
|
||||||
if prop.name == 'brightness':
|
if prop.name == 'brightness':
|
||||||
if isinstance(prop.value_range, dict):
|
if prop.value_range:
|
||||||
self._brightness_scale = (
|
self._brightness_scale = (
|
||||||
prop.value_range['min'], prop.value_range['max'])
|
prop.value_range.min_, prop.value_range.max_)
|
||||||
self._prop_brightness = prop
|
self._prop_brightness = prop
|
||||||
elif (
|
elif (
|
||||||
self._mode_list is None
|
self._mode_map is None
|
||||||
and isinstance(prop.value_list, list)
|
|
||||||
and prop.value_list
|
and prop.value_list
|
||||||
):
|
):
|
||||||
# For value-list brightness
|
# For value-list brightness
|
||||||
self._mode_list = {
|
self._mode_map = prop.value_list.to_map()
|
||||||
item['value']: item['description']
|
self._attr_effect_list = list(self._mode_map.values())
|
||||||
for item in prop.value_list}
|
|
||||||
self._attr_effect_list = list(self._mode_list.values())
|
|
||||||
self._attr_supported_features |= LightEntityFeature.EFFECT
|
self._attr_supported_features |= LightEntityFeature.EFFECT
|
||||||
self._prop_mode = prop
|
self._prop_mode = prop
|
||||||
else:
|
else:
|
||||||
@ -153,13 +150,13 @@ class Light(MIoTServiceEntity, LightEntity):
|
|||||||
continue
|
continue
|
||||||
# color-temperature
|
# color-temperature
|
||||||
if prop.name == 'color-temperature':
|
if prop.name == 'color-temperature':
|
||||||
if not isinstance(prop.value_range, dict):
|
if not prop.value_range:
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
'invalid color-temperature value_range format, %s',
|
'invalid color-temperature value_range format, %s',
|
||||||
self.entity_id)
|
self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._attr_min_color_temp_kelvin = prop.value_range['min']
|
self._attr_min_color_temp_kelvin = prop.value_range.min_
|
||||||
self._attr_max_color_temp_kelvin = prop.value_range['max']
|
self._attr_max_color_temp_kelvin = prop.value_range.max_
|
||||||
self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP)
|
self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP)
|
||||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||||
self._prop_color_temp = prop
|
self._prop_color_temp = prop
|
||||||
@ -171,20 +168,15 @@ class Light(MIoTServiceEntity, LightEntity):
|
|||||||
# mode
|
# mode
|
||||||
if prop.name == 'mode':
|
if prop.name == 'mode':
|
||||||
mode_list = None
|
mode_list = None
|
||||||
if (
|
if prop.value_list:
|
||||||
isinstance(prop.value_list, list)
|
mode_list = prop.value_list.to_map()
|
||||||
and prop.value_list
|
elif prop.value_range:
|
||||||
):
|
|
||||||
mode_list = {
|
|
||||||
item['value']: item['description']
|
|
||||||
for item in prop.value_list}
|
|
||||||
elif isinstance(prop.value_range, dict):
|
|
||||||
mode_list = {}
|
mode_list = {}
|
||||||
if (
|
if (
|
||||||
int((
|
int((
|
||||||
prop.value_range['max']
|
prop.value_range.max_
|
||||||
- prop.value_range['min']
|
- prop.value_range.min_
|
||||||
) / prop.value_range['step'])
|
) / prop.value_range.step)
|
||||||
> self._VALUE_RANGE_MODE_COUNT_MAX
|
> self._VALUE_RANGE_MODE_COUNT_MAX
|
||||||
):
|
):
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
@ -192,13 +184,13 @@ class Light(MIoTServiceEntity, LightEntity):
|
|||||||
self.entity_id, prop.name, prop.value_range)
|
self.entity_id, prop.name, prop.value_range)
|
||||||
else:
|
else:
|
||||||
for value in range(
|
for value in range(
|
||||||
prop.value_range['min'],
|
prop.value_range.min_,
|
||||||
prop.value_range['max'],
|
prop.value_range.max_,
|
||||||
prop.value_range['step']):
|
prop.value_range.step):
|
||||||
mode_list[value] = f'mode {value}'
|
mode_list[value] = f'mode {value}'
|
||||||
if mode_list:
|
if mode_list:
|
||||||
self._mode_list = mode_list
|
self._mode_map = mode_list
|
||||||
self._attr_effect_list = list(self._mode_list.values())
|
self._attr_effect_list = list(self._mode_map.values())
|
||||||
self._attr_supported_features |= LightEntityFeature.EFFECT
|
self._attr_supported_features |= LightEntityFeature.EFFECT
|
||||||
self._prop_mode = prop
|
self._prop_mode = prop
|
||||||
else:
|
else:
|
||||||
@ -213,21 +205,6 @@ class Light(MIoTServiceEntity, LightEntity):
|
|||||||
self._attr_supported_color_modes.add(ColorMode.ONOFF)
|
self._attr_supported_color_modes.add(ColorMode.ONOFF)
|
||||||
self._attr_color_mode = ColorMode.ONOFF
|
self._attr_color_mode = ColorMode.ONOFF
|
||||||
|
|
||||||
def __get_mode_description(self, key: int) -> Optional[str]:
|
|
||||||
"""Convert mode value to description."""
|
|
||||||
if self._mode_list is None:
|
|
||||||
return None
|
|
||||||
return self._mode_list.get(key, None)
|
|
||||||
|
|
||||||
def __get_mode_value(self, description: str) -> Optional[int]:
|
|
||||||
"""Convert mode description to value."""
|
|
||||||
if self._mode_list is None:
|
|
||||||
return None
|
|
||||||
for key, value in self._mode_list.items():
|
|
||||||
if value == description:
|
|
||||||
return key
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> Optional[bool]:
|
def is_on(self) -> Optional[bool]:
|
||||||
"""Return if the light is on."""
|
"""Return if the light is on."""
|
||||||
@ -264,7 +241,8 @@ class Light(MIoTServiceEntity, LightEntity):
|
|||||||
@property
|
@property
|
||||||
def effect(self) -> Optional[str]:
|
def effect(self) -> Optional[str]:
|
||||||
"""Return the current mode."""
|
"""Return the current mode."""
|
||||||
return self.__get_mode_description(
|
return self.get_map_value(
|
||||||
|
map_=self._mode_map,
|
||||||
key=self.get_prop_value(prop=self._prop_mode))
|
key=self.get_prop_value(prop=self._prop_mode))
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs) -> None:
|
async def async_turn_on(self, **kwargs) -> None:
|
||||||
@ -275,7 +253,7 @@ class Light(MIoTServiceEntity, LightEntity):
|
|||||||
result: bool = False
|
result: bool = False
|
||||||
# on
|
# on
|
||||||
# Dirty logic for lumi.gateway.mgl03 indicator light
|
# Dirty logic for lumi.gateway.mgl03 indicator light
|
||||||
value_on = True if self._prop_on.format_ == 'bool' else 1
|
value_on = True if self._prop_on.format_ == bool else 1
|
||||||
result = await self.set_property_async(
|
result = await self.set_property_async(
|
||||||
prop=self._prop_on, value=value_on)
|
prop=self._prop_on, value=value_on)
|
||||||
# brightness
|
# brightness
|
||||||
@ -303,11 +281,12 @@ class Light(MIoTServiceEntity, LightEntity):
|
|||||||
if ATTR_EFFECT in kwargs:
|
if ATTR_EFFECT in kwargs:
|
||||||
result = await self.set_property_async(
|
result = await self.set_property_async(
|
||||||
prop=self._prop_mode,
|
prop=self._prop_mode,
|
||||||
value=self.__get_mode_value(description=kwargs[ATTR_EFFECT]))
|
value=self.get_map_key(
|
||||||
|
map_=self._mode_map, value=kwargs[ATTR_EFFECT]))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs) -> None:
|
async def async_turn_off(self, **kwargs) -> None:
|
||||||
"""Turn the light off."""
|
"""Turn the light off."""
|
||||||
# Dirty logic for lumi.gateway.mgl03 indicator light
|
# Dirty logic for lumi.gateway.mgl03 indicator light
|
||||||
value_on = False if self._prop_on.format_ == 'bool' else 0
|
value_on = False if self._prop_on.format_ == bool else 0
|
||||||
return await self.set_property_async(prop=self._prop_on, value=value_on)
|
return await self.set_property_async(prop=self._prop_on, value=value_on)
|
||||||
|
|||||||
@ -45,11 +45,14 @@ off Xiaomi or its affiliates' products.
|
|||||||
|
|
||||||
Common utilities.
|
Common utilities.
|
||||||
"""
|
"""
|
||||||
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from os import path
|
from os import path
|
||||||
import random
|
import random
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
import hashlib
|
import hashlib
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
from urllib.request import Request, urlopen
|
||||||
from paho.mqtt.matcher import MQTTMatcher
|
from paho.mqtt.matcher import MQTTMatcher
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@ -83,10 +86,12 @@ def randomize_int(value: int, ratio: float) -> int:
|
|||||||
"""Randomize an integer value."""
|
"""Randomize an integer value."""
|
||||||
return int(value * (1 - ratio + random.random()*2*ratio))
|
return int(value * (1 - ratio + random.random()*2*ratio))
|
||||||
|
|
||||||
|
|
||||||
def randomize_float(value: float, ratio: float) -> float:
|
def randomize_float(value: float, ratio: float) -> float:
|
||||||
"""Randomize a float value."""
|
"""Randomize a float value."""
|
||||||
return value * (1 - ratio + random.random()*2*ratio)
|
return value * (1 - ratio + random.random()*2*ratio)
|
||||||
|
|
||||||
|
|
||||||
class MIoTMatcher(MQTTMatcher):
|
class MIoTMatcher(MQTTMatcher):
|
||||||
"""MIoT Pub/Sub topic matcher."""
|
"""MIoT Pub/Sub topic matcher."""
|
||||||
|
|
||||||
@ -105,3 +110,68 @@ class MIoTMatcher(MQTTMatcher):
|
|||||||
return self[topic]
|
return self[topic]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class MIoTHttp:
|
||||||
|
"""MIoT Common HTTP API."""
|
||||||
|
@staticmethod
|
||||||
|
def get(
|
||||||
|
url: str, params: Optional[dict] = None, headers: Optional[dict] = None
|
||||||
|
) -> Optional[str]:
|
||||||
|
full_url = url
|
||||||
|
if params:
|
||||||
|
encoded_params = urlencode(params)
|
||||||
|
full_url = f'{url}?{encoded_params}'
|
||||||
|
request = Request(full_url, method='GET', headers=headers or {})
|
||||||
|
content: Optional[bytes] = None
|
||||||
|
with urlopen(request) as response:
|
||||||
|
content = response.read()
|
||||||
|
return str(content, 'utf-8') if content else None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_json(
|
||||||
|
url: str, params: Optional[dict] = None, headers: Optional[dict] = None
|
||||||
|
) -> Optional[dict]:
|
||||||
|
response = MIoTHttp.get(url, params, headers)
|
||||||
|
return json.loads(response) if response else None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def post(
|
||||||
|
url: str, data: Optional[dict] = None, headers: Optional[dict] = None
|
||||||
|
) -> Optional[str]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def post_json(
|
||||||
|
url: str, data: Optional[dict] = None, headers: Optional[dict] = None
|
||||||
|
) -> Optional[dict]:
|
||||||
|
response = MIoTHttp.post(url, data, headers)
|
||||||
|
return json.loads(response) if response else None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get_async(
|
||||||
|
url: str, params: Optional[dict] = None, headers: Optional[dict] = None,
|
||||||
|
loop: Optional[asyncio.AbstractEventLoop] = None
|
||||||
|
) -> Optional[str]:
|
||||||
|
# TODO: Use aiohttp
|
||||||
|
ev_loop = loop or asyncio.get_running_loop()
|
||||||
|
return await ev_loop.run_in_executor(
|
||||||
|
None, MIoTHttp.get, url, params, headers)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get_json_async(
|
||||||
|
url: str, params: Optional[dict] = None, headers: Optional[dict] = None,
|
||||||
|
loop: Optional[asyncio.AbstractEventLoop] = None
|
||||||
|
) -> Optional[dict]:
|
||||||
|
ev_loop = loop or asyncio.get_running_loop()
|
||||||
|
return await ev_loop.run_in_executor(
|
||||||
|
None, MIoTHttp.get_json, url, params, headers)
|
||||||
|
|
||||||
|
@ staticmethod
|
||||||
|
async def post_async(
|
||||||
|
url: str, data: Optional[dict] = None, headers: Optional[dict] = None,
|
||||||
|
loop: Optional[asyncio.AbstractEventLoop] = None
|
||||||
|
) -> Optional[str]:
|
||||||
|
ev_loop = loop or asyncio.get_running_loop()
|
||||||
|
return await ev_loop.run_in_executor(
|
||||||
|
None, MIoTHttp.post, url, data, headers)
|
||||||
|
|||||||
@ -744,7 +744,7 @@ class MIoTHttpClient:
|
|||||||
prop_obj['fut'].set_result(None)
|
prop_obj['fut'].set_result(None)
|
||||||
if props_req:
|
if props_req:
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
'get prop from cloud failed, %s, %s', len(key), props_req)
|
'get prop from cloud failed, %s', props_req)
|
||||||
|
|
||||||
if self._get_prop_list:
|
if self._get_prop_list:
|
||||||
self._get_prop_timer = self._main_loop.call_later(
|
self._get_prop_timer = self._main_loop.call_later(
|
||||||
|
|||||||
@ -94,7 +94,9 @@ from .miot_spec import (
|
|||||||
MIoTSpecEvent,
|
MIoTSpecEvent,
|
||||||
MIoTSpecInstance,
|
MIoTSpecInstance,
|
||||||
MIoTSpecProperty,
|
MIoTSpecProperty,
|
||||||
MIoTSpecService
|
MIoTSpecService,
|
||||||
|
MIoTSpecValueList,
|
||||||
|
MIoTSpecValueRange
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -142,7 +144,7 @@ class MIoTDevice:
|
|||||||
_room_id: str
|
_room_id: str
|
||||||
_room_name: str
|
_room_name: str
|
||||||
|
|
||||||
_suggested_area: str
|
_suggested_area: Optional[str]
|
||||||
|
|
||||||
_device_state_sub_list: dict[str, Callable[[str, MIoTDeviceState], None]]
|
_device_state_sub_list: dict[str, Callable[[str, MIoTDeviceState], None]]
|
||||||
|
|
||||||
@ -153,7 +155,7 @@ class MIoTDevice:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, miot_client: MIoTClient,
|
self, miot_client: MIoTClient,
|
||||||
device_info: dict[str, str],
|
device_info: dict[str, Any],
|
||||||
spec_instance: MIoTSpecInstance
|
spec_instance: MIoTSpecInstance
|
||||||
) -> None:
|
) -> None:
|
||||||
self.miot_client = miot_client
|
self.miot_client = miot_client
|
||||||
@ -243,25 +245,29 @@ class MIoTDevice:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def sub_property(
|
def sub_property(
|
||||||
self, handler: Callable[[dict, Any], None], siid: int = None,
|
self, handler: Callable[[dict, Any], None], siid: Optional[int] = None,
|
||||||
piid: int = None, handler_ctx: Any = None
|
piid: Optional[int] = None, handler_ctx: Any = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
return self.miot_client.sub_prop(
|
return self.miot_client.sub_prop(
|
||||||
did=self._did, handler=handler, siid=siid, piid=piid,
|
did=self._did, handler=handler, siid=siid, piid=piid,
|
||||||
handler_ctx=handler_ctx)
|
handler_ctx=handler_ctx)
|
||||||
|
|
||||||
def unsub_property(self, siid: int = None, piid: int = None) -> bool:
|
def unsub_property(
|
||||||
|
self, siid: Optional[int] = None, piid: Optional[int] = None
|
||||||
|
) -> bool:
|
||||||
return self.miot_client.unsub_prop(did=self._did, siid=siid, piid=piid)
|
return self.miot_client.unsub_prop(did=self._did, siid=siid, piid=piid)
|
||||||
|
|
||||||
def sub_event(
|
def sub_event(
|
||||||
self, handler: Callable[[dict, Any], None], siid: int = None,
|
self, handler: Callable[[dict, Any], None], siid: Optional[int] = None,
|
||||||
eiid: int = None, handler_ctx: Any = None
|
eiid: Optional[int] = None, handler_ctx: Any = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
return self.miot_client.sub_event(
|
return self.miot_client.sub_event(
|
||||||
did=self._did, handler=handler, siid=siid, eiid=eiid,
|
did=self._did, handler=handler, siid=siid, eiid=eiid,
|
||||||
handler_ctx=handler_ctx)
|
handler_ctx=handler_ctx)
|
||||||
|
|
||||||
def unsub_event(self, siid: int = None, eiid: int = None) -> bool:
|
def unsub_event(
|
||||||
|
self, siid: Optional[int] = None, eiid: Optional[int] = None
|
||||||
|
) -> bool:
|
||||||
return self.miot_client.unsub_event(
|
return self.miot_client.unsub_event(
|
||||||
did=self._did, siid=siid, eiid=eiid)
|
did=self._did, siid=siid, eiid=eiid)
|
||||||
|
|
||||||
@ -507,7 +513,7 @@ class MIoTDevice:
|
|||||||
if prop_access != (SPEC_PROP_TRANS_MAP[
|
if prop_access != (SPEC_PROP_TRANS_MAP[
|
||||||
'entities'][platform]['access']):
|
'entities'][platform]['access']):
|
||||||
return None
|
return None
|
||||||
if prop.format_ not in SPEC_PROP_TRANS_MAP[
|
if prop.format_.__name__ not in SPEC_PROP_TRANS_MAP[
|
||||||
'entities'][platform]['format']:
|
'entities'][platform]['format']:
|
||||||
return None
|
return None
|
||||||
if prop.unit:
|
if prop.unit:
|
||||||
@ -560,9 +566,9 @@ class MIoTDevice:
|
|||||||
# general conversion
|
# general conversion
|
||||||
if not prop.platform:
|
if not prop.platform:
|
||||||
if prop.writable:
|
if prop.writable:
|
||||||
if prop.format_ == 'str':
|
if prop.format_ == str:
|
||||||
prop.platform = 'text'
|
prop.platform = 'text'
|
||||||
elif prop.format_ == 'bool':
|
elif prop.format_ == bool:
|
||||||
prop.platform = 'switch'
|
prop.platform = 'switch'
|
||||||
prop.device_class = SwitchDeviceClass.SWITCH
|
prop.device_class = SwitchDeviceClass.SWITCH
|
||||||
elif prop.value_list:
|
elif prop.value_list:
|
||||||
@ -703,7 +709,7 @@ class MIoTDevice:
|
|||||||
def __on_device_state_changed(
|
def __on_device_state_changed(
|
||||||
self, did: str, state: MIoTDeviceState, ctx: Any
|
self, did: str, state: MIoTDeviceState, ctx: Any
|
||||||
) -> None:
|
) -> None:
|
||||||
self._online = state
|
self._online = state == MIoTDeviceState.ONLINE
|
||||||
for key, handler in self._device_state_sub_list.items():
|
for key, handler in self._device_state_sub_list.items():
|
||||||
self.miot_client.main_loop.call_soon_threadsafe(
|
self.miot_client.main_loop.call_soon_threadsafe(
|
||||||
handler, key, state)
|
handler, key, state)
|
||||||
@ -719,7 +725,8 @@ class MIoTServiceEntity(Entity):
|
|||||||
_main_loop: asyncio.AbstractEventLoop
|
_main_loop: asyncio.AbstractEventLoop
|
||||||
_prop_value_map: dict[MIoTSpecProperty, Any]
|
_prop_value_map: dict[MIoTSpecProperty, Any]
|
||||||
|
|
||||||
_event_occurred_handler: Callable[[MIoTSpecEvent, dict], None]
|
_event_occurred_handler: Optional[
|
||||||
|
Callable[[MIoTSpecEvent, dict], None]]
|
||||||
_prop_changed_subs: dict[
|
_prop_changed_subs: dict[
|
||||||
MIoTSpecProperty, Callable[[MIoTSpecProperty, Any], None]]
|
MIoTSpecProperty, Callable[[MIoTSpecProperty, Any], None]]
|
||||||
|
|
||||||
@ -763,7 +770,9 @@ class MIoTServiceEntity(Entity):
|
|||||||
self.entity_id)
|
self.entity_id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def event_occurred_handler(self) -> Callable[[MIoTSpecEvent, dict], None]:
|
def event_occurred_handler(
|
||||||
|
self
|
||||||
|
) -> Optional[Callable[[MIoTSpecEvent, dict], None]]:
|
||||||
return self._event_occurred_handler
|
return self._event_occurred_handler
|
||||||
|
|
||||||
@event_occurred_handler.setter
|
@event_occurred_handler.setter
|
||||||
@ -784,7 +793,7 @@ class MIoTServiceEntity(Entity):
|
|||||||
self._prop_changed_subs.pop(prop, None)
|
self._prop_changed_subs.pop(prop, None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> dict:
|
def device_info(self) -> Optional[DeviceInfo]:
|
||||||
return self.miot_device.device_info
|
return self.miot_device.device_info
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
@ -829,18 +838,20 @@ class MIoTServiceEntity(Entity):
|
|||||||
self.miot_device.unsub_event(
|
self.miot_device.unsub_event(
|
||||||
siid=event.service.iid, eiid=event.iid)
|
siid=event.service.iid, eiid=event.iid)
|
||||||
|
|
||||||
def get_map_description(self, map_: dict[int, Any], key: int) -> Any:
|
def get_map_value(
|
||||||
|
self, map_: dict[int, Any], key: int
|
||||||
|
) -> Any:
|
||||||
if map_ is None:
|
if map_ is None:
|
||||||
return None
|
return None
|
||||||
return map_.get(key, None)
|
return map_.get(key, None)
|
||||||
|
|
||||||
def get_map_value(
|
def get_map_key(
|
||||||
self, map_: dict[int, Any], description: Any
|
self, map_: dict[int, Any], value: Any
|
||||||
) -> Optional[int]:
|
) -> Optional[int]:
|
||||||
if map_ is None:
|
if map_ is None:
|
||||||
return None
|
return None
|
||||||
for key, value in map_.items():
|
for key, value_ in map_.items():
|
||||||
if value == description:
|
if value_ == value:
|
||||||
return key
|
return key
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -999,10 +1010,9 @@ class MIoTPropertyEntity(Entity):
|
|||||||
service: MIoTSpecService
|
service: MIoTSpecService
|
||||||
|
|
||||||
_main_loop: asyncio.AbstractEventLoop
|
_main_loop: asyncio.AbstractEventLoop
|
||||||
# {'min':int, 'max':int, 'step': int}
|
_value_range: Optional[MIoTSpecValueRange]
|
||||||
_value_range: dict[str, int]
|
|
||||||
# {Any: Any}
|
# {Any: Any}
|
||||||
_value_list: dict[Any, Any]
|
_value_list: Optional[MIoTSpecValueList]
|
||||||
_value: Any
|
_value: Any
|
||||||
|
|
||||||
_pending_write_ha_state_timer: Optional[asyncio.TimerHandle]
|
_pending_write_ha_state_timer: Optional[asyncio.TimerHandle]
|
||||||
@ -1015,11 +1025,7 @@ class MIoTPropertyEntity(Entity):
|
|||||||
self.service = spec.service
|
self.service = spec.service
|
||||||
self._main_loop = miot_device.miot_client.main_loop
|
self._main_loop = miot_device.miot_client.main_loop
|
||||||
self._value_range = spec.value_range
|
self._value_range = spec.value_range
|
||||||
if spec.value_list:
|
self._value_list = spec.value_list
|
||||||
self._value_list = {
|
|
||||||
item['value']: item['description'] for item in spec.value_list}
|
|
||||||
else:
|
|
||||||
self._value_list = None
|
|
||||||
self._value = None
|
self._value = None
|
||||||
self._pending_write_ha_state_timer = None
|
self._pending_write_ha_state_timer = None
|
||||||
# Gen entity_id
|
# Gen entity_id
|
||||||
@ -1042,7 +1048,7 @@ class MIoTPropertyEntity(Entity):
|
|||||||
self._value_list)
|
self._value_list)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> dict:
|
def device_info(self) -> Optional[DeviceInfo]:
|
||||||
return self.miot_device.device_info
|
return self.miot_device.device_info
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
@ -1067,18 +1073,15 @@ class MIoTPropertyEntity(Entity):
|
|||||||
self.miot_device.unsub_property(
|
self.miot_device.unsub_property(
|
||||||
siid=self.service.iid, piid=self.spec.iid)
|
siid=self.service.iid, piid=self.spec.iid)
|
||||||
|
|
||||||
def get_vlist_description(self, value: Any) -> str:
|
def get_vlist_description(self, value: Any) -> Optional[str]:
|
||||||
if not self._value_list:
|
if not self._value_list:
|
||||||
return None
|
return None
|
||||||
return self._value_list.get(value, None)
|
return self._value_list.get_description_by_value(value)
|
||||||
|
|
||||||
def get_vlist_value(self, description: str) -> Any:
|
def get_vlist_value(self, description: str) -> Any:
|
||||||
if not self._value_list:
|
if not self._value_list:
|
||||||
return None
|
return None
|
||||||
for key, value in self._value_list.items():
|
return self._value_list.get_value_by_description(description)
|
||||||
if value == description:
|
|
||||||
return key
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def set_property_async(self, value: Any) -> bool:
|
async def set_property_async(self, value: Any) -> bool:
|
||||||
if not self.spec.writable:
|
if not self.spec.writable:
|
||||||
@ -1184,7 +1187,7 @@ class MIoTEventEntity(Entity):
|
|||||||
spec.device_class, self.entity_id)
|
spec.device_class, self.entity_id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> dict:
|
def device_info(self) -> Optional[DeviceInfo]:
|
||||||
return self.miot_device.device_info
|
return self.miot_device.device_info
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
@ -1286,7 +1289,7 @@ class MIoTActionEntity(Entity):
|
|||||||
spec.device_class, self.entity_id)
|
spec.device_class, self.entity_id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> dict:
|
def device_info(self) -> Optional[DeviceInfo]:
|
||||||
return self.miot_device.device_info
|
return self.miot_device.device_info
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
@ -1298,7 +1301,9 @@ class MIoTActionEntity(Entity):
|
|||||||
self.miot_device.unsub_device_state(
|
self.miot_device.unsub_device_state(
|
||||||
key=f'{self.action_platform}.{ self.service.iid}.{self.spec.iid}')
|
key=f'{self.action_platform}.{ self.service.iid}.{self.spec.iid}')
|
||||||
|
|
||||||
async def action_async(self, in_list: list = None) -> Optional[list]:
|
async def action_async(
|
||||||
|
self, in_list: Optional[list] = None
|
||||||
|
) -> Optional[list]:
|
||||||
try:
|
try:
|
||||||
return await self.miot_device.miot_client.action_async(
|
return await self.miot_device.miot_client.action_async(
|
||||||
did=self.miot_device.did,
|
did=self.miot_device.did,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -719,250 +719,6 @@ class MIoTCert:
|
|||||||
return binascii.hexlify(sha1_hash.finalize()).decode('utf-8')
|
return binascii.hexlify(sha1_hash.finalize()).decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
class SpecMultiLang:
|
|
||||||
"""
|
|
||||||
MIoT-Spec-V2 multi-language for entities.
|
|
||||||
"""
|
|
||||||
MULTI_LANG_FILE = 'specs/multi_lang.json'
|
|
||||||
_main_loop: asyncio.AbstractEventLoop
|
|
||||||
_lang: str
|
|
||||||
_data: Optional[dict[str, dict]]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, lang: str, loop: Optional[asyncio.AbstractEventLoop] = None
|
|
||||||
) -> None:
|
|
||||||
self._main_loop = loop or asyncio.get_event_loop()
|
|
||||||
self._lang = lang
|
|
||||||
self._data = None
|
|
||||||
|
|
||||||
async def init_async(self) -> None:
|
|
||||||
if isinstance(self._data, dict):
|
|
||||||
return
|
|
||||||
multi_lang_data = None
|
|
||||||
self._data = {}
|
|
||||||
try:
|
|
||||||
multi_lang_data = await self._main_loop.run_in_executor(
|
|
||||||
None, load_json_file,
|
|
||||||
os.path.join(
|
|
||||||
os.path.dirname(os.path.abspath(__file__)),
|
|
||||||
self.MULTI_LANG_FILE))
|
|
||||||
except Exception as err: # pylint: disable=broad-exception-caught
|
|
||||||
_LOGGER.error('multi lang, load file error, %s', err)
|
|
||||||
return
|
|
||||||
# Check if the file is a valid JSON file
|
|
||||||
if not isinstance(multi_lang_data, dict):
|
|
||||||
_LOGGER.error('multi lang, invalid file data')
|
|
||||||
return
|
|
||||||
for lang_data in multi_lang_data.values():
|
|
||||||
if not isinstance(lang_data, dict):
|
|
||||||
_LOGGER.error('multi lang, invalid lang data')
|
|
||||||
return
|
|
||||||
for data in lang_data.values():
|
|
||||||
if not isinstance(data, dict):
|
|
||||||
_LOGGER.error('multi lang, invalid lang data item')
|
|
||||||
return
|
|
||||||
self._data = multi_lang_data
|
|
||||||
|
|
||||||
async def deinit_async(self) -> str:
|
|
||||||
self._data = None
|
|
||||||
|
|
||||||
async def translate_async(self, urn_key: str) -> dict[str, str]:
|
|
||||||
"""MUST call init_async() first."""
|
|
||||||
if urn_key in self._data:
|
|
||||||
return self._data[urn_key].get(self._lang, {})
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
class SpecBoolTranslation:
|
|
||||||
"""
|
|
||||||
Boolean value translation.
|
|
||||||
"""
|
|
||||||
BOOL_TRANS_FILE = 'specs/bool_trans.json'
|
|
||||||
_main_loop: asyncio.AbstractEventLoop
|
|
||||||
_lang: str
|
|
||||||
_data: Optional[dict[str, dict]]
|
|
||||||
_data_default: dict[str, dict]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, lang: str, loop: Optional[asyncio.AbstractEventLoop] = None
|
|
||||||
) -> None:
|
|
||||||
self._main_loop = loop or asyncio.get_event_loop()
|
|
||||||
self._lang = lang
|
|
||||||
self._data = None
|
|
||||||
|
|
||||||
async def init_async(self) -> None:
|
|
||||||
if isinstance(self._data, dict):
|
|
||||||
return
|
|
||||||
data = None
|
|
||||||
self._data = {}
|
|
||||||
try:
|
|
||||||
data = await self._main_loop.run_in_executor(
|
|
||||||
None, load_json_file,
|
|
||||||
os.path.join(
|
|
||||||
os.path.dirname(os.path.abspath(__file__)),
|
|
||||||
self.BOOL_TRANS_FILE))
|
|
||||||
except Exception as err: # pylint: disable=broad-exception-caught
|
|
||||||
_LOGGER.error('bool trans, load file error, %s', err)
|
|
||||||
return
|
|
||||||
# Check if the file is a valid JSON file
|
|
||||||
if (
|
|
||||||
not isinstance(data, dict)
|
|
||||||
or 'data' not in data
|
|
||||||
or not isinstance(data['data'], dict)
|
|
||||||
or 'translate' not in data
|
|
||||||
or not isinstance(data['translate'], dict)
|
|
||||||
):
|
|
||||||
_LOGGER.error('bool trans, valid file')
|
|
||||||
return
|
|
||||||
|
|
||||||
if 'default' in data['translate']:
|
|
||||||
data_default = (
|
|
||||||
data['translate']['default'].get(self._lang, None)
|
|
||||||
or data['translate']['default'].get(
|
|
||||||
DEFAULT_INTEGRATION_LANGUAGE, None))
|
|
||||||
if data_default:
|
|
||||||
self._data_default = [
|
|
||||||
{'value': True, 'description': data_default['true']},
|
|
||||||
{'value': False, 'description': data_default['false']}
|
|
||||||
]
|
|
||||||
|
|
||||||
for urn, key in data['data'].items():
|
|
||||||
if key not in data['translate']:
|
|
||||||
_LOGGER.error('bool trans, unknown key, %s, %s', urn, key)
|
|
||||||
continue
|
|
||||||
trans_data = (
|
|
||||||
data['translate'][key].get(self._lang, None)
|
|
||||||
or data['translate'][key].get(
|
|
||||||
DEFAULT_INTEGRATION_LANGUAGE, None))
|
|
||||||
if trans_data:
|
|
||||||
self._data[urn] = [
|
|
||||||
{'value': True, 'description': trans_data['true']},
|
|
||||||
{'value': False, 'description': trans_data['false']}
|
|
||||||
]
|
|
||||||
|
|
||||||
async def deinit_async(self) -> None:
|
|
||||||
self._data = None
|
|
||||||
self._data_default = None
|
|
||||||
|
|
||||||
async def translate_async(self, urn: str) -> list[dict[bool, str]]:
|
|
||||||
"""
|
|
||||||
MUST call init_async() before calling this method.
|
|
||||||
[
|
|
||||||
{'value': True, 'description': 'True'},
|
|
||||||
{'value': False, 'description': 'False'}
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._data.get(urn, self._data_default)
|
|
||||||
|
|
||||||
|
|
||||||
class SpecFilter:
|
|
||||||
"""
|
|
||||||
MIoT-Spec-V2 filter for entity conversion.
|
|
||||||
"""
|
|
||||||
SPEC_FILTER_FILE = 'specs/spec_filter.json'
|
|
||||||
_main_loop: asyncio.AbstractEventLoop
|
|
||||||
_data: dict[str, dict[str, set]]
|
|
||||||
_cache: Optional[dict]
|
|
||||||
|
|
||||||
def __init__(self, loop: Optional[asyncio.AbstractEventLoop]) -> None:
|
|
||||||
self._main_loop = loop or asyncio.get_event_loop()
|
|
||||||
self._data = None
|
|
||||||
self._cache = None
|
|
||||||
|
|
||||||
async def init_async(self) -> None:
|
|
||||||
if isinstance(self._data, dict):
|
|
||||||
return
|
|
||||||
filter_data = None
|
|
||||||
self._data = {}
|
|
||||||
try:
|
|
||||||
filter_data = await self._main_loop.run_in_executor(
|
|
||||||
None, load_json_file,
|
|
||||||
os.path.join(
|
|
||||||
os.path.dirname(os.path.abspath(__file__)),
|
|
||||||
self.SPEC_FILTER_FILE))
|
|
||||||
except Exception as err: # pylint: disable=broad-exception-caught
|
|
||||||
_LOGGER.error('spec filter, load file error, %s', err)
|
|
||||||
return
|
|
||||||
if not isinstance(filter_data, dict):
|
|
||||||
_LOGGER.error('spec filter, invalid spec filter content')
|
|
||||||
return
|
|
||||||
for values in list(filter_data.values()):
|
|
||||||
if not isinstance(values, dict):
|
|
||||||
_LOGGER.error('spec filter, invalid spec filter data')
|
|
||||||
return
|
|
||||||
for value in values.values():
|
|
||||||
if not isinstance(value, list):
|
|
||||||
_LOGGER.error('spec filter, invalid spec filter rules')
|
|
||||||
return
|
|
||||||
|
|
||||||
self._data = filter_data
|
|
||||||
|
|
||||||
async def deinit_async(self) -> None:
|
|
||||||
self._cache = None
|
|
||||||
self._data = None
|
|
||||||
|
|
||||||
def filter_spec(self, urn_key: str) -> None:
|
|
||||||
"""MUST call init_async() first."""
|
|
||||||
if not self._data:
|
|
||||||
return
|
|
||||||
self._cache = self._data.get(urn_key, None)
|
|
||||||
|
|
||||||
def filter_service(self, siid: int) -> bool:
|
|
||||||
"""Filter service by siid.
|
|
||||||
MUST call init_async() and filter_spec() first."""
|
|
||||||
if (
|
|
||||||
self._cache
|
|
||||||
and 'services' in self._cache
|
|
||||||
and (
|
|
||||||
str(siid) in self._cache['services']
|
|
||||||
or '*' in self._cache['services'])
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def filter_property(self, siid: int, piid: int) -> bool:
|
|
||||||
"""Filter property by piid.
|
|
||||||
MUST call init_async() and filter_spec() first."""
|
|
||||||
if (
|
|
||||||
self._cache
|
|
||||||
and 'properties' in self._cache
|
|
||||||
and (
|
|
||||||
f'{siid}.{piid}' in self._cache['properties']
|
|
||||||
or f'{siid}.*' in self._cache['properties'])
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def filter_event(self, siid: int, eiid: int) -> bool:
|
|
||||||
"""Filter event by eiid.
|
|
||||||
MUST call init_async() and filter_spec() first."""
|
|
||||||
if (
|
|
||||||
self._cache
|
|
||||||
and 'events' in self._cache
|
|
||||||
and (
|
|
||||||
f'{siid}.{eiid}' in self._cache['events']
|
|
||||||
or f'{siid}.*' in self._cache['events']
|
|
||||||
)
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def filter_action(self, siid: int, aiid: int) -> bool:
|
|
||||||
""""Filter action by aiid.
|
|
||||||
MUST call init_async() and filter_spec() first."""
|
|
||||||
if (
|
|
||||||
self._cache
|
|
||||||
and 'actions' in self._cache
|
|
||||||
and (
|
|
||||||
f'{siid}.{aiid}' in self._cache['actions']
|
|
||||||
or f'{siid}.*' in self._cache['actions'])
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceManufacturer:
|
class DeviceManufacturer:
|
||||||
"""Device manufacturer."""
|
"""Device manufacturer."""
|
||||||
DOMAIN: str = 'miot_specs'
|
DOMAIN: str = 'miot_specs'
|
||||||
|
|||||||
@ -1,295 +0,0 @@
|
|||||||
{
|
|
||||||
"data": {
|
|
||||||
"urn:miot-spec-v2:property:air-cooler:000000EB": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:alarm:00000012": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:anion:00000025": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:anti-fake:00000130": "yes_no",
|
|
||||||
"urn:miot-spec-v2:property:arrhythmia:000000B4": "yes_no",
|
|
||||||
"urn:miot-spec-v2:property:auto-cleanup:00000124": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:auto-deodorization:00000125": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:auto-keep-warm:0000002B": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:automatic-feeding:000000F0": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:blow:000000CD": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:card-insertion-state:00000106": "yes_no",
|
|
||||||
"urn:miot-spec-v2:property:contact-state:0000007C": "contact_state",
|
|
||||||
"urn:miot-spec-v2:property:current-physical-control-lock:00000099": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:delay:0000014F": "yes_no",
|
|
||||||
"urn:miot-spec-v2:property:deodorization:000000C6": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:dns-auto-mode:000000DC": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:driving-status:000000B9": "yes_no",
|
|
||||||
"urn:miot-spec-v2:property:dryer:00000027": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:eco:00000024": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:glimmer-full-color:00000089": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:guard-mode:000000B6": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:heater:00000026": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:heating:000000C7": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:horizontal-swing:00000017": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:hot-water-recirculation:0000011C": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:image-distortion-correction:0000010F": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:local-storage:0000011E": "yes_no",
|
|
||||||
"urn:miot-spec-v2:property:motion-detection:00000056": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:motion-state:0000007D": "motion_state",
|
|
||||||
"urn:miot-spec-v2:property:motion-tracking:0000008A": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:motor-reverse:00000072": "yes_no",
|
|
||||||
"urn:miot-spec-v2:property:mute:00000040": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:off-delay:00000053": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:on:00000006": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:physical-controls-locked:0000001D": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:plasma:00000132": "yes_no",
|
|
||||||
"urn:miot-spec-v2:property:preheat:00000103": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:seating-state:000000B8": "yes_no",
|
|
||||||
"urn:miot-spec-v2:property:silent-execution:000000FB": "yes_no",
|
|
||||||
"urn:miot-spec-v2:property:sleep-aid-mode:0000010B": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:sleep-mode:00000028": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:snore-state:0000012A": "yes_no",
|
|
||||||
"urn:miot-spec-v2:property:soft-wind:000000CF": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:speed-control:000000E8": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:submersion-state:0000007E": "yes_no",
|
|
||||||
"urn:miot-spec-v2:property:time-watermark:00000087": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:un-straight-blowing:00000100": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:uv:00000029": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:valve-switch:000000FE": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:ventilation:000000CE": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:vertical-swing:00000018": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:wake-up-mode:00000107": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:water-pump:000000F2": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:watering:000000CC": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:wdr-mode:00000088": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:wet:0000002A": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:wifi-band-combine:000000E0": "open_close",
|
|
||||||
"urn:miot-spec-v2:property:wifi-ssid-hidden:000000E3": "yes_no",
|
|
||||||
"urn:miot-spec-v2:property:wind-reverse:00000117": "yes_no"
|
|
||||||
},
|
|
||||||
"translate": {
|
|
||||||
"default": {
|
|
||||||
"de": {
|
|
||||||
"true": "Wahr",
|
|
||||||
"false": "Falsch"
|
|
||||||
},
|
|
||||||
"en": {
|
|
||||||
"true": "True",
|
|
||||||
"false": "False"
|
|
||||||
},
|
|
||||||
"es": {
|
|
||||||
"true": "Verdadero",
|
|
||||||
"false": "Falso"
|
|
||||||
},
|
|
||||||
"fr": {
|
|
||||||
"true": "Vrai",
|
|
||||||
"false": "Faux"
|
|
||||||
},
|
|
||||||
"ja": {
|
|
||||||
"true": "真",
|
|
||||||
"false": "偽"
|
|
||||||
},
|
|
||||||
"nl": {
|
|
||||||
"true": "True",
|
|
||||||
"false": "False"
|
|
||||||
},
|
|
||||||
"pt": {
|
|
||||||
"true": "True",
|
|
||||||
"false": "False"
|
|
||||||
},
|
|
||||||
"pt-BR": {
|
|
||||||
"true": "True",
|
|
||||||
"false": "False"
|
|
||||||
},
|
|
||||||
"ru": {
|
|
||||||
"true": "Истина",
|
|
||||||
"false": "Ложь"
|
|
||||||
},
|
|
||||||
"zh-Hans": {
|
|
||||||
"true": "真",
|
|
||||||
"false": "假"
|
|
||||||
},
|
|
||||||
"zh-Hant": {
|
|
||||||
"true": "真",
|
|
||||||
"false": "假"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"open_close": {
|
|
||||||
"de": {
|
|
||||||
"true": "Öffnen",
|
|
||||||
"false": "Schließen"
|
|
||||||
},
|
|
||||||
"en": {
|
|
||||||
"true": "Open",
|
|
||||||
"false": "Close"
|
|
||||||
},
|
|
||||||
"es": {
|
|
||||||
"true": "Abierto",
|
|
||||||
"false": "Cerrado"
|
|
||||||
},
|
|
||||||
"fr": {
|
|
||||||
"true": "Ouvert",
|
|
||||||
"false": "Fermer"
|
|
||||||
},
|
|
||||||
"ja": {
|
|
||||||
"true": "開く",
|
|
||||||
"false": "閉じる"
|
|
||||||
},
|
|
||||||
"nl": {
|
|
||||||
"true": "Open",
|
|
||||||
"false": "Dicht"
|
|
||||||
},
|
|
||||||
"pt": {
|
|
||||||
"true": "Aberto",
|
|
||||||
"false": "Fechado"
|
|
||||||
},
|
|
||||||
"pt-BR": {
|
|
||||||
"true": "Aberto",
|
|
||||||
"false": "Fechado"
|
|
||||||
},
|
|
||||||
"ru": {
|
|
||||||
"true": "Открыть",
|
|
||||||
"false": "Закрыть"
|
|
||||||
},
|
|
||||||
"zh-Hans": {
|
|
||||||
"true": "开启",
|
|
||||||
"false": "关闭"
|
|
||||||
},
|
|
||||||
"zh-Hant": {
|
|
||||||
"true": "開啟",
|
|
||||||
"false": "關閉"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"yes_no": {
|
|
||||||
"de": {
|
|
||||||
"true": "Ja",
|
|
||||||
"false": "Nein"
|
|
||||||
},
|
|
||||||
"en": {
|
|
||||||
"true": "Yes",
|
|
||||||
"false": "No"
|
|
||||||
},
|
|
||||||
"es": {
|
|
||||||
"true": "Sí",
|
|
||||||
"false": "No"
|
|
||||||
},
|
|
||||||
"fr": {
|
|
||||||
"true": "Oui",
|
|
||||||
"false": "Non"
|
|
||||||
},
|
|
||||||
"ja": {
|
|
||||||
"true": "はい",
|
|
||||||
"false": "いいえ"
|
|
||||||
},
|
|
||||||
"nl": {
|
|
||||||
"true": "Ja",
|
|
||||||
"false": "Nee"
|
|
||||||
},
|
|
||||||
"pt": {
|
|
||||||
"true": "Sim",
|
|
||||||
"false": "Não"
|
|
||||||
},
|
|
||||||
"pt-BR": {
|
|
||||||
"true": "Sim",
|
|
||||||
"false": "Não"
|
|
||||||
},
|
|
||||||
"ru": {
|
|
||||||
"true": "Да",
|
|
||||||
"false": "Нет"
|
|
||||||
},
|
|
||||||
"zh-Hans": {
|
|
||||||
"true": "是",
|
|
||||||
"false": "否"
|
|
||||||
},
|
|
||||||
"zh-Hant": {
|
|
||||||
"true": "是",
|
|
||||||
"false": "否"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"motion_state": {
|
|
||||||
"de": {
|
|
||||||
"true": "Bewegung erkannt",
|
|
||||||
"false": "Keine Bewegung erkannt"
|
|
||||||
},
|
|
||||||
"en": {
|
|
||||||
"true": "Motion Detected",
|
|
||||||
"false": "No Motion Detected"
|
|
||||||
},
|
|
||||||
"es": {
|
|
||||||
"true": "Movimiento detectado",
|
|
||||||
"false": "No se detecta movimiento"
|
|
||||||
},
|
|
||||||
"fr": {
|
|
||||||
"true": "Mouvement détecté",
|
|
||||||
"false": "Aucun mouvement détecté"
|
|
||||||
},
|
|
||||||
"ja": {
|
|
||||||
"true": "動きを検知",
|
|
||||||
"false": "動きが検出されません"
|
|
||||||
},
|
|
||||||
"nl": {
|
|
||||||
"true": "Contact",
|
|
||||||
"false": "Geen contact"
|
|
||||||
},
|
|
||||||
"pt": {
|
|
||||||
"true": "Contato",
|
|
||||||
"false": "Sem contato"
|
|
||||||
},
|
|
||||||
"pt-BR": {
|
|
||||||
"true": "Contato",
|
|
||||||
"false": "Sem contato"
|
|
||||||
},
|
|
||||||
"ru": {
|
|
||||||
"true": "Обнаружено движение",
|
|
||||||
"false": "Движение не обнаружено"
|
|
||||||
},
|
|
||||||
"zh-Hans": {
|
|
||||||
"true": "有人",
|
|
||||||
"false": "无人"
|
|
||||||
},
|
|
||||||
"zh-Hant": {
|
|
||||||
"true": "有人",
|
|
||||||
"false": "無人"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"contact_state": {
|
|
||||||
"de": {
|
|
||||||
"true": "Kontakt",
|
|
||||||
"false": "Kein Kontakt"
|
|
||||||
},
|
|
||||||
"en": {
|
|
||||||
"true": "Contact",
|
|
||||||
"false": "No Contact"
|
|
||||||
},
|
|
||||||
"es": {
|
|
||||||
"true": "Contacto",
|
|
||||||
"false": "Sin contacto"
|
|
||||||
},
|
|
||||||
"fr": {
|
|
||||||
"true": "Contact",
|
|
||||||
"false": "Pas de contact"
|
|
||||||
},
|
|
||||||
"ja": {
|
|
||||||
"true": "接触",
|
|
||||||
"false": "非接触"
|
|
||||||
},
|
|
||||||
"nl": {
|
|
||||||
"true": "Contact",
|
|
||||||
"false": "Geen contact"
|
|
||||||
},
|
|
||||||
"pt": {
|
|
||||||
"true": "Contato",
|
|
||||||
"false": "Sem contato"
|
|
||||||
},
|
|
||||||
"pt-BR": {
|
|
||||||
"true": "Contato",
|
|
||||||
"false": "Sem contato"
|
|
||||||
},
|
|
||||||
"ru": {
|
|
||||||
"true": "Контакт",
|
|
||||||
"false": "Нет контакта"
|
|
||||||
},
|
|
||||||
"zh-Hans": {
|
|
||||||
"true": "接触",
|
|
||||||
"false": "分离"
|
|
||||||
},
|
|
||||||
"zh-Hant": {
|
|
||||||
"true": "接觸",
|
|
||||||
"false": "分離"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
231
custom_components/xiaomi_home/miot/specs/bool_trans.yaml
Normal file
231
custom_components/xiaomi_home/miot/specs/bool_trans.yaml
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
data:
|
||||||
|
urn:miot-spec-v2:property:air-cooler:000000EB: open_close
|
||||||
|
urn:miot-spec-v2:property:alarm:00000012: open_close
|
||||||
|
urn:miot-spec-v2:property:anion:00000025: open_close
|
||||||
|
urn:miot-spec-v2:property:anti-fake:00000130: yes_no
|
||||||
|
urn:miot-spec-v2:property:arrhythmia:000000B4: yes_no
|
||||||
|
urn:miot-spec-v2:property:auto-cleanup:00000124: open_close
|
||||||
|
urn:miot-spec-v2:property:auto-deodorization:00000125: open_close
|
||||||
|
urn:miot-spec-v2:property:auto-keep-warm:0000002B: open_close
|
||||||
|
urn:miot-spec-v2:property:automatic-feeding:000000F0: open_close
|
||||||
|
urn:miot-spec-v2:property:blow:000000CD: open_close
|
||||||
|
urn:miot-spec-v2:property:card-insertion-state:00000106: yes_no
|
||||||
|
urn:miot-spec-v2:property:contact-state:0000007C: contact_state
|
||||||
|
urn:miot-spec-v2:property:current-physical-control-lock:00000099: open_close
|
||||||
|
urn:miot-spec-v2:property:delay:0000014F: yes_no
|
||||||
|
urn:miot-spec-v2:property:deodorization:000000C6: open_close
|
||||||
|
urn:miot-spec-v2:property:dns-auto-mode:000000DC: open_close
|
||||||
|
urn:miot-spec-v2:property:driving-status:000000B9: yes_no
|
||||||
|
urn:miot-spec-v2:property:dryer:00000027: open_close
|
||||||
|
urn:miot-spec-v2:property:eco:00000024: open_close
|
||||||
|
urn:miot-spec-v2:property:glimmer-full-color:00000089: open_close
|
||||||
|
urn:miot-spec-v2:property:guard-mode:000000B6: open_close
|
||||||
|
urn:miot-spec-v2:property:heater:00000026: open_close
|
||||||
|
urn:miot-spec-v2:property:heating:000000C7: open_close
|
||||||
|
urn:miot-spec-v2:property:horizontal-swing:00000017: open_close
|
||||||
|
urn:miot-spec-v2:property:hot-water-recirculation:0000011C: open_close
|
||||||
|
urn:miot-spec-v2:property:image-distortion-correction:0000010F: open_close
|
||||||
|
urn:miot-spec-v2:property:local-storage:0000011E: yes_no
|
||||||
|
urn:miot-spec-v2:property:motion-detection:00000056: open_close
|
||||||
|
urn:miot-spec-v2:property:motion-state:0000007D: motion_state
|
||||||
|
urn:miot-spec-v2:property:motion-tracking:0000008A: open_close
|
||||||
|
urn:miot-spec-v2:property:motor-reverse:00000072: yes_no
|
||||||
|
urn:miot-spec-v2:property:mute:00000040: open_close
|
||||||
|
urn:miot-spec-v2:property:off-delay:00000053: open_close
|
||||||
|
urn:miot-spec-v2:property:on:00000006: open_close
|
||||||
|
urn:miot-spec-v2:property:physical-controls-locked:0000001D: open_close
|
||||||
|
urn:miot-spec-v2:property:plasma:00000132: yes_no
|
||||||
|
urn:miot-spec-v2:property:preheat:00000103: open_close
|
||||||
|
urn:miot-spec-v2:property:seating-state:000000B8: yes_no
|
||||||
|
urn:miot-spec-v2:property:silent-execution:000000FB: yes_no
|
||||||
|
urn:miot-spec-v2:property:sleep-aid-mode:0000010B: open_close
|
||||||
|
urn:miot-spec-v2:property:sleep-mode:00000028: open_close
|
||||||
|
urn:miot-spec-v2:property:snore-state:0000012A: yes_no
|
||||||
|
urn:miot-spec-v2:property:soft-wind:000000CF: open_close
|
||||||
|
urn:miot-spec-v2:property:speed-control:000000E8: open_close
|
||||||
|
urn:miot-spec-v2:property:submersion-state:0000007E: yes_no
|
||||||
|
urn:miot-spec-v2:property:time-watermark:00000087: open_close
|
||||||
|
urn:miot-spec-v2:property:un-straight-blowing:00000100: open_close
|
||||||
|
urn:miot-spec-v2:property:uv:00000029: open_close
|
||||||
|
urn:miot-spec-v2:property:valve-switch:000000FE: open_close
|
||||||
|
urn:miot-spec-v2:property:ventilation:000000CE: open_close
|
||||||
|
urn:miot-spec-v2:property:vertical-swing:00000018: open_close
|
||||||
|
urn:miot-spec-v2:property:wake-up-mode:00000107: open_close
|
||||||
|
urn:miot-spec-v2:property:water-pump:000000F2: open_close
|
||||||
|
urn:miot-spec-v2:property:watering:000000CC: open_close
|
||||||
|
urn:miot-spec-v2:property:wdr-mode:00000088: open_close
|
||||||
|
urn:miot-spec-v2:property:wet:0000002A: open_close
|
||||||
|
urn:miot-spec-v2:property:wifi-band-combine:000000E0: open_close
|
||||||
|
urn:miot-spec-v2:property:wifi-ssid-hidden:000000E3: yes_no
|
||||||
|
urn:miot-spec-v2:property:wind-reverse:00000117: yes_no
|
||||||
|
translate:
|
||||||
|
default:
|
||||||
|
de:
|
||||||
|
true: Wahr
|
||||||
|
false: Falsch
|
||||||
|
en:
|
||||||
|
true: True
|
||||||
|
false: False
|
||||||
|
es:
|
||||||
|
true: Verdadero
|
||||||
|
false: Falso
|
||||||
|
fr:
|
||||||
|
true: Vrai
|
||||||
|
false: Faux
|
||||||
|
ja:
|
||||||
|
true: 真
|
||||||
|
false: 偽
|
||||||
|
nl:
|
||||||
|
true: True
|
||||||
|
false: False
|
||||||
|
pt:
|
||||||
|
true: True
|
||||||
|
false: False
|
||||||
|
pt-BR:
|
||||||
|
true: True
|
||||||
|
false: False
|
||||||
|
ru:
|
||||||
|
true: Истина
|
||||||
|
false: Ложь
|
||||||
|
zh-Hans:
|
||||||
|
true: 真
|
||||||
|
false: 假
|
||||||
|
zh-Hant:
|
||||||
|
true: 真
|
||||||
|
false: 假
|
||||||
|
open_close:
|
||||||
|
de:
|
||||||
|
true: Öffnen
|
||||||
|
false: Schließen
|
||||||
|
en:
|
||||||
|
true: Open
|
||||||
|
false: Close
|
||||||
|
es:
|
||||||
|
true: Abierto
|
||||||
|
false: Cerrado
|
||||||
|
fr:
|
||||||
|
true: Ouvert
|
||||||
|
false: Fermer
|
||||||
|
ja:
|
||||||
|
true: 開く
|
||||||
|
false: 閉じる
|
||||||
|
nl:
|
||||||
|
true: Open
|
||||||
|
false: Dicht
|
||||||
|
pt:
|
||||||
|
true: Aberto
|
||||||
|
false: Fechado
|
||||||
|
pt-BR:
|
||||||
|
true: Aberto
|
||||||
|
false: Fechado
|
||||||
|
ru:
|
||||||
|
true: Открыть
|
||||||
|
false: Закрыть
|
||||||
|
zh-Hans:
|
||||||
|
true: 开启
|
||||||
|
false: 关闭
|
||||||
|
zh-Hant:
|
||||||
|
true: 開啟
|
||||||
|
false: 關閉
|
||||||
|
yes_no:
|
||||||
|
de:
|
||||||
|
true: Ja
|
||||||
|
false: Nein
|
||||||
|
en:
|
||||||
|
true: Yes
|
||||||
|
false: No
|
||||||
|
es:
|
||||||
|
true: Sí
|
||||||
|
false: No
|
||||||
|
fr:
|
||||||
|
true: Oui
|
||||||
|
false: Non
|
||||||
|
ja:
|
||||||
|
true: はい
|
||||||
|
false: いいえ
|
||||||
|
nl:
|
||||||
|
true: Ja
|
||||||
|
false: Nee
|
||||||
|
pt:
|
||||||
|
true: Sim
|
||||||
|
false: Não
|
||||||
|
pt-BR:
|
||||||
|
true: Sim
|
||||||
|
false: Não
|
||||||
|
ru:
|
||||||
|
true: Да
|
||||||
|
false: Нет
|
||||||
|
zh-Hans:
|
||||||
|
true: 是
|
||||||
|
false: 否
|
||||||
|
zh-Hant:
|
||||||
|
true: 是
|
||||||
|
false: 否
|
||||||
|
motion_state:
|
||||||
|
de:
|
||||||
|
true: Bewegung erkannt
|
||||||
|
false: Keine Bewegung erkannt
|
||||||
|
en:
|
||||||
|
true: Motion Detected
|
||||||
|
false: No Motion Detected
|
||||||
|
es:
|
||||||
|
true: Movimiento detectado
|
||||||
|
false: No se detecta movimiento
|
||||||
|
fr:
|
||||||
|
true: Mouvement détecté
|
||||||
|
false: Aucun mouvement détecté
|
||||||
|
ja:
|
||||||
|
true: 動きを検知
|
||||||
|
false: 動きが検出されません
|
||||||
|
nl:
|
||||||
|
true: Contact
|
||||||
|
false: Geen contact
|
||||||
|
pt:
|
||||||
|
true: Contato
|
||||||
|
false: Sem contato
|
||||||
|
pt-BR:
|
||||||
|
true: Contato
|
||||||
|
false: Sem contato
|
||||||
|
ru:
|
||||||
|
true: Обнаружено движение
|
||||||
|
false: Движение не обнаружено
|
||||||
|
zh-Hans:
|
||||||
|
true: 有人
|
||||||
|
false: 无人
|
||||||
|
zh-Hant:
|
||||||
|
true: 有人
|
||||||
|
false: 無人
|
||||||
|
contact_state:
|
||||||
|
de:
|
||||||
|
true: Kontakt
|
||||||
|
false: Kein Kontakt
|
||||||
|
en:
|
||||||
|
true: Contact
|
||||||
|
false: No Contact
|
||||||
|
es:
|
||||||
|
true: Contacto
|
||||||
|
false: Sin contacto
|
||||||
|
fr:
|
||||||
|
true: Contact
|
||||||
|
false: Pas de contact
|
||||||
|
ja:
|
||||||
|
true: 接触
|
||||||
|
false: 非接触
|
||||||
|
nl:
|
||||||
|
true: Contact
|
||||||
|
false: Geen contact
|
||||||
|
pt:
|
||||||
|
true: Contato
|
||||||
|
false: Sem contato
|
||||||
|
pt-BR:
|
||||||
|
true: Contato
|
||||||
|
false: Sem contato
|
||||||
|
ru:
|
||||||
|
true: Контакт
|
||||||
|
false: Нет контакта
|
||||||
|
zh-Hans:
|
||||||
|
true: 接触
|
||||||
|
false: 分离
|
||||||
|
zh-Hant:
|
||||||
|
true: 接觸
|
||||||
|
false: 分離
|
||||||
@ -1,172 +0,0 @@
|
|||||||
{
|
|
||||||
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
|
|
||||||
"de": {
|
|
||||||
"service:001": "Geräteinformationen",
|
|
||||||
"service:001:property:003": "Geräte-ID",
|
|
||||||
"service:001:property:005": "Seriennummer (SN)",
|
|
||||||
"service:002": "Gateway",
|
|
||||||
"service:002:event:001": "Netzwerk geändert",
|
|
||||||
"service:002:event:002": "Netzwerk geändert",
|
|
||||||
"service:002:property:001": "Zugriffsmethode",
|
|
||||||
"service:002:property:001:valuelist:000": "Kabelgebunden",
|
|
||||||
"service:002:property:001:valuelist:001": "5G Drahtlos",
|
|
||||||
"service:002:property:001:valuelist:002": "2.4G Drahtlos",
|
|
||||||
"service:002:property:002": "IP-Adresse",
|
|
||||||
"service:002:property:003": "WiFi-Netzwerkname",
|
|
||||||
"service:002:property:004": "Aktuelle Zeit",
|
|
||||||
"service:002:property:005": "DHCP-Server-MAC-Adresse",
|
|
||||||
"service:003": "Anzeigelampe",
|
|
||||||
"service:003:property:001": "Schalter",
|
|
||||||
"service:004": "Virtueller Dienst",
|
|
||||||
"service:004:action:001": "Virtuelles Ereignis erzeugen",
|
|
||||||
"service:004:event:001": "Virtuelles Ereignis aufgetreten",
|
|
||||||
"service:004:property:001": "Ereignisname"
|
|
||||||
},
|
|
||||||
"en": {
|
|
||||||
"service:001": "Device Information",
|
|
||||||
"service:001:property:003": "Device ID",
|
|
||||||
"service:001:property:005": "Serial Number (SN)",
|
|
||||||
"service:002": "Gateway",
|
|
||||||
"service:002:event:001": "Network Changed",
|
|
||||||
"service:002:event:002": "Network Changed",
|
|
||||||
"service:002:property:001": "Access Method",
|
|
||||||
"service:002:property:001:valuelist:000": "Wired",
|
|
||||||
"service:002:property:001:valuelist:001": "5G Wireless",
|
|
||||||
"service:002:property:001:valuelist:002": "2.4G Wireless",
|
|
||||||
"service:002:property:002": "IP Address",
|
|
||||||
"service:002:property:003": "WiFi Network Name",
|
|
||||||
"service:002:property:004": "Current Time",
|
|
||||||
"service:002:property:005": "DHCP Server MAC Address",
|
|
||||||
"service:003": "Indicator Light",
|
|
||||||
"service:003:property:001": "Switch",
|
|
||||||
"service:004": "Virtual Service",
|
|
||||||
"service:004:action:001": "Generate Virtual Event",
|
|
||||||
"service:004:event:001": "Virtual Event Occurred",
|
|
||||||
"service:004:property:001": "Event Name"
|
|
||||||
},
|
|
||||||
"es": {
|
|
||||||
"service:001": "Información del dispositivo",
|
|
||||||
"service:001:property:003": "ID del dispositivo",
|
|
||||||
"service:001:property:005": "Número de serie (SN)",
|
|
||||||
"service:002": "Puerta de enlace",
|
|
||||||
"service:002:event:001": "Cambio de red",
|
|
||||||
"service:002:event:002": "Cambio de red",
|
|
||||||
"service:002:property:001": "Método de acceso",
|
|
||||||
"service:002:property:001:valuelist:000": "Cableado",
|
|
||||||
"service:002:property:001:valuelist:001": "5G inalámbrico",
|
|
||||||
"service:002:property:001:valuelist:002": "2.4G inalámbrico",
|
|
||||||
"service:002:property:002": "Dirección IP",
|
|
||||||
"service:002:property:003": "Nombre de red WiFi",
|
|
||||||
"service:002:property:004": "Hora actual",
|
|
||||||
"service:002:property:005": "Dirección MAC del servidor DHCP",
|
|
||||||
"service:003": "Luz indicadora",
|
|
||||||
"service:003:property:001": "Interruptor",
|
|
||||||
"service:004": "Servicio virtual",
|
|
||||||
"service:004:action:001": "Generar evento virtual",
|
|
||||||
"service:004:event:001": "Ocurrió un evento virtual",
|
|
||||||
"service:004:property:001": "Nombre del evento"
|
|
||||||
},
|
|
||||||
"fr": {
|
|
||||||
"service:001": "Informations sur l'appareil",
|
|
||||||
"service:001:property:003": "ID de l'appareil",
|
|
||||||
"service:001:property:005": "Numéro de série (SN)",
|
|
||||||
"service:002": "Passerelle",
|
|
||||||
"service:002:event:001": "Changement de réseau",
|
|
||||||
"service:002:event:002": "Changement de réseau",
|
|
||||||
"service:002:property:001": "Méthode d'accès",
|
|
||||||
"service:002:property:001:valuelist:000": "Câblé",
|
|
||||||
"service:002:property:001:valuelist:001": "Sans fil 5G",
|
|
||||||
"service:002:property:001:valuelist:002": "Sans fil 2.4G",
|
|
||||||
"service:002:property:002": "Adresse IP",
|
|
||||||
"service:002:property:003": "Nom du réseau WiFi",
|
|
||||||
"service:002:property:004": "Heure actuelle",
|
|
||||||
"service:002:property:005": "Adresse MAC du serveur DHCP",
|
|
||||||
"service:003": "Voyant lumineux",
|
|
||||||
"service:003:property:001": "Interrupteur",
|
|
||||||
"service:004": "Service virtuel",
|
|
||||||
"service:004:action:001": "Générer un événement virtuel",
|
|
||||||
"service:004:event:001": "Événement virtuel survenu",
|
|
||||||
"service:004:property:001": "Nom de l'événement"
|
|
||||||
},
|
|
||||||
"ja": {
|
|
||||||
"service:001": "デバイス情報",
|
|
||||||
"service:001:property:003": "デバイスID",
|
|
||||||
"service:001:property:005": "シリアル番号 (SN)",
|
|
||||||
"service:002": "ゲートウェイ",
|
|
||||||
"service:002:event:001": "ネットワークが変更されました",
|
|
||||||
"service:002:event:002": "ネットワークが変更されました",
|
|
||||||
"service:002:property:001": "アクセス方法",
|
|
||||||
"service:002:property:001:valuelist:000": "有線",
|
|
||||||
"service:002:property:001:valuelist:001": "5G ワイヤレス",
|
|
||||||
"service:002:property:001:valuelist:002": "2.4G ワイヤレス",
|
|
||||||
"service:002:property:002": "IPアドレス",
|
|
||||||
"service:002:property:003": "WiFiネットワーク名",
|
|
||||||
"service:002:property:004": "現在の時間",
|
|
||||||
"service:002:property:005": "DHCPサーバーMACアドレス",
|
|
||||||
"service:003": "インジケータライト",
|
|
||||||
"service:003:property:001": "スイッチ",
|
|
||||||
"service:004": "バーチャルサービス",
|
|
||||||
"service:004:action:001": "バーチャルイベントを生成",
|
|
||||||
"service:004:event:001": "バーチャルイベントが発生しました",
|
|
||||||
"service:004:property:001": "イベント名"
|
|
||||||
},
|
|
||||||
"ru": {
|
|
||||||
"service:001": "Информация об устройстве",
|
|
||||||
"service:001:property:003": "ID устройства",
|
|
||||||
"service:001:property:005": "Серийный номер (SN)",
|
|
||||||
"service:002": "Шлюз",
|
|
||||||
"service:002:event:001": "Сеть изменена",
|
|
||||||
"service:002:event:002": "Сеть изменена",
|
|
||||||
"service:002:property:001": "Метод доступа",
|
|
||||||
"service:002:property:001:valuelist:000": "Проводной",
|
|
||||||
"service:002:property:001:valuelist:001": "5G Беспроводной",
|
|
||||||
"service:002:property:001:valuelist:002": "2.4G Беспроводной",
|
|
||||||
"service:002:property:002": "IP Адрес",
|
|
||||||
"service:002:property:003": "Название WiFi сети",
|
|
||||||
"service:002:property:004": "Текущее время",
|
|
||||||
"service:002:property:005": "MAC адрес DHCP сервера",
|
|
||||||
"service:003": "Световой индикатор",
|
|
||||||
"service:003:property:001": "Переключатель",
|
|
||||||
"service:004": "Виртуальная служба",
|
|
||||||
"service:004:action:001": "Создать виртуальное событие",
|
|
||||||
"service:004:event:001": "Произошло виртуальное событие",
|
|
||||||
"service:004:property:001": "Название события"
|
|
||||||
},
|
|
||||||
"zh-Hant": {
|
|
||||||
"service:001": "設備信息",
|
|
||||||
"service:001:property:003": "設備ID",
|
|
||||||
"service:001:property:005": "序號 (SN)",
|
|
||||||
"service:002": "網關",
|
|
||||||
"service:002:event:001": "網路發生變化",
|
|
||||||
"service:002:event:002": "網路發生變化",
|
|
||||||
"service:002:property:001": "接入方式",
|
|
||||||
"service:002:property:001:valuelist:000": "有線",
|
|
||||||
"service:002:property:001:valuelist:001": "5G 無線",
|
|
||||||
"service:002:property:001:valuelist:002": "2.4G 無線",
|
|
||||||
"service:002:property:002": "IP地址",
|
|
||||||
"service:002:property:003": "WiFi網路名稱",
|
|
||||||
"service:002:property:004": "當前時間",
|
|
||||||
"service:002:property:005": "DHCP伺服器MAC地址",
|
|
||||||
"service:003": "指示燈",
|
|
||||||
"service:003:property:001": "開關",
|
|
||||||
"service:004": "虛擬服務",
|
|
||||||
"service:004:action:001": "產生虛擬事件",
|
|
||||||
"service:004:event:001": "虛擬事件發生",
|
|
||||||
"service:004:property:001": "事件名稱"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"urn:miot-spec-v2:device:switch:0000A003:lumi-acn040:1": {
|
|
||||||
"en": {
|
|
||||||
"service:011": "Right Button On and Off",
|
|
||||||
"service:011:property:001": "Right Button On and Off",
|
|
||||||
"service:015:action:001": "Left Button Identify",
|
|
||||||
"service:016:action:001": "Middle Button Identify",
|
|
||||||
"service:017:action:001": "Right Button Identify"
|
|
||||||
},
|
|
||||||
"zh-Hans": {
|
|
||||||
"service:015:action:001": "左键确认",
|
|
||||||
"service:016:action:001": "中键确认",
|
|
||||||
"service:017:action:001": "右键确认"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
{
|
|
||||||
"urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4": {
|
|
||||||
"properties": [
|
|
||||||
"9.*",
|
|
||||||
"13.*",
|
|
||||||
"15.*"
|
|
||||||
],
|
|
||||||
"services": [
|
|
||||||
"10"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"urn:miot-spec-v2:device:curtain:0000A00C:lumi-hmcn01": {
|
|
||||||
"properties": [
|
|
||||||
"5.1"
|
|
||||||
],
|
|
||||||
"services": [
|
|
||||||
"4",
|
|
||||||
"7",
|
|
||||||
"8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
|
|
||||||
"events": [
|
|
||||||
"2.1"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"urn:miot-spec-v2:device:health-pot:0000A051:chunmi-a1": {
|
|
||||||
"services": [
|
|
||||||
"5"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"urn:miot-spec-v2:device:light:0000A001:philips-strip3": {
|
|
||||||
"properties": [
|
|
||||||
"2.2"
|
|
||||||
],
|
|
||||||
"services": [
|
|
||||||
"1",
|
|
||||||
"3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"urn:miot-spec-v2:device:light:0000A001:yeelink-color2": {
|
|
||||||
"properties": [
|
|
||||||
"3.*",
|
|
||||||
"2.5"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"urn:miot-spec-v2:device:light:0000A001:yeelink-dnlight2": {
|
|
||||||
"services": [
|
|
||||||
"3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"urn:miot-spec-v2:device:light:0000A001:yeelink-mbulb3": {
|
|
||||||
"services": [
|
|
||||||
"3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"urn:miot-spec-v2:device:motion-sensor:0000A014:xiaomi-pir1": {
|
|
||||||
"services": [
|
|
||||||
"1",
|
|
||||||
"5"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"urn:miot-spec-v2:device:router:0000A036:xiaomi-rd03": {
|
|
||||||
"services": [
|
|
||||||
"*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
43
custom_components/xiaomi_home/miot/specs/spec_filter.yaml
Normal file
43
custom_components/xiaomi_home/miot/specs/spec_filter.yaml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4:
|
||||||
|
properties:
|
||||||
|
- "9.*"
|
||||||
|
- "13.*"
|
||||||
|
- "15.*"
|
||||||
|
services:
|
||||||
|
- "10"
|
||||||
|
urn:miot-spec-v2:device:curtain:0000A00C:lumi-hmcn01:
|
||||||
|
properties:
|
||||||
|
- "5.1"
|
||||||
|
services:
|
||||||
|
- "4"
|
||||||
|
- "7"
|
||||||
|
- "8"
|
||||||
|
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:
|
||||||
|
events:
|
||||||
|
- "2.1"
|
||||||
|
urn:miot-spec-v2:device:health-pot:0000A051:chunmi-a1:
|
||||||
|
services:
|
||||||
|
- "5"
|
||||||
|
urn:miot-spec-v2:device:light:0000A001:philips-strip3:
|
||||||
|
properties:
|
||||||
|
- "2.2"
|
||||||
|
services:
|
||||||
|
- "1"
|
||||||
|
- "3"
|
||||||
|
urn:miot-spec-v2:device:light:0000A001:yeelink-color2:
|
||||||
|
properties:
|
||||||
|
- "3.*"
|
||||||
|
- "2.5"
|
||||||
|
urn:miot-spec-v2:device:light:0000A001:yeelink-dnlight2:
|
||||||
|
services:
|
||||||
|
- "3"
|
||||||
|
urn:miot-spec-v2:device:light:0000A001:yeelink-mbulb3:
|
||||||
|
services:
|
||||||
|
- "3"
|
||||||
|
urn:miot-spec-v2:device:motion-sensor:0000A014:xiaomi-pir1:
|
||||||
|
services:
|
||||||
|
- "1"
|
||||||
|
- "5"
|
||||||
|
urn:miot-spec-v2:device:router:0000A036:xiaomi-rd03:
|
||||||
|
services:
|
||||||
|
- "*"
|
||||||
@ -90,7 +90,7 @@ class Notify(MIoTActionEntity, NotifyEntity):
|
|||||||
super().__init__(miot_device=miot_device, spec=spec)
|
super().__init__(miot_device=miot_device, spec=spec)
|
||||||
self._attr_extra_state_attributes = {}
|
self._attr_extra_state_attributes = {}
|
||||||
action_in: str = ', '.join([
|
action_in: str = ', '.join([
|
||||||
f'{prop.description_trans}({prop.format_})'
|
f'{prop.description_trans}({prop.format_.__name__})'
|
||||||
for prop in self.spec.in_])
|
for prop in self.spec.in_])
|
||||||
self._attr_extra_state_attributes['action params'] = f'[{action_in}]'
|
self._attr_extra_state_attributes['action params'] = f'[{action_in}]'
|
||||||
|
|
||||||
@ -122,24 +122,24 @@ class Notify(MIoTActionEntity, NotifyEntity):
|
|||||||
return
|
return
|
||||||
in_value: list[dict] = []
|
in_value: list[dict] = []
|
||||||
for index, prop in enumerate(self.spec.in_):
|
for index, prop in enumerate(self.spec.in_):
|
||||||
if prop.format_ == 'str':
|
if prop.format_ == str:
|
||||||
if isinstance(in_list[index], (bool, int, float, str)):
|
if isinstance(in_list[index], (bool, int, float, str)):
|
||||||
in_value.append(
|
in_value.append(
|
||||||
{'piid': prop.iid, 'value': str(in_list[index])})
|
{'piid': prop.iid, 'value': str(in_list[index])})
|
||||||
continue
|
continue
|
||||||
elif prop.format_ == 'bool':
|
elif prop.format_ == bool:
|
||||||
if isinstance(in_list[index], (bool, int)):
|
if isinstance(in_list[index], (bool, int)):
|
||||||
# yes, no, on, off, true, false and other bool types
|
# yes, no, on, off, true, false and other bool types
|
||||||
# will also be parsed as 0 and 1 of int.
|
# will also be parsed as 0 and 1 of int.
|
||||||
in_value.append(
|
in_value.append(
|
||||||
{'piid': prop.iid, 'value': bool(in_list[index])})
|
{'piid': prop.iid, 'value': bool(in_list[index])})
|
||||||
continue
|
continue
|
||||||
elif prop.format_ == 'float':
|
elif prop.format_ == float:
|
||||||
if isinstance(in_list[index], (int, float)):
|
if isinstance(in_list[index], (int, float)):
|
||||||
in_value.append(
|
in_value.append(
|
||||||
{'piid': prop.iid, 'value': in_list[index]})
|
{'piid': prop.iid, 'value': in_list[index]})
|
||||||
continue
|
continue
|
||||||
elif prop.format_ == 'int':
|
elif prop.format_ == int:
|
||||||
if isinstance(in_list[index], int):
|
if isinstance(in_list[index], int):
|
||||||
in_value.append(
|
in_value.append(
|
||||||
{'piid': prop.iid, 'value': in_list[index]})
|
{'piid': prop.iid, 'value': in_list[index]})
|
||||||
|
|||||||
@ -92,9 +92,9 @@ class Number(MIoTPropertyEntity, NumberEntity):
|
|||||||
self._attr_icon = self.spec.icon
|
self._attr_icon = self.spec.icon
|
||||||
# Set value range
|
# Set value range
|
||||||
if self._value_range:
|
if self._value_range:
|
||||||
self._attr_native_min_value = self._value_range['min']
|
self._attr_native_min_value = self._value_range.min_
|
||||||
self._attr_native_max_value = self._value_range['max']
|
self._attr_native_max_value = self._value_range.max_
|
||||||
self._attr_native_step = self._value_range['step']
|
self._attr_native_step = self._value_range.step
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> Optional[float]:
|
def native_value(self) -> Optional[float]:
|
||||||
|
|||||||
@ -82,7 +82,8 @@ class Select(MIoTPropertyEntity, SelectEntity):
|
|||||||
def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) -> None:
|
def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) -> None:
|
||||||
"""Initialize the Select."""
|
"""Initialize the Select."""
|
||||||
super().__init__(miot_device=miot_device, spec=spec)
|
super().__init__(miot_device=miot_device, spec=spec)
|
||||||
self._attr_options = list(self._value_list.values())
|
if self._value_list:
|
||||||
|
self._attr_options = self._value_list.descriptions
|
||||||
|
|
||||||
async def async_select_option(self, option: str) -> None:
|
async def async_select_option(self, option: str) -> None:
|
||||||
"""Change the selected option."""
|
"""Change the selected option."""
|
||||||
|
|||||||
@ -91,7 +91,7 @@ class Sensor(MIoTPropertyEntity, SensorEntity):
|
|||||||
self._attr_device_class = SensorDeviceClass.ENUM
|
self._attr_device_class = SensorDeviceClass.ENUM
|
||||||
self._attr_icon = 'mdi:message-text'
|
self._attr_icon = 'mdi:message-text'
|
||||||
self._attr_native_unit_of_measurement = None
|
self._attr_native_unit_of_measurement = None
|
||||||
self._attr_options = list(self._value_list.values())
|
self._attr_options = self._value_list.descriptions
|
||||||
else:
|
else:
|
||||||
self._attr_device_class = spec.device_class
|
self._attr_device_class = spec.device_class
|
||||||
if spec.external_unit:
|
if spec.external_unit:
|
||||||
@ -115,14 +115,14 @@ class Sensor(MIoTPropertyEntity, SensorEntity):
|
|||||||
"""Return the current value of the sensor."""
|
"""Return the current value of the sensor."""
|
||||||
if self._value_range and isinstance(self._value, (int, float)):
|
if self._value_range and isinstance(self._value, (int, float)):
|
||||||
if (
|
if (
|
||||||
self._value < self._value_range['min']
|
self._value < self._value_range.min_
|
||||||
or self._value > self._value_range['max']
|
or self._value > self._value_range.max_
|
||||||
):
|
):
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
'%s, data exception, out of range, %s, %s',
|
'%s, data exception, out of range, %s, %s',
|
||||||
self.entity_id, self._value, self._value_range)
|
self.entity_id, self._value, self._value_range)
|
||||||
if self._value_list:
|
if self._value_list:
|
||||||
return self._value_list.get(self._value, None)
|
return self.get_vlist_description(self._value)
|
||||||
if isinstance(self._value, str):
|
if isinstance(self._value, str):
|
||||||
return self._value[:255]
|
return self._value[:255]
|
||||||
return self._value
|
return self._value
|
||||||
|
|||||||
@ -111,7 +111,7 @@ class ActionText(MIoTActionEntity, TextEntity):
|
|||||||
self._attr_extra_state_attributes = {}
|
self._attr_extra_state_attributes = {}
|
||||||
self._attr_native_value = ''
|
self._attr_native_value = ''
|
||||||
action_in: str = ', '.join([
|
action_in: str = ', '.join([
|
||||||
f'{prop.description_trans}({prop.format_})'
|
f'{prop.description_trans}({prop.format_.__name__})'
|
||||||
for prop in self.spec.in_])
|
for prop in self.spec.in_])
|
||||||
self._attr_extra_state_attributes['action params'] = f'[{action_in}]'
|
self._attr_extra_state_attributes['action params'] = f'[{action_in}]'
|
||||||
# For action debug
|
# For action debug
|
||||||
@ -141,24 +141,24 @@ class ActionText(MIoTActionEntity, TextEntity):
|
|||||||
f'invalid action params, {value}')
|
f'invalid action params, {value}')
|
||||||
in_value: list[dict] = []
|
in_value: list[dict] = []
|
||||||
for index, prop in enumerate(self.spec.in_):
|
for index, prop in enumerate(self.spec.in_):
|
||||||
if prop.format_ == 'str':
|
if prop.format_ == str:
|
||||||
if isinstance(in_list[index], (bool, int, float, str)):
|
if isinstance(in_list[index], (bool, int, float, str)):
|
||||||
in_value.append(
|
in_value.append(
|
||||||
{'piid': prop.iid, 'value': str(in_list[index])})
|
{'piid': prop.iid, 'value': str(in_list[index])})
|
||||||
continue
|
continue
|
||||||
elif prop.format_ == 'bool':
|
elif prop.format_ == bool:
|
||||||
if isinstance(in_list[index], (bool, int)):
|
if isinstance(in_list[index], (bool, int)):
|
||||||
# yes, no, on, off, true, false and other bool types
|
# yes, no, on, off, true, false and other bool types
|
||||||
# will also be parsed as 0 and 1 of int.
|
# will also be parsed as 0 and 1 of int.
|
||||||
in_value.append(
|
in_value.append(
|
||||||
{'piid': prop.iid, 'value': bool(in_list[index])})
|
{'piid': prop.iid, 'value': bool(in_list[index])})
|
||||||
continue
|
continue
|
||||||
elif prop.format_ == 'float':
|
elif prop.format_ == float:
|
||||||
if isinstance(in_list[index], (int, float)):
|
if isinstance(in_list[index], (int, float)):
|
||||||
in_value.append(
|
in_value.append(
|
||||||
{'piid': prop.iid, 'value': in_list[index]})
|
{'piid': prop.iid, 'value': in_list[index]})
|
||||||
continue
|
continue
|
||||||
elif prop.format_ == 'int':
|
elif prop.format_ == int:
|
||||||
if isinstance(in_list[index], int):
|
if isinstance(in_list[index], int):
|
||||||
in_value.append(
|
in_value.append(
|
||||||
{'piid': prop.iid, 'value': in_list[index]})
|
{'piid': prop.iid, 'value': in_list[index]})
|
||||||
|
|||||||
@ -120,28 +120,18 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
|
|||||||
# properties
|
# properties
|
||||||
for prop in entity_data.props:
|
for prop in entity_data.props:
|
||||||
if prop.name == 'status':
|
if prop.name == 'status':
|
||||||
if (
|
if not prop.value_list:
|
||||||
not isinstance(prop.value_list, list)
|
|
||||||
or not prop.value_list
|
|
||||||
):
|
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'invalid status value_list, %s', self.entity_id)
|
'invalid status value_list, %s', self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._status_map = {
|
self._status_map = prop.value_list.to_map()
|
||||||
item['value']: item['description']
|
|
||||||
for item in prop.value_list}
|
|
||||||
self._prop_status = prop
|
self._prop_status = prop
|
||||||
elif prop.name == 'fan-level':
|
elif prop.name == 'fan-level':
|
||||||
if (
|
if not prop.value_list:
|
||||||
not isinstance(prop.value_list, list)
|
|
||||||
or not prop.value_list
|
|
||||||
):
|
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'invalid fan-level value_list, %s', self.entity_id)
|
'invalid fan-level value_list, %s', self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._fan_level_map = {
|
self._fan_level_map = prop.value_list.to_map()
|
||||||
item['value']: item['description']
|
|
||||||
for item in prop.value_list}
|
|
||||||
self._attr_fan_speed_list = list(self._fan_level_map.values())
|
self._attr_fan_speed_list = list(self._fan_level_map.values())
|
||||||
self._attr_supported_features |= VacuumEntityFeature.FAN_SPEED
|
self._attr_supported_features |= VacuumEntityFeature.FAN_SPEED
|
||||||
self._prop_fan_level = prop
|
self._prop_fan_level = prop
|
||||||
@ -202,7 +192,7 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
|
|||||||
@property
|
@property
|
||||||
def state(self) -> Optional[str]:
|
def state(self) -> Optional[str]:
|
||||||
"""Return the current state of the vacuum cleaner."""
|
"""Return the current state of the vacuum cleaner."""
|
||||||
return self.get_map_description(
|
return self.get_map_value(
|
||||||
map_=self._status_map,
|
map_=self._status_map,
|
||||||
key=self.get_prop_value(prop=self._prop_status))
|
key=self.get_prop_value(prop=self._prop_status))
|
||||||
|
|
||||||
@ -214,6 +204,6 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
|
|||||||
@property
|
@property
|
||||||
def fan_speed(self) -> Optional[str]:
|
def fan_speed(self) -> Optional[str]:
|
||||||
"""Return the current fan speed of the vacuum cleaner."""
|
"""Return the current fan speed of the vacuum cleaner."""
|
||||||
return self.get_map_description(
|
return self.get_map_value(
|
||||||
map_=self._fan_level_map,
|
map_=self._fan_level_map,
|
||||||
key=self.get_prop_value(prop=self._prop_fan_level))
|
key=self.get_prop_value(prop=self._prop_fan_level))
|
||||||
|
|||||||
@ -93,7 +93,7 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
_prop_target_temp: Optional[MIoTSpecProperty]
|
_prop_target_temp: Optional[MIoTSpecProperty]
|
||||||
_prop_mode: Optional[MIoTSpecProperty]
|
_prop_mode: Optional[MIoTSpecProperty]
|
||||||
|
|
||||||
_mode_list: Optional[dict[Any, Any]]
|
_mode_map: Optional[dict[Any, Any]]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
||||||
@ -106,7 +106,7 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
self._prop_temp = None
|
self._prop_temp = None
|
||||||
self._prop_target_temp = None
|
self._prop_target_temp = None
|
||||||
self._prop_mode = None
|
self._prop_mode = None
|
||||||
self._mode_list = None
|
self._mode_map = None
|
||||||
|
|
||||||
# properties
|
# properties
|
||||||
for prop in entity_data.props:
|
for prop in entity_data.props:
|
||||||
@ -115,7 +115,7 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
self._prop_on = prop
|
self._prop_on = prop
|
||||||
# temperature
|
# temperature
|
||||||
if prop.name == 'temperature':
|
if prop.name == 'temperature':
|
||||||
if isinstance(prop.value_range, dict):
|
if prop.value_range:
|
||||||
if (
|
if (
|
||||||
self._attr_temperature_unit is None
|
self._attr_temperature_unit is None
|
||||||
and prop.external_unit
|
and prop.external_unit
|
||||||
@ -128,9 +128,14 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
self.entity_id)
|
self.entity_id)
|
||||||
# target-temperature
|
# target-temperature
|
||||||
if prop.name == 'target-temperature':
|
if prop.name == 'target-temperature':
|
||||||
self._attr_min_temp = prop.value_range['min']
|
if not prop.value_range:
|
||||||
self._attr_max_temp = prop.value_range['max']
|
_LOGGER.error(
|
||||||
self._attr_precision = prop.value_range['step']
|
'invalid target-temperature value_range format, %s',
|
||||||
|
self.entity_id)
|
||||||
|
continue
|
||||||
|
self._attr_min_temp = prop.value_range.min_
|
||||||
|
self._attr_max_temp = prop.value_range.max_
|
||||||
|
self._attr_precision = prop.value_range.step
|
||||||
if self._attr_temperature_unit is None and prop.external_unit:
|
if self._attr_temperature_unit is None and prop.external_unit:
|
||||||
self._attr_temperature_unit = prop.external_unit
|
self._attr_temperature_unit = prop.external_unit
|
||||||
self._attr_supported_features |= (
|
self._attr_supported_features |= (
|
||||||
@ -138,17 +143,12 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
self._prop_target_temp = prop
|
self._prop_target_temp = prop
|
||||||
# mode
|
# mode
|
||||||
if prop.name == 'mode':
|
if prop.name == 'mode':
|
||||||
if (
|
if not prop.value_list:
|
||||||
not isinstance(prop.value_list, list)
|
|
||||||
or not prop.value_list
|
|
||||||
):
|
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'mode value_list is None, %s', self.entity_id)
|
'mode value_list is None, %s', self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._mode_list = {
|
self._mode_map = prop.value_list.to_map()
|
||||||
item['value']: item['description']
|
self._attr_operation_list = list(self._mode_map.values())
|
||||||
for item in prop.value_list}
|
|
||||||
self._attr_operation_list = list(self._mode_list.values())
|
|
||||||
self._attr_supported_features |= (
|
self._attr_supported_features |= (
|
||||||
WaterHeaterEntityFeature.OPERATION_MODE)
|
WaterHeaterEntityFeature.OPERATION_MODE)
|
||||||
self._prop_mode = prop
|
self._prop_mode = prop
|
||||||
@ -184,7 +184,9 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
prop=self._prop_on, value=True, update=False)
|
prop=self._prop_on, value=True, update=False)
|
||||||
await self.set_property_async(
|
await self.set_property_async(
|
||||||
prop=self._prop_mode,
|
prop=self._prop_mode,
|
||||||
value=self.__get_mode_value(description=operation_mode))
|
value=self.get_map_key(
|
||||||
|
map_=self._mode_map,
|
||||||
|
value=operation_mode))
|
||||||
|
|
||||||
async def async_turn_away_mode_on(self) -> None:
|
async def async_turn_away_mode_on(self) -> None:
|
||||||
"""Set the water heater to away mode."""
|
"""Set the water heater to away mode."""
|
||||||
@ -207,20 +209,6 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
return STATE_OFF
|
return STATE_OFF
|
||||||
if not self._prop_mode and self.get_prop_value(prop=self._prop_on):
|
if not self._prop_mode and self.get_prop_value(prop=self._prop_on):
|
||||||
return STATE_ON
|
return STATE_ON
|
||||||
return self.__get_mode_description(
|
return self.get_map_value(
|
||||||
|
map_=self._mode_map,
|
||||||
key=self.get_prop_value(prop=self._prop_mode))
|
key=self.get_prop_value(prop=self._prop_mode))
|
||||||
|
|
||||||
def __get_mode_description(self, key: int) -> Optional[str]:
|
|
||||||
"""Convert mode value to description."""
|
|
||||||
if self._mode_list is None:
|
|
||||||
return None
|
|
||||||
return self._mode_list.get(key, None)
|
|
||||||
|
|
||||||
def __get_mode_value(self, description: str) -> Optional[int]:
|
|
||||||
"""Convert mode description to value."""
|
|
||||||
if self._mode_list is None:
|
|
||||||
return None
|
|
||||||
for key, value in self._mode_list.items():
|
|
||||||
if value == description:
|
|
||||||
return key
|
|
||||||
return None
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user