mirror of
https://github.com/XiaoMi/ha_xiaomi_home.git
synced 2026-10-27 16:10:49 +08:00
fix(mips): return None instead of raising when getDevList times out (#1793)
Some checks failed
Tests / check-rule-format (push) Has been cancelled
Validate / validate-hassfest (push) Has been cancelled
Validate / validate-hacs (push) Has been cancelled
Validate / validate-lint (push) Has been cancelled
Validate / validate-setup (push) Has been cancelled
Some checks failed
Tests / check-rule-format (push) Has been cancelled
Validate / validate-hassfest (push) Has been cancelled
Validate / validate-hacs (push) Has been cancelled
Validate / validate-lint (push) Has been cancelled
Validate / validate-setup (push) Has been cancelled
`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 <noreply@anthropic.com>
This commit is contained in:
@@ -482,7 +482,7 @@ class _MipsClient(ABC):
|
|||||||
self,
|
self,
|
||||||
payload: Optional[str] = None,
|
payload: Optional[str] = None,
|
||||||
timeout_ms: int = 10000
|
timeout_ms: int = 10000
|
||||||
) -> dict[str, dict]: ...
|
) -> Optional[dict[str, dict]]: ...
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def get_prop_async(
|
async def get_prop_async(
|
||||||
@@ -1031,7 +1031,7 @@ class MipsCloudClient(_MipsClient):
|
|||||||
|
|
||||||
async def get_dev_list_async(
|
async def get_dev_list_async(
|
||||||
self, payload: Optional[str] = None, timeout_ms: int = 10000
|
self, payload: Optional[str] = None, timeout_ms: int = 10000
|
||||||
) -> dict[str, dict]:
|
) -> Optional[dict[str, dict]]:
|
||||||
raise NotImplementedError('please call in http client')
|
raise NotImplementedError('please call in http client')
|
||||||
|
|
||||||
async def get_prop_async(
|
async def get_prop_async(
|
||||||
@@ -1385,12 +1385,13 @@ class MipsLocalClient(_MipsClient):
|
|||||||
@final
|
@final
|
||||||
async def get_dev_list_async(
|
async def get_dev_list_async(
|
||||||
self, payload: Optional[str] = None, timeout_ms: int = 10000
|
self, payload: Optional[str] = None, timeout_ms: int = 10000
|
||||||
) -> dict[str, dict]:
|
) -> Optional[dict[str, dict]]:
|
||||||
result_obj = await self.__request_async(
|
result_obj = await self.__request_async(
|
||||||
topic='proxy/getDevList', payload=payload or '{}',
|
topic='proxy/getDevList', payload=payload or '{}',
|
||||||
timeout_ms=timeout_ms)
|
timeout_ms=timeout_ms)
|
||||||
if not result_obj or 'devList' not in result_obj:
|
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 = {}
|
device_list = {}
|
||||||
for did, info in result_obj['devList'].items():
|
for did, info in result_obj['devList'].items():
|
||||||
name: str = info.get('name', None)
|
name: str = info.get('name', None)
|
||||||
|
|||||||
Reference in New Issue
Block a user