From bf38d93068acfe8f608a28cca4f7f857c53a9636 Mon Sep 17 00:00:00 2001 From: zealllot <502372679@qq.com> Date: Thu, 24 Sep 2026 13:06:18 +0800 Subject: [PATCH] fix(mips): return None instead of raising when getDevList times out (#1793) `MipsLocalClient.get_dev_list_async()` raises `MIoTMipsError` when the central hub gateway does not answer in time, but both of its callers in `miot_client.py` are written to handle a `None` return: gw_list = await mips.get_dev_list_async(payload=json.dumps(payload)) if gw_list is None: ... return Because the coroutine raises instead, those `None` branches are dead code and the exception escapes as an unretrieved task exception. The consequence is severe for `__refresh_gw_devices_with_group_id_async()`, which is the only place that populates `_device_list_gateway` in full (it runs when the local mips connection comes up, and queries every device with no `filter`). One unreachable device is enough to time out the whole query, and when it does: - `_device_list_gateway` stays empty, so every device keeps `gw_state` False; - the retry in the `gw_list is None` branch never runs, so it never recovers. `__check_device_state()` is `cloud or gateway or lan`, so with the gateway path silently dead the cloud becomes the only source of truth. The next time the cloud MQTT connection drops, every device is marked offline at once, even though the central hub gateway is connected and healthy the whole time. Observed on a 256-device / 2697-entity setup: an unplugged rice cooker (8 entities, permanently unavailable) caused 18 of 30 such exceptions over four days. Each cloud MQTT `Connection reset by peer` then took all 2643 gateway-backed entities offline until the cloud reconnected. Returning `None` makes the existing caller logic work as written: the failure is logged and, for the full refresh, retried until it succeeds. `MIoTMipsError` remains in use elsewhere in the file, and the return type is widened to `Optional[dict[str, dict]]` on the abstract method and on `MipsCloudClient` to match. Co-authored-by: Claude Opus 5 --- custom_components/xiaomi_home/miot/miot_mips.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/custom_components/xiaomi_home/miot/miot_mips.py b/custom_components/xiaomi_home/miot/miot_mips.py index d083bcc..b2df4ff 100644 --- a/custom_components/xiaomi_home/miot/miot_mips.py +++ b/custom_components/xiaomi_home/miot/miot_mips.py @@ -482,7 +482,7 @@ class _MipsClient(ABC): self, payload: Optional[str] = None, timeout_ms: int = 10000 - ) -> dict[str, dict]: ... + ) -> Optional[dict[str, dict]]: ... @abstractmethod async def get_prop_async( @@ -1031,7 +1031,7 @@ class MipsCloudClient(_MipsClient): async def get_dev_list_async( self, payload: Optional[str] = None, timeout_ms: int = 10000 - ) -> dict[str, dict]: + ) -> Optional[dict[str, dict]]: raise NotImplementedError('please call in http client') async def get_prop_async( @@ -1385,12 +1385,13 @@ class MipsLocalClient(_MipsClient): @final async def get_dev_list_async( self, payload: Optional[str] = None, timeout_ms: int = 10000 - ) -> dict[str, dict]: + ) -> Optional[dict[str, dict]]: result_obj = await self.__request_async( topic='proxy/getDevList', payload=payload or '{}', timeout_ms=timeout_ms) if not result_obj or 'devList' not in result_obj: - raise MIoTMipsError('invalid result') + self.log_error(f'get dev list failed, invalid result, {payload}') + return None device_list = {} for did, info in result_obj['devList'].items(): name: str = info.get('name', None)