feat: State→Props 命名统一 + 新增 Plug/Gateway 设备 + 分类系统重构

**命名统一(State → Props)**
- 所有设备类型:XxxState → XxxProps,GetState() → GetProps(),FetchState() → FetchProps()
- 回调接口:OnStateChanged → OnPropsChanged
- Bridge 层类型别名、示例代码、文档同步更名

**新增设备类型**
- KindPlug + Plug 驱动:智能插座/插头(TurnOn/TurnOff/IsOn/OnPropsChanged)
- KindGateway + Gateway 驱动:网关设备类型识别
- KindOccupancySensor 从 KindSensor 独立,添加 OccupancyProps

**设备分类系统改进**
- classifyByModel 改为按 key 长度降序匹配(防止 sensor 先于 sensor-occupy)
- 新增下划线变体识别(sensor_occupy、sensor_temp 等)
- outlet 重新归类为 Plug(非 Switch)
- 新增 Or() 组合过滤器

**连接桥增强**
- 设备创建成功后自动订阅属性变更
- 新增 nil 防护检查
- 支持 Plug/Speaker/Gateway/Occupancy 类型的 FetchProps 刷新
- 日志级别 Info → Debug

**AC 驱动增强**
- 新增 HasPower()、GetElectricPower()、GetPowerConsumption() 功率接口
- ACProps 新增 ElectricPower、PowerConsumption 字段
- valMapper:Desc 为空时 fallback 用 Name,key 统一小写

**MIoT 核心**
- 设备列表刷新改用 reflect.DeepEqual 全字段变更检测
- 移除 BLE/代理设备默认在线 hack
- RefreshDeviceAllProps 增加离线检查
- 新增 miot_errors.go:MIoT 错误码定义
- 全局日志级别 Info→Debug/Warn(降低噪音)

**示例与文档**
- 所有示例代码同步 API 变更
- 传感器示例改用 Or 组合过滤器
- ARCH_PLAN 等迁移文档同步更新
This commit is contained in:
2026-08-05 17:54:08 +08:00
parent b808e51b3e
commit 04333750ab
37 changed files with 916 additions and 536 deletions
+45 -33
View File
@@ -4,6 +4,7 @@ package miot
import (
"fmt"
"reflect"
"regexp"
"strings"
"time"
@@ -103,12 +104,6 @@ func (c *MIoTClient) RefreshDevices(homeIDs []string) error {
info[k] = v
}
// BLE/proxy devices default online
connectType := getConnectType(info)
if connectType == 23 || connectType == 8 || connectType == 12 || connectType == 0 {
info["online"] = true
}
// Handle sub-devices: .s\d+ suffix
match := subDevPattern.FindString(did)
if match != "" {
@@ -165,20 +160,21 @@ func (c *MIoTClient) RefreshDevices(homeIDs []string) error {
} else {
c.deviceListCache[did] = cloneMap(info)
}
c.lgr.Infof("[MIoTClient] Device added: %s (%v)\n", did, info["name"])
c.lgr.Debugf("[MIoTClient] Device added: %s (%v)\n", did, info["name"])
changed = true
} else {
// Check if online status changed
newOnline := getOnline(info)
oldOnline := getOnline(oldInfo)
if newOnline != oldOnline {
oldInfo["online"] = newOnline
c.deviceListCloud[did] = oldInfo
if cacheInfo, ok := c.deviceListCache[did]; ok {
cacheInfo["online"] = newOnline
// 检测并同步所有变更字段(name/model/nickname 等)
for k, v := range info {
if oldV, ok := oldInfo[k]; !ok || !reflect.DeepEqual(oldV, v) {
oldInfo[k] = v
changed = true
}
}
c.deviceListCloud[did] = oldInfo
if cacheInfo, ok := c.deviceListCache[did]; ok {
for k, v := range info {
cacheInfo[k] = v
}
c.lgr.Infof("[MIoTClient] Device online changed: %s online=%v\n", did, newOnline)
changed = true
}
}
}
@@ -277,27 +273,43 @@ func (c *MIoTClient) aggregateDeviceState(did string) {
return
}
// Aggregate from three sources
// OR 聚合:任一来源显式 online=true → 在线;
// 否则任一来源显式 online=false → 离线;
// 否则(无来源提供 online 字段)→ 保留缓存旧值,缓存也没有则默认在线。
online := false
explicitTrue := false
explicitFalse := false
if cloudInfo, exists := c.deviceListCloud[did]; exists {
if o, ok := cloudInfo["online"].(bool); ok && o {
online = true
sources := []map[string]interface{}{
c.deviceListCloud[did],
c.deviceListGateway[did],
c.deviceListLan[did],
}
for _, info := range sources {
if o, ok := info["online"].(bool); ok {
if o {
explicitTrue = true
} else {
explicitFalse = true
}
} else if _, exists := info["online"]; exists {
c.lgr.Debugf("[MIoTClient] %s online type mismatch: %v (%T)\n", did, info["online"], info["online"])
}
}
if !online {
if gwInfo, exists := c.deviceListGateway[did]; exists {
if o, ok := gwInfo["online"].(bool); ok && o {
online = true
}
}
}
if !online {
if lanInfo, exists := c.deviceListLan[did]; exists {
if o, ok := lanInfo["online"].(bool); ok && o {
online = true
}
switch {
case explicitTrue:
online = true
case explicitFalse:
online = false
default:
if oldOnline, ok := cacheInfo["online"].(bool); ok {
online = oldOnline
} else {
online = true // 云列表里有但没 online 字段,假设在线
}
c.lgr.Debugf("[MIoTClient] %s no online field from cloud/gw/lan, fallback online=%v (cache=%v)\n",
did, online, cacheInfo["online"])
}
cacheInfo["online"] = online