中枢网关一次 getDevList 超时后本地通道永久失效:get_dev_list_async 抛异常逃逸,使「Retry until success」成为死代码(相关 #1679)
相关:#1679 报告了同样的现象(BE6500PRO 内置中枢不回
getDevList),并且 @vshijiav 已经查明中枢侧的根因 —— 中枢miot_central容器里的proxy_service在开机初始化阶段(Mesh 回程尚未建立时)死锁挂起,之后所有 MIPS 请求全部超时。本 issue 想补充的是集成侧的问题:即使中枢侧的 bug 由小米修好,集成现在的处理方式也会把「中枢一次抽风」放大成「本地通道永久失效且完全静默」。另外补充一个数据点:这个故障不只发生在 BE6500PRO 上,独立的
xiaomi.gateway.hub1(固件 3.5.0_0011)同样会。
Describe the Bug / 描述问题
中枢网关一旦有一次 proxy/getDevList 不回应,本地中枢通道就会永久失效,而且不会有任何告警,直到重启 Home Assistant。
我的中枢是独立的 xiaomi.gateway.hub1(固件 3.5.0_0011,有线接入)。它已连续运行 8.8 天、未死机、米家 App 控制一切正常(App 走云端),但对 HA 的本地 MIPS 请求一律不回。我绕开 HA 用 paho-mqtt 按同样的协议直连中枢复现了一遍:TLS 客户端证书握手成功、MQTT CONNACK Success(换一个没注册的 client_id 会得到 Not authorized,说明白名单在正常工作)、订阅 <virtual-did>/# 拿到 Granted QoS 2、QoS 2 的 PUBLISH 收到完整 PUBCOMP —— 但 master/proxy/getDevList、master/proxy/get、master/proxy/getMijiaActionGroupList 全部零回复。也就是 broker 层正常、proxy_service 那一层不应答,与 #1679 里的结论一致。重启中枢后立即恢复。
MIoTClient.__refresh_gw_devices_with_group_id_async() 里:
gw_list: dict = await mips.get_dev_list_async(
payload=json.dumps(payload))
if gw_list is None:
_LOGGER.error(
'refresh gw devices with group_id failed, %s, %s',
self._uid, group_id)
# Retry until success
self.__request_refresh_gw_devices_by_group_id(
group_id=group_id)
return
但 MipsLocalClient.get_dev_list_async() 在超时时是 raise MIoTMipsError('invalid result'),不是返回 None:
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')
调用方没有 try/except,异常从 create_task() 里逃逸(Home Assistant 记录为 Task exception was never retrieved),因此 if gw_list is None: 这段「Retry until success」是死代码,永远不会执行。
后果:_device_list_gateway 里所有设备永远是 online: False。而 __check_device_state(cloud_state, gw_state, lan_state) 是三者取或,于是设备可用性完全依赖云端 MQTT。云端 MQTT 每次断线重连(mips disconnect, 7),整个账号下所有在线实体一起变成 unavailable,而中枢明明就在局域网里、连接也是正常的。
我这边的规模:一次云端断线让 1465 个实体同时 unavailable,约十几秒后恢复,每天 5 次左右。
为什么这个失败是持久的:__request_refresh_gw_devices_by_group_id() 全文只有两个调用点,且 gateway 设备表没有任何周期性刷新定时器(云端设备表、props、token、cert 都各有 call_later 定时器):
- 上面那段死代码;
__on_mips_local_state_changed(state=True)—— 本地 MQTT 重连时。但本地是局域网连接(RTT ~1.6 ms),实测数天不断一次。
另外 __on_gw_device_list_changed() 里的 get_dev_list_async() 也没有 try/except,同样会抛异常,而且它只对变化的 did 做增量更新,无法重建整张表。
所以实际效果就是:中枢一次没回应 → 本地通道永久失效,直到重启 Home Assistant。
How to Reproduce / 复现步骤
- 一个配置了中枢网关(
xiaomi.gateway.hub1)的家庭,ctrl_mode: auto。 - 让中枢在集成建立本地 MIPS 连接后的那一次
master/proxy/getDevList请求上不回应(我这里是中枢的本地代理服务自己卡住了:MQTT broker、TLS 客户端证书校验、client_id 白名单、topic ACL 全部正常,QoS 2 的 PUBLISH 也收到了 PUBCOMP,但master/proxy/*的任何请求都不回;proxy/getDevList、proxy/get、proxy/getMijiaActionGroupList都试过,全部零回复。中枢已连续运行 8.8 天,未死机,米家 App 控制设备完全正常,因为 App 走的是云端)。 - 日志里出现
on mips request timeout, ..., master/proxy/getDevList,紧接着一条Task exception was never retrieved+MIoTMipsError: invalid result。 - 此后不会再有任何
refresh gw devices with group_id的重试,除非重启 Home Assistant。 - 等下一次云端 MQTT 断线 —— 该账号下所有实体一起
unavailable。
补充:我把超时从 10 秒临时改到 45 秒重测,中枢照样不回,所以这不是超时值调小了的问题。而中枢重启后刚起来时,getDevList 需要 23–34 秒才返回(热起来之后 0.1 秒返回 64 个设备)。这意味着如果 Home Assistant 和中枢差不多时间一起重启(停电、固件升级),默认 10 秒超时几乎必然超时 —— 此时「能不能重试」比「超时值多大」重要得多。
Expected Behavior / 预期结果
get_dev_list_async() 失败时应被捕获,让已经写好的「Retry until success」逻辑真正生效;中枢恢复后本地通道应能自动恢复,无需重启 Home Assistant。
Reproduce Time / 问题复现的时间点
2026-09-10 19:13:25
Home Assistant Logs / 系统日志
(已脱敏:<virtual-did> = 集成的 virtual_did,<group-id> = 家庭的 group_id,<uid> = 小米账号 uid,<uuid> = 集成实例 uuid)
集成加载后,本地 MIPS 连上 → 请求全量设备表 → 10 秒超时 → 异常逃逸,之后再无重试:
2026-09-10 19:13:12.276 INFO [custom_components.xiaomi_home.miot.miot_client] local mips state changed, <group-id>, True
2026-09-10 19:13:15.280 DEBUG [custom_components.xiaomi_home.miot.miot_client] refresh gw devices with group_id, <group-id>
2026-09-10 19:13:15.292 DEBUG [custom_components.xiaomi_home.miot.miot_client] <home-name>, mips local call api, True, 620821611, master/proxy/getDevList, {"info": ["name", "model", "urn", "online", "specV2Access", "pushAvailable"]}
2026-09-10 19:13:25.295 ERROR [custom_components.xiaomi_home.miot.miot_client] <home-name>, on mips request timeout, 620821611, master/proxy/getDevList, {"info": ["name", "model", "urn", "online", "specV2Access", "pushAvailable"]}
2026-09-10 19:13:25.296 ERROR [homeassistant] Error doing job: Task exception was never retrieved (task: None)
raise MIoTMipsError('invalid result')
custom_components.xiaomi_home.miot.miot_error.MIoTMipsError: invalid result
云端 MQTT 断线,导致全部实体 unavailable(因为 gateway 兜底失效):
2026-09-10 18:21:43.447 ERROR [custom_components.xiaomi_home.miot.miot_client] ha.<uuid>, mips disconnect, 7, None
2026-09-10 18:21:43.448 ERROR [custom_components.xiaomi_home.miot.miot_client] ha.<uuid>, mips try reconnect after 10s
同一秒内 1465 个实体转为 unavailable(用 recorder 数据库按秒聚合统计得到)。
__on_gw_device_list_changed() 里同样的未捕获异常:
File "/config/custom_components/xiaomi_home/miot/miot_client.py", line 1268, in __on_gw_device_list_changed
gw_list = await mips.get_dev_list_async(
File "/config/custom_components/xiaomi_home/miot/miot_mips.py", line 1383, in get_dev_list_async
raise MIoTMipsError('invalid result')
custom_components.xiaomi_home.miot.miot_error.MIoTMipsError: invalid result
Log Timezone / 日志时区
Australia/Sydney
Home Assistant Core Version / Home Assistant Core 版本
2026.9.1
Home Assistant Operation System Version / Home Assistant Operation System 版本
18.2
Xiaomi Home Integration Version / 米家集成版本
v0.4.7
Additional Context / 其他说明
本地临时补丁(已在我这里运行,中枢重启后可自动恢复,无需重启 Home Assistant):
try:
gw_list: dict = await mips.get_dev_list_async(
payload=json.dumps(payload))
except Exception as err: # pylint: disable=broad-except
fails = getattr(self, '_gw_refresh_fails', {})
count = fails.get(group_id, 0) + 1
fails[group_id] = count
self._gw_refresh_fails = fails
delay = min(300, 15 * count)
_LOGGER.warning(
'get gw device list failed (#%d), retry in %ds, %s, %s',
count, delay, group_id, err)
self._main_loop.call_later(
delay, lambda: self._main_loop.create_task(
self.__refresh_gw_devices_with_group_id_async(
group_id=group_id)))
return
if gw_list is None:
...
加了退避(15s × 失败次数,上限 5 分钟)是为了避免中枢长期故障时刷屏 —— 默认的 REFRESH_GATEWAY_DEVICES_DELAY = 3 配上 10 秒超时会变成每 13 秒重试一次。
建议一并考虑:
__on_gw_device_list_changed()里同样的未捕获调用;- 是否给 gateway 设备表加一个周期性刷新定时器(对齐云端设备表 / props / token / cert 的做法),这样本地通道失效时能自愈;
- 中枢刚启动时
getDevList需要 20–35 秒,默认 10 秒超时对「HA 与中枢同时重启」这个很常见的场景偏紧。
English summary
MipsLocalClient.get_dev_list_async() raises MIoTMipsError on timeout instead of returning None, but MIoTClient.__refresh_gw_devices_with_group_id_async() only handles if gw_list is None:. With no try/except, the exception escapes the create_task() (Task exception was never retrieved), so the # Retry until success branch is dead code.
_device_list_gateway then stays all-online: False forever. Since __check_device_state() ORs cloud/gateway/lan, entity availability falls back to the cloud MQTT link alone — every cloud reconnect (mips disconnect, 7) turns all 1465 entities of the account unavailable for ~15 s, several times a day, even though the central hub is healthy on the LAN.
The failure is effectively permanent: __request_refresh_gw_devices_by_group_id() has only two call sites (the dead branch above, and local-MQTT reconnect), and there is no periodic refresh timer for the gateway device list — unlike cloud devices, props, token and cert, which all have one. The LAN MQTT link does not reconnect on its own for days. __on_gw_device_list_changed() has the same unguarded call and only does incremental updates, so it cannot rebuild the table either.
Raising the timeout does not help (tested 45 s — the hub still never answered). But note the hub needs 23–34 s to answer getDevList right after it boots (0.1 s once warm), so a simultaneous HA + hub restart will hit the 10 s default almost every time — which makes retrying far more important than the timeout value.
Source: XiaoMi/ha_xiaomi_home