mirror of
https://github.com/XiaoMi/ha_xiaomi_home.git
synced 2025-06-21 10:40:31 +08:00
feat: support modify spec and value conversion (#663)
* 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 * feat: update prop expr logic * feat: add spec modify * feat: update device sub id logic * feat: update get miot client instance logic * fix: fix some type error * feat: update miot device unit and icon trans * perf: update spec trans entity logic * feat: update spec trans entity rule * feat: update spec_modify * feat: update sensor ENUM icon * fix: fix miot device error * fix: fix miot spec error * featL update format check and spec modify file * feat: update checkout rule format * feat: handle special property.unit * feat: add expr for cuco-cp1md * feat: fix climate hvac error * feat: set sensor suggested display precision * feat: update climate set hvac logic * feat: add expr for cuco-v3 * feat: update spec expr for chuangmi-212a01
This commit is contained in:
@ -150,7 +150,7 @@ class MIoTClient:
|
||||
# Device list update timestamp
|
||||
_device_list_update_ts: int
|
||||
|
||||
_sub_source_list: dict[str, str]
|
||||
_sub_source_list: dict[str, Optional[str]]
|
||||
_sub_tree: MIoTMatcher
|
||||
_sub_device_state: dict[str, MipsDeviceState]
|
||||
|
||||
@ -620,7 +620,7 @@ class MIoTClient:
|
||||
# Priority local control
|
||||
if self._ctrl_mode == CtrlMode.AUTO:
|
||||
# Gateway control
|
||||
device_gw: dict = self._device_list_gateway.get(did, None)
|
||||
device_gw = self._device_list_gateway.get(did, None)
|
||||
if (
|
||||
device_gw and device_gw.get('online', False)
|
||||
and device_gw.get('specv2_access', False)
|
||||
@ -641,7 +641,7 @@ class MIoTClient:
|
||||
raise MIoTClientError(
|
||||
self.__get_exec_error_with_rc(rc=rc))
|
||||
# Lan control
|
||||
device_lan: dict = self._device_list_lan.get(did, None)
|
||||
device_lan = self._device_list_lan.get(did, None)
|
||||
if device_lan and device_lan.get('online', False):
|
||||
result = await self._miot_lan.set_prop_async(
|
||||
did=did, siid=siid, piid=piid, value=value)
|
||||
@ -657,7 +657,7 @@ class MIoTClient:
|
||||
# Cloud control
|
||||
device_cloud = self._device_list_cloud.get(did, None)
|
||||
if device_cloud and device_cloud.get('online', False):
|
||||
result: list = await self._http.set_prop_async(
|
||||
result = await self._http.set_prop_async(
|
||||
params=[
|
||||
{'did': did, 'siid': siid, 'piid': piid, 'value': value}
|
||||
])
|
||||
@ -746,7 +746,7 @@ class MIoTClient:
|
||||
if did not in self._device_list_cache:
|
||||
raise MIoTClientError(f'did not exist, {did}')
|
||||
|
||||
device_gw: dict = self._device_list_gateway.get(did, None)
|
||||
device_gw = self._device_list_gateway.get(did, None)
|
||||
# Priority local control
|
||||
if self._ctrl_mode == CtrlMode.AUTO:
|
||||
if (
|
||||
@ -782,7 +782,7 @@ class MIoTClient:
|
||||
self.__get_exec_error_with_rc(rc=rc))
|
||||
# Cloud control
|
||||
device_cloud = self._device_list_cloud.get(did, None)
|
||||
if device_cloud.get('online', False):
|
||||
if device_cloud and device_cloud.get('online', False):
|
||||
result: dict = await self._http.action_async(
|
||||
did=did, siid=siid, aiid=aiid, in_list=in_list)
|
||||
if result:
|
||||
@ -798,14 +798,15 @@ class MIoTClient:
|
||||
dids=[did]))
|
||||
raise MIoTClientError(
|
||||
self.__get_exec_error_with_rc(rc=rc))
|
||||
# Show error message
|
||||
# TODO: Show error message
|
||||
_LOGGER.error(
|
||||
'client action failed, %s.%d.%d', did, siid, aiid)
|
||||
return None
|
||||
return []
|
||||
|
||||
def sub_prop(
|
||||
self, did: str, handler: Callable[[dict, Any], None],
|
||||
siid: int = None, piid: int = None, handler_ctx: Any = None
|
||||
siid: Optional[int] = None, piid: Optional[int] = None,
|
||||
handler_ctx: Any = None
|
||||
) -> bool:
|
||||
if did not in self._device_list_cache:
|
||||
raise MIoTClientError(f'did not exist, {did}')
|
||||
@ -818,7 +819,9 @@ class MIoTClient:
|
||||
_LOGGER.debug('client sub prop, %s', topic)
|
||||
return True
|
||||
|
||||
def unsub_prop(self, did: str, siid: int = None, piid: int = None) -> bool:
|
||||
def unsub_prop(
|
||||
self, did: str, siid: Optional[int] = None, piid: Optional[int] = None
|
||||
) -> bool:
|
||||
topic = (
|
||||
f'{did}/p/'
|
||||
f'{"#" if siid is None or piid is None else f"{siid}/{piid}"}')
|
||||
@ -829,7 +832,8 @@ class MIoTClient:
|
||||
|
||||
def sub_event(
|
||||
self, did: str, handler: Callable[[dict, Any], None],
|
||||
siid: int = None, eiid: int = None, handler_ctx: Any = None
|
||||
siid: Optional[int] = None, eiid: Optional[int] = None,
|
||||
handler_ctx: Any = None
|
||||
) -> bool:
|
||||
if did not in self._device_list_cache:
|
||||
raise MIoTClientError(f'did not exist, {did}')
|
||||
@ -841,7 +845,9 @@ class MIoTClient:
|
||||
_LOGGER.debug('client sub event, %s', topic)
|
||||
return True
|
||||
|
||||
def unsub_event(self, did: str, siid: int = None, eiid: int = None) -> bool:
|
||||
def unsub_event(
|
||||
self, did: str, siid: Optional[int] = None, eiid: Optional[int] = None
|
||||
) -> bool:
|
||||
topic = (
|
||||
f'{did}/e/'
|
||||
f'{"#" if siid is None or eiid is None else f"{siid}/{eiid}"}')
|
||||
@ -1081,7 +1087,7 @@ class MIoTClient:
|
||||
if state_old == state_new:
|
||||
continue
|
||||
self._device_list_cache[did]['online'] = state_new
|
||||
sub: MipsDeviceState = self._sub_device_state.get(did, None)
|
||||
sub = self._sub_device_state.get(did, None)
|
||||
if sub and sub.handler:
|
||||
sub.handler(did, MIoTDeviceState.OFFLINE, sub.handler_ctx)
|
||||
self.__request_show_devices_changed_notify()
|
||||
@ -1091,8 +1097,8 @@ class MIoTClient:
|
||||
self, group_id: str, state: bool
|
||||
) -> None:
|
||||
_LOGGER.info('local mips state changed, %s, %s', group_id, state)
|
||||
mips: MipsLocalClient = self._mips_local.get(group_id, None)
|
||||
if mips is None:
|
||||
mips = self._mips_local.get(group_id, None)
|
||||
if not mips:
|
||||
_LOGGER.error(
|
||||
'local mips state changed, mips not exist, %s', group_id)
|
||||
return
|
||||
@ -1124,7 +1130,7 @@ class MIoTClient:
|
||||
if state_old == state_new:
|
||||
continue
|
||||
self._device_list_cache[did]['online'] = state_new
|
||||
sub: MipsDeviceState = self._sub_device_state.get(did, None)
|
||||
sub = self._sub_device_state.get(did, None)
|
||||
if sub and sub.handler:
|
||||
sub.handler(did, MIoTDeviceState.OFFLINE, sub.handler_ctx)
|
||||
self.__request_show_devices_changed_notify()
|
||||
@ -1171,7 +1177,7 @@ class MIoTClient:
|
||||
if state_old == state_new:
|
||||
continue
|
||||
self._device_list_cache[did]['online'] = state_new
|
||||
sub: MipsDeviceState = self._sub_device_state.get(did, None)
|
||||
sub = self._sub_device_state.get(did, None)
|
||||
if sub and sub.handler:
|
||||
sub.handler(did, MIoTDeviceState.OFFLINE, sub.handler_ctx)
|
||||
self._device_list_lan = {}
|
||||
@ -1201,7 +1207,7 @@ class MIoTClient:
|
||||
if state_old == state_new:
|
||||
return
|
||||
self._device_list_cache[did]['online'] = state_new
|
||||
sub: MipsDeviceState = self._sub_device_state.get(did, None)
|
||||
sub = self._sub_device_state.get(did, None)
|
||||
if sub and sub.handler:
|
||||
sub.handler(
|
||||
did, MIoTDeviceState.ONLINE if state_new
|
||||
@ -1257,7 +1263,7 @@ class MIoTClient:
|
||||
if state_old == state_new:
|
||||
return
|
||||
self._device_list_cache[did]['online'] = state_new
|
||||
sub: MipsDeviceState = self._sub_device_state.get(did, None)
|
||||
sub = self._sub_device_state.get(did, None)
|
||||
if sub and sub.handler:
|
||||
sub.handler(
|
||||
did, MIoTDeviceState.ONLINE if state_new
|
||||
@ -1301,9 +1307,8 @@ class MIoTClient:
|
||||
async def __load_cache_device_async(self) -> None:
|
||||
"""Load device list from cache."""
|
||||
cache_list: Optional[dict[str, dict]] = await self._storage.load_async(
|
||||
domain='miot_devices',
|
||||
name=f'{self._uid}_{self._cloud_server}',
|
||||
type_=dict)
|
||||
domain='miot_devices', name=f'{self._uid}_{self._cloud_server}',
|
||||
type_=dict) # type: ignore
|
||||
if not cache_list:
|
||||
self.__show_client_error_notify(
|
||||
message=self._i18n.translate(
|
||||
@ -1346,7 +1351,7 @@ class MIoTClient:
|
||||
cloud_state_old: Optional[bool] = self._device_list_cloud.get(
|
||||
did, {}).get('online', None)
|
||||
cloud_state_new: Optional[bool] = None
|
||||
device_new: dict = cloud_list.pop(did, None)
|
||||
device_new = cloud_list.pop(did, None)
|
||||
if device_new:
|
||||
cloud_state_new = device_new.get('online', None)
|
||||
# Update cache device info
|
||||
@ -1371,7 +1376,7 @@ class MIoTClient:
|
||||
continue
|
||||
info['online'] = state_new
|
||||
# Call device state changed callback
|
||||
sub: MipsDeviceState = self._sub_device_state.get(did, None)
|
||||
sub = self._sub_device_state.get(did, None)
|
||||
if sub and sub.handler:
|
||||
sub.handler(
|
||||
did, MIoTDeviceState.ONLINE if state_new
|
||||
@ -1426,8 +1431,7 @@ class MIoTClient:
|
||||
self, dids: list[str]
|
||||
) -> None:
|
||||
_LOGGER.debug('refresh cloud device with dids, %s', dids)
|
||||
cloud_list: dict[str, dict] = (
|
||||
await self._http.get_devices_with_dids_async(dids=dids))
|
||||
cloud_list = await self._http.get_devices_with_dids_async(dids=dids)
|
||||
if cloud_list is None:
|
||||
_LOGGER.error('cloud http get_dev_list_async failed, %s', dids)
|
||||
return
|
||||
@ -1466,11 +1470,11 @@ class MIoTClient:
|
||||
for did, info in self._device_list_cache.items():
|
||||
if did not in filter_dids:
|
||||
continue
|
||||
device_old: dict = self._device_list_gateway.get(did, None)
|
||||
device_old = self._device_list_gateway.get(did, None)
|
||||
gw_state_old = device_old.get(
|
||||
'online', False) if device_old else False
|
||||
gw_state_new: bool = False
|
||||
device_new: dict = gw_list.pop(did, None)
|
||||
device_new = gw_list.pop(did, None)
|
||||
if device_new:
|
||||
# Update gateway device info
|
||||
self._device_list_gateway[did] = {
|
||||
@ -1493,7 +1497,7 @@ class MIoTClient:
|
||||
if state_old == state_new:
|
||||
continue
|
||||
info['online'] = state_new
|
||||
sub: MipsDeviceState = self._sub_device_state.get(did, None)
|
||||
sub = self._sub_device_state.get(did, None)
|
||||
if sub and sub.handler:
|
||||
sub.handler(
|
||||
did, MIoTDeviceState.ONLINE if state_new
|
||||
@ -1518,7 +1522,7 @@ class MIoTClient:
|
||||
if state_old == state_new:
|
||||
continue
|
||||
self._device_list_cache[did]['online'] = state_new
|
||||
sub: MipsDeviceState = self._sub_device_state.get(did, None)
|
||||
sub = self._sub_device_state.get(did, None)
|
||||
if sub and sub.handler:
|
||||
sub.handler(
|
||||
did, MIoTDeviceState.ONLINE if state_new
|
||||
@ -1533,7 +1537,7 @@ class MIoTClient:
|
||||
'refresh gw devices with group_id, %s', group_id)
|
||||
# Remove timer
|
||||
self._mips_local_state_changed_timers.pop(group_id, None)
|
||||
mips: MipsLocalClient = self._mips_local.get(group_id, None)
|
||||
mips = self._mips_local.get(group_id, None)
|
||||
if not mips:
|
||||
_LOGGER.error('mips not exist, %s', group_id)
|
||||
return
|
||||
@ -1900,77 +1904,73 @@ async def get_miot_instance_async(
|
||||
) -> MIoTClient:
|
||||
if entry_id is None:
|
||||
raise MIoTClientError('invalid entry_id')
|
||||
miot_client: MIoTClient = None
|
||||
if a := hass.data[DOMAIN].get('miot_clients', {}).get(entry_id, None):
|
||||
miot_client = hass.data[DOMAIN].get('miot_clients', {}).get(entry_id, None)
|
||||
if miot_client:
|
||||
_LOGGER.info('instance exist, %s', entry_id)
|
||||
miot_client = a
|
||||
else:
|
||||
if entry_data is None:
|
||||
raise MIoTClientError('entry data is None')
|
||||
# Get running loop
|
||||
loop: asyncio.AbstractEventLoop = asyncio.get_running_loop()
|
||||
if loop is None:
|
||||
raise MIoTClientError('loop is None')
|
||||
# MIoT storage
|
||||
storage: Optional[MIoTStorage] = hass.data[DOMAIN].get(
|
||||
'miot_storage', None)
|
||||
if not storage:
|
||||
storage = MIoTStorage(
|
||||
root_path=entry_data['storage_path'], loop=loop)
|
||||
hass.data[DOMAIN]['miot_storage'] = storage
|
||||
_LOGGER.info('create miot_storage instance')
|
||||
global_config: dict = await storage.load_user_config_async(
|
||||
uid='global_config', cloud_server='all',
|
||||
keys=['network_detect_addr', 'net_interfaces', 'enable_subscribe'])
|
||||
# MIoT network
|
||||
network_detect_addr: dict = global_config.get(
|
||||
'network_detect_addr', {})
|
||||
network: Optional[MIoTNetwork] = hass.data[DOMAIN].get(
|
||||
'miot_network', None)
|
||||
if not network:
|
||||
network = MIoTNetwork(
|
||||
ip_addr_list=network_detect_addr.get('ip', []),
|
||||
url_addr_list=network_detect_addr.get('url', []),
|
||||
refresh_interval=NETWORK_REFRESH_INTERVAL,
|
||||
loop=loop)
|
||||
hass.data[DOMAIN]['miot_network'] = network
|
||||
await network.init_async()
|
||||
_LOGGER.info('create miot_network instance')
|
||||
# MIoT service
|
||||
mips_service: Optional[MipsService] = hass.data[DOMAIN].get(
|
||||
'mips_service', None)
|
||||
if not mips_service:
|
||||
aiozc = await zeroconf.async_get_async_instance(hass)
|
||||
mips_service = MipsService(aiozc=aiozc, loop=loop)
|
||||
hass.data[DOMAIN]['mips_service'] = mips_service
|
||||
await mips_service.init_async()
|
||||
_LOGGER.info('create mips_service instance')
|
||||
# MIoT lan
|
||||
miot_lan: Optional[MIoTLan] = hass.data[DOMAIN].get(
|
||||
'miot_lan', None)
|
||||
if not miot_lan:
|
||||
miot_lan = MIoTLan(
|
||||
net_ifs=global_config.get('net_interfaces', []),
|
||||
network=network,
|
||||
mips_service=mips_service,
|
||||
enable_subscribe=global_config.get('enable_subscribe', False),
|
||||
loop=loop)
|
||||
hass.data[DOMAIN]['miot_lan'] = miot_lan
|
||||
_LOGGER.info('create miot_lan instance')
|
||||
# MIoT client
|
||||
miot_client = MIoTClient(
|
||||
entry_id=entry_id,
|
||||
entry_data=entry_data,
|
||||
return miot_client
|
||||
# Create new instance
|
||||
if not entry_data:
|
||||
raise MIoTClientError('entry data is None')
|
||||
# Get running loop
|
||||
loop: asyncio.AbstractEventLoop = asyncio.get_running_loop()
|
||||
if not loop:
|
||||
raise MIoTClientError('loop is None')
|
||||
# MIoT storage
|
||||
storage: Optional[MIoTStorage] = hass.data[DOMAIN].get(
|
||||
'miot_storage', None)
|
||||
if not storage:
|
||||
storage = MIoTStorage(
|
||||
root_path=entry_data['storage_path'], loop=loop)
|
||||
hass.data[DOMAIN]['miot_storage'] = storage
|
||||
_LOGGER.info('create miot_storage instance')
|
||||
global_config: dict = await storage.load_user_config_async(
|
||||
uid='global_config', cloud_server='all',
|
||||
keys=['network_detect_addr', 'net_interfaces', 'enable_subscribe'])
|
||||
# MIoT network
|
||||
network_detect_addr: dict = global_config.get('network_detect_addr', {})
|
||||
network: Optional[MIoTNetwork] = hass.data[DOMAIN].get(
|
||||
'miot_network', None)
|
||||
if not network:
|
||||
network = MIoTNetwork(
|
||||
ip_addr_list=network_detect_addr.get('ip', []),
|
||||
url_addr_list=network_detect_addr.get('url', []),
|
||||
refresh_interval=NETWORK_REFRESH_INTERVAL,
|
||||
loop=loop)
|
||||
hass.data[DOMAIN]['miot_network'] = network
|
||||
await network.init_async()
|
||||
_LOGGER.info('create miot_network instance')
|
||||
# MIoT service
|
||||
mips_service: Optional[MipsService] = hass.data[DOMAIN].get(
|
||||
'mips_service', None)
|
||||
if not mips_service:
|
||||
aiozc = await zeroconf.async_get_async_instance(hass)
|
||||
mips_service = MipsService(aiozc=aiozc, loop=loop)
|
||||
hass.data[DOMAIN]['mips_service'] = mips_service
|
||||
await mips_service.init_async()
|
||||
_LOGGER.info('create mips_service instance')
|
||||
# MIoT lan
|
||||
miot_lan: Optional[MIoTLan] = hass.data[DOMAIN].get('miot_lan', None)
|
||||
if not miot_lan:
|
||||
miot_lan = MIoTLan(
|
||||
net_ifs=global_config.get('net_interfaces', []),
|
||||
network=network,
|
||||
storage=storage,
|
||||
mips_service=mips_service,
|
||||
miot_lan=miot_lan,
|
||||
loop=loop
|
||||
)
|
||||
miot_client.persistent_notify = persistent_notify
|
||||
hass.data[DOMAIN]['miot_clients'].setdefault(entry_id, miot_client)
|
||||
_LOGGER.info(
|
||||
'new miot_client instance, %s, %s', entry_id, entry_data)
|
||||
await miot_client.init_async()
|
||||
|
||||
enable_subscribe=global_config.get('enable_subscribe', False),
|
||||
loop=loop)
|
||||
hass.data[DOMAIN]['miot_lan'] = miot_lan
|
||||
_LOGGER.info('create miot_lan instance')
|
||||
# MIoT client
|
||||
miot_client = MIoTClient(
|
||||
entry_id=entry_id,
|
||||
entry_data=entry_data,
|
||||
network=network,
|
||||
storage=storage,
|
||||
mips_service=mips_service,
|
||||
miot_lan=miot_lan,
|
||||
loop=loop
|
||||
)
|
||||
miot_client.persistent_notify = persistent_notify
|
||||
hass.data[DOMAIN]['miot_clients'].setdefault(entry_id, miot_client)
|
||||
_LOGGER.info('new miot_client instance, %s, %s', entry_id, entry_data)
|
||||
await miot_client.init_async()
|
||||
return miot_client
|
||||
|
Reference in New Issue
Block a user