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:
+16
-5
@@ -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
|
||||
|
||||
@@ -180,6 +180,19 @@ func (b *Bridge) Devices() map[string]string {
|
||||
return out
|
||||
}
|
||||
|
||||
// pruneControllers 从本地控制器集合中剔除「米家已不存在」的设备,返回被剔除的 DID。
|
||||
// 没有这一步,AC()/Switch()/Fan() 等访问器会继续为已删设备返回控制器,控制必然失败。
|
||||
func pruneControllers(dv map[string]interface{}, alive map[string]struct{}) []string {
|
||||
var removed []string
|
||||
for did := range dv {
|
||||
if _, ok := alive[did]; !ok {
|
||||
delete(dv, did)
|
||||
removed = append(removed, did)
|
||||
}
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
func deviceTypeName(d interface{}) string {
|
||||
switch d.(type) {
|
||||
case devices.AirConditioner: return devices.TypeAirConditioner
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package bridge
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestPruneControllers_RemovesAbsentDevice 米家已删除设备的控制器必须被剔除,
|
||||
// 否则 AC()/Switch() 等访问器仍会为已删设备返回控制器。
|
||||
func TestPruneControllers_RemovesAbsentDevice(t *testing.T) {
|
||||
dv := map[string]interface{}{
|
||||
"did-keep": struct{}{},
|
||||
"did-gone": struct{}{},
|
||||
}
|
||||
|
||||
removed := pruneControllers(dv, map[string]struct{}{"did-keep": {}})
|
||||
|
||||
if len(removed) != 1 || removed[0] != "did-gone" {
|
||||
t.Fatalf("removed = %v, want [did-gone]", removed)
|
||||
}
|
||||
if _, ok := dv["did-gone"]; ok {
|
||||
t.Error("已删设备应被剔除")
|
||||
}
|
||||
if _, ok := dv["did-keep"]; !ok {
|
||||
t.Error("云端仍存在的设备不应被剔除")
|
||||
}
|
||||
}
|
||||
|
||||
// TestPruneControllers_NoChange 全部设备仍存在时不做任何改动。
|
||||
func TestPruneControllers_NoChange(t *testing.T) {
|
||||
dv := map[string]interface{}{"did1": struct{}{}, "did2": struct{}{}}
|
||||
|
||||
removed := pruneControllers(dv, map[string]struct{}{"did1": {}, "did2": {}, "did3": {}})
|
||||
|
||||
if len(removed) != 0 {
|
||||
t.Errorf("removed = %v, want empty", removed)
|
||||
}
|
||||
if len(dv) != 2 {
|
||||
t.Errorf("dv len = %d, want 2", len(dv))
|
||||
}
|
||||
}
|
||||
|
||||
// TestPruneControllers_EmptyAlive RemovedAll:设备列表为空时全部剔除(调用方保证非空列表)。
|
||||
func TestPruneControllers_EmptyAlive(t *testing.T) {
|
||||
dv := map[string]interface{}{"did1": struct{}{}, "did2": struct{}{}}
|
||||
|
||||
removed := pruneControllers(dv, nil)
|
||||
|
||||
if len(removed) != 2 {
|
||||
t.Fatalf("removed = %v, want 2 entries", removed)
|
||||
}
|
||||
if len(dv) != 0 {
|
||||
t.Errorf("dv len = %d, want 0", len(dv))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user