`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>
MipsCloudClient._on_mips_message decodes the push payload with a bare
payload.decode('utf-8'). Some cloud pushes carry payloads that are not
valid UTF-8, which raises UnicodeDecodeError inside paho's on_message,
aborts the read loop and silently drops the message. Lost device
online/offline pushes can leave a device stuck unavailable until the
config entry is reloaded manually.
Catch UnicodeDecodeError, log the topic and the payload hex for
diagnosis, and skip the message so that one malformed push cannot
break message processing.
Related to #1749
* feat: subscribe the proxy gateway child device up messages even though the device is offline (#1313)
* feat: do not subscribe proxy gateway child device online/offline state message
* fix: reset the reconnect interval when connected (#1175)
* feat: set the default reconnect delay time as 10 seconds
* fix: get the minimum reconnect interval
* fix: cuco.plug.cp2 voltage and power value ratio
* fix: cgllc.airmonitor.s1 unit ppb
* fix: roswan.waterpuri.lte01 tds unit
* fix: lumi.relay.c2acn01 power consumption unit
* fix: xiaomi.bhf_light.s1 fan level of ventilation
* fix: error log
* fix: fix type error, wrong use of any and Any
* fix: wrong use of session close
* fix: fix test_lan type error
* fix: remove __del__
* feat: oauth, http add deinit_async