mirror of
https://github.com/XiaoMi/ha_xiaomi_home.git
synced 2025-03-28 21:35:29 +08:00
refactor: refactor miot device and spec (#592)
* fix: fix miot_device type error * fix: fix type error * feat: remove spec cache storage * feat: update std_lib and multi_lang logic * feat: update entity value-range * feat: update value-list logic * feat: update prop.format_ logic * fix: fix miot cloud log error * fix: fix fan entity * style: ignore type error * style: rename spec_filter func name * feat: move bool_trans from storage to spec * feat: move sepc_filter from storage to spec, use the YAML format file * feat: same prop supports multiple sub * feat: same event supports multiple sub * fix: fix device remove error * feat: add func slugify_did * fix: fix multi lang error * feat: update action debug logic * feat: ignore normal disconnect log * feat: support binary mode * feat: change miot spec name type define * style: ignore i18n tranlate type error * fix: fix pylint warning * fix: miot storage type error * feat: support binary display mode configure * feat: set default sensor state_class * fix: fix sensor entity type error * fix: fix __init__ type error * feat: update test case logic * fix: github actions add dependencies lib * fix: fix some type error * feat: update device list changed notify logic
This commit is contained in:
parent
bf116e13a4
commit
ef56448dbb
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pytest pytest-asyncio pytest-dependency zeroconf paho.mqtt psutil cryptography
|
||||
pip install pytest pytest-asyncio pytest-dependency zeroconf paho.mqtt psutil cryptography slugify
|
||||
|
||||
- name: Check rule format with pytest
|
||||
run: |
|
||||
|
@ -54,6 +54,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.components import persistent_notification
|
||||
from homeassistant.helpers import device_registry, entity_registry
|
||||
|
||||
from .miot.common import slugify_did
|
||||
from .miot.miot_storage import (
|
||||
DeviceManufacturer, MIoTStorage, MIoTCert)
|
||||
from .miot.miot_spec import (
|
||||
@ -92,7 +93,7 @@ async def async_setup_entry(
|
||||
"""Send messages in Notifications dialog box."""
|
||||
if title:
|
||||
persistent_notification.async_create(
|
||||
hass=hass, message=message,
|
||||
hass=hass, message=message or '',
|
||||
title=title, notification_id=notify_id)
|
||||
else:
|
||||
persistent_notification.async_dismiss(
|
||||
@ -125,9 +126,8 @@ async def async_setup_entry(
|
||||
miot_devices: list[MIoTDevice] = []
|
||||
er = entity_registry.async_get(hass=hass)
|
||||
for did, info in miot_client.device_list.items():
|
||||
spec_instance: MIoTSpecInstance = await spec_parser.parse(
|
||||
urn=info['urn'])
|
||||
if spec_instance is None:
|
||||
spec_instance = await spec_parser.parse(urn=info['urn'])
|
||||
if not isinstance(spec_instance, MIoTSpecInstance):
|
||||
_LOGGER.error('spec content is None, %s, %s', did, info)
|
||||
continue
|
||||
device: MIoTDevice = MIoTDevice(
|
||||
@ -155,7 +155,8 @@ async def async_setup_entry(
|
||||
for entity in filter_entities:
|
||||
device.entity_list[platform].remove(entity)
|
||||
entity_id = device.gen_service_entity_id(
|
||||
ha_domain=platform, siid=entity.spec.iid)
|
||||
ha_domain=platform,
|
||||
siid=entity.spec.iid) # type: ignore
|
||||
if er.async_get(entity_id_or_uuid=entity_id):
|
||||
er.async_remove(entity_id=entity_id)
|
||||
if platform in device.prop_list:
|
||||
@ -208,12 +209,7 @@ async def async_setup_entry(
|
||||
if er.async_get(entity_id_or_uuid=entity_id):
|
||||
er.async_remove(entity_id=entity_id)
|
||||
# Action debug
|
||||
if miot_client.action_debug:
|
||||
if 'notify' in device.action_list:
|
||||
# Add text entity for debug action
|
||||
device.action_list['action_text'] = (
|
||||
device.action_list['notify'])
|
||||
else:
|
||||
if not miot_client.action_debug:
|
||||
# Remove text entity for debug action
|
||||
for action in device.action_list.get('notify', []):
|
||||
entity_id = device.gen_action_entity_id(
|
||||
@ -221,6 +217,21 @@ async def async_setup_entry(
|
||||
siid=action.service.iid, aiid=action.iid)
|
||||
if er.async_get(entity_id_or_uuid=entity_id):
|
||||
er.async_remove(entity_id=entity_id)
|
||||
# Binary sensor display
|
||||
if not miot_client.display_binary_bool:
|
||||
for prop in device.prop_list.get('binary_sensor', []):
|
||||
entity_id = device.gen_prop_entity_id(
|
||||
ha_domain='binary_sensor', spec_name=prop.name,
|
||||
siid=prop.service.iid, piid=prop.iid)
|
||||
if er.async_get(entity_id_or_uuid=entity_id):
|
||||
er.async_remove(entity_id=entity_id)
|
||||
if not miot_client.display_binary_text:
|
||||
for prop in device.prop_list.get('binary_sensor', []):
|
||||
entity_id = device.gen_prop_entity_id(
|
||||
ha_domain='sensor', spec_name=prop.name,
|
||||
siid=prop.service.iid, piid=prop.iid)
|
||||
if er.async_get(entity_id_or_uuid=entity_id):
|
||||
er.async_remove(entity_id=entity_id)
|
||||
|
||||
hass.data[DOMAIN]['devices'][config_entry.entry_id] = miot_devices
|
||||
await hass.config_entries.async_forward_entry_setups(
|
||||
@ -237,7 +248,7 @@ async def async_setup_entry(
|
||||
device_entry = dr.async_get_device(
|
||||
identifiers={(
|
||||
DOMAIN,
|
||||
MIoTDevice.gen_did_tag(
|
||||
slugify_did(
|
||||
cloud_server=config_entry.data['cloud_server'],
|
||||
did=did))},
|
||||
connections=None)
|
||||
@ -330,21 +341,10 @@ async def async_remove_config_entry_device(
|
||||
'remove device failed, invalid domain, %s, %s',
|
||||
device_entry.id, device_entry.identifiers)
|
||||
return False
|
||||
device_info = identifiers[1].split('_')
|
||||
if len(device_info) != 2:
|
||||
_LOGGER.error(
|
||||
'remove device failed, invalid device info, %s, %s',
|
||||
device_entry.id, device_entry.identifiers)
|
||||
return False
|
||||
did = device_info[1]
|
||||
if did not in miot_client.device_list:
|
||||
_LOGGER.error(
|
||||
'remove device failed, device not found, %s, %s',
|
||||
device_entry.id, device_entry.identifiers)
|
||||
return False
|
||||
|
||||
# Remove device
|
||||
await miot_client.remove_device_async(did)
|
||||
await miot_client.remove_device2_async(did_tag=identifiers[1])
|
||||
device_registry.async_get(hass).async_remove_device(device_entry.id)
|
||||
_LOGGER.info(
|
||||
'remove device, %s, %s, %s', device_info[0], did, device_entry.id)
|
||||
'remove device, %s, %s', identifiers[1], device_entry.id)
|
||||
return True
|
||||
|
@ -68,9 +68,10 @@ async def async_setup_entry(
|
||||
|
||||
new_entities = []
|
||||
for miot_device in device_list:
|
||||
for prop in miot_device.prop_list.get('binary_sensor', []):
|
||||
new_entities.append(BinarySensor(
|
||||
miot_device=miot_device, spec=prop))
|
||||
if miot_device.miot_client.display_binary_bool:
|
||||
for prop in miot_device.prop_list.get('binary_sensor', []):
|
||||
new_entities.append(BinarySensor(
|
||||
miot_device=miot_device, spec=prop))
|
||||
|
||||
if new_entities:
|
||||
async_add_entities(new_entities)
|
||||
|
@ -156,64 +156,56 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
|
||||
_LOGGER.error(
|
||||
'unknown on property, %s', self.entity_id)
|
||||
elif prop.name == 'mode':
|
||||
if (
|
||||
not isinstance(prop.value_list, list)
|
||||
or not prop.value_list
|
||||
):
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'invalid mode value_list, %s', self.entity_id)
|
||||
continue
|
||||
self._hvac_mode_map = {}
|
||||
for item in prop.value_list:
|
||||
if item['name'].lower() in {'off', 'idle'}:
|
||||
self._hvac_mode_map[item['value']] = HVACMode.OFF
|
||||
elif item['name'].lower() in {'auto'}:
|
||||
self._hvac_mode_map[item['value']] = HVACMode.AUTO
|
||||
elif item['name'].lower() in {'cool'}:
|
||||
self._hvac_mode_map[item['value']] = HVACMode.COOL
|
||||
elif item['name'].lower() in {'heat'}:
|
||||
self._hvac_mode_map[item['value']] = HVACMode.HEAT
|
||||
elif item['name'].lower() in {'dry'}:
|
||||
self._hvac_mode_map[item['value']] = HVACMode.DRY
|
||||
elif item['name'].lower() in {'fan'}:
|
||||
self._hvac_mode_map[item['value']] = HVACMode.FAN_ONLY
|
||||
for item in prop.value_list.items:
|
||||
if item.name in {'off', 'idle'}:
|
||||
self._hvac_mode_map[item.value] = HVACMode.OFF
|
||||
elif item.name in {'auto'}:
|
||||
self._hvac_mode_map[item.value] = HVACMode.AUTO
|
||||
elif item.name in {'cool'}:
|
||||
self._hvac_mode_map[item.value] = HVACMode.COOL
|
||||
elif item.name in {'heat'}:
|
||||
self._hvac_mode_map[item.value] = HVACMode.HEAT
|
||||
elif item.name in {'dry'}:
|
||||
self._hvac_mode_map[item.value] = HVACMode.DRY
|
||||
elif item.name in {'fan'}:
|
||||
self._hvac_mode_map[item.value] = HVACMode.FAN_ONLY
|
||||
self._attr_hvac_modes = list(self._hvac_mode_map.values())
|
||||
self._prop_mode = prop
|
||||
elif prop.name == 'target-temperature':
|
||||
if not isinstance(prop.value_range, dict):
|
||||
if not prop.value_range:
|
||||
_LOGGER.error(
|
||||
'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_target_temperature_step = prop.value_range['step']
|
||||
self._attr_min_temp = prop.value_range.min_
|
||||
self._attr_max_temp = prop.value_range.max_
|
||||
self._attr_target_temperature_step = prop.value_range.step
|
||||
self._attr_temperature_unit = prop.external_unit
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE)
|
||||
self._prop_target_temp = prop
|
||||
elif prop.name == 'target-humidity':
|
||||
if not isinstance(prop.value_range, dict):
|
||||
if not prop.value_range:
|
||||
_LOGGER.error(
|
||||
'invalid target-humidity value_range format, %s',
|
||||
self.entity_id)
|
||||
continue
|
||||
self._attr_min_humidity = prop.value_range['min']
|
||||
self._attr_max_humidity = prop.value_range['max']
|
||||
self._attr_min_humidity = prop.value_range.min_
|
||||
self._attr_max_humidity = prop.value_range.max_
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TARGET_HUMIDITY)
|
||||
self._prop_target_humi = prop
|
||||
elif prop.name == 'fan-level':
|
||||
if (
|
||||
not isinstance(prop.value_list, list)
|
||||
or not prop.value_list
|
||||
):
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'invalid fan-level value_list, %s', self.entity_id)
|
||||
continue
|
||||
self._fan_mode_map = {
|
||||
item['value']: item['description']
|
||||
for item in prop.value_list}
|
||||
self._fan_mode_map = prop.value_list.to_map()
|
||||
self._attr_fan_modes = list(self._fan_mode_map.values())
|
||||
self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
|
||||
self._prop_fan_level = prop
|
||||
@ -269,8 +261,8 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
|
||||
elif self.get_prop_value(prop=self._prop_on) is False:
|
||||
await self.set_property_async(prop=self._prop_on, value=True)
|
||||
# set mode
|
||||
mode_value = self.get_map_value(
|
||||
map_=self._hvac_mode_map, description=hvac_mode)
|
||||
mode_value = self.get_map_key(
|
||||
map_=self._hvac_mode_map, value=hvac_mode)
|
||||
if (
|
||||
mode_value is None or
|
||||
not await self.set_property_async(
|
||||
@ -339,8 +331,8 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
|
||||
|
||||
async def async_set_fan_mode(self, fan_mode):
|
||||
"""Set new target fan mode."""
|
||||
mode_value = self.get_map_value(
|
||||
map_=self._fan_mode_map, description=fan_mode)
|
||||
mode_value = self.get_map_key(
|
||||
map_=self._fan_mode_map, value=fan_mode)
|
||||
if mode_value is None or not await self.set_property_async(
|
||||
prop=self._prop_fan_level, value=mode_value):
|
||||
raise RuntimeError(
|
||||
@ -376,9 +368,9 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
|
||||
"""Return the hvac mode. e.g., heat, cool mode."""
|
||||
if self.get_prop_value(prop=self._prop_on) is False:
|
||||
return HVACMode.OFF
|
||||
return self.get_map_description(
|
||||
return self.get_map_key(
|
||||
map_=self._hvac_mode_map,
|
||||
key=self.get_prop_value(prop=self._prop_mode))
|
||||
value=self.get_prop_value(prop=self._prop_mode))
|
||||
|
||||
@property
|
||||
def fan_mode(self) -> Optional[str]:
|
||||
@ -386,7 +378,7 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
|
||||
|
||||
Requires ClimateEntityFeature.FAN_MODE.
|
||||
"""
|
||||
return self.get_map_description(
|
||||
return self.get_map_value(
|
||||
map_=self._fan_mode_map,
|
||||
key=self.get_prop_value(prop=self._prop_fan_level))
|
||||
|
||||
@ -446,8 +438,8 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
|
||||
}.get(v_ac_state['M'], None)
|
||||
if mode:
|
||||
self.set_prop_value(
|
||||
prop=self._prop_mode, value=self.get_map_value(
|
||||
map_=self._hvac_mode_map, description=mode))
|
||||
prop=self._prop_mode, value=self.get_map_key(
|
||||
map_=self._hvac_mode_map, value=mode))
|
||||
# T: target temperature
|
||||
if 'T' in v_ac_state and self._prop_target_temp:
|
||||
self.set_prop_value(prop=self._prop_target_temp,
|
||||
@ -517,29 +509,24 @@ class Heater(MIoTServiceEntity, ClimateEntity):
|
||||
ClimateEntityFeature.TURN_OFF)
|
||||
self._prop_on = prop
|
||||
elif prop.name == 'target-temperature':
|
||||
if not isinstance(prop.value_range, dict):
|
||||
if not prop.value_range:
|
||||
_LOGGER.error(
|
||||
'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_target_temperature_step = prop.value_range['step']
|
||||
self._attr_min_temp = prop.value_range.min_
|
||||
self._attr_max_temp = prop.value_range.max_
|
||||
self._attr_target_temperature_step = prop.value_range.step
|
||||
self._attr_temperature_unit = prop.external_unit
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE)
|
||||
self._prop_target_temp = prop
|
||||
elif prop.name == 'heat-level':
|
||||
if (
|
||||
not isinstance(prop.value_list, list)
|
||||
or not prop.value_list
|
||||
):
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'invalid heat-level value_list, %s', self.entity_id)
|
||||
continue
|
||||
self._heat_level_map = {
|
||||
item['value']: item['description']
|
||||
for item in prop.value_list}
|
||||
self._heat_level_map = prop.value_list.to_map()
|
||||
self._attr_preset_modes = list(self._heat_level_map.values())
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.PRESET_MODE)
|
||||
@ -582,8 +569,8 @@ class Heater(MIoTServiceEntity, ClimateEntity):
|
||||
"""Set the preset mode."""
|
||||
await self.set_property_async(
|
||||
self._prop_heat_level,
|
||||
value=self.get_map_value(
|
||||
map_=self._heat_level_map, description=preset_mode))
|
||||
value=self.get_map_key(
|
||||
map_=self._heat_level_map, value=preset_mode))
|
||||
|
||||
@property
|
||||
def target_temperature(self) -> Optional[float]:
|
||||
@ -613,7 +600,7 @@ class Heater(MIoTServiceEntity, ClimateEntity):
|
||||
@property
|
||||
def preset_mode(self) -> Optional[str]:
|
||||
return (
|
||||
self.get_map_description(
|
||||
self.get_map_value(
|
||||
map_=self._heat_level_map,
|
||||
key=self.get_prop_value(prop=self._prop_heat_level))
|
||||
if self._prop_heat_level else None)
|
||||
|
@ -124,6 +124,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
_area_name_rule: str
|
||||
_action_debug: bool
|
||||
_hide_non_standard_entities: bool
|
||||
_display_binary_mode: list[str]
|
||||
_display_devices_changed_notify: list[str]
|
||||
|
||||
_cloud_server: str
|
||||
@ -158,6 +159,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self._area_name_rule = self.DEFAULT_AREA_NAME_RULE
|
||||
self._action_debug = False
|
||||
self._hide_non_standard_entities = False
|
||||
self._display_binary_mode = ['bool']
|
||||
self._display_devices_changed_notify = ['add', 'del', 'offline']
|
||||
self._auth_info = {}
|
||||
self._nick_name = DEFAULT_NICK_NAME
|
||||
@ -473,6 +475,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
await self._miot_oauth.deinit_async()
|
||||
self._miot_oauth = None
|
||||
return self.async_show_progress_done(next_step_id='homes_select')
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
return self.async_show_progress(
|
||||
step_id='oauth',
|
||||
progress_action='oauth',
|
||||
@ -481,7 +484,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
f'<a href="{self._cc_oauth_auth_url}" target="_blank">',
|
||||
'link_right': '</a>'
|
||||
},
|
||||
progress_task=self._cc_task_oauth,
|
||||
progress_task=self._cc_task_oauth, # type: ignore
|
||||
)
|
||||
|
||||
async def __check_oauth_async(self) -> None:
|
||||
@ -727,6 +730,8 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
'action_debug', self._action_debug)
|
||||
self._hide_non_standard_entities = user_input.get(
|
||||
'hide_non_standard_entities', self._hide_non_standard_entities)
|
||||
self._display_binary_mode = user_input.get(
|
||||
'display_binary_mode', self._display_binary_mode)
|
||||
self._display_devices_changed_notify = user_input.get(
|
||||
'display_devices_changed_notify',
|
||||
self._display_devices_changed_notify)
|
||||
@ -749,6 +754,12 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
'hide_non_standard_entities',
|
||||
default=self._hide_non_standard_entities # type: ignore
|
||||
): bool,
|
||||
vol.Required(
|
||||
'display_binary_mode',
|
||||
default=self._display_binary_mode # type: ignore
|
||||
): cv.multi_select(
|
||||
self._miot_i18n.translate(
|
||||
key='config.binary_mode')), # type: ignore
|
||||
vol.Required(
|
||||
'display_devices_changed_notify',
|
||||
default=self._display_devices_changed_notify # type: ignore
|
||||
@ -931,6 +942,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
'action_debug': self._action_debug,
|
||||
'hide_non_standard_entities':
|
||||
self._hide_non_standard_entities,
|
||||
'display_binary_mode': self._display_binary_mode,
|
||||
'display_devices_changed_notify':
|
||||
self._display_devices_changed_notify
|
||||
})
|
||||
@ -972,6 +984,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
_devices_filter: dict
|
||||
_action_debug: bool
|
||||
_hide_non_standard_entities: bool
|
||||
_display_binary_mode: list[str]
|
||||
_display_devs_notify: list[str]
|
||||
|
||||
_oauth_redirect_url_full: str
|
||||
@ -986,6 +999,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
_nick_name_new: Optional[str]
|
||||
_action_debug_new: bool
|
||||
_hide_non_standard_entities_new: bool
|
||||
_display_binary_mode_new: list[str]
|
||||
_update_user_info: bool
|
||||
_update_devices: bool
|
||||
_update_trans_rules: bool
|
||||
@ -1024,6 +1038,8 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
self._action_debug = self._entry_data.get('action_debug', False)
|
||||
self._hide_non_standard_entities = self._entry_data.get(
|
||||
'hide_non_standard_entities', False)
|
||||
self._display_binary_mode = self._entry_data.get(
|
||||
'display_binary_mode', ['text'])
|
||||
self._display_devs_notify = self._entry_data.get(
|
||||
'display_devices_changed_notify', ['add', 'del', 'offline'])
|
||||
self._home_selected_list = list(
|
||||
@ -1042,6 +1058,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
self._nick_name_new = None
|
||||
self._action_debug_new = False
|
||||
self._hide_non_standard_entities_new = False
|
||||
self._display_binary_mode_new = []
|
||||
self._update_user_info = False
|
||||
self._update_devices = False
|
||||
self._update_trans_rules = False
|
||||
@ -1196,7 +1213,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
err, traceback.format_exc())
|
||||
self._cc_config_rc = str(err)
|
||||
return self.async_show_progress_done(next_step_id='oauth_error')
|
||||
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
return self.async_show_progress(
|
||||
step_id='oauth',
|
||||
progress_action='oauth',
|
||||
@ -1205,7 +1222,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
f'<a href="{self._cc_oauth_auth_url}" target="_blank">',
|
||||
'link_right': '</a>'
|
||||
},
|
||||
progress_task=self._cc_task_oauth,
|
||||
progress_task=self._cc_task_oauth, # type: ignore
|
||||
)
|
||||
|
||||
async def __check_oauth_async(self) -> None:
|
||||
@ -1308,6 +1325,12 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
'hide_non_standard_entities',
|
||||
default=self._hide_non_standard_entities # type: ignore
|
||||
): bool,
|
||||
vol.Required(
|
||||
'display_binary_mode',
|
||||
default=self._display_binary_mode # type: ignore
|
||||
): cv.multi_select(
|
||||
self._miot_i18n.translate(
|
||||
'config.binary_mode')), # type: ignore
|
||||
vol.Required(
|
||||
'update_trans_rules',
|
||||
default=self._update_trans_rules # type: ignore
|
||||
@ -1336,6 +1359,8 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
'action_debug', self._action_debug)
|
||||
self._hide_non_standard_entities_new = user_input.get(
|
||||
'hide_non_standard_entities', self._hide_non_standard_entities)
|
||||
self._display_binary_mode_new = user_input.get(
|
||||
'display_binary_mode', self._display_binary_mode)
|
||||
self._display_devs_notify = user_input.get(
|
||||
'display_devices_changed_notify', self._display_devs_notify)
|
||||
self._update_trans_rules = user_input.get(
|
||||
@ -1939,6 +1964,10 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
self._entry_data['hide_non_standard_entities'] = (
|
||||
self._hide_non_standard_entities_new)
|
||||
self._need_reload = True
|
||||
if set(self._display_binary_mode) != set(self._display_binary_mode_new):
|
||||
self._entry_data['display_binary_mode'] = (
|
||||
self._display_binary_mode_new)
|
||||
self._need_reload = True
|
||||
# Update display_devices_changed_notify
|
||||
self._entry_data['display_devices_changed_notify'] = (
|
||||
self._display_devs_notify)
|
||||
|
@ -132,53 +132,47 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'motor-control':
|
||||
if (
|
||||
not isinstance(prop.value_list, list)
|
||||
or not prop.value_list
|
||||
):
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'motor-control value_list is None, %s', self.entity_id)
|
||||
continue
|
||||
for item in prop.value_list:
|
||||
if item['name'].lower() in ['open']:
|
||||
for item in prop.value_list.items:
|
||||
if item.name in {'open'}:
|
||||
self._attr_supported_features |= (
|
||||
CoverEntityFeature.OPEN)
|
||||
self._prop_motor_value_open = item['value']
|
||||
elif item['name'].lower() in ['close']:
|
||||
self._prop_motor_value_open = item.value
|
||||
elif item.name in {'close'}:
|
||||
self._attr_supported_features |= (
|
||||
CoverEntityFeature.CLOSE)
|
||||
self._prop_motor_value_close = item['value']
|
||||
elif item['name'].lower() in ['pause']:
|
||||
self._prop_motor_value_close = item.value
|
||||
elif item.name in {'pause'}:
|
||||
self._attr_supported_features |= (
|
||||
CoverEntityFeature.STOP)
|
||||
self._prop_motor_value_pause = item['value']
|
||||
self._prop_motor_value_pause = item.value
|
||||
self._prop_motor_control = prop
|
||||
elif prop.name == 'status':
|
||||
if (
|
||||
not isinstance(prop.value_list, list)
|
||||
or not prop.value_list
|
||||
):
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'status value_list is None, %s', self.entity_id)
|
||||
continue
|
||||
for item in prop.value_list:
|
||||
if item['name'].lower() in ['opening', 'open']:
|
||||
self._prop_status_opening = item['value']
|
||||
elif item['name'].lower() in ['closing', 'close']:
|
||||
self._prop_status_closing = item['value']
|
||||
elif item['name'].lower() in ['stop', 'pause']:
|
||||
self._prop_status_stop = item['value']
|
||||
for item in prop.value_list.items:
|
||||
if item.name in {'opening', 'open'}:
|
||||
self._prop_status_opening = item.value
|
||||
elif item.name in {'closing', 'close'}:
|
||||
self._prop_status_closing = item.value
|
||||
elif item.name in {'stop', 'pause'}:
|
||||
self._prop_status_stop = item.value
|
||||
self._prop_status = prop
|
||||
elif prop.name == 'current-position':
|
||||
self._prop_current_position = prop
|
||||
elif prop.name == 'target-position':
|
||||
if not isinstance(prop.value_range, dict):
|
||||
if not prop.value_range:
|
||||
_LOGGER.error(
|
||||
'invalid target-position value_range format, %s',
|
||||
self.entity_id)
|
||||
continue
|
||||
self._prop_position_value_min = prop.value_range['min']
|
||||
self._prop_position_value_max = prop.value_range['max']
|
||||
self._prop_position_value_min = prop.value_range.min_
|
||||
self._prop_position_value_max = prop.value_range.max_
|
||||
self._prop_position_value_range = (
|
||||
self._prop_position_value_max -
|
||||
self._prop_position_value_min)
|
||||
|
@ -85,6 +85,8 @@ class Event(MIoTEventEntity, EventEntity):
|
||||
# Set device_class
|
||||
self._attr_device_class = spec.device_class
|
||||
|
||||
def on_event_occurred(self, name: str, arguments: list[dict[int, Any]]):
|
||||
def on_event_occurred(
|
||||
self, name: str, arguments: dict[str, Any] | None = None
|
||||
) -> None:
|
||||
"""An event is occurred."""
|
||||
self._trigger_event(event_type=name, event_attributes=arguments)
|
||||
|
@ -87,7 +87,7 @@ async def async_setup_entry(
|
||||
class Fan(MIoTServiceEntity, FanEntity):
|
||||
"""Fan entities for Xiaomi Home."""
|
||||
# pylint: disable=unused-argument
|
||||
_prop_on: Optional[MIoTSpecProperty]
|
||||
_prop_on: MIoTSpecProperty
|
||||
_prop_fan_level: Optional[MIoTSpecProperty]
|
||||
_prop_mode: Optional[MIoTSpecProperty]
|
||||
_prop_horizontal_swing: Optional[MIoTSpecProperty]
|
||||
@ -100,7 +100,7 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
_speed_step: int
|
||||
_speed_names: Optional[list]
|
||||
_speed_name_map: Optional[dict[int, str]]
|
||||
_mode_list: Optional[dict[Any, Any]]
|
||||
_mode_map: Optional[dict[Any, Any]]
|
||||
|
||||
def __init__(
|
||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
||||
@ -111,7 +111,7 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
self._attr_current_direction = None
|
||||
self._attr_supported_features = FanEntityFeature(0)
|
||||
|
||||
self._prop_on = None
|
||||
# _prop_on is required
|
||||
self._prop_fan_level = None
|
||||
self._prop_mode = None
|
||||
self._prop_horizontal_swing = None
|
||||
@ -124,7 +124,7 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
self._speed_names = []
|
||||
self._speed_name_map = {}
|
||||
|
||||
self._mode_list = None
|
||||
self._mode_map = None
|
||||
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
@ -133,42 +133,34 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
self._attr_supported_features |= FanEntityFeature.TURN_OFF
|
||||
self._prop_on = prop
|
||||
elif prop.name == 'fan-level':
|
||||
if isinstance(prop.value_range, dict):
|
||||
if prop.value_range:
|
||||
# Fan level with value-range
|
||||
self._speed_min = prop.value_range['min']
|
||||
self._speed_max = prop.value_range['max']
|
||||
self._speed_step = prop.value_range['step']
|
||||
self._speed_min = prop.value_range.min_
|
||||
self._speed_max = prop.value_range.max_
|
||||
self._speed_step = prop.value_range.step
|
||||
self._attr_speed_count = int((
|
||||
self._speed_max - self._speed_min)/self._speed_step)+1
|
||||
self._attr_supported_features |= FanEntityFeature.SET_SPEED
|
||||
self._prop_fan_level = prop
|
||||
elif (
|
||||
self._prop_fan_level is None
|
||||
and isinstance(prop.value_list, list)
|
||||
and prop.value_list
|
||||
):
|
||||
# Fan level with value-list
|
||||
# Fan level with value-range is prior to fan level with
|
||||
# value-list when a fan has both fan level properties.
|
||||
self._speed_name_map = {
|
||||
item['value']: item['description']
|
||||
for item in prop.value_list}
|
||||
self._speed_name_map = prop.value_list.to_map()
|
||||
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._prop_fan_level = prop
|
||||
elif prop.name == 'mode':
|
||||
if (
|
||||
not isinstance(prop.value_list, list)
|
||||
or not prop.value_list
|
||||
):
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'mode value_list is None, %s', self.entity_id)
|
||||
continue
|
||||
self._mode_list = {
|
||||
item['value']: item['description']
|
||||
for item in prop.value_list}
|
||||
self._attr_preset_modes = list(self._mode_list.values())
|
||||
self._mode_map = prop.value_list.to_map()
|
||||
self._attr_preset_modes = list(self._mode_map.values())
|
||||
self._attr_supported_features |= FanEntityFeature.PRESET_MODE
|
||||
self._prop_mode = prop
|
||||
elif prop.name == 'horizontal-swing':
|
||||
@ -178,16 +170,11 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
if prop.format_ == 'bool':
|
||||
self._prop_wind_reverse_forward = False
|
||||
self._prop_wind_reverse_reverse = True
|
||||
elif (
|
||||
isinstance(prop.value_list, list)
|
||||
and prop.value_list
|
||||
):
|
||||
for item in prop.value_list:
|
||||
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']
|
||||
elif prop.value_list:
|
||||
for item in prop.value_list.items:
|
||||
if item.name in {'foreward'}:
|
||||
self._prop_wind_reverse_forward = item.value
|
||||
self._prop_wind_reverse_reverse = item.value
|
||||
if (
|
||||
self._prop_wind_reverse_forward 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._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(
|
||||
self, percentage: int = None, preset_mode: str = None, **kwargs: Any
|
||||
self, percentage: Optional[int] = None,
|
||||
preset_mode: Optional[str] = None, **kwargs: Any
|
||||
) -> None:
|
||||
"""Turn the fan on.
|
||||
|
||||
@ -225,12 +200,12 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
# percentage
|
||||
if percentage:
|
||||
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(
|
||||
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:
|
||||
await self.set_property_async(
|
||||
prop=self._prop_fan_level,
|
||||
@ -241,7 +216,8 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
if preset_mode:
|
||||
await self.set_property_async(
|
||||
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:
|
||||
"""Turn the fan off."""
|
||||
@ -255,12 +231,12 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
"""Set the percentage of the fan speed."""
|
||||
if percentage > 0:
|
||||
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(
|
||||
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:
|
||||
await self.set_property_async(
|
||||
prop=self._prop_fan_level,
|
||||
@ -277,7 +253,8 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
"""Set the preset mode."""
|
||||
await self.set_property_async(
|
||||
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:
|
||||
"""Set the direction of the fan."""
|
||||
@ -306,7 +283,8 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
"""Return the current preset mode,
|
||||
e.g., auto, smart, eco, favorite."""
|
||||
return (
|
||||
self.__get_mode_description(
|
||||
self.get_map_value(
|
||||
map_=self._mode_map,
|
||||
key=self.get_prop_value(prop=self._prop_mode))
|
||||
if self._prop_mode else None)
|
||||
|
||||
|
@ -97,7 +97,7 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
||||
_prop_target_humidity: Optional[MIoTSpecProperty]
|
||||
_prop_humidity: Optional[MIoTSpecProperty]
|
||||
|
||||
_mode_list: dict[Any, Any]
|
||||
_mode_map: dict[Any, Any]
|
||||
|
||||
def __init__(
|
||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
||||
@ -110,7 +110,7 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
||||
self._prop_mode = None
|
||||
self._prop_target_humidity = None
|
||||
self._prop_humidity = None
|
||||
self._mode_list = None
|
||||
self._mode_map = None
|
||||
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
@ -119,28 +119,23 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
||||
self._prop_on = prop
|
||||
# target-humidity
|
||||
elif prop.name == 'target-humidity':
|
||||
if not isinstance(prop.value_range, dict):
|
||||
if not prop.value_range:
|
||||
_LOGGER.error(
|
||||
'invalid target-humidity value_range format, %s',
|
||||
self.entity_id)
|
||||
continue
|
||||
self._attr_min_humidity = prop.value_range['min']
|
||||
self._attr_max_humidity = prop.value_range['max']
|
||||
self._attr_min_humidity = prop.value_range.min_
|
||||
self._attr_max_humidity = prop.value_range.max_
|
||||
self._prop_target_humidity = prop
|
||||
# mode
|
||||
elif prop.name == 'mode':
|
||||
if (
|
||||
not isinstance(prop.value_list, list)
|
||||
or not prop.value_list
|
||||
):
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'mode value_list is None, %s', self.entity_id)
|
||||
continue
|
||||
self._mode_list = {
|
||||
item['value']: item['description']
|
||||
for item in prop.value_list}
|
||||
self._mode_map = prop.value_list.to_map()
|
||||
self._attr_available_modes = list(
|
||||
self._mode_list.values())
|
||||
self._mode_map.values())
|
||||
self._attr_supported_features |= HumidifierEntityFeature.MODES
|
||||
self._prop_mode = prop
|
||||
# relative-humidity
|
||||
@ -163,7 +158,8 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
||||
async def async_set_mode(self, mode: str) -> None:
|
||||
"""Set new target preset mode."""
|
||||
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
|
||||
def is_on(self) -> Optional[bool]:
|
||||
@ -183,20 +179,6 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
||||
@property
|
||||
def mode(self) -> Optional[str]:
|
||||
"""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))
|
||||
|
||||
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."""
|
||||
# pylint: disable=unused-argument
|
||||
_VALUE_RANGE_MODE_COUNT_MAX = 30
|
||||
_prop_on: Optional[MIoTSpecProperty]
|
||||
_prop_on: MIoTSpecProperty
|
||||
_prop_brightness: Optional[MIoTSpecProperty]
|
||||
_prop_color_temp: Optional[MIoTSpecProperty]
|
||||
_prop_color: Optional[MIoTSpecProperty]
|
||||
_prop_mode: Optional[MIoTSpecProperty]
|
||||
|
||||
_brightness_scale: Optional[tuple[int, int]]
|
||||
_mode_list: Optional[dict[Any, Any]]
|
||||
_mode_map: Optional[dict[Any, Any]]
|
||||
|
||||
def __init__(
|
||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
||||
@ -122,7 +122,7 @@ class Light(MIoTServiceEntity, LightEntity):
|
||||
self._prop_color = None
|
||||
self._prop_mode = None
|
||||
self._brightness_scale = None
|
||||
self._mode_list = None
|
||||
self._mode_map = None
|
||||
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
@ -131,20 +131,17 @@ class Light(MIoTServiceEntity, LightEntity):
|
||||
self._prop_on = prop
|
||||
# brightness
|
||||
if prop.name == 'brightness':
|
||||
if isinstance(prop.value_range, dict):
|
||||
if prop.value_range:
|
||||
self._brightness_scale = (
|
||||
prop.value_range['min'], prop.value_range['max'])
|
||||
prop.value_range.min_, prop.value_range.max_)
|
||||
self._prop_brightness = prop
|
||||
elif (
|
||||
self._mode_list is None
|
||||
and isinstance(prop.value_list, list)
|
||||
self._mode_map is None
|
||||
and prop.value_list
|
||||
):
|
||||
# For value-list brightness
|
||||
self._mode_list = {
|
||||
item['value']: item['description']
|
||||
for item in prop.value_list}
|
||||
self._attr_effect_list = list(self._mode_list.values())
|
||||
self._mode_map = prop.value_list.to_map()
|
||||
self._attr_effect_list = list(self._mode_map.values())
|
||||
self._attr_supported_features |= LightEntityFeature.EFFECT
|
||||
self._prop_mode = prop
|
||||
else:
|
||||
@ -153,13 +150,13 @@ class Light(MIoTServiceEntity, LightEntity):
|
||||
continue
|
||||
# color-temperature
|
||||
if prop.name == 'color-temperature':
|
||||
if not isinstance(prop.value_range, dict):
|
||||
if not prop.value_range:
|
||||
_LOGGER.info(
|
||||
'invalid color-temperature value_range format, %s',
|
||||
self.entity_id)
|
||||
continue
|
||||
self._attr_min_color_temp_kelvin = prop.value_range['min']
|
||||
self._attr_max_color_temp_kelvin = prop.value_range['max']
|
||||
self._attr_min_color_temp_kelvin = prop.value_range.min_
|
||||
self._attr_max_color_temp_kelvin = prop.value_range.max_
|
||||
self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP)
|
||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||
self._prop_color_temp = prop
|
||||
@ -171,20 +168,15 @@ class Light(MIoTServiceEntity, LightEntity):
|
||||
# mode
|
||||
if prop.name == 'mode':
|
||||
mode_list = None
|
||||
if (
|
||||
isinstance(prop.value_list, list)
|
||||
and prop.value_list
|
||||
):
|
||||
mode_list = {
|
||||
item['value']: item['description']
|
||||
for item in prop.value_list}
|
||||
elif isinstance(prop.value_range, dict):
|
||||
if prop.value_list:
|
||||
mode_list = prop.value_list.to_map()
|
||||
elif prop.value_range:
|
||||
mode_list = {}
|
||||
if (
|
||||
int((
|
||||
prop.value_range['max']
|
||||
- prop.value_range['min']
|
||||
) / prop.value_range['step'])
|
||||
prop.value_range.max_
|
||||
- prop.value_range.min_
|
||||
) / prop.value_range.step)
|
||||
> self._VALUE_RANGE_MODE_COUNT_MAX
|
||||
):
|
||||
_LOGGER.info(
|
||||
@ -192,13 +184,13 @@ class Light(MIoTServiceEntity, LightEntity):
|
||||
self.entity_id, prop.name, prop.value_range)
|
||||
else:
|
||||
for value in range(
|
||||
prop.value_range['min'],
|
||||
prop.value_range['max'],
|
||||
prop.value_range['step']):
|
||||
prop.value_range.min_,
|
||||
prop.value_range.max_,
|
||||
prop.value_range.step):
|
||||
mode_list[value] = f'mode {value}'
|
||||
if mode_list:
|
||||
self._mode_list = mode_list
|
||||
self._attr_effect_list = list(self._mode_list.values())
|
||||
self._mode_map = mode_list
|
||||
self._attr_effect_list = list(self._mode_map.values())
|
||||
self._attr_supported_features |= LightEntityFeature.EFFECT
|
||||
self._prop_mode = prop
|
||||
else:
|
||||
@ -213,21 +205,6 @@ class Light(MIoTServiceEntity, LightEntity):
|
||||
self._attr_supported_color_modes.add(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
|
||||
def is_on(self) -> Optional[bool]:
|
||||
"""Return if the light is on."""
|
||||
@ -264,7 +241,8 @@ class Light(MIoTServiceEntity, LightEntity):
|
||||
@property
|
||||
def effect(self) -> Optional[str]:
|
||||
"""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))
|
||||
|
||||
async def async_turn_on(self, **kwargs) -> None:
|
||||
@ -275,7 +253,7 @@ class Light(MIoTServiceEntity, LightEntity):
|
||||
result: bool = False
|
||||
# on
|
||||
# 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(
|
||||
prop=self._prop_on, value=value_on)
|
||||
# brightness
|
||||
@ -303,11 +281,12 @@ class Light(MIoTServiceEntity, LightEntity):
|
||||
if ATTR_EFFECT in kwargs:
|
||||
result = await self.set_property_async(
|
||||
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
|
||||
|
||||
async def async_turn_off(self, **kwargs) -> None:
|
||||
"""Turn the light off."""
|
||||
# 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)
|
||||
|
@ -45,13 +45,17 @@ off Xiaomi or its affiliates' products.
|
||||
|
||||
Common utilities.
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
from os import path
|
||||
import random
|
||||
from typing import Any, Optional
|
||||
import hashlib
|
||||
from urllib.parse import urlencode
|
||||
from urllib.request import Request, urlopen
|
||||
from paho.mqtt.matcher import MQTTMatcher
|
||||
import yaml
|
||||
from slugify import slugify
|
||||
|
||||
MIOT_ROOT_PATH: str = path.dirname(path.abspath(__file__))
|
||||
|
||||
@ -83,10 +87,22 @@ def randomize_int(value: int, ratio: float) -> int:
|
||||
"""Randomize an integer value."""
|
||||
return int(value * (1 - ratio + random.random()*2*ratio))
|
||||
|
||||
|
||||
def randomize_float(value: float, ratio: float) -> float:
|
||||
"""Randomize a float value."""
|
||||
return value * (1 - ratio + random.random()*2*ratio)
|
||||
|
||||
|
||||
def slugify_name(name: str, separator: str = '_') -> str:
|
||||
"""Slugify a name."""
|
||||
return slugify(name, separator=separator)
|
||||
|
||||
|
||||
def slugify_did(cloud_server: str, did: str) -> str:
|
||||
"""Slugify a device id."""
|
||||
return slugify(f'{cloud_server}_{did}', separator='_')
|
||||
|
||||
|
||||
class MIoTMatcher(MQTTMatcher):
|
||||
"""MIoT Pub/Sub topic matcher."""
|
||||
|
||||
@ -105,3 +121,68 @@ class MIoTMatcher(MQTTMatcher):
|
||||
return self[topic]
|
||||
except KeyError:
|
||||
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)
|
||||
|
@ -54,6 +54,10 @@
|
||||
"enable": "aktivieren",
|
||||
"disable": "deaktivieren"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Textsensor-Entität",
|
||||
"bool": "Binärsensor-Entität"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "hinzufügen",
|
||||
"del": "nicht verfügbar",
|
||||
|
@ -54,6 +54,10 @@
|
||||
"enable": "Enable",
|
||||
"disable": "Disable"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Text Sensor Entity",
|
||||
"bool": "Binary Sensor Entity"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "Add",
|
||||
"del": "Unavailable",
|
||||
|
@ -54,6 +54,10 @@
|
||||
"enable": "habilitar",
|
||||
"disable": "deshabilitar"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Entidad del sensor de texto",
|
||||
"bool": "Entidad del sensor binario"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "agregar",
|
||||
"del": "no disponible",
|
||||
|
@ -54,6 +54,10 @@
|
||||
"enable": "activer",
|
||||
"disable": "désactiver"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Entité du capteur de texte",
|
||||
"bool": "Entité du capteur binaire"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "Ajouter",
|
||||
"del": "Supprimer",
|
||||
|
@ -54,6 +54,10 @@
|
||||
"enable": "Abilita",
|
||||
"disable": "Disabilita"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Entità Sensore Testo",
|
||||
"bool": "Entità Sensore Binario"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "Aggiungere",
|
||||
"del": "Non disponibile",
|
||||
@ -149,4 +153,4 @@
|
||||
"-706014006": "Descrizione del dispositivo non trovata"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -54,6 +54,10 @@
|
||||
"enable": "有効",
|
||||
"disable": "無効"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "テキストセンサーエンティティ",
|
||||
"bool": "バイナリセンサーエンティティ"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "追加",
|
||||
"del": "利用不可",
|
||||
|
@ -54,6 +54,10 @@
|
||||
"enable": "Inschakelen",
|
||||
"disable": "Uitschakelen"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Tekstsensor-entiteit",
|
||||
"bool": "Binairesensor-entiteit"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "Toevoegen",
|
||||
"del": "Niet beschikbaar",
|
||||
|
@ -54,6 +54,10 @@
|
||||
"enable": "habilitado",
|
||||
"disable": "desabilitado"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Entidade do sensor de texto",
|
||||
"bool": "Entidade do sensor binário"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "adicionar",
|
||||
"del": "indisponível",
|
||||
|
@ -54,6 +54,10 @@
|
||||
"enable": "Habilitar",
|
||||
"disable": "Desabilitar"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Entidade do sensor de texto",
|
||||
"bool": "Entidade do sensor binário"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "Adicionar",
|
||||
"del": "Indisponível",
|
||||
|
@ -54,6 +54,10 @@
|
||||
"enable": "Включить",
|
||||
"disable": "Отключить"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Сущность текстового датчика",
|
||||
"bool": "Сущность бинарного датчика"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "Добавить",
|
||||
"del": "Недоступно",
|
||||
|
@ -54,6 +54,10 @@
|
||||
"enable": "启用",
|
||||
"disable": "禁用"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "文本传感器实体",
|
||||
"bool": "二进制传感器实体"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "新增",
|
||||
"del": "不可用",
|
||||
|
@ -54,6 +54,10 @@
|
||||
"enable": "啟用",
|
||||
"disable": "禁用"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "文本傳感器實體",
|
||||
"bool": "二進制傳感器實體"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "新增",
|
||||
"del": "不可用",
|
||||
|
@ -59,7 +59,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.components import zeroconf
|
||||
|
||||
# pylint: disable=relative-beyond-top-level
|
||||
from .common import MIoTMatcher
|
||||
from .common import MIoTMatcher, slugify_did
|
||||
from .const import (
|
||||
DEFAULT_CTRL_MODE, DEFAULT_INTEGRATION_LANGUAGE, DEFAULT_NICK_NAME, DOMAIN,
|
||||
MIHOME_CERT_EXPIRE_MARGIN, NETWORK_REFRESH_INTERVAL,
|
||||
@ -150,7 +150,7 @@ class MIoTClient:
|
||||
# Device list update timestamp
|
||||
_device_list_update_ts: int
|
||||
|
||||
_sub_source_list: dict[str]
|
||||
_sub_source_list: dict[str, str]
|
||||
_sub_tree: MIoTMatcher
|
||||
_sub_device_state: dict[str, MipsDeviceState]
|
||||
|
||||
@ -169,6 +169,10 @@ class MIoTClient:
|
||||
_show_devices_changed_notify_timer: Optional[asyncio.TimerHandle]
|
||||
# Display devices changed notify
|
||||
_display_devs_notify: list[str]
|
||||
_display_notify_content_hash: Optional[int]
|
||||
# Display binary mode
|
||||
_display_binary_text: bool
|
||||
_display_binary_bool: bool
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -235,6 +239,11 @@ class MIoTClient:
|
||||
|
||||
self._display_devs_notify = entry_data.get(
|
||||
'display_devices_changed_notify', ['add', 'del', 'offline'])
|
||||
self._display_notify_content_hash = None
|
||||
self._display_binary_text = 'text' in entry_data.get(
|
||||
'display_binary_mode', ['text'])
|
||||
self._display_binary_bool = 'bool' in entry_data.get(
|
||||
'display_binary_mode', ['text'])
|
||||
|
||||
async def init_async(self) -> None:
|
||||
# Load user config and check
|
||||
@ -469,6 +478,14 @@ class MIoTClient:
|
||||
def display_devices_changed_notify(self) -> list[str]:
|
||||
return self._display_devs_notify
|
||||
|
||||
@property
|
||||
def display_binary_text(self) -> bool:
|
||||
return self._display_binary_text
|
||||
|
||||
@property
|
||||
def display_binary_bool(self) -> bool:
|
||||
return self._display_binary_bool
|
||||
|
||||
@display_devices_changed_notify.setter
|
||||
def display_devices_changed_notify(self, value: list[str]) -> None:
|
||||
if set(value) == set(self._display_devs_notify):
|
||||
@ -543,7 +560,8 @@ class MIoTClient:
|
||||
return True
|
||||
except Exception as err:
|
||||
self.__show_client_error_notify(
|
||||
message=self._i18n.translate('miot.client.invalid_oauth_info'),
|
||||
message=self._i18n.translate(
|
||||
'miot.client.invalid_oauth_info'), # type: ignore
|
||||
notify_key='oauth_info')
|
||||
_LOGGER.error(
|
||||
'refresh oauth info error (%s, %s), %s, %s',
|
||||
@ -586,7 +604,8 @@ class MIoTClient:
|
||||
return True
|
||||
except MIoTClientError as error:
|
||||
self.__show_client_error_notify(
|
||||
message=self._i18n.translate('miot.client.invalid_cert_info'),
|
||||
message=self._i18n.translate(
|
||||
'miot.client.invalid_cert_info'), # type: ignore
|
||||
notify_key='user_cert')
|
||||
_LOGGER.error(
|
||||
'refresh user cert error, %s, %s',
|
||||
@ -872,8 +891,16 @@ class MIoTClient:
|
||||
# Update notify
|
||||
self.__request_show_devices_changed_notify()
|
||||
|
||||
async def remove_device2_async(self, did_tag: str) -> None:
|
||||
for did in self._device_list_cache:
|
||||
d_tag = slugify_did(cloud_server=self._cloud_server, did=did)
|
||||
if did_tag == d_tag:
|
||||
await self.remove_device_async(did)
|
||||
break
|
||||
|
||||
def __get_exec_error_with_rc(self, rc: int) -> str:
|
||||
err_msg: str = self._i18n.translate(key=f'error.common.{rc}')
|
||||
err_msg: str = self._i18n.translate(
|
||||
key=f'error.common.{rc}') # type: ignore
|
||||
if not err_msg:
|
||||
err_msg = f'{self._i18n.translate(key="error.common.-10000")}, '
|
||||
err_msg += f'code={rc}'
|
||||
@ -1280,7 +1307,7 @@ class MIoTClient:
|
||||
if not cache_list:
|
||||
self.__show_client_error_notify(
|
||||
message=self._i18n.translate(
|
||||
'miot.client.invalid_device_cache'),
|
||||
'miot.client.invalid_device_cache'), # type: ignore
|
||||
notify_key='device_cache')
|
||||
raise MIoTClientError('load device list from cache error')
|
||||
else:
|
||||
@ -1368,7 +1395,8 @@ class MIoTClient:
|
||||
home_ids=list(self._entry_data.get('home_selected', {}).keys()))
|
||||
if not result and 'devices' not in result:
|
||||
self.__show_client_error_notify(
|
||||
message=self._i18n.translate('miot.client.device_cloud_error'),
|
||||
message=self._i18n.translate(
|
||||
'miot.client.device_cloud_error'), # type: ignore
|
||||
notify_key='device_cloud')
|
||||
return
|
||||
else:
|
||||
@ -1725,13 +1753,14 @@ class MIoTClient:
|
||||
|
||||
@final
|
||||
def __show_client_error_notify(
|
||||
self, message: str, notify_key: str = ''
|
||||
self, message: Optional[str], notify_key: str = ''
|
||||
) -> None:
|
||||
if message:
|
||||
|
||||
self._persistence_notify(
|
||||
f'{DOMAIN}{self._uid}{self._cloud_server}{notify_key}error',
|
||||
self._i18n.translate(
|
||||
key='miot.client.xiaomi_home_error_title'),
|
||||
key='miot.client.xiaomi_home_error_title'), # type: ignore
|
||||
self._i18n.translate(
|
||||
key='miot.client.xiaomi_home_error',
|
||||
replace={
|
||||
@ -1739,8 +1768,7 @@ class MIoTClient:
|
||||
'nick_name', DEFAULT_NICK_NAME),
|
||||
'uid': self._uid,
|
||||
'cloud_server': self._cloud_server,
|
||||
'message': message
|
||||
}))
|
||||
'message': message})) # type: ignore
|
||||
else:
|
||||
self._persistence_notify(
|
||||
f'{DOMAIN}{self._uid}{self._cloud_server}{notify_key}error',
|
||||
@ -1806,27 +1834,34 @@ class MIoTClient:
|
||||
key='miot.client.device_list_add',
|
||||
replace={
|
||||
'count': count_add,
|
||||
'message': message_add})
|
||||
'message': message_add}) # type: ignore
|
||||
if 'del' in self._display_devs_notify and count_del:
|
||||
message += self._i18n.translate(
|
||||
key='miot.client.device_list_del',
|
||||
replace={
|
||||
'count': count_del,
|
||||
'message': message_del})
|
||||
'message': message_del}) # type: ignore
|
||||
if 'offline' in self._display_devs_notify and count_offline:
|
||||
message += self._i18n.translate(
|
||||
key='miot.client.device_list_offline',
|
||||
replace={
|
||||
'count': count_offline,
|
||||
'message': message_offline})
|
||||
'message': message_offline}) # type: ignore
|
||||
if message != '':
|
||||
msg_hash = hash(message)
|
||||
if msg_hash == self._display_notify_content_hash:
|
||||
# Notify content no change, return
|
||||
_LOGGER.debug(
|
||||
'device list changed notify content no change, return')
|
||||
return
|
||||
network_status = self._i18n.translate(
|
||||
key='miot.client.network_status_online'
|
||||
if self._network.network_status
|
||||
else 'miot.client.network_status_offline')
|
||||
self._persistence_notify(
|
||||
self.__gen_notify_key('dev_list_changed'),
|
||||
self._i18n.translate('miot.client.device_list_changed_title'),
|
||||
self._i18n.translate(
|
||||
'miot.client.device_list_changed_title'), # type: ignore
|
||||
self._i18n.translate(
|
||||
key='miot.client.device_list_changed',
|
||||
replace={
|
||||
@ -1835,8 +1870,8 @@ class MIoTClient:
|
||||
'uid': self._uid,
|
||||
'cloud_server': self._cloud_server,
|
||||
'network_status': network_status,
|
||||
'message': message
|
||||
}))
|
||||
'message': message})) # type: ignore
|
||||
self._display_notify_content_hash = msg_hash
|
||||
_LOGGER.debug(
|
||||
'show device list changed notify, add %s, del %s, offline %s',
|
||||
count_add, count_del, count_offline)
|
||||
|
@ -744,7 +744,7 @@ class MIoTHttpClient:
|
||||
prop_obj['fut'].set_result(None)
|
||||
if props_req:
|
||||
_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:
|
||||
self._get_prop_timer = self._main_loop.call_later(
|
||||
|
@ -75,7 +75,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.components.switch import SwitchDeviceClass
|
||||
from homeassistant.util import slugify
|
||||
|
||||
|
||||
# pylint: disable=relative-beyond-top-level
|
||||
from .specs.specv2entity import (
|
||||
@ -85,6 +85,7 @@ from .specs.specv2entity import (
|
||||
SPEC_PROP_TRANS_MAP,
|
||||
SPEC_SERVICE_TRANS_MAP
|
||||
)
|
||||
from .common import slugify_name, slugify_did
|
||||
from .const import DOMAIN
|
||||
from .miot_client import MIoTClient
|
||||
from .miot_error import MIoTClientError, MIoTDeviceError
|
||||
@ -94,7 +95,9 @@ from .miot_spec import (
|
||||
MIoTSpecEvent,
|
||||
MIoTSpecInstance,
|
||||
MIoTSpecProperty,
|
||||
MIoTSpecService
|
||||
MIoTSpecService,
|
||||
MIoTSpecValueList,
|
||||
MIoTSpecValueRange
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -142,9 +145,12 @@ class MIoTDevice:
|
||||
_room_id: str
|
||||
_room_name: str
|
||||
|
||||
_suggested_area: str
|
||||
_suggested_area: Optional[str]
|
||||
|
||||
_device_state_sub_list: dict[str, Callable[[str, MIoTDeviceState], None]]
|
||||
_sub_id: int
|
||||
_device_state_sub_list: dict[str, dict[
|
||||
str, Callable[[str, MIoTDeviceState], None]]]
|
||||
_value_sub_list: dict[str, dict[str, Callable[[dict, Any], None]]]
|
||||
|
||||
_entity_list: dict[str, list[MIoTEntityData]]
|
||||
_prop_list: dict[str, list[MIoTSpecProperty]]
|
||||
@ -153,7 +159,7 @@ class MIoTDevice:
|
||||
|
||||
def __init__(
|
||||
self, miot_client: MIoTClient,
|
||||
device_info: dict[str, str],
|
||||
device_info: dict[str, Any],
|
||||
spec_instance: MIoTSpecInstance
|
||||
) -> None:
|
||||
self.miot_client = miot_client
|
||||
@ -183,7 +189,9 @@ class MIoTDevice:
|
||||
case _:
|
||||
self._suggested_area = None
|
||||
|
||||
self._sub_id = 0
|
||||
self._device_state_sub_list = {}
|
||||
self._value_sub_list = {}
|
||||
self._entity_list = {}
|
||||
self._prop_list = {}
|
||||
self._event_list = {}
|
||||
@ -234,36 +242,76 @@ class MIoTDevice:
|
||||
|
||||
def sub_device_state(
|
||||
self, key: str, handler: Callable[[str, MIoTDeviceState], None]
|
||||
) -> bool:
|
||||
self._device_state_sub_list[key] = handler
|
||||
return True
|
||||
) -> int:
|
||||
self._sub_id += 1
|
||||
if key in self._device_state_sub_list:
|
||||
self._device_state_sub_list[key][str(self._sub_id)] = handler
|
||||
else:
|
||||
self._device_state_sub_list[key] = {str(self._sub_id): handler}
|
||||
return self._sub_id
|
||||
|
||||
def unsub_device_state(self, key: str) -> bool:
|
||||
self._device_state_sub_list.pop(key, None)
|
||||
return True
|
||||
def unsub_device_state(self, key: str, sub_id: int) -> None:
|
||||
sub_list = self._device_state_sub_list.get(key, None)
|
||||
if sub_list:
|
||||
sub_list.pop(str(sub_id), None)
|
||||
if not sub_list:
|
||||
self._device_state_sub_list.pop(key, None)
|
||||
|
||||
def sub_property(
|
||||
self, handler: Callable[[dict, Any], None], siid: int = None,
|
||||
piid: int = None, handler_ctx: Any = None
|
||||
) -> bool:
|
||||
return self.miot_client.sub_prop(
|
||||
did=self._did, handler=handler, siid=siid, piid=piid,
|
||||
handler_ctx=handler_ctx)
|
||||
self, handler: Callable[[dict, Any], None], siid: int, piid: int
|
||||
) -> int:
|
||||
key: str = f'p.{siid}.{piid}'
|
||||
|
||||
def unsub_property(self, siid: int = None, piid: int = None) -> bool:
|
||||
return self.miot_client.unsub_prop(did=self._did, siid=siid, piid=piid)
|
||||
def _on_prop_changed(params: dict, ctx: Any) -> None:
|
||||
for handler in self._value_sub_list[key].values():
|
||||
handler(params, ctx)
|
||||
|
||||
self._sub_id += 1
|
||||
if key in self._value_sub_list:
|
||||
self._value_sub_list[key][str(self._sub_id)] = handler
|
||||
else:
|
||||
self._value_sub_list[key] = {str(self._sub_id): handler}
|
||||
self.miot_client.sub_prop(
|
||||
did=self._did, handler=_on_prop_changed, siid=siid, piid=piid)
|
||||
return self._sub_id
|
||||
|
||||
def unsub_property(self, siid: int, piid: int, sub_id: int) -> None:
|
||||
key: str = f'p.{siid}.{piid}'
|
||||
|
||||
sub_list = self._value_sub_list.get(key, None)
|
||||
if sub_list:
|
||||
sub_list.pop(str(sub_id), None)
|
||||
if not sub_list:
|
||||
self.miot_client.unsub_prop(did=self._did, siid=siid, piid=piid)
|
||||
self._value_sub_list.pop(key, None)
|
||||
|
||||
def sub_event(
|
||||
self, handler: Callable[[dict, Any], None], siid: int = None,
|
||||
eiid: int = None, handler_ctx: Any = None
|
||||
) -> bool:
|
||||
return self.miot_client.sub_event(
|
||||
did=self._did, handler=handler, siid=siid, eiid=eiid,
|
||||
handler_ctx=handler_ctx)
|
||||
self, handler: Callable[[dict, Any], None], siid: int, eiid: int
|
||||
) -> int:
|
||||
key: str = f'e.{siid}.{eiid}'
|
||||
|
||||
def unsub_event(self, siid: int = None, eiid: int = None) -> bool:
|
||||
return self.miot_client.unsub_event(
|
||||
did=self._did, siid=siid, eiid=eiid)
|
||||
def _on_event_occurred(params: dict, ctx: Any) -> None:
|
||||
for handler in self._value_sub_list[key].values():
|
||||
handler(params, ctx)
|
||||
|
||||
self._sub_id += 1
|
||||
if key in self._value_sub_list:
|
||||
self._value_sub_list[key][str(self._sub_id)] = handler
|
||||
else:
|
||||
self._value_sub_list[key] = {str(self._sub_id): handler}
|
||||
self.miot_client.sub_event(
|
||||
did=self._did, handler=_on_event_occurred, siid=siid, eiid=eiid)
|
||||
return self._sub_id
|
||||
|
||||
def unsub_event(self, siid: int, eiid: int, sub_id: int) -> None:
|
||||
key: str = f'e.{siid}.{eiid}'
|
||||
|
||||
sub_list = self._value_sub_list.get(key, None)
|
||||
if sub_list:
|
||||
sub_list.pop(str(sub_id), None)
|
||||
if not sub_list:
|
||||
self.miot_client.unsub_event(did=self._did, siid=siid, eiid=eiid)
|
||||
self._value_sub_list.pop(key, None)
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
@ -287,11 +335,8 @@ class MIoTDevice:
|
||||
|
||||
@property
|
||||
def did_tag(self) -> str:
|
||||
return slugify(f'{self.miot_client.cloud_server}_{self._did}')
|
||||
|
||||
@staticmethod
|
||||
def gen_did_tag(cloud_server: str, did: str) -> str:
|
||||
return slugify(f'{cloud_server}_{did}')
|
||||
return slugify_did(
|
||||
cloud_server=self.miot_client.cloud_server, did=self._did)
|
||||
|
||||
def gen_device_entity_id(self, ha_domain: str) -> str:
|
||||
return (
|
||||
@ -308,21 +353,24 @@ class MIoTDevice:
|
||||
) -> str:
|
||||
return (
|
||||
f'{ha_domain}.{self._model_strs[0][:9]}_{self.did_tag}_'
|
||||
f'{self._model_strs[-1][:20]}_{slugify(spec_name)}_p_{siid}_{piid}')
|
||||
f'{self._model_strs[-1][:20]}_{slugify_name(spec_name)}'
|
||||
f'_p_{siid}_{piid}')
|
||||
|
||||
def gen_event_entity_id(
|
||||
self, ha_domain: str, spec_name: str, siid: int, eiid: int
|
||||
) -> str:
|
||||
return (
|
||||
f'{ha_domain}.{self._model_strs[0][:9]}_{self.did_tag}_'
|
||||
f'{self._model_strs[-1][:20]}_{slugify(spec_name)}_e_{siid}_{eiid}')
|
||||
f'{self._model_strs[-1][:20]}_{slugify_name(spec_name)}'
|
||||
f'_e_{siid}_{eiid}')
|
||||
|
||||
def gen_action_entity_id(
|
||||
self, ha_domain: str, spec_name: str, siid: int, aiid: int
|
||||
) -> str:
|
||||
return (
|
||||
f'{ha_domain}.{self._model_strs[0][:9]}_{self.did_tag}_'
|
||||
f'{self._model_strs[-1][:20]}_{slugify(spec_name)}_a_{siid}_{aiid}')
|
||||
f'{self._model_strs[-1][:20]}_{slugify_name(spec_name)}'
|
||||
f'_a_{siid}_{aiid}')
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
@ -341,14 +389,20 @@ class MIoTDevice:
|
||||
self._entity_list[entity_data.platform].append(entity_data)
|
||||
|
||||
def append_prop(self, prop: MIoTSpecProperty) -> None:
|
||||
if not prop.platform:
|
||||
return
|
||||
self._prop_list.setdefault(prop.platform, [])
|
||||
self._prop_list[prop.platform].append(prop)
|
||||
|
||||
def append_event(self, event: MIoTSpecEvent) -> None:
|
||||
if not event.platform:
|
||||
return
|
||||
self._event_list.setdefault(event.platform, [])
|
||||
self._event_list[event.platform].append(event)
|
||||
|
||||
def append_action(self, action: MIoTSpecAction) -> None:
|
||||
if not action.platform:
|
||||
return
|
||||
self._action_list.setdefault(action.platform, [])
|
||||
self._action_list[action.platform].append(action)
|
||||
|
||||
@ -507,7 +561,7 @@ class MIoTDevice:
|
||||
if prop_access != (SPEC_PROP_TRANS_MAP[
|
||||
'entities'][platform]['access']):
|
||||
return None
|
||||
if prop.format_ not in SPEC_PROP_TRANS_MAP[
|
||||
if prop.format_.__name__ not in SPEC_PROP_TRANS_MAP[
|
||||
'entities'][platform]['format']:
|
||||
return None
|
||||
if prop.unit:
|
||||
@ -560,9 +614,9 @@ class MIoTDevice:
|
||||
# general conversion
|
||||
if not prop.platform:
|
||||
if prop.writable:
|
||||
if prop.format_ == 'str':
|
||||
if prop.format_ == str:
|
||||
prop.platform = 'text'
|
||||
elif prop.format_ == 'bool':
|
||||
elif prop.format_ == bool:
|
||||
prop.platform = 'switch'
|
||||
prop.device_class = SwitchDeviceClass.SWITCH
|
||||
elif prop.value_list:
|
||||
@ -573,9 +627,11 @@ class MIoTDevice:
|
||||
# Irregular property will not be transformed.
|
||||
pass
|
||||
elif prop.readable or prop.notifiable:
|
||||
prop.platform = 'sensor'
|
||||
if prop.platform:
|
||||
self.append_prop(prop=prop)
|
||||
if prop.format_ == bool:
|
||||
prop.platform = 'binary_sensor'
|
||||
else:
|
||||
prop.platform = 'sensor'
|
||||
self.append_prop(prop=prop)
|
||||
# STEP 3.2: event conversion
|
||||
for event in service.events:
|
||||
if event.platform:
|
||||
@ -703,10 +759,11 @@ class MIoTDevice:
|
||||
def __on_device_state_changed(
|
||||
self, did: str, state: MIoTDeviceState, ctx: Any
|
||||
) -> None:
|
||||
self._online = state
|
||||
for key, handler in self._device_state_sub_list.items():
|
||||
self.miot_client.main_loop.call_soon_threadsafe(
|
||||
handler, key, state)
|
||||
self._online = state == MIoTDeviceState.ONLINE
|
||||
for key, sub_list in self._device_state_sub_list.items():
|
||||
for handler in sub_list.values():
|
||||
self.miot_client.main_loop.call_soon_threadsafe(
|
||||
handler, key, state)
|
||||
|
||||
|
||||
class MIoTServiceEntity(Entity):
|
||||
@ -718,8 +775,11 @@ class MIoTServiceEntity(Entity):
|
||||
|
||||
_main_loop: asyncio.AbstractEventLoop
|
||||
_prop_value_map: dict[MIoTSpecProperty, Any]
|
||||
_state_sub_id: int
|
||||
_value_sub_ids: dict[str, int]
|
||||
|
||||
_event_occurred_handler: Callable[[MIoTSpecEvent, dict], None]
|
||||
_event_occurred_handler: Optional[
|
||||
Callable[[MIoTSpecEvent, dict], None]]
|
||||
_prop_changed_subs: dict[
|
||||
MIoTSpecProperty, Callable[[MIoTSpecProperty, Any], None]]
|
||||
|
||||
@ -738,13 +798,15 @@ class MIoTServiceEntity(Entity):
|
||||
self.entity_data = entity_data
|
||||
self._main_loop = miot_device.miot_client.main_loop
|
||||
self._prop_value_map = {}
|
||||
self._state_sub_id = 0
|
||||
self._value_sub_ids = {}
|
||||
# Gen entity id
|
||||
if isinstance(entity_data.spec, MIoTSpecInstance):
|
||||
if isinstance(self.entity_data.spec, MIoTSpecInstance):
|
||||
self.entity_id = miot_device.gen_device_entity_id(DOMAIN)
|
||||
self._attr_name = f' {self.entity_data.spec.description_trans}'
|
||||
elif isinstance(entity_data.spec, MIoTSpecService):
|
||||
elif isinstance(self.entity_data.spec, MIoTSpecService):
|
||||
self.entity_id = miot_device.gen_service_entity_id(
|
||||
DOMAIN, siid=entity_data.spec.iid)
|
||||
DOMAIN, siid=self.entity_data.spec.iid)
|
||||
self._attr_name = (
|
||||
f'{"* "if self.entity_data.spec.proprietary else " "}'
|
||||
f'{self.entity_data.spec.description_trans}')
|
||||
@ -763,7 +825,9 @@ class MIoTServiceEntity(Entity):
|
||||
self.entity_id)
|
||||
|
||||
@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
|
||||
|
||||
@event_occurred_handler.setter
|
||||
@ -784,25 +848,27 @@ class MIoTServiceEntity(Entity):
|
||||
self._prop_changed_subs.pop(prop, None)
|
||||
|
||||
@property
|
||||
def device_info(self) -> dict:
|
||||
def device_info(self) -> Optional[DeviceInfo]:
|
||||
return self.miot_device.device_info
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
state_id = 's.0'
|
||||
if isinstance(self.entity_data.spec, MIoTSpecService):
|
||||
state_id = f's.{self.entity_data.spec.iid}'
|
||||
self.miot_device.sub_device_state(
|
||||
self._state_sub_id = self.miot_device.sub_device_state(
|
||||
key=state_id, handler=self.__on_device_state_changed)
|
||||
# Sub prop
|
||||
for prop in self.entity_data.props:
|
||||
if not prop.notifiable and not prop.readable:
|
||||
continue
|
||||
self.miot_device.sub_property(
|
||||
key = f'p.{prop.service.iid}.{prop.iid}'
|
||||
self._value_sub_ids[key] = self.miot_device.sub_property(
|
||||
handler=self.__on_properties_changed,
|
||||
siid=prop.service.iid, piid=prop.iid)
|
||||
# Sub event
|
||||
for event in self.entity_data.events:
|
||||
self.miot_device.sub_event(
|
||||
key = f'e.{event.service.iid}.{event.iid}'
|
||||
self._value_sub_ids[key] = self.miot_device.sub_event(
|
||||
handler=self.__on_event_occurred,
|
||||
siid=event.service.iid, eiid=event.iid)
|
||||
|
||||
@ -817,30 +883,39 @@ class MIoTServiceEntity(Entity):
|
||||
state_id = 's.0'
|
||||
if isinstance(self.entity_data.spec, MIoTSpecService):
|
||||
state_id = f's.{self.entity_data.spec.iid}'
|
||||
self.miot_device.unsub_device_state(key=state_id)
|
||||
self.miot_device.unsub_device_state(
|
||||
key=state_id, sub_id=self._state_sub_id)
|
||||
# Unsub prop
|
||||
for prop in self.entity_data.props:
|
||||
if not prop.notifiable and not prop.readable:
|
||||
continue
|
||||
self.miot_device.unsub_property(
|
||||
siid=prop.service.iid, piid=prop.iid)
|
||||
sub_id = self._value_sub_ids.pop(
|
||||
f'p.{prop.service.iid}.{prop.iid}', None)
|
||||
if sub_id:
|
||||
self.miot_device.unsub_property(
|
||||
siid=prop.service.iid, piid=prop.iid, sub_id=sub_id)
|
||||
# Unsub event
|
||||
for event in self.entity_data.events:
|
||||
self.miot_device.unsub_event(
|
||||
siid=event.service.iid, eiid=event.iid)
|
||||
sub_id = self._value_sub_ids.pop(
|
||||
f'e.{event.service.iid}.{event.iid}', None)
|
||||
if sub_id:
|
||||
self.miot_device.unsub_event(
|
||||
siid=event.service.iid, eiid=event.iid, sub_id=sub_id)
|
||||
|
||||
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:
|
||||
return None
|
||||
return map_.get(key, None)
|
||||
|
||||
def get_map_value(
|
||||
self, map_: dict[int, Any], description: Any
|
||||
def get_map_key(
|
||||
self, map_: dict[int, Any], value: Any
|
||||
) -> Optional[int]:
|
||||
if map_ is None:
|
||||
return None
|
||||
for key, value in map_.items():
|
||||
if value == description:
|
||||
for key, value_ in map_.items():
|
||||
if value_ == value:
|
||||
return key
|
||||
return None
|
||||
|
||||
@ -999,11 +1074,12 @@ class MIoTPropertyEntity(Entity):
|
||||
service: MIoTSpecService
|
||||
|
||||
_main_loop: asyncio.AbstractEventLoop
|
||||
# {'min':int, 'max':int, 'step': int}
|
||||
_value_range: dict[str, int]
|
||||
_value_range: Optional[MIoTSpecValueRange]
|
||||
# {Any: Any}
|
||||
_value_list: dict[Any, Any]
|
||||
_value_list: Optional[MIoTSpecValueList]
|
||||
_value: Any
|
||||
_state_sub_id: int
|
||||
_value_sub_id: int
|
||||
|
||||
_pending_write_ha_state_timer: Optional[asyncio.TimerHandle]
|
||||
|
||||
@ -1015,12 +1091,10 @@ class MIoTPropertyEntity(Entity):
|
||||
self.service = spec.service
|
||||
self._main_loop = miot_device.miot_client.main_loop
|
||||
self._value_range = spec.value_range
|
||||
if spec.value_list:
|
||||
self._value_list = {
|
||||
item['value']: item['description'] for item in spec.value_list}
|
||||
else:
|
||||
self._value_list = None
|
||||
self._value_list = spec.value_list
|
||||
self._value = None
|
||||
self._state_sub_id = 0
|
||||
self._value_sub_id = 0
|
||||
self._pending_write_ha_state_timer = None
|
||||
# Gen entity_id
|
||||
self.entity_id = self.miot_device.gen_prop_entity_id(
|
||||
@ -1042,16 +1116,16 @@ class MIoTPropertyEntity(Entity):
|
||||
self._value_list)
|
||||
|
||||
@property
|
||||
def device_info(self) -> dict:
|
||||
def device_info(self) -> Optional[DeviceInfo]:
|
||||
return self.miot_device.device_info
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
# Sub device state changed
|
||||
self.miot_device.sub_device_state(
|
||||
self._state_sub_id = self.miot_device.sub_device_state(
|
||||
key=f'{ self.service.iid}.{self.spec.iid}',
|
||||
handler=self.__on_device_state_changed)
|
||||
# Sub value changed
|
||||
self.miot_device.sub_property(
|
||||
self._value_sub_id = self.miot_device.sub_property(
|
||||
handler=self.__on_value_changed,
|
||||
siid=self.service.iid, piid=self.spec.iid)
|
||||
# Refresh value
|
||||
@ -1063,22 +1137,21 @@ class MIoTPropertyEntity(Entity):
|
||||
self._pending_write_ha_state_timer.cancel()
|
||||
self._pending_write_ha_state_timer = None
|
||||
self.miot_device.unsub_device_state(
|
||||
key=f'{ self.service.iid}.{self.spec.iid}')
|
||||
key=f'{ self.service.iid}.{self.spec.iid}',
|
||||
sub_id=self._state_sub_id)
|
||||
self.miot_device.unsub_property(
|
||||
siid=self.service.iid, piid=self.spec.iid)
|
||||
siid=self.service.iid, piid=self.spec.iid,
|
||||
sub_id=self._value_sub_id)
|
||||
|
||||
def get_vlist_description(self, value: Any) -> str:
|
||||
def get_vlist_description(self, value: Any) -> Optional[str]:
|
||||
if not self._value_list:
|
||||
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:
|
||||
if not self._value_list:
|
||||
return None
|
||||
for key, value in self._value_list.items():
|
||||
if value == description:
|
||||
return key
|
||||
return None
|
||||
return self._value_list.get_value_by_description(description)
|
||||
|
||||
async def set_property_async(self, value: Any) -> bool:
|
||||
if not self.spec.writable:
|
||||
@ -1148,9 +1221,10 @@ class MIoTEventEntity(Entity):
|
||||
service: MIoTSpecService
|
||||
|
||||
_main_loop: asyncio.AbstractEventLoop
|
||||
_value: Any
|
||||
_attr_event_types: list[str]
|
||||
_arguments_map: dict[int, str]
|
||||
_state_sub_id: int
|
||||
_value_sub_id: int
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecEvent) -> None:
|
||||
if miot_device is None or spec is None or spec.service is None:
|
||||
@ -1159,7 +1233,6 @@ class MIoTEventEntity(Entity):
|
||||
self.spec = spec
|
||||
self.service = spec.service
|
||||
self._main_loop = miot_device.miot_client.main_loop
|
||||
self._value = None
|
||||
# Gen entity_id
|
||||
self.entity_id = self.miot_device.gen_event_entity_id(
|
||||
ha_domain=DOMAIN, spec_name=spec.name,
|
||||
@ -1177,6 +1250,8 @@ class MIoTEventEntity(Entity):
|
||||
self._arguments_map = {}
|
||||
for prop in spec.argument:
|
||||
self._arguments_map[prop.iid] = prop.description_trans
|
||||
self._state_sub_id = 0
|
||||
self._value_sub_id = 0
|
||||
|
||||
_LOGGER.info(
|
||||
'new miot event entity, %s, %s, %s, %s, %s',
|
||||
@ -1184,29 +1259,31 @@ class MIoTEventEntity(Entity):
|
||||
spec.device_class, self.entity_id)
|
||||
|
||||
@property
|
||||
def device_info(self) -> dict:
|
||||
def device_info(self) -> Optional[DeviceInfo]:
|
||||
return self.miot_device.device_info
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
# Sub device state changed
|
||||
self.miot_device.sub_device_state(
|
||||
self._state_sub_id = self.miot_device.sub_device_state(
|
||||
key=f'event.{ self.service.iid}.{self.spec.iid}',
|
||||
handler=self.__on_device_state_changed)
|
||||
# Sub value changed
|
||||
self.miot_device.sub_event(
|
||||
self._value_sub_id = self.miot_device.sub_event(
|
||||
handler=self.__on_event_occurred,
|
||||
siid=self.service.iid, eiid=self.spec.iid)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
self.miot_device.unsub_device_state(
|
||||
key=f'event.{ self.service.iid}.{self.spec.iid}')
|
||||
key=f'event.{ self.service.iid}.{self.spec.iid}',
|
||||
sub_id=self._state_sub_id)
|
||||
self.miot_device.unsub_event(
|
||||
siid=self.service.iid, eiid=self.spec.iid)
|
||||
siid=self.service.iid, eiid=self.spec.iid,
|
||||
sub_id=self._value_sub_id)
|
||||
|
||||
@abstractmethod
|
||||
def on_event_occurred(
|
||||
self, name: str, arguments: list[dict[int, Any]]
|
||||
): ...
|
||||
self, name: str, arguments: dict[str, Any] | None = None
|
||||
) -> None: ...
|
||||
|
||||
def __on_event_occurred(self, params: dict, ctx: Any) -> None:
|
||||
_LOGGER.debug('event occurred, %s', params)
|
||||
@ -1253,11 +1330,11 @@ class MIoTActionEntity(Entity):
|
||||
miot_device: MIoTDevice
|
||||
spec: MIoTSpecAction
|
||||
service: MIoTSpecService
|
||||
action_platform: str
|
||||
|
||||
_main_loop: asyncio.AbstractEventLoop
|
||||
_in_map: dict[int, MIoTSpecProperty]
|
||||
_out_map: dict[int, MIoTSpecProperty]
|
||||
_state_sub_id: int
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecAction) -> None:
|
||||
if miot_device is None or spec is None or spec.service is None:
|
||||
@ -1265,8 +1342,8 @@ class MIoTActionEntity(Entity):
|
||||
self.miot_device = miot_device
|
||||
self.spec = spec
|
||||
self.service = spec.service
|
||||
self.action_platform = 'action'
|
||||
self._main_loop = miot_device.miot_client.main_loop
|
||||
self._state_sub_id = 0
|
||||
# Gen entity_id
|
||||
self.entity_id = self.miot_device.gen_action_entity_id(
|
||||
ha_domain=DOMAIN, spec_name=spec.name,
|
||||
@ -1286,19 +1363,22 @@ class MIoTActionEntity(Entity):
|
||||
spec.device_class, self.entity_id)
|
||||
|
||||
@property
|
||||
def device_info(self) -> dict:
|
||||
def device_info(self) -> Optional[DeviceInfo]:
|
||||
return self.miot_device.device_info
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
self.miot_device.sub_device_state(
|
||||
key=f'{self.action_platform}.{ self.service.iid}.{self.spec.iid}',
|
||||
self._state_sub_id = self.miot_device.sub_device_state(
|
||||
key=f'a.{ self.service.iid}.{self.spec.iid}',
|
||||
handler=self.__on_device_state_changed)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
self.miot_device.unsub_device_state(
|
||||
key=f'{self.action_platform}.{ self.service.iid}.{self.spec.iid}')
|
||||
key=f'a.{ self.service.iid}.{self.spec.iid}',
|
||||
sub_id=self._state_sub_id)
|
||||
|
||||
async def action_async(self, in_list: list = None) -> Optional[list]:
|
||||
async def action_async(
|
||||
self, in_list: Optional[list] = None
|
||||
) -> Optional[list]:
|
||||
try:
|
||||
return await self.miot_device.miot_client.action_async(
|
||||
did=self.miot_device.did,
|
||||
|
@ -663,7 +663,8 @@ class _MipsClient(ABC):
|
||||
|
||||
def __on_disconnect(self, client, user_data, rc, props) -> None:
|
||||
if self._mqtt_state:
|
||||
self.log_error(f'mips disconnect, {rc}, {props}')
|
||||
(self.log_info if rc == 0 else self.log_error)(
|
||||
f'mips disconnect, {rc}, {props}')
|
||||
self._mqtt_state = False
|
||||
if self._mqtt_timer:
|
||||
self._mqtt_timer.cancel()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -58,7 +58,6 @@ from enum import Enum, auto
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Union
|
||||
import logging
|
||||
from urllib.request import Request, urlopen
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.x509.oid import NameOID
|
||||
@ -66,13 +65,13 @@ from cryptography import x509
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import ed25519
|
||||
|
||||
|
||||
# pylint: disable=relative-beyond-top-level
|
||||
from .common import load_json_file
|
||||
from .const import (
|
||||
DEFAULT_INTEGRATION_LANGUAGE,
|
||||
MANUFACTURER_EFFECTIVE_TIME,
|
||||
MIHOME_CA_CERT_STR,
|
||||
MIHOME_CA_CERT_SHA256)
|
||||
from .common import MIoTHttp
|
||||
from .miot_error import MIoTCertError, MIoTError, MIoTStorageError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -93,10 +92,10 @@ class MIoTStorage:
|
||||
|
||||
User data will be stored in the `.storage` directory of Home Assistant.
|
||||
"""
|
||||
_main_loop: asyncio.AbstractEventLoop = None
|
||||
_main_loop: asyncio.AbstractEventLoop
|
||||
_file_future: dict[str, tuple[MIoTStorageType, asyncio.Future]]
|
||||
|
||||
_root_path: str = None
|
||||
_root_path: str
|
||||
|
||||
def __init__(
|
||||
self, root_path: str,
|
||||
@ -140,7 +139,7 @@ class MIoTStorage:
|
||||
if r_data is None:
|
||||
_LOGGER.error('load error, empty file, %s', full_path)
|
||||
return None
|
||||
data_bytes: bytes = None
|
||||
data_bytes: bytes
|
||||
# Hash check
|
||||
if with_hash_check:
|
||||
if len(r_data) <= 32:
|
||||
@ -209,17 +208,17 @@ class MIoTStorage:
|
||||
else:
|
||||
os.makedirs(os.path.dirname(full_path), exist_ok=True)
|
||||
try:
|
||||
type_: type = type(data)
|
||||
w_bytes: bytes = None
|
||||
if type_ == bytes:
|
||||
w_bytes: bytes
|
||||
if isinstance(data, bytes):
|
||||
w_bytes = data
|
||||
elif type_ == str:
|
||||
elif isinstance(data, str):
|
||||
w_bytes = data.encode('utf-8')
|
||||
elif type_ in [dict, list]:
|
||||
elif isinstance(data, (dict, list)):
|
||||
w_bytes = json.dumps(data).encode('utf-8')
|
||||
else:
|
||||
_LOGGER.error(
|
||||
'save error, unsupported data type, %s', type_.__name__)
|
||||
'save error, unsupported data type, %s',
|
||||
type(data).__name__)
|
||||
return False
|
||||
with open(full_path, 'wb') as w_file:
|
||||
w_file.write(w_bytes)
|
||||
@ -353,7 +352,8 @@ class MIoTStorage:
|
||||
def load_file(self, domain: str, name_with_suffix: str) -> Optional[bytes]:
|
||||
full_path = os.path.join(self._root_path, domain, name_with_suffix)
|
||||
return self.__load(
|
||||
full_path=full_path, type_=bytes, with_hash_check=False)
|
||||
full_path=full_path, type_=bytes,
|
||||
with_hash_check=False) # type: ignore
|
||||
|
||||
async def load_file_async(
|
||||
self, domain: str, name_with_suffix: str
|
||||
@ -371,7 +371,7 @@ class MIoTStorage:
|
||||
None, self.__load, full_path, bytes, False)
|
||||
if not fut.done():
|
||||
self.__add_file_future(full_path, MIoTStorageType.LOAD_FILE, fut)
|
||||
return await fut
|
||||
return await fut # type: ignore
|
||||
|
||||
def remove_file(self, domain: str, name_with_suffix: str) -> bool:
|
||||
full_path = os.path.join(self._root_path, domain, name_with_suffix)
|
||||
@ -438,7 +438,7 @@ class MIoTStorage:
|
||||
domain=config_domain, name=config_name, data=config)
|
||||
local_config = (self.load(domain=config_domain,
|
||||
name=config_name, type_=dict)) or {}
|
||||
local_config.update(config)
|
||||
local_config.update(config) # type: ignore
|
||||
return self.save(
|
||||
domain=config_domain, name=config_name, data=local_config)
|
||||
|
||||
@ -474,27 +474,31 @@ class MIoTStorage:
|
||||
domain=config_domain, name=config_name, data=config)
|
||||
local_config = (await self.load_async(
|
||||
domain=config_domain, name=config_name, type_=dict)) or {}
|
||||
local_config.update(config)
|
||||
local_config.update(config) # type: ignore
|
||||
return await self.save_async(
|
||||
domain=config_domain, name=config_name, data=local_config)
|
||||
|
||||
def load_user_config(
|
||||
self, uid: str, cloud_server: str, keys: Optional[list[str]] = None
|
||||
) -> dict[str, Any]:
|
||||
if keys is not None and len(keys) == 0:
|
||||
if isinstance(keys, list) and len(keys) == 0:
|
||||
# Do nothing
|
||||
return {}
|
||||
config_domain = 'miot_config'
|
||||
config_name = f'{uid}_{cloud_server}'
|
||||
local_config = (self.load(domain=config_domain,
|
||||
name=config_name, type_=dict)) or {}
|
||||
name=config_name, type_=dict))
|
||||
if not isinstance(local_config, dict):
|
||||
return {}
|
||||
if keys is None:
|
||||
return local_config
|
||||
return {key: local_config.get(key, None) for key in keys}
|
||||
return {
|
||||
key: local_config[key] for key in keys
|
||||
if key in local_config}
|
||||
|
||||
async def load_user_config_async(
|
||||
self, uid: str, cloud_server: str, keys: Optional[list[str]] = None
|
||||
) -> dict[str, Any]:
|
||||
) -> dict:
|
||||
"""Load user configuration.
|
||||
|
||||
Args:
|
||||
@ -505,13 +509,15 @@ class MIoTStorage:
|
||||
Returns:
|
||||
dict[str, Any]: query result
|
||||
"""
|
||||
if keys is not None and len(keys) == 0:
|
||||
if isinstance(keys, list) and len(keys) == 0:
|
||||
# Do nothing
|
||||
return {}
|
||||
config_domain = 'miot_config'
|
||||
config_name = f'{uid}_{cloud_server}'
|
||||
local_config = (await self.load_async(
|
||||
domain=config_domain, name=config_name, type_=dict)) or {}
|
||||
domain=config_domain, name=config_name, type_=dict))
|
||||
if not isinstance(local_config, dict):
|
||||
return {}
|
||||
if keys is None:
|
||||
return local_config
|
||||
return {
|
||||
@ -519,7 +525,8 @@ class MIoTStorage:
|
||||
if key in local_config}
|
||||
|
||||
def gen_storage_path(
|
||||
self, domain: str = None, name_with_suffix: str = None
|
||||
self, domain: Optional[str] = None,
|
||||
name_with_suffix: Optional[str] = None
|
||||
) -> str:
|
||||
"""Generate file path."""
|
||||
result = self._root_path
|
||||
@ -609,9 +616,8 @@ class MIoTCert:
|
||||
if cert_data is None:
|
||||
return 0
|
||||
# Check user cert
|
||||
user_cert: x509.Certificate = None
|
||||
try:
|
||||
user_cert = x509.load_pem_x509_certificate(
|
||||
user_cert: x509.Certificate = x509.load_pem_x509_certificate(
|
||||
cert_data, default_backend())
|
||||
cert_info = {}
|
||||
for attribute in user_cert.subject:
|
||||
@ -669,7 +675,8 @@ class MIoTCert:
|
||||
NameOID.COMMON_NAME, f'mips.{self._uid}.{did_hash}.2'),
|
||||
]))
|
||||
csr = builder.sign(
|
||||
private_key, algorithm=None, backend=default_backend())
|
||||
private_key, algorithm=None, # type: ignore
|
||||
backend=default_backend())
|
||||
return csr.public_bytes(serialization.Encoding.PEM).decode('utf-8')
|
||||
|
||||
async def load_user_key_async(self) -> Optional[str]:
|
||||
@ -719,250 +726,6 @@ class MIoTCert:
|
||||
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:
|
||||
"""Device manufacturer."""
|
||||
DOMAIN: str = 'miot_specs'
|
||||
@ -976,12 +739,11 @@ class DeviceManufacturer:
|
||||
) -> None:
|
||||
self._main_loop = loop or asyncio.get_event_loop()
|
||||
self._storage = storage
|
||||
self._data = None
|
||||
self._data = {}
|
||||
|
||||
async def init_async(self) -> None:
|
||||
if self._data:
|
||||
return
|
||||
data_cache: dict = None
|
||||
data_cache = await self._storage.load_async(
|
||||
domain=self.DOMAIN, name='manufacturer', type_=dict)
|
||||
if (
|
||||
@ -995,8 +757,15 @@ class DeviceManufacturer:
|
||||
_LOGGER.debug('load manufacturer data success')
|
||||
return
|
||||
|
||||
data_cloud = await self._main_loop.run_in_executor(
|
||||
None, self.__get_manufacturer_data)
|
||||
data_cloud = None
|
||||
try:
|
||||
data_cloud = await MIoTHttp.get_json_async(
|
||||
url='https://cdn.cnbj1.fds.api.mi-img.com/res-conf/xiaomi-home/'
|
||||
'manufacturer.json',
|
||||
loop=self._main_loop)
|
||||
except Exception as err: # pylint: disable=broad-exception-caught
|
||||
_LOGGER.error('get manufacturer info failed, %s', err)
|
||||
|
||||
if data_cloud:
|
||||
await self._storage.save_async(
|
||||
domain=self.DOMAIN, name='manufacturer',
|
||||
@ -1004,32 +773,16 @@ class DeviceManufacturer:
|
||||
self._data = data_cloud
|
||||
_LOGGER.debug('update manufacturer data success')
|
||||
else:
|
||||
if data_cache:
|
||||
self._data = data_cache.get('data', None)
|
||||
if isinstance(data_cache, dict):
|
||||
self._data = data_cache.get('data', {})
|
||||
_LOGGER.error('load manufacturer data failed, use local data')
|
||||
else:
|
||||
_LOGGER.error('load manufacturer data failed')
|
||||
|
||||
async def deinit_async(self) -> None:
|
||||
self._data = None
|
||||
self._data.clear()
|
||||
|
||||
def get_name(self, short_name: str) -> str:
|
||||
if not self._data or not short_name or short_name not in self._data:
|
||||
return short_name
|
||||
return self._data[short_name].get('name', None) or short_name
|
||||
|
||||
def __get_manufacturer_data(self) -> dict:
|
||||
try:
|
||||
request = Request(
|
||||
'https://cdn.cnbj1.fds.api.mi-img.com/res-conf/xiaomi-home/'
|
||||
'manufacturer.json',
|
||||
method='GET')
|
||||
content: bytes = None
|
||||
with urlopen(request) as response:
|
||||
content = response.read()
|
||||
return (
|
||||
json.loads(str(content, 'utf-8'))
|
||||
if content else None)
|
||||
except Exception as err: # pylint: disable=broad-exception-caught
|
||||
_LOGGER.error('get manufacturer info failed, %s', err)
|
||||
return None
|
||||
|
@ -1,315 +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"
|
||||
},
|
||||
"it": {
|
||||
"true": "Vero",
|
||||
"false": "Falso"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"it": {
|
||||
"true": "Aperto",
|
||||
"false": "Chiuso"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"it": {
|
||||
"true": "Si",
|
||||
"false": "No"
|
||||
},
|
||||
"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é"
|
||||
},
|
||||
"it": {
|
||||
"true": "Movimento Rilevato",
|
||||
"false": "Nessun Movimento Rilevato"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"it": {
|
||||
"true": "Contatto",
|
||||
"false": "Nessun Contatto"
|
||||
},
|
||||
"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": "分離"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
246
custom_components/xiaomi_home/miot/specs/bool_trans.yaml
Normal file
246
custom_components/xiaomi_home/miot/specs/bool_trans.yaml
Normal file
@ -0,0 +1,246 @@
|
||||
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:
|
||||
contact_state:
|
||||
de:
|
||||
'false': Kein Kontakt
|
||||
'true': Kontakt
|
||||
en:
|
||||
'false': No Contact
|
||||
'true': Contact
|
||||
es:
|
||||
'false': Sin contacto
|
||||
'true': Contacto
|
||||
fr:
|
||||
'false': Pas de contact
|
||||
'true': Contact
|
||||
it:
|
||||
'false': Nessun contatto
|
||||
'true': Contatto
|
||||
ja:
|
||||
'false': 非接触
|
||||
'true': 接触
|
||||
nl:
|
||||
'false': Geen contact
|
||||
'true': Contact
|
||||
pt:
|
||||
'false': Sem contato
|
||||
'true': Contato
|
||||
pt-BR:
|
||||
'false': Sem contato
|
||||
'true': Contato
|
||||
ru:
|
||||
'false': Нет контакта
|
||||
'true': Контакт
|
||||
zh-Hans:
|
||||
'false': 分离
|
||||
'true': 接触
|
||||
zh-Hant:
|
||||
'false': 分離
|
||||
'true': 接觸
|
||||
default:
|
||||
de:
|
||||
'false': Falsch
|
||||
'true': Wahr
|
||||
en:
|
||||
'false': 'False'
|
||||
'true': 'True'
|
||||
es:
|
||||
'false': Falso
|
||||
'true': Verdadero
|
||||
fr:
|
||||
'false': Faux
|
||||
'true': Vrai
|
||||
it:
|
||||
'false': Falso
|
||||
'true': Vero
|
||||
ja:
|
||||
'false': 偽
|
||||
'true': 真
|
||||
nl:
|
||||
'false': 'False'
|
||||
'true': 'True'
|
||||
pt:
|
||||
'false': 'False'
|
||||
'true': 'True'
|
||||
pt-BR:
|
||||
'false': 'False'
|
||||
'true': 'True'
|
||||
ru:
|
||||
'false': Ложь
|
||||
'true': Истина
|
||||
zh-Hans:
|
||||
'false': 假
|
||||
'true': 真
|
||||
zh-Hant:
|
||||
'false': 假
|
||||
'true': 真
|
||||
motion_state:
|
||||
de:
|
||||
'false': Keine Bewegung erkannt
|
||||
'true': Bewegung erkannt
|
||||
en:
|
||||
'false': No Motion Detected
|
||||
'true': Motion Detected
|
||||
es:
|
||||
'false': No se detecta movimiento
|
||||
'true': Movimiento detectado
|
||||
fr:
|
||||
'false': Aucun mouvement détecté
|
||||
'true': Mouvement détecté
|
||||
it:
|
||||
'false': Nessun Movimento Rilevato
|
||||
'true': Movimento Rilevato
|
||||
ja:
|
||||
'false': 動きが検出されません
|
||||
'true': 動きを検知
|
||||
nl:
|
||||
'false': Geen contact
|
||||
'true': Contact
|
||||
pt:
|
||||
'false': Sem contato
|
||||
'true': Contato
|
||||
pt-BR:
|
||||
'false': Sem contato
|
||||
'true': Contato
|
||||
ru:
|
||||
'false': Движение не обнаружено
|
||||
'true': Обнаружено движение
|
||||
zh-Hans:
|
||||
'false': 无人
|
||||
'true': 有人
|
||||
zh-Hant:
|
||||
'false': 無人
|
||||
'true': 有人
|
||||
open_close:
|
||||
de:
|
||||
'false': Schließen
|
||||
'true': Öffnen
|
||||
en:
|
||||
'false': Close
|
||||
'true': Open
|
||||
es:
|
||||
'false': Cerrado
|
||||
'true': Abierto
|
||||
fr:
|
||||
'false': Fermer
|
||||
'true': Ouvert
|
||||
it:
|
||||
'false': Chiuso
|
||||
'true': Aperto
|
||||
ja:
|
||||
'false': 閉じる
|
||||
'true': 開く
|
||||
nl:
|
||||
'false': Dicht
|
||||
'true': Open
|
||||
pt:
|
||||
'false': Fechado
|
||||
'true': Aberto
|
||||
pt-BR:
|
||||
'false': Fechado
|
||||
'true': Aberto
|
||||
ru:
|
||||
'false': Закрыть
|
||||
'true': Открыть
|
||||
zh-Hans:
|
||||
'false': 关闭
|
||||
'true': 开启
|
||||
zh-Hant:
|
||||
'false': 關閉
|
||||
'true': 開啟
|
||||
yes_no:
|
||||
de:
|
||||
'false': Nein
|
||||
'true': Ja
|
||||
en:
|
||||
'false': 'No'
|
||||
'true': 'Yes'
|
||||
es:
|
||||
'false': 'No'
|
||||
'true': Sí
|
||||
fr:
|
||||
'false': Non
|
||||
'true': Oui
|
||||
it:
|
||||
'false': 'No'
|
||||
'true': Si
|
||||
ja:
|
||||
'false': いいえ
|
||||
'true': はい
|
||||
nl:
|
||||
'false': Nee
|
||||
'true': Ja
|
||||
pt:
|
||||
'false': Não
|
||||
'true': Sim
|
||||
pt-BR:
|
||||
'false': Não
|
||||
'true': Sim
|
||||
ru:
|
||||
'false': Нет
|
||||
'true': Да
|
||||
zh-Hans:
|
||||
'false': 否
|
||||
'true': 是
|
||||
zh-Hant:
|
||||
'false': 否
|
||||
'true': 是
|
@ -1,194 +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"
|
||||
},
|
||||
"it": {
|
||||
"service:001": "Informazioni sul Dispositivo",
|
||||
"service:001:property:003": "ID Dispositivo",
|
||||
"service:001:property:005": "Numero di Serie (SN)",
|
||||
"service:002": "Gateway",
|
||||
"service:002:event:001": "Rete Modificata",
|
||||
"service:002:event:002": "Rete Modificata",
|
||||
"service:002:property:001": "Metodo di Accesso",
|
||||
"service:002:property:001:valuelist:000": "Cablato",
|
||||
"service:002:property:001:valuelist:001": "Wireless 5G",
|
||||
"service:002:property:001:valuelist:002": "Wireless 2.4G",
|
||||
"service:002:property:002": "Indirizzo IP",
|
||||
"service:002:property:003": "Nome Rete WiFi",
|
||||
"service:002:property:004": "Ora Attuale",
|
||||
"service:002:property:005": "Indirizzo MAC del Server DHCP",
|
||||
"service:003": "Luce Indicatore",
|
||||
"service:003:property:001": "Interruttore",
|
||||
"service:004": "Servizio Virtuale",
|
||||
"service:004:action:001": "Genera Evento Virtuale",
|
||||
"service:004:event:001": "Evento Virtuale Avvenuto",
|
||||
"service:004:property:001": "Nome Evento"
|
||||
},
|
||||
"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)
|
||||
self._attr_extra_state_attributes = {}
|
||||
action_in: str = ', '.join([
|
||||
f'{prop.description_trans}({prop.format_})'
|
||||
f'{prop.description_trans}({prop.format_.__name__})'
|
||||
for prop in self.spec.in_])
|
||||
self._attr_extra_state_attributes['action params'] = f'[{action_in}]'
|
||||
|
||||
@ -122,24 +122,24 @@ class Notify(MIoTActionEntity, NotifyEntity):
|
||||
return
|
||||
in_value: list[dict] = []
|
||||
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)):
|
||||
in_value.append(
|
||||
{'piid': prop.iid, 'value': str(in_list[index])})
|
||||
continue
|
||||
elif prop.format_ == 'bool':
|
||||
elif prop.format_ == bool:
|
||||
if isinstance(in_list[index], (bool, int)):
|
||||
# yes, no, on, off, true, false and other bool types
|
||||
# will also be parsed as 0 and 1 of int.
|
||||
in_value.append(
|
||||
{'piid': prop.iid, 'value': bool(in_list[index])})
|
||||
continue
|
||||
elif prop.format_ == 'float':
|
||||
elif prop.format_ == float:
|
||||
if isinstance(in_list[index], (int, float)):
|
||||
in_value.append(
|
||||
{'piid': prop.iid, 'value': in_list[index]})
|
||||
continue
|
||||
elif prop.format_ == 'int':
|
||||
elif prop.format_ == int:
|
||||
if isinstance(in_list[index], int):
|
||||
in_value.append(
|
||||
{'piid': prop.iid, 'value': in_list[index]})
|
||||
|
@ -92,9 +92,9 @@ class Number(MIoTPropertyEntity, NumberEntity):
|
||||
self._attr_icon = self.spec.icon
|
||||
# Set value range
|
||||
if self._value_range:
|
||||
self._attr_native_min_value = self._value_range['min']
|
||||
self._attr_native_max_value = self._value_range['max']
|
||||
self._attr_native_step = self._value_range['step']
|
||||
self._attr_native_min_value = self._value_range.min_
|
||||
self._attr_native_max_value = self._value_range.max_
|
||||
self._attr_native_step = self._value_range.step
|
||||
|
||||
@property
|
||||
def native_value(self) -> Optional[float]:
|
||||
|
@ -82,7 +82,8 @@ class Select(MIoTPropertyEntity, SelectEntity):
|
||||
def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) -> None:
|
||||
"""Initialize the Select."""
|
||||
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:
|
||||
"""Change the selected option."""
|
||||
|
@ -76,6 +76,12 @@ async def async_setup_entry(
|
||||
for prop in miot_device.prop_list.get('sensor', []):
|
||||
new_entities.append(Sensor(miot_device=miot_device, spec=prop))
|
||||
|
||||
if miot_device.miot_client.display_binary_text:
|
||||
for prop in miot_device.prop_list.get('binary_sensor', []):
|
||||
if not prop.value_list:
|
||||
continue
|
||||
new_entities.append(Sensor(miot_device=miot_device, spec=prop))
|
||||
|
||||
if new_entities:
|
||||
async_add_entities(new_entities)
|
||||
|
||||
@ -91,7 +97,7 @@ class Sensor(MIoTPropertyEntity, SensorEntity):
|
||||
self._attr_device_class = SensorDeviceClass.ENUM
|
||||
self._attr_icon = 'mdi:message-text'
|
||||
self._attr_native_unit_of_measurement = None
|
||||
self._attr_options = list(self._value_list.values())
|
||||
self._attr_options = self._value_list.descriptions
|
||||
else:
|
||||
self._attr_device_class = spec.device_class
|
||||
if spec.external_unit:
|
||||
@ -100,29 +106,29 @@ class Sensor(MIoTPropertyEntity, SensorEntity):
|
||||
# device_class is not empty but unit is empty.
|
||||
# Set the default unit according to device_class.
|
||||
unit_sets = DEVICE_CLASS_UNITS.get(
|
||||
self._attr_device_class, None)
|
||||
self._attr_device_class, None) # type: ignore
|
||||
self._attr_native_unit_of_measurement = list(
|
||||
unit_sets)[0] if unit_sets else None
|
||||
# Set state_class
|
||||
if spec.state_class:
|
||||
self._attr_state_class = spec.state_class
|
||||
# Set icon
|
||||
if spec.icon:
|
||||
self._attr_icon = spec.icon
|
||||
# Set state_class
|
||||
if spec.state_class:
|
||||
self._attr_state_class = spec.state_class
|
||||
|
||||
@property
|
||||
def native_value(self) -> Any:
|
||||
"""Return the current value of the sensor."""
|
||||
if self._value_range and isinstance(self._value, (int, float)):
|
||||
if (
|
||||
self._value < self._value_range['min']
|
||||
or self._value > self._value_range['max']
|
||||
self._value < self._value_range.min_
|
||||
or self._value > self._value_range.max_
|
||||
):
|
||||
_LOGGER.info(
|
||||
'%s, data exception, out of range, %s, %s',
|
||||
self.entity_id, self._value, self._value_range)
|
||||
if self._value_list:
|
||||
return self._value_list.get(self._value, None)
|
||||
return self.get_vlist_description(self._value)
|
||||
if isinstance(self._value, str):
|
||||
return self._value[:255]
|
||||
return self._value
|
||||
|
@ -76,9 +76,10 @@ async def async_setup_entry(
|
||||
for prop in miot_device.prop_list.get('text', []):
|
||||
new_entities.append(Text(miot_device=miot_device, spec=prop))
|
||||
|
||||
for action in miot_device.action_list.get('action_text', []):
|
||||
new_entities.append(ActionText(
|
||||
miot_device=miot_device, spec=action))
|
||||
if miot_device.miot_client.action_debug:
|
||||
for action in miot_device.action_list.get('notify', []):
|
||||
new_entities.append(ActionText(
|
||||
miot_device=miot_device, spec=action))
|
||||
|
||||
if new_entities:
|
||||
async_add_entities(new_entities)
|
||||
@ -111,11 +112,9 @@ class ActionText(MIoTActionEntity, TextEntity):
|
||||
self._attr_extra_state_attributes = {}
|
||||
self._attr_native_value = ''
|
||||
action_in: str = ', '.join([
|
||||
f'{prop.description_trans}({prop.format_})'
|
||||
f'{prop.description_trans}({prop.format_.__name__})'
|
||||
for prop in self.spec.in_])
|
||||
self._attr_extra_state_attributes['action params'] = f'[{action_in}]'
|
||||
# For action debug
|
||||
self.action_platform = 'action_text'
|
||||
|
||||
async def async_set_value(self, value: str) -> None:
|
||||
if not value:
|
||||
@ -141,24 +140,24 @@ class ActionText(MIoTActionEntity, TextEntity):
|
||||
f'invalid action params, {value}')
|
||||
in_value: list[dict] = []
|
||||
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)):
|
||||
in_value.append(
|
||||
{'piid': prop.iid, 'value': str(in_list[index])})
|
||||
continue
|
||||
elif prop.format_ == 'bool':
|
||||
elif prop.format_ == bool:
|
||||
if isinstance(in_list[index], (bool, int)):
|
||||
# yes, no, on, off, true, false and other bool types
|
||||
# will also be parsed as 0 and 1 of int.
|
||||
in_value.append(
|
||||
{'piid': prop.iid, 'value': bool(in_list[index])})
|
||||
continue
|
||||
elif prop.format_ == 'float':
|
||||
elif prop.format_ == float:
|
||||
if isinstance(in_list[index], (int, float)):
|
||||
in_value.append(
|
||||
{'piid': prop.iid, 'value': in_list[index]})
|
||||
continue
|
||||
elif prop.format_ == 'int':
|
||||
elif prop.format_ == int:
|
||||
if isinstance(in_list[index], int):
|
||||
in_value.append(
|
||||
{'piid': prop.iid, 'value': in_list[index]})
|
||||
|
@ -42,12 +42,13 @@
|
||||
},
|
||||
"advanced_options": {
|
||||
"title": "Erweiterte Einstellungen",
|
||||
"description": "## Gebrauchsanweisung\r\n### Wenn Sie die Bedeutung der folgenden Optionen nicht genau kennen, belassen Sie sie bitte auf den Standardeinstellungen.\r\n### Geräte filtern\r\nUnterstützt das Filtern von Geräten nach Raumnamen und Gerätetypen sowie das Filtern nach Gerätedimensionen.\r\n\r\n### Steuerungsmodus\r\n- Automatisch: Wenn ein verfügbarer Xiaomi-Hub im lokalen Netzwerk vorhanden ist, priorisiert Home Assistant das Senden von Steuerbefehlen über den Hub, um eine lokale Steuerung zu ermöglichen. Wenn kein Hub vorhanden ist, wird versucht, Steuerbefehle über das Xiaomi-OT-Protokoll zu senden. Nur wenn diese Bedingungen für die lokale Steuerung nicht erfüllt sind, werden die Befehle über die Cloud gesendet.\r\n- Cloud: Steuerbefehle werden ausschließlich über die Cloud gesendet.\r\n### Action-Debug-Modus\r\nFür Methoden, die von MIoT-Spec-V2-Geräten definiert werden, wird neben der Benachrichtigungsentität auch eine Texteingabe-Entität erstellt, mit der Sie während des Debuggens Steuerbefehle an das Gerät senden können.\r\n### Nicht standardmäßige Entitäten ausblenden\r\nBlendet Entitäten aus, die von nicht-standardmäßigen MIoT-Spec-V2-Instanzen generiert werden und deren Name mit „*“ beginnt.\r\n### Gerätestatusänderungen anzeigen\r\nDetaillierte Anzeige von Gerätestatusänderungen, es werden nur die ausgewählten Benachrichtigungen angezeigt.",
|
||||
"description": "## Gebrauchsanweisung\r\n### Wenn Sie die Bedeutung der folgenden Optionen nicht genau kennen, belassen Sie sie bitte auf den Standardeinstellungen.\r\n### Geräte filtern\r\nUnterstützt das Filtern von Geräten nach Raumnamen und Gerätetypen sowie das Filtern nach Gerätedimensionen.\r\n\r\n### Steuerungsmodus\r\n- Automatisch: Wenn ein verfügbarer Xiaomi-Hub im lokalen Netzwerk vorhanden ist, priorisiert Home Assistant das Senden von Steuerbefehlen über den Hub, um eine lokale Steuerung zu ermöglichen. Wenn kein Hub vorhanden ist, wird versucht, Steuerbefehle über das Xiaomi-OT-Protokoll zu senden. Nur wenn diese Bedingungen für die lokale Steuerung nicht erfüllt sind, werden die Befehle über die Cloud gesendet.\r\n- Cloud: Steuerbefehle werden ausschließlich über die Cloud gesendet.\r\n### Action-Debug-Modus\r\nFür Methoden, die von MIoT-Spec-V2-Geräten definiert werden, wird neben der Benachrichtigungsentität auch eine Texteingabe-Entität erstellt, mit der Sie während des Debuggens Steuerbefehle an das Gerät senden können.\r\n### Nicht standardmäßige Entitäten ausblenden\r\nBlendet Entitäten aus, die von nicht-standardmäßigen MIoT-Spec-V2-Instanzen generiert werden und deren Name mit „*“ beginnt.\r\n### Binärsensor-Anzeigemodus\r\nZeigt Binärsensoren in Xiaomi Home als Textsensor-Entität oder Binärsensor-Entität an。\r\n### Gerätestatusänderungen anzeigen\r\nDetaillierte Anzeige von Gerätestatusänderungen, es werden nur die ausgewählten Benachrichtigungen angezeigt.",
|
||||
"data": {
|
||||
"devices_filter": "Geräte filtern",
|
||||
"ctrl_mode": "Steuerungsmodus",
|
||||
"action_debug": "Action-Debug-Modus",
|
||||
"hide_non_standard_entities": "Nicht standardmäßige Entitäten ausblenden",
|
||||
"display_binary_mode": "Binärsensor-Anzeigemodus",
|
||||
"display_devices_changed_notify": "Gerätestatusänderungen anzeigen"
|
||||
}
|
||||
},
|
||||
@ -119,6 +120,7 @@
|
||||
"update_devices": "Geräteliste aktualisieren",
|
||||
"action_debug": "Action-Debug-Modus",
|
||||
"hide_non_standard_entities": "Verstecke Nicht-Standard-Entitäten",
|
||||
"display_binary_mode": "Binärsensor-Anzeigemodus",
|
||||
"display_devices_changed_notify": "Gerätestatusänderungen anzeigen",
|
||||
"update_trans_rules": "Entitätskonvertierungsregeln aktualisieren",
|
||||
"update_lan_ctrl_config": "LAN-Steuerungskonfiguration aktualisieren",
|
||||
|
@ -42,12 +42,13 @@
|
||||
},
|
||||
"advanced_options": {
|
||||
"title": "Advanced Settings",
|
||||
"description": "## Introduction\r\n### Unless you are very clear about the meaning of the following options, please keep the default settings.\r\n### Filter Devices\r\nSupports filtering devices by room name and device type, and also supports device dimension filtering.\r\n### Control Mode\r\n- Auto: When there is an available Xiaomi central hub gateway in the local area network, Home Assistant will prioritize sending device control commands through the central hub gateway to achieve local control. If there is no central hub gateway in the local area network, it will attempt to send control commands through Xiaomi OT protocol to achieve local control. Only when the above local control conditions are not met, the device control commands will be sent through the cloud.\r\n- Cloud: All control commands are sent through the cloud.\r\n### Action Debug Mode\r\nFor the methods defined by the device MIoT-Spec-V2, in addition to generating notification entities, a text input box entity will also be generated. You can use it to send control commands to the device during debugging.\r\n### Hide Non-Standard Generated Entities\r\nHide entities generated by non-standard MIoT-Spec-V2 instances with names starting with \"*\".\r\n### Display Device Status Change Notifications\r\nDisplay detailed device status change notifications, only showing the selected notifications.",
|
||||
"description": "## Introduction\r\n### Unless you are very clear about the meaning of the following options, please keep the default settings.\r\n### Filter Devices\r\nSupports filtering devices by room name and device type, and also supports device dimension filtering.\r\n### Control Mode\r\n- Auto: When there is an available Xiaomi central hub gateway in the local area network, Home Assistant will prioritize sending device control commands through the central hub gateway to achieve local control. If there is no central hub gateway in the local area network, it will attempt to send control commands through Xiaomi OT protocol to achieve local control. Only when the above local control conditions are not met, the device control commands will be sent through the cloud.\r\n- Cloud: All control commands are sent through the cloud.\r\n### Action Debug Mode\r\nFor the methods defined by the device MIoT-Spec-V2, in addition to generating notification entities, a text input box entity will also be generated. You can use it to send control commands to the device during debugging.\r\n### Hide Non-Standard Generated Entities\r\nHide entities generated by non-standard MIoT-Spec-V2 instances with names starting with \"*\".\r\n### Binary Sensor Display Mode\r\nDisplay binary sensors in Xiaomi Home as text sensor entity or binary sensor entity。\r\n### Display Device Status Change Notifications\r\nDisplay detailed device status change notifications, only showing the selected notifications.",
|
||||
"data": {
|
||||
"devices_filter": "Filter Devices",
|
||||
"ctrl_mode": "Control Mode",
|
||||
"action_debug": "Action Debug Mode",
|
||||
"hide_non_standard_entities": "Hide Non-Standard Generated Entities",
|
||||
"display_binary_mode": "Binary Sensor Display Mode",
|
||||
"display_devices_changed_notify": "Display Device Status Change Notifications"
|
||||
}
|
||||
},
|
||||
@ -119,6 +120,7 @@
|
||||
"update_devices": "Update device list",
|
||||
"action_debug": "Debug mode for action",
|
||||
"hide_non_standard_entities": "Hide non-standard created entities",
|
||||
"display_binary_mode": "Binary Sensor Display Mode",
|
||||
"display_devices_changed_notify": "Display device status change notifications",
|
||||
"update_trans_rules": "Update entity conversion rules",
|
||||
"update_lan_ctrl_config": "Update LAN control configuration",
|
||||
|
@ -42,12 +42,13 @@
|
||||
},
|
||||
"advanced_options": {
|
||||
"title": "Opciones Avanzadas",
|
||||
"description": "## Introducción\r\n### A menos que entienda claramente el significado de las siguientes opciones, manténgalas en su configuración predeterminada.\r\n### Filtrar dispositivos\r\nAdmite la filtración de dispositivos por nombre de habitación y tipo de dispositivo, y también admite la filtración por familia.\r\n### Modo de Control\r\n- Automático: Cuando hay una puerta de enlace central de Xiaomi disponible en la red local, Home Assistant enviará comandos de control de dispositivos a través de la puerta de enlace central para lograr la función de control local. Cuando no hay una puerta de enlace central en la red local, intentará enviar comandos de control a través del protocolo OT de Xiaomi para lograr la función de control local. Solo cuando no se cumplan las condiciones de control local anteriores, los comandos de control de dispositivos se enviarán a través de la nube.\r\n- Nube: Los comandos de control solo se envían a través de la nube.\r\n### Modo de Depuración de Acciones\r\nPara los métodos definidos por el dispositivo MIoT-Spec-V2, además de generar una entidad de notificación, también se generará una entidad de cuadro de texto que se puede utilizar para enviar comandos de control al dispositivo durante la depuración.\r\n### Ocultar Entidades Generadas No Estándar\r\nOcultar entidades generadas por instancias MIoT-Spec-V2 no estándar que comienzan con \"*\".\r\n### Mostrar notificaciones de cambio de estado del dispositivo\r\nMostrar notificaciones detalladas de cambio de estado del dispositivo, mostrando solo las notificaciones seleccionadas.",
|
||||
"description": "## Introducción\r\n### A menos que entienda claramente el significado de las siguientes opciones, manténgalas en su configuración predeterminada.\r\n### Filtrar dispositivos\r\nAdmite la filtración de dispositivos por nombre de habitación y tipo de dispositivo, y también admite la filtración por familia.\r\n### Modo de Control\r\n- Automático: Cuando hay una puerta de enlace central de Xiaomi disponible en la red local, Home Assistant enviará comandos de control de dispositivos a través de la puerta de enlace central para lograr la función de control local. Cuando no hay una puerta de enlace central en la red local, intentará enviar comandos de control a través del protocolo OT de Xiaomi para lograr la función de control local. Solo cuando no se cumplan las condiciones de control local anteriores, los comandos de control de dispositivos se enviarán a través de la nube.\r\n- Nube: Los comandos de control solo se envían a través de la nube.\r\n### Modo de Depuración de Acciones\r\nPara los métodos definidos por el dispositivo MIoT-Spec-V2, además de generar una entidad de notificación, también se generará una entidad de cuadro de texto que se puede utilizar para enviar comandos de control al dispositivo durante la depuración.\r\n### Ocultar Entidades Generadas No Estándar\r\nOcultar entidades generadas por instancias MIoT-Spec-V2 no estándar que comienzan con \"*\".\r\n### Modo de visualización del sensor binario\r\nMuestra los sensores binarios en Xiaomi Home como entidad de sensor de texto o entidad de sensor binario。\r\n### Mostrar notificaciones de cambio de estado del dispositivo\r\nMostrar notificaciones detalladas de cambio de estado del dispositivo, mostrando solo las notificaciones seleccionadas.",
|
||||
"data": {
|
||||
"devices_filter": "Filtrar Dispositivos",
|
||||
"ctrl_mode": "Modo de Control",
|
||||
"action_debug": "Modo de Depuración de Acciones",
|
||||
"hide_non_standard_entities": "Ocultar Entidades Generadas No Estándar",
|
||||
"display_binary_mode": "Modo de visualización del sensor binario",
|
||||
"display_devices_changed_notify": "Mostrar notificaciones de cambio de estado del dispositivo"
|
||||
}
|
||||
},
|
||||
@ -119,6 +120,7 @@
|
||||
"update_devices": "Actualizar lista de dispositivos",
|
||||
"action_debug": "Modo de depuración de Action",
|
||||
"hide_non_standard_entities": "Ocultar entidades generadas no estándar",
|
||||
"display_binary_mode": "Modo de visualización del sensor binario",
|
||||
"display_devices_changed_notify": "Mostrar notificaciones de cambio de estado del dispositivo",
|
||||
"update_trans_rules": "Actualizar reglas de conversión de entidad",
|
||||
"update_lan_ctrl_config": "Actualizar configuración de control LAN",
|
||||
|
@ -42,12 +42,13 @@
|
||||
},
|
||||
"advanced_options": {
|
||||
"title": "Paramètres Avancés",
|
||||
"description": "## Introduction\r\n### Sauf si vous comprenez très bien la signification des options suivantes, veuillez les laisser par défaut.\r\n### Filtrer les appareils\r\nPrend en charge le filtrage des appareils en fonction du nom de la pièce et du type d'appareil, ainsi que le filtrage basé sur les appareils.\r\n### Mode de Contrôle\r\n- Automatique : Lorsqu'une passerelle Xiaomi est disponible dans le réseau local, Home Assistant enverra les commandes de contrôle des appareils via la passerelle pour permettre le contrôle local. Si aucune passerelle n'est disponible dans le réseau local, Home Assistant essaiera d'envoyer les commandes de contrôle des appareils via le protocole OT Xiaomi pour permettre le contrôle local. Seules si les conditions de contrôle local ci-dessus ne sont pas remplies, les commandes de contrôle des appareils seront envoyées via le cloud.\r\n- Cloud : Les commandes de contrôle des appareils sont envoyées uniquement via le cloud.\r\n### Mode de Débogage d’Actions\r\nPour les méthodes définies par les appareils MIoT-Spec-V2, en plus de générer une entité de notification, une entité de champ de texte sera également générée pour vous permettre d'envoyer des commandes de contrôle aux appareils lors du débogage.\r\n### Masquer les Entités Non Standard\r\nMasquer les entités générées par des instances MIoT-Spec-V2 non standard et commençant par \"*\".\r\n### Afficher les notifications de changement d'état de l'appareil\r\nAfficher les notifications détaillées de changement d'état de l'appareil, en affichant uniquement les notifications sélectionnées.",
|
||||
"description": "## Introduction\r\n### Sauf si vous comprenez très bien la signification des options suivantes, veuillez les laisser par défaut.\r\n### Filtrer les appareils\r\nPrend en charge le filtrage des appareils en fonction du nom de la pièce et du type d'appareil, ainsi que le filtrage basé sur les appareils.\r\n### Mode de Contrôle\r\n- Automatique : Lorsqu'une passerelle Xiaomi est disponible dans le réseau local, Home Assistant enverra les commandes de contrôle des appareils via la passerelle pour permettre le contrôle local. Si aucune passerelle n'est disponible dans le réseau local, Home Assistant essaiera d'envoyer les commandes de contrôle des appareils via le protocole OT Xiaomi pour permettre le contrôle local. Seules si les conditions de contrôle local ci-dessus ne sont pas remplies, les commandes de contrôle des appareils seront envoyées via le cloud.\r\n- Cloud : Les commandes de contrôle des appareils sont envoyées uniquement via le cloud.\r\n### Mode de Débogage d’Actions\r\nPour les méthodes définies par les appareils MIoT-Spec-V2, en plus de générer une entité de notification, une entité de champ de texte sera également générée pour vous permettre d'envoyer des commandes de contrôle aux appareils lors du débogage.\r\n### Masquer les Entités Non Standard\r\nMasquer les entités générées par des instances MIoT-Spec-V2 non standard et commençant par \"*\".\r\n### Mode d'affichage du capteur binaire\r\nAffiche les capteurs binaires dans Xiaomi Home comme entité de capteur de texte ou entité de capteur binaire。\r\n### Afficher les notifications de changement d'état de l'appareil\r\nAfficher les notifications détaillées de changement d'état de l'appareil, en affichant uniquement les notifications sélectionnées.",
|
||||
"data": {
|
||||
"devices_filter": "Filtrer les Appareils",
|
||||
"ctrl_mode": "Mode de Contrôle",
|
||||
"action_debug": "Mode de Débogage d’Actions",
|
||||
"hide_non_standard_entities": "Masquer les Entités Non Standard",
|
||||
"display_binary_mode": "Mode d'affichage du capteur binaire",
|
||||
"display_devices_changed_notify": "Afficher les notifications de changement d'état de l'appareil"
|
||||
}
|
||||
},
|
||||
@ -119,6 +120,7 @@
|
||||
"update_devices": "Mettre à jour la liste des appareils",
|
||||
"action_debug": "Mode de débogage d'action",
|
||||
"hide_non_standard_entities": "Masquer les entités générées non standard",
|
||||
"display_binary_mode": "Mode d'affichage du capteur binaire",
|
||||
"display_devices_changed_notify": "Afficher les notifications de changement d'état de l'appareil",
|
||||
"update_trans_rules": "Mettre à jour les règles de conversion d'entités",
|
||||
"update_lan_ctrl_config": "Mettre à jour la configuration de contrôle LAN",
|
||||
|
@ -16,7 +16,7 @@
|
||||
"cloud_server": "Regione di Login",
|
||||
"integration_language": "Lingua",
|
||||
"oauth_redirect_url": "URL di reindirizzamento OAuth2",
|
||||
"network_detect_config": "Configurazione di rete integrata"
|
||||
"network_detect_config": "Configurazione di rete integrata"
|
||||
}
|
||||
},
|
||||
"network_detect_config": {
|
||||
@ -42,12 +42,13 @@
|
||||
},
|
||||
"advanced_options": {
|
||||
"title": "Impostazioni Avanzate",
|
||||
"description": "## Introduzione\r\n### A meno che tu non abbia chiaro il significato delle seguenti opzioni, si prega di mantenere le impostazioni predefinite.\r\n### Filtra Dispositivi\r\nSupporta il filtraggio dei dispositivi per nome della stanza e tipo di dispositivo, e supporta anche il filtraggio delle dimensioni del dispositivo.\r\n### Modalità di Controllo\r\n- Automatico: Quando è disponibile un gateway hub centrale Xiaomi nella rete locale, Home Assistant darà priorità all'invio dei comandi di controllo dei dispositivi tramite il gateway hub centrale per ottenere il controllo locale. Se non è presente un gateway hub centrale nella rete locale, tenterà di inviare comandi di controllo tramite il protocollo OT di Xiaomi per ottenere il controllo locale. Solo quando le condizioni di controllo locale sopra indicate non sono soddisfatte, i comandi di controllo del dispositivo verranno inviati tramite il cloud.\r\n- Cloud: Tutti i comandi di controllo vengono inviati tramite il cloud.\r\n### Modalità di Debug delle Azioni\r\nPer i metodi definiti dal dispositivo MIoT-Spec-V2, oltre a generare entità di notifica, verrà generata anche un'entità di casella di input di testo. È possibile utilizzarla per inviare comandi di controllo al dispositivo durante il debug.\r\n### Nascondi Entità Generate Non Standard\r\nNasconde le entità generate da istanze non standard MIoT-Spec-V2 con nomi che iniziano con \"*\".\r\n### Mostra Notifiche di Cambio di Stato del Dispositivo\r\nMostra notifiche dettagliate sui cambiamenti di stato del dispositivo, mostrando solo le notifiche selezionate.",
|
||||
"description": "## Introduzione\r\n### A meno che tu non abbia chiaro il significato delle seguenti opzioni, si prega di mantenere le impostazioni predefinite.\r\n### Filtra Dispositivi\r\nSupporta il filtraggio dei dispositivi per nome della stanza e tipo di dispositivo, e supporta anche il filtraggio delle dimensioni del dispositivo.\r\n### Modalità di Controllo\r\n- Automatico: Quando è disponibile un gateway hub centrale Xiaomi nella rete locale, Home Assistant darà priorità all'invio dei comandi di controllo dei dispositivi tramite il gateway hub centrale per ottenere il controllo locale. Se non è presente un gateway hub centrale nella rete locale, tenterà di inviare comandi di controllo tramite il protocollo OT di Xiaomi per ottenere il controllo locale. Solo quando le condizioni di controllo locale sopra indicate non sono soddisfatte, i comandi di controllo del dispositivo verranno inviati tramite il cloud.\r\n- Cloud: Tutti i comandi di controllo vengono inviati tramite il cloud.\r\n### Modalità di Debug delle Azioni\r\nPer i metodi definiti dal dispositivo MIoT-Spec-V2, oltre a generare entità di notifica, verrà generata anche un'entità di casella di input di testo. È possibile utilizzarla per inviare comandi di controllo al dispositivo durante il debug.\r\n### Nascondi Entità Generate Non Standard\r\nNasconde le entità generate da istanze non standard MIoT-Spec-V2 con nomi che iniziano con \"*\".\r\n### Modalità di visualizzazione del sensore binario\r\nVisualizza i sensori binari in Mi Home come entità del sensore di testo o entità del sensore binario。\r\n### Mostra Notifiche di Cambio di Stato del Dispositivo\r\nMostra notifiche dettagliate sui cambiamenti di stato del dispositivo, mostrando solo le notifiche selezionate.",
|
||||
"data": {
|
||||
"devices_filter": "Filtra Dispositivi",
|
||||
"ctrl_mode": "Modalità di Controllo",
|
||||
"action_debug": "Modalità di Debug delle Azioni",
|
||||
"hide_non_standard_entities": "Nascondi Entità Generate Non Standard",
|
||||
"display_binary_mode": "Modalità di visualizzazione del sensore binario",
|
||||
"display_devices_changed_notify": "Mostra Notifiche di Cambio di Stato del Dispositivo"
|
||||
}
|
||||
},
|
||||
@ -119,6 +120,7 @@
|
||||
"update_devices": "Aggiorna l'elenco dei dispositivi",
|
||||
"action_debug": "Modalità debug per azione",
|
||||
"hide_non_standard_entities": "Nascondi entità create non standard",
|
||||
"display_binary_mode": "Modalità di visualizzazione del sensore binario",
|
||||
"display_devices_changed_notify": "Mostra notifiche di cambio stato del dispositivo",
|
||||
"update_trans_rules": "Aggiorna le regole di conversione delle entità",
|
||||
"update_lan_ctrl_config": "Aggiorna configurazione del controllo LAN",
|
||||
@ -219,4 +221,4 @@
|
||||
"inconsistent_account": "Le informazioni dell'account sono incoerenti."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -42,12 +42,13 @@
|
||||
},
|
||||
"advanced_options": {
|
||||
"title": "高度な設定オプション",
|
||||
"description": "## 紹介\r\n### 以下のオプションの意味がよくわからない場合は、デフォルトのままにしてください。\r\n### デバイスのフィルタリング\r\n部屋名とデバイスタイプでデバイスをフィルタリングすることができます。デバイスの次元でフィルタリングすることもできます。\r\n### コントロールモード\r\n- 自動:ローカルネットワーク内に利用可能なXiaomi中央ゲートウェイがある場合、Home Assistantはデバイス制御命令を送信するために優先的に中央ゲートウェイを使用します。ローカルネットワークに中央ゲートウェイがない場合、Xiaomi OTプロトコルを使用してデバイス制御命令を送信し、ローカル制御機能を実現します。上記のローカル制御条件が満たされない場合のみ、デバイス制御命令はクラウドを介して送信されます。\r\n- クラウド:制御命令はクラウドを介してのみ送信されます。\r\n### Actionデバッグモード\r\nデバイスが定義するMIoT-Spec-V2のメソッドに対して、通知エンティティを生成するだけでなく、デバイスに制御命令を送信するためのテキスト入力ボックスエンティティも生成されます。デバッグ時にデバイスに制御命令を送信するために使用できます。\r\n### 非標準生成エンティティを隠す\r\n「*」で始まる名前の非標準MIoT-Spec-V2インスタンスによって生成されたエンティティを非表示にします。\r\n### デバイスの状態変化通知を表示\r\nデバイスの状態変化通知を詳細に表示し、選択された通知のみを表示します。",
|
||||
"description": "## 紹介\r\n### 以下のオプションの意味がよくわからない場合は、デフォルトのままにしてください。\r\n### デバイスのフィルタリング\r\n部屋名とデバイスタイプでデバイスをフィルタリングすることができます。デバイスの次元でフィルタリングすることもできます。\r\n### コントロールモード\r\n- 自動:ローカルネットワーク内に利用可能なXiaomi中央ゲートウェイがある場合、Home Assistantはデバイス制御命令を送信するために優先的に中央ゲートウェイを使用します。ローカルネットワークに中央ゲートウェイがない場合、Xiaomi OTプロトコルを使用してデバイス制御命令を送信し、ローカル制御機能を実現します。上記のローカル制御条件が満たされない場合のみ、デバイス制御命令はクラウドを介して送信されます。\r\n- クラウド:制御命令はクラウドを介してのみ送信されます。\r\n### Actionデバッグモード\r\nデバイスが定義するMIoT-Spec-V2のメソッドに対して、通知エンティティを生成するだけでなく、デバイスに制御命令を送信するためのテキスト入力ボックスエンティティも生成されます。デバッグ時にデバイスに制御命令を送信するために使用できます。\r\n### 非標準生成エンティティを隠す\r\n「*」で始まる名前の非標準MIoT-Spec-V2インスタンスによって生成されたエンティティを非表示にします。\r\n### バイナリセンサー表示モード\r\nXiaomi Homeのバイナリセンサーをテキストセンサーエンティティまたはバイナリセンサーエンティティとして表示します。\r\n### デバイスの状態変化通知を表示\r\nデバイスの状態変化通知を詳細に表示し、選択された通知のみを表示します。",
|
||||
"data": {
|
||||
"devices_filter": "デバイスをフィルタリング",
|
||||
"ctrl_mode": "コントロールモード",
|
||||
"action_debug": "Actionデバッグモード",
|
||||
"hide_non_standard_entities": "非標準生成エンティティを隠す",
|
||||
"display_binary_mode": "バイナリセンサー表示モード",
|
||||
"display_devices_changed_notify": "デバイスの状態変化通知を表示"
|
||||
}
|
||||
},
|
||||
@ -119,6 +120,7 @@
|
||||
"update_devices": "デバイスリストを更新する",
|
||||
"action_debug": "Action デバッグモード",
|
||||
"hide_non_standard_entities": "非標準生成エンティティを非表示にする",
|
||||
"display_binary_mode": "バイナリセンサー表示モード",
|
||||
"display_devices_changed_notify": "デバイスの状態変化通知を表示",
|
||||
"update_trans_rules": "エンティティ変換ルールを更新する",
|
||||
"update_lan_ctrl_config": "LAN制御構成を更新する",
|
||||
|
@ -42,12 +42,13 @@
|
||||
},
|
||||
"advanced_options": {
|
||||
"title": "Geavanceerde Instellingen",
|
||||
"description": "## Inleiding\r\n### Tenzij u zeer goed op de hoogte bent van de betekenis van de volgende opties, houdt u de standaardinstellingen.\r\n### Apparaten filteren\r\nOndersteunt het filteren van apparaten op basis van kamer- en apparaattypen, en ondersteunt ook apparaatdimensiefiltering.\r\n### Besturingsmodus\r\n- Automatisch: Wanneer er een beschikbare Xiaomi centrale hubgateway in het lokale netwerk is, zal Home Assistant eerst apparaatbesturingsinstructies via de centrale hubgateway verzenden om lokale controlefunctionaliteit te bereiken. Als er geen centrale hub in het lokale netwerk is, zal het proberen om besturingsinstructies via het Xiaomi OT-protocol te verzenden om lokale controlefunctionaliteit te bereiken. Alleen als de bovenstaande lokale controlevoorwaarden niet worden vervuld, worden apparaatbesturingsinstructies via de cloud verzonden.\r\n- Cloud: Besturingsinstructies worden alleen via de cloud verzonden.\r\n### Actie-debugmodus\r\nVoor methoden die zijn gedefinieerd in de MIoT-Spec-V2 van het apparaat, wordt naast het genereren van een meldingsentiteit ook een tekstinvoerveldentiteit gegenereerd. U kunt dit gebruiken om besturingsinstructies naar het apparaat te sturen tijdens het debuggen.\r\n### Niet-standaard entiteiten verbergen\r\nVerberg entiteiten die zijn gegenereerd door niet-standaard MIoT-Spec-V2-instanties die beginnen met \"*\".\r\n### Apparaatstatuswijzigingen weergeven\r\nGedetailleerde apparaatstatuswijzigingen weergeven, alleen de geselecteerde meldingen weergeven.",
|
||||
"description": "## Inleiding\r\n### Tenzij u zeer goed op de hoogte bent van de betekenis van de volgende opties, houdt u de standaardinstellingen.\r\n### Apparaten filteren\r\nOndersteunt het filteren van apparaten op basis van kamer- en apparaattypen, en ondersteunt ook apparaatdimensiefiltering.\r\n### Besturingsmodus\r\n- Automatisch: Wanneer er een beschikbare Xiaomi centrale hubgateway in het lokale netwerk is, zal Home Assistant eerst apparaatbesturingsinstructies via de centrale hubgateway verzenden om lokale controlefunctionaliteit te bereiken. Als er geen centrale hub in het lokale netwerk is, zal het proberen om besturingsinstructies via het Xiaomi OT-protocol te verzenden om lokale controlefunctionaliteit te bereiken. Alleen als de bovenstaande lokale controlevoorwaarden niet worden vervuld, worden apparaatbesturingsinstructies via de cloud verzonden.\r\n- Cloud: Besturingsinstructies worden alleen via de cloud verzonden.\r\n### Actie-debugmodus\r\nVoor methoden die zijn gedefinieerd in de MIoT-Spec-V2 van het apparaat, wordt naast het genereren van een meldingsentiteit ook een tekstinvoerveldentiteit gegenereerd. U kunt dit gebruiken om besturingsinstructies naar het apparaat te sturen tijdens het debuggen.\r\n### Niet-standaard entiteiten verbergen\r\nVerberg entiteiten die zijn gegenereerd door niet-standaard MIoT-Spec-V2-instanties die beginnen met \"*\".\r\n### Binaire sensorweergavemodus\r\nToont binaire sensoren in Xiaomi Home als tekstsensor-entiteit of binairesensor-entiteit。\r\n### Apparaatstatuswijzigingen weergeven\r\nGedetailleerde apparaatstatuswijzigingen weergeven, alleen de geselecteerde meldingen weergeven.",
|
||||
"data": {
|
||||
"devices_filter": "Apparaten filteren",
|
||||
"ctrl_mode": "Besturingsmodus",
|
||||
"action_debug": "Actie-debugmodus",
|
||||
"hide_non_standard_entities": "Niet-standaard entiteiten verbergen",
|
||||
"display_binary_mode": "Binaire sensorweergavemodus",
|
||||
"display_devices_changed_notify": "Apparaatstatuswijzigingen weergeven"
|
||||
}
|
||||
},
|
||||
@ -119,6 +120,7 @@
|
||||
"update_devices": "Werk apparatenlijst bij",
|
||||
"action_debug": "Debugmodus voor actie",
|
||||
"hide_non_standard_entities": "Verberg niet-standaard gemaakte entiteiten",
|
||||
"display_binary_mode": "Binaire sensorweergavemodus",
|
||||
"display_devices_changed_notify": "Apparaatstatuswijzigingen weergeven",
|
||||
"update_trans_rules": "Werk entiteitsconversieregels bij",
|
||||
"update_lan_ctrl_config": "Werk LAN controleconfiguratie bij",
|
||||
|
@ -42,12 +42,13 @@
|
||||
},
|
||||
"advanced_options": {
|
||||
"title": "Configurações Avançadas",
|
||||
"description": "## Introdução\r\n### A menos que você entenda claramente o significado das opções a seguir, mantenha as configurações padrão.\r\n### Filtrar Dispositivos\r\nSuporte para filtrar dispositivos por nome da sala e tipo de dispositivo, bem como filtragem por família.\r\n### Modo de Controle\r\n- Automático: Quando um gateway central Xiaomi disponível na rede local está disponível, o Home Assistant enviará comandos de controle de dispositivo através do gateway central para realizar a função de controle local. Quando não há gateway central na rede local, ele tentará enviar comandos de controle através do protocolo OT da Xiaomi para realizar a função de controle local. Somente quando as condições de controle local acima não forem atendidas, os comandos de controle do dispositivo serão enviados através da nuvem.\r\n- Nuvem: Os comandos de controle são enviados apenas através da nuvem.\r\n### Modo de Depuração de Ações\r\nPara métodos definidos pelo MIoT-Spec-V2 do dispositivo, além de gerar uma entidade de notificação, também será gerada uma entidade de caixa de texto para você enviar comandos de controle ao dispositivo durante a depuração.\r\n### Ocultar Entidades Geradas Não Padrão\r\nOcultar entidades geradas por instâncias MIoT-Spec-V2 não padrão que começam com \"*\".\r\n### Exibir notificações de mudança de status do dispositivo\r\nExibir notificações detalhadas de mudança de status do dispositivo, mostrando apenas as notificações selecionadas.",
|
||||
"description": "## Introdução\r\n### A menos que você entenda claramente o significado das opções a seguir, mantenha as configurações padrão.\r\n### Filtrar Dispositivos\r\nSuporte para filtrar dispositivos por nome da sala e tipo de dispositivo, bem como filtragem por família.\r\n### Modo de Controle\r\n- Automático: Quando um gateway central Xiaomi disponível na rede local está disponível, o Home Assistant enviará comandos de controle de dispositivo através do gateway central para realizar a função de controle local. Quando não há gateway central na rede local, ele tentará enviar comandos de controle através do protocolo OT da Xiaomi para realizar a função de controle local. Somente quando as condições de controle local acima não forem atendidas, os comandos de controle do dispositivo serão enviados através da nuvem.\r\n- Nuvem: Os comandos de controle são enviados apenas através da nuvem.\r\n### Modo de Depuração de Ações\r\nPara métodos definidos pelo MIoT-Spec-V2 do dispositivo, além de gerar uma entidade de notificação, também será gerada uma entidade de caixa de texto para você enviar comandos de controle ao dispositivo durante a depuração.\r\n### Ocultar Entidades Geradas Não Padrão\r\nOcultar entidades geradas por instâncias MIoT-Spec-V2 não padrão que começam com \"*\".\r\n### Modo de exibição do sensor binário\r\nExibe sensores binários no Xiaomi Home como entidade de sensor de texto ou entidade de sensor binário。\r\n### Exibir notificações de mudança de status do dispositivo\r\nExibir notificações detalhadas de mudança de status do dispositivo, mostrando apenas as notificações selecionadas.",
|
||||
"data": {
|
||||
"devices_filter": "Filtrar Dispositivos",
|
||||
"ctrl_mode": "Modo de Controle",
|
||||
"action_debug": "Modo de Depuração de Ações",
|
||||
"hide_non_standard_entities": "Ocultar Entidades Geradas Não Padrão",
|
||||
"display_binary_mode": "Modo de exibição do sensor binário",
|
||||
"display_devices_changed_notify": "Exibir notificações de mudança de status do dispositivo"
|
||||
}
|
||||
},
|
||||
@ -119,6 +120,7 @@
|
||||
"update_devices": "Atualizar lista de dispositivos",
|
||||
"action_debug": "Modo de depuração para ação",
|
||||
"hide_non_standard_entities": "Ocultar entidades não padrão criadas",
|
||||
"display_binary_mode": "Modo de exibição do sensor binário",
|
||||
"display_devices_changed_notify": "Exibir notificações de mudança de status do dispositivo",
|
||||
"update_trans_rules": "Atualizar regras de conversão de entidades",
|
||||
"update_lan_ctrl_config": "Atualizar configuração de controle LAN",
|
||||
|
@ -42,12 +42,13 @@
|
||||
},
|
||||
"advanced_options": {
|
||||
"title": "Opções Avançadas",
|
||||
"description": "## Introdução\r\n### A menos que você entenda claramente o significado das opções abaixo, mantenha as configurações padrão.\r\n### Filtrar Dispositivos\r\nSuporte para filtrar dispositivos por nome da sala e tipo de dispositivo, bem como filtragem por família.\r\n### Modo de Controle\r\n- Automático: Quando um gateway central Xiaomi está disponível na rede local, o Home Assistant enviará comandos de controlo de dispositivos através do gateway central para realizar o controlo local. Quando não há gateway central na rede local, tentará enviar comandos de controlo através do protocolo Xiaomi OT para realizar o controlo local. Apenas quando as condições de controlo local acima não são atendidas, os comandos de controlo de dispositivos serão enviados através da nuvem.\r\n- Nuvem: Os comandos de controlo são enviados apenas através da nuvem.\r\n### Modo de Depuração de Ações\r\nPara métodos definidos pelo MIoT-Spec-V2, além de gerar uma entidade de notificação, também será gerada uma entidade de caixa de texto para depuração de controlo de dispositivos.\r\n### Ocultar Entidades Geradas Não Padrão\r\nOcultar entidades geradas por instâncias MIoT-Spec-V2 não padrão, cujos nomes começam com \"*\".\r\n### Exibir notificações de mudança de status do dispositivo\r\nExibir notificações detalhadas de mudança de status do dispositivo, mostrando apenas as notificações selecionadas.",
|
||||
"description": "## Introdução\r\n### A menos que você entenda claramente o significado das opções abaixo, mantenha as configurações padrão.\r\n### Filtrar Dispositivos\r\nSuporte para filtrar dispositivos por nome da sala e tipo de dispositivo, bem como filtragem por família.\r\n### Modo de Controle\r\n- Automático: Quando um gateway central Xiaomi está disponível na rede local, o Home Assistant enviará comandos de controlo de dispositivos através do gateway central para realizar o controlo local. Quando não há gateway central na rede local, tentará enviar comandos de controlo através do protocolo Xiaomi OT para realizar o controlo local. Apenas quando as condições de controlo local acima não são atendidas, os comandos de controlo de dispositivos serão enviados através da nuvem.\r\n- Nuvem: Os comandos de controlo são enviados apenas através da nuvem.\r\n### Modo de Depuração de Ações\r\nPara métodos definidos pelo MIoT-Spec-V2, além de gerar uma entidade de notificação, também será gerada uma entidade de caixa de texto para depuração de controlo de dispositivos.\r\n### Ocultar Entidades Geradas Não Padrão\r\nOcultar entidades geradas por instâncias MIoT-Spec-V2 não padrão, cujos nomes começam com \"*\".\r\n### Modo de exibição do sensor binário\r\nExibe sensores binários no Xiaomi Home como entidade de sensor de texto ou entidade de sensor binário。\r\n### Exibir notificações de mudança de status do dispositivo\r\nExibir notificações detalhadas de mudança de status do dispositivo, mostrando apenas as notificações selecionadas.",
|
||||
"data": {
|
||||
"devices_filter": "Filtrar Dispositivos",
|
||||
"ctrl_mode": "Modo de Controlo",
|
||||
"action_debug": "Modo de Depuração de Ações",
|
||||
"hide_non_standard_entities": "Ocultar Entidades Geradas Não Padrão",
|
||||
"display_binary_mode": "Modo de exibição do sensor binário",
|
||||
"display_devices_changed_notify": "Exibir notificações de mudança de status do dispositivo"
|
||||
}
|
||||
},
|
||||
@ -119,6 +120,7 @@
|
||||
"update_devices": "Atualizar lista de dispositivos",
|
||||
"action_debug": "Modo de depuração de ação",
|
||||
"hide_non_standard_entities": "Ocultar entidades não padrão",
|
||||
"display_binary_mode": "Modo de exibição do sensor binário",
|
||||
"display_devices_changed_notify": "Exibir notificações de mudança de status do dispositivo",
|
||||
"update_trans_rules": "Atualizar regras de conversão de entidades",
|
||||
"update_lan_ctrl_config": "Atualizar configuração de controlo LAN",
|
||||
|
@ -42,12 +42,13 @@
|
||||
},
|
||||
"advanced_options": {
|
||||
"title": "Расширенные настройки",
|
||||
"description": "## Введение\r\n### Если вы не очень хорошо понимаете значение следующих параметров, оставьте их по умолчанию.\r\n### Фильтрация устройств\r\nПоддерживает фильтрацию устройств по названию комнаты и типу устройства, а также фильтрацию по уровню устройства.\r\n### Режим управления\r\n- Автоматически: при наличии доступного центрального шлюза Xiaomi в локальной сети Home Assistant Home Assistant будет отправлять команды управления устройствами через центральный шлюз для локального управления. Если центрального шлюза нет в локальной сети, Home Assistant попытается отправить команды управления устройствами через протокол OT Xiaomi для локального управления. Только если вышеуказанные условия локального управления не выполняются, команды управления устройствами будут отправляться через облако.\r\n- Облако: команды управления отправляются только через облако.\r\n### Режим отладки действий\r\nДля методов, определенных устройством MIoT-Spec-V2, помимо создания уведомления, будет создана сущность текстового поля, которую вы можете использовать для отправки команд управления устройством во время отладки.\r\n### Скрыть нестандартные сущности\r\nСкрыть сущности, созданные нестандартными экземплярами MIoT-Spec-V2, имена которых начинаются с «*».\r\n### Отображать уведомления о изменении состояния устройства\r\nОтображать подробные уведомления о изменении состояния устройства, показывая только выбранные уведомления.",
|
||||
"description": "## Введение\r\n### Если вы не очень хорошо понимаете значение следующих параметров, оставьте их по умолчанию.\r\n### Фильтрация устройств\r\nПоддерживает фильтрацию устройств по названию комнаты и типу устройства, а также фильтрацию по уровню устройства.\r\n### Режим управления\r\n- Автоматически: при наличии доступного центрального шлюза Xiaomi в локальной сети Home Assistant Home Assistant будет отправлять команды управления устройствами через центральный шлюз для локального управления. Если центрального шлюза нет в локальной сети, Home Assistant попытается отправить команды управления устройствами через протокол OT Xiaomi для локального управления. Только если вышеуказанные условия локального управления не выполняются, команды управления устройствами будут отправляться через облако.\r\n- Облако: команды управления отправляются только через облако.\r\n### Режим отладки действий\r\nДля методов, определенных устройством MIoT-Spec-V2, помимо создания уведомления, будет создана сущность текстового поля, которую вы можете использовать для отправки команд управления устройством во время отладки.\r\n### Скрыть нестандартные сущности\r\nСкрыть сущности, созданные нестандартными экземплярами MIoT-Spec-V2, имена которых начинаются с «*».\r\n### Режим отображения бинарного датчика\r\nОтображает бинарные датчики в Xiaomi Home как сущность текстового датчика или сущность бинарного датчика。\r\n### Отображать уведомления о изменении состояния устройства\r\nОтображать подробные уведомления о изменении состояния устройства, показывая только выбранные уведомления.",
|
||||
"data": {
|
||||
"devices_filter": "Фильтрация устройств",
|
||||
"ctrl_mode": "Режим управления",
|
||||
"action_debug": "Режим отладки действий",
|
||||
"hide_non_standard_entities": "Скрыть нестандартные сущности",
|
||||
"display_binary_mode": "Режим отображения бинарного датчика",
|
||||
"display_devices_changed_notify": "Отображать уведомления о изменении состояния устройства"
|
||||
}
|
||||
},
|
||||
@ -119,6 +120,7 @@
|
||||
"update_devices": "Обновить список устройств",
|
||||
"action_debug": "Режим отладки Action",
|
||||
"hide_non_standard_entities": "Скрыть нестандартные сущности",
|
||||
"display_binary_mode": "Режим отображения бинарного датчика",
|
||||
"display_devices_changed_notify": "Отображать уведомления о изменении состояния устройства",
|
||||
"update_trans_rules": "Обновить правила преобразования сущностей",
|
||||
"update_lan_ctrl_config": "Обновить конфигурацию управления LAN",
|
||||
|
@ -42,12 +42,13 @@
|
||||
},
|
||||
"advanced_options": {
|
||||
"title": "高级设置选项",
|
||||
"description": "## 使用介绍\r\n### 除非您非常清楚下列选项的含义,否则请保持默认。\r\n### 筛选设备\r\n支持按照家庭房间名称、设备接入类型、设备型号筛选设备,同时也支持设备维度筛选。\r\n### 控制模式\r\n- 自动:本地局域网内存在可用的小米中枢网关时, Home Assistant 会优先通过中枢网关发送设备控制指令,以实现本地化控制功能。本地局域网不存在中枢时,会尝试通过小米OT协议发送控制指令,以实现本地化控制功能。只有当上述本地化控制条件不满足时,设备控制指令才会通过云端发送。\r\n- 云端:控制指令仅通过云端发送。\r\n### Action 调试模式\r\n对于设备 MIoT-Spec-V2 定义的方法,在生成通知实体之外,还会生成一个文本输入框实体,您可以在调试时用它向设备发送控制指令。\r\n### 隐藏非标准生成实体\r\n隐藏名称以“*”开头的非标准 MIoT-Spec-V2 实例生成的实体。\r\n### 显示设备状态变化通知\r\n细化显示设备状态变化通知,只显示勾选的通知消息。",
|
||||
"description": "## 使用介绍\r\n### 除非您非常清楚下列选项的含义,否则请保持默认。\r\n### 筛选设备\r\n支持按照家庭房间名称、设备接入类型、设备型号筛选设备,同时也支持设备维度筛选。\r\n### 控制模式\r\n- 自动:本地局域网内存在可用的小米中枢网关时, Home Assistant 会优先通过中枢网关发送设备控制指令,以实现本地化控制功能。本地局域网不存在中枢时,会尝试通过小米OT协议发送控制指令,以实现本地化控制功能。只有当上述本地化控制条件不满足时,设备控制指令才会通过云端发送。\r\n- 云端:控制指令仅通过云端发送。\r\n### Action 调试模式\r\n对于设备 MIoT-Spec-V2 定义的方法,在生成通知实体之外,还会生成一个文本输入框实体,您可以在调试时用它向设备发送控制指令。\r\n### 隐藏非标准生成实体\r\n隐藏名称以“*”开头的非标准 MIoT-Spec-V2 实例生成的实体。\r\n### 二进制传感器显示模式\r\n将米家中的二进制传感器显示为文本传感器实体或者二进制传感器实体。\r\n### 显示设备状态变化通知\r\n细化显示设备状态变化通知,只显示勾选的通知消息。",
|
||||
"data": {
|
||||
"devices_filter": "筛选设备",
|
||||
"ctrl_mode": "控制模式",
|
||||
"action_debug": "Action 调试模式",
|
||||
"hide_non_standard_entities": "隐藏非标准生成实体",
|
||||
"display_binary_mode": "二进制传感器显示模式",
|
||||
"display_devices_changed_notify": "显示设备状态变化通知"
|
||||
}
|
||||
},
|
||||
@ -119,6 +120,7 @@
|
||||
"update_devices": "更新设备列表",
|
||||
"action_debug": "Action 调试模式",
|
||||
"hide_non_standard_entities": "隐藏非标准生成实体",
|
||||
"display_binary_mode": "二进制传感器显示模式",
|
||||
"display_devices_changed_notify": "显示设备状态变化通知",
|
||||
"update_trans_rules": "更新实体转换规则",
|
||||
"update_lan_ctrl_config": "更新局域网控制配置",
|
||||
|
@ -42,12 +42,13 @@
|
||||
},
|
||||
"advanced_options": {
|
||||
"title": "高級設置選項",
|
||||
"description": "## 使用介紹\r\n### 除非您非常清楚下列選項的含義,否則請保持默認。\r\n### 篩選設備\r\n支持按照房間名稱和設備類型篩選設備,同時也支持設備維度篩選。\r\n### 控制模式\r\n- 自動:本地局域網內存在可用的小米中樞網關時, Home Assistant 會優先通過中樞網關發送設備控制指令,以實現本地化控制功能。本地局域網不存在中樞時,會嘗試通過小米OT協議發送控制指令,以實現本地化控制功能。只有當上述本地化控制條件不滿足時,設備控制指令才會通過雲端發送。\r\n- 雲端:控制指令僅通過雲端發送。\r\n### Action 調試模式\r\n對於設備 MIoT-Spec-V2 定義的方法,在生成通知實體之外,還會生成一個文本輸入框實體,您可以在調試時用它向設備發送控制指令。\r\n### 隱藏非標準生成實體\r\n隱藏名稱以“*”開頭的非標準 MIoT-Spec-V2 實例生成的實體。\r\n### 顯示設備狀態變化通知\r\n細化顯示設備狀態變化通知,只顯示勾選的通知消息。",
|
||||
"description": "## 使用介紹\r\n### 除非您非常清楚下列選項的含義,否則請保持默認。\r\n### 篩選設備\r\n支持按照房間名稱和設備類型篩選設備,同時也支持設備維度篩選。\r\n### 控制模式\r\n- 自動:本地局域網內存在可用的小米中樞網關時, Home Assistant 會優先通過中樞網關發送設備控制指令,以實現本地化控制功能。本地局域網不存在中樞時,會嘗試通過小米OT協議發送控制指令,以實現本地化控制功能。只有當上述本地化控制條件不滿足時,設備控制指令才會通過雲端發送。\r\n- 雲端:控制指令僅通過雲端發送。\r\n### Action 調試模式\r\n對於設備 MIoT-Spec-V2 定義的方法,在生成通知實體之外,還會生成一個文本輸入框實體,您可以在調試時用它向設備發送控制指令。\r\n### 隱藏非標準生成實體\r\n隱藏名稱以“*”開頭的非標準 MIoT-Spec-V2 實例生成的實體。\r\n### 二進制傳感器顯示模式\r\n將米家中的二進制傳感器顯示為文本傳感器實體或者二進制傳感器實體。\r\n### 顯示設備狀態變化通知\r\n細化顯示設備狀態變化通知,只顯示勾選的通知消息。",
|
||||
"data": {
|
||||
"devices_filter": "篩選設備",
|
||||
"ctrl_mode": "控制模式",
|
||||
"action_debug": "Action 調試模式",
|
||||
"hide_non_standard_entities": "隱藏非標準生成實體",
|
||||
"display_binary_mode": "二進制傳感器顯示模式",
|
||||
"display_devices_changed_notify": "顯示設備狀態變化通知"
|
||||
}
|
||||
},
|
||||
@ -119,6 +120,7 @@
|
||||
"update_devices": "更新設備列表",
|
||||
"action_debug": "Action 調試模式",
|
||||
"hide_non_standard_entities": "隱藏非標準生成實體",
|
||||
"display_binary_mode": "二進制傳感器顯示模式",
|
||||
"display_devices_changed_notify": "顯示設備狀態變化通知",
|
||||
"update_trans_rules": "更新實體轉換規則",
|
||||
"update_lan_ctrl_config": "更新局域網控制配置",
|
||||
|
@ -120,28 +120,18 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'status':
|
||||
if (
|
||||
not isinstance(prop.value_list, list)
|
||||
or not prop.value_list
|
||||
):
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'invalid status value_list, %s', self.entity_id)
|
||||
continue
|
||||
self._status_map = {
|
||||
item['value']: item['description']
|
||||
for item in prop.value_list}
|
||||
self._status_map = prop.value_list.to_map()
|
||||
self._prop_status = prop
|
||||
elif prop.name == 'fan-level':
|
||||
if (
|
||||
not isinstance(prop.value_list, list)
|
||||
or not prop.value_list
|
||||
):
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'invalid fan-level value_list, %s', self.entity_id)
|
||||
continue
|
||||
self._fan_level_map = {
|
||||
item['value']: item['description']
|
||||
for item in prop.value_list}
|
||||
self._fan_level_map = prop.value_list.to_map()
|
||||
self._attr_fan_speed_list = list(self._fan_level_map.values())
|
||||
self._attr_supported_features |= VacuumEntityFeature.FAN_SPEED
|
||||
self._prop_fan_level = prop
|
||||
@ -202,7 +192,7 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
|
||||
@property
|
||||
def state(self) -> Optional[str]:
|
||||
"""Return the current state of the vacuum cleaner."""
|
||||
return self.get_map_description(
|
||||
return self.get_map_value(
|
||||
map_=self._status_map,
|
||||
key=self.get_prop_value(prop=self._prop_status))
|
||||
|
||||
@ -214,6 +204,6 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
|
||||
@property
|
||||
def fan_speed(self) -> Optional[str]:
|
||||
"""Return the current fan speed of the vacuum cleaner."""
|
||||
return self.get_map_description(
|
||||
return self.get_map_value(
|
||||
map_=self._fan_level_map,
|
||||
key=self.get_prop_value(prop=self._prop_fan_level))
|
||||
|
@ -93,7 +93,7 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
_prop_target_temp: Optional[MIoTSpecProperty]
|
||||
_prop_mode: Optional[MIoTSpecProperty]
|
||||
|
||||
_mode_list: Optional[dict[Any, Any]]
|
||||
_mode_map: Optional[dict[Any, Any]]
|
||||
|
||||
def __init__(
|
||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
||||
@ -106,7 +106,7 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
self._prop_temp = None
|
||||
self._prop_target_temp = None
|
||||
self._prop_mode = None
|
||||
self._mode_list = None
|
||||
self._mode_map = None
|
||||
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
@ -115,7 +115,7 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
self._prop_on = prop
|
||||
# temperature
|
||||
if prop.name == 'temperature':
|
||||
if isinstance(prop.value_range, dict):
|
||||
if prop.value_range:
|
||||
if (
|
||||
self._attr_temperature_unit is None
|
||||
and prop.external_unit
|
||||
@ -128,9 +128,14 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
self.entity_id)
|
||||
# target-temperature
|
||||
if prop.name == 'target-temperature':
|
||||
self._attr_min_temp = prop.value_range['min']
|
||||
self._attr_max_temp = prop.value_range['max']
|
||||
self._attr_precision = prop.value_range['step']
|
||||
if not prop.value_range:
|
||||
_LOGGER.error(
|
||||
'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:
|
||||
self._attr_temperature_unit = prop.external_unit
|
||||
self._attr_supported_features |= (
|
||||
@ -138,17 +143,12 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
self._prop_target_temp = prop
|
||||
# mode
|
||||
if prop.name == 'mode':
|
||||
if (
|
||||
not isinstance(prop.value_list, list)
|
||||
or not prop.value_list
|
||||
):
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'mode value_list is None, %s', self.entity_id)
|
||||
continue
|
||||
self._mode_list = {
|
||||
item['value']: item['description']
|
||||
for item in prop.value_list}
|
||||
self._attr_operation_list = list(self._mode_list.values())
|
||||
self._mode_map = prop.value_list.to_map()
|
||||
self._attr_operation_list = list(self._mode_map.values())
|
||||
self._attr_supported_features |= (
|
||||
WaterHeaterEntityFeature.OPERATION_MODE)
|
||||
self._prop_mode = prop
|
||||
@ -184,7 +184,9 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
prop=self._prop_on, value=True, update=False)
|
||||
await self.set_property_async(
|
||||
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:
|
||||
"""Set the water heater to away mode."""
|
||||
@ -207,20 +209,6 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
return STATE_OFF
|
||||
if not self._prop_mode and self.get_prop_value(prop=self._prop_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))
|
||||
|
||||
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
|
||||
|
@ -16,13 +16,10 @@ MIOT_I18N_RELATIVE_PATH: str = path.join(
|
||||
ROOT_PATH, '../custom_components/xiaomi_home/miot/i18n')
|
||||
SPEC_BOOL_TRANS_FILE = path.join(
|
||||
ROOT_PATH,
|
||||
'../custom_components/xiaomi_home/miot/specs/bool_trans.json')
|
||||
SPEC_MULTI_LANG_FILE = path.join(
|
||||
ROOT_PATH,
|
||||
'../custom_components/xiaomi_home/miot/specs/multi_lang.json')
|
||||
'../custom_components/xiaomi_home/miot/specs/bool_trans.yaml')
|
||||
SPEC_FILTER_FILE = path.join(
|
||||
ROOT_PATH,
|
||||
'../custom_components/xiaomi_home/miot/specs/spec_filter.json')
|
||||
'../custom_components/xiaomi_home/miot/specs/spec_filter.yaml')
|
||||
|
||||
|
||||
def load_json_file(file_path: str) -> Optional[dict]:
|
||||
@ -54,6 +51,12 @@ def load_yaml_file(file_path: str) -> Optional[dict]:
|
||||
return None
|
||||
|
||||
|
||||
def save_yaml_file(file_path: str, data: dict) -> None:
|
||||
with open(file_path, 'w', encoding='utf-8') as file:
|
||||
yaml.safe_dump(
|
||||
data, file, default_flow_style=False, allow_unicode=True, indent=2)
|
||||
|
||||
|
||||
def dict_str_str(d: dict) -> bool:
|
||||
"""restricted format: dict[str, str]"""
|
||||
if not isinstance(d, dict):
|
||||
@ -161,25 +164,17 @@ def compare_dict_structure(dict1: dict, dict2: dict) -> bool:
|
||||
|
||||
|
||||
def sort_bool_trans(file_path: str):
|
||||
trans_data: dict = load_json_file(file_path=file_path)
|
||||
trans_data = load_yaml_file(file_path=file_path)
|
||||
assert isinstance(trans_data, dict), f'{file_path} format error'
|
||||
trans_data['data'] = dict(sorted(trans_data['data'].items()))
|
||||
for key, trans in trans_data['translate'].items():
|
||||
trans_data['translate'][key] = dict(sorted(trans.items()))
|
||||
return trans_data
|
||||
|
||||
|
||||
def sort_multi_lang(file_path: str):
|
||||
multi_lang: dict = load_json_file(file_path=file_path)
|
||||
multi_lang = dict(sorted(multi_lang.items()))
|
||||
for urn, trans in multi_lang.items():
|
||||
multi_lang[urn] = dict(sorted(trans.items()))
|
||||
for lang, spec in multi_lang[urn].items():
|
||||
multi_lang[urn][lang] = dict(sorted(spec.items()))
|
||||
return multi_lang
|
||||
|
||||
|
||||
def sort_spec_filter(file_path: str):
|
||||
filter_data: dict = load_json_file(file_path=file_path)
|
||||
filter_data = load_yaml_file(file_path=file_path)
|
||||
assert isinstance(filter_data, dict), f'{file_path} format error'
|
||||
filter_data = dict(sorted(filter_data.items()))
|
||||
for urn, spec in filter_data.items():
|
||||
filter_data[urn] = dict(sorted(spec.items()))
|
||||
@ -188,30 +183,26 @@ def sort_spec_filter(file_path: str):
|
||||
|
||||
@pytest.mark.github
|
||||
def test_bool_trans():
|
||||
data: dict = load_json_file(SPEC_BOOL_TRANS_FILE)
|
||||
data = load_yaml_file(SPEC_BOOL_TRANS_FILE)
|
||||
assert isinstance(data, dict)
|
||||
assert data, f'load {SPEC_BOOL_TRANS_FILE} failed'
|
||||
assert bool_trans(data), f'{SPEC_BOOL_TRANS_FILE} format error'
|
||||
|
||||
|
||||
@pytest.mark.github
|
||||
def test_spec_filter():
|
||||
data: dict = load_json_file(SPEC_FILTER_FILE)
|
||||
data = load_yaml_file(SPEC_FILTER_FILE)
|
||||
assert isinstance(data, dict)
|
||||
assert data, f'load {SPEC_FILTER_FILE} failed'
|
||||
assert spec_filter(data), f'{SPEC_FILTER_FILE} format error'
|
||||
|
||||
|
||||
@pytest.mark.github
|
||||
def test_multi_lang():
|
||||
data: dict = load_json_file(SPEC_MULTI_LANG_FILE)
|
||||
assert data, f'load {SPEC_MULTI_LANG_FILE} failed'
|
||||
assert nested_3_dict_str_str(data), f'{SPEC_MULTI_LANG_FILE} format error'
|
||||
|
||||
|
||||
@pytest.mark.github
|
||||
def test_miot_i18n():
|
||||
for file_name in listdir(MIOT_I18N_RELATIVE_PATH):
|
||||
file_path: str = path.join(MIOT_I18N_RELATIVE_PATH, file_name)
|
||||
data: dict = load_json_file(file_path)
|
||||
data = load_json_file(file_path)
|
||||
assert isinstance(data, dict)
|
||||
assert data, f'load {file_path} failed'
|
||||
assert nested_3_dict_str_str(data), f'{file_path} format error'
|
||||
|
||||
@ -220,7 +211,8 @@ def test_miot_i18n():
|
||||
def test_translations():
|
||||
for file_name in listdir(TRANS_RELATIVE_PATH):
|
||||
file_path: str = path.join(TRANS_RELATIVE_PATH, file_name)
|
||||
data: dict = load_json_file(file_path)
|
||||
data = load_json_file(file_path)
|
||||
assert isinstance(data, dict)
|
||||
assert data, f'load {file_path} failed'
|
||||
assert dict_str_dict(data), f'{file_path} format error'
|
||||
|
||||
@ -237,15 +229,16 @@ def test_miot_lang_integrity():
|
||||
i18n_names: set[str] = set(listdir(MIOT_I18N_RELATIVE_PATH))
|
||||
assert len(i18n_names) == len(translations_names)
|
||||
assert i18n_names == translations_names
|
||||
bool_trans_data: set[str] = load_json_file(SPEC_BOOL_TRANS_FILE)
|
||||
bool_trans_data = load_yaml_file(SPEC_BOOL_TRANS_FILE)
|
||||
assert isinstance(bool_trans_data, dict)
|
||||
bool_trans_names: set[str] = set(
|
||||
bool_trans_data['translate']['default'].keys())
|
||||
assert len(bool_trans_names) == len(translations_names)
|
||||
# Check translation files structure
|
||||
default_dict: dict = load_json_file(
|
||||
default_dict = load_json_file(
|
||||
path.join(TRANS_RELATIVE_PATH, integration_lang_list[0]))
|
||||
for name in list(integration_lang_list)[1:]:
|
||||
compare_dict: dict = load_json_file(
|
||||
compare_dict = load_json_file(
|
||||
path.join(TRANS_RELATIVE_PATH, name))
|
||||
if not compare_dict_structure(default_dict, compare_dict):
|
||||
_LOGGER.info(
|
||||
@ -255,7 +248,7 @@ def test_miot_lang_integrity():
|
||||
default_dict = load_json_file(
|
||||
path.join(MIOT_I18N_RELATIVE_PATH, integration_lang_list[0]))
|
||||
for name in list(integration_lang_list)[1:]:
|
||||
compare_dict: dict = load_json_file(
|
||||
compare_dict = load_json_file(
|
||||
path.join(MIOT_I18N_RELATIVE_PATH, name))
|
||||
if not compare_dict_structure(default_dict, compare_dict):
|
||||
_LOGGER.info(
|
||||
@ -272,19 +265,13 @@ def test_miot_data_sort():
|
||||
'INTEGRATION_LANGUAGES not sorted, correct order\r\n'
|
||||
f'{list(sort_langs.keys())}')
|
||||
assert json.dumps(
|
||||
load_json_file(file_path=SPEC_BOOL_TRANS_FILE)) == json.dumps(
|
||||
load_yaml_file(file_path=SPEC_BOOL_TRANS_FILE)) == json.dumps(
|
||||
sort_bool_trans(file_path=SPEC_BOOL_TRANS_FILE)), (
|
||||
f'{SPEC_BOOL_TRANS_FILE} not sorted, goto project root path'
|
||||
' and run the following command sorting, ',
|
||||
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||
assert json.dumps(
|
||||
load_json_file(file_path=SPEC_MULTI_LANG_FILE)) == json.dumps(
|
||||
sort_multi_lang(file_path=SPEC_MULTI_LANG_FILE)), (
|
||||
f'{SPEC_MULTI_LANG_FILE} not sorted, goto project root path'
|
||||
' and run the following command sorting, ',
|
||||
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||
assert json.dumps(
|
||||
load_json_file(file_path=SPEC_FILTER_FILE)) == json.dumps(
|
||||
load_yaml_file(file_path=SPEC_FILTER_FILE)) == json.dumps(
|
||||
sort_spec_filter(file_path=SPEC_FILTER_FILE)), (
|
||||
f'{SPEC_FILTER_FILE} not sorted, goto project root path'
|
||||
' and run the following command sorting, ',
|
||||
@ -294,11 +281,8 @@ def test_miot_data_sort():
|
||||
@pytest.mark.update
|
||||
def test_sort_spec_data():
|
||||
sort_data: dict = sort_bool_trans(file_path=SPEC_BOOL_TRANS_FILE)
|
||||
save_json_file(file_path=SPEC_BOOL_TRANS_FILE, data=sort_data)
|
||||
save_yaml_file(file_path=SPEC_BOOL_TRANS_FILE, data=sort_data)
|
||||
_LOGGER.info('%s formatted.', SPEC_BOOL_TRANS_FILE)
|
||||
sort_data = sort_multi_lang(file_path=SPEC_MULTI_LANG_FILE)
|
||||
save_json_file(file_path=SPEC_MULTI_LANG_FILE, data=sort_data)
|
||||
_LOGGER.info('%s formatted.', SPEC_MULTI_LANG_FILE)
|
||||
sort_data = sort_spec_filter(file_path=SPEC_FILTER_FILE)
|
||||
save_json_file(file_path=SPEC_FILTER_FILE, data=sort_data)
|
||||
save_yaml_file(file_path=SPEC_FILTER_FILE, data=sort_data)
|
||||
_LOGGER.info('%s formatted.', SPEC_FILTER_FILE)
|
||||
|
Loading…
x
Reference in New Issue
Block a user