fix(miot): 米家已删除设备从缓存、控制器与订阅中彻底剔除

在米家 App 删除设备后设备列表仍能获取到:删除检测只 delete(deviceListCloud),
deviceListCache 仅置 online=false,而对外 DeviceList()/GetDevices() 读的正是
cache;缓存又会在停机时落盘到 .dict、启动时原样恢复,已删设备跨重启复活。

- pruneRemovedDevicesLocked:候选集改为「缓存 ∪ 云列表」,云端不存在且网关/
  局域网非在线的设备从四张表彻底删除(旧实现只遍历 deviceListCloud,启动时它
  为空,停机期间被删的设备永远检测不到)
- 护栏:云端返回空列表时跳过剔除,避免接口异常清库
- homeScope:只刷新部分家庭时不再误删其它家庭的云列表
- 子设备(xxx.s1)不参与判定,它被归并到父设备的 sub_devices
- 命中剔除的设备同时清理 c.devices 实例与 MQTT 订阅路由
- bridge: pruneControllers 剔除已删设备的控制器,AC()/Switch() 不再返回实例
- 新增 7 个 miot 用例(含 httptest 打桩的端到端)+ 3 个 bridge 用例
This commit is contained in:
2026-09-12 19:29:46 +08:00
parent c18c0adaae
commit a595330144
5 changed files with 437 additions and 15 deletions
+16 -5
View File
@@ -55,6 +55,21 @@ func (b *Bridge) RefreshDevices(ctx context.Context, homeIDs ...int64) error {
b.mu.Lock()
defer b.mu.Unlock()
// 全量设备列表(SDK 已在 RefreshDevices 中剔除米家已删除的设备),
// 本地控制器集合必须与它保持一致
all, err := b.client.GetDevices(ctx)
if err != nil {
return err
}
alive := make(map[string]struct{}, len(all))
for _, info := range all {
alive[info.DID] = struct{}{}
}
if removed := pruneControllers(b.dv, alive); len(removed) > 0 {
b.lgr.Infof("[bridge] %d: %d device(s) removed from Mi Home, controllers dropped: %v",
b.id, len(removed), removed)
}
if len(homeIDs) > 0 {
for _, hid := range homeIDs {
devs, err := b.client.GetDevices(ctx, xiaomi.FilterByHome(strconv.FormatInt(hid, 10)))
@@ -74,11 +89,7 @@ func (b *Bridge) RefreshDevices(ctx context.Context, homeIDs ...int64) error {
}
// 全量
devList, err := b.client.GetDevices(ctx)
if err != nil {
return err
}
for _, info := range devList {
for _, info := range all {
if _, exists := b.dv[info.DID]; !exists {
if d, createErr := devices.Create(b.client, info); createErr == nil && d != nil {
b.dv[info.DID] = d