feat: AC 三合一设备支持 + 新增 Plug/Gateway 驱动 + Cover 结构化重构

**AC 驱动增强**
- 支持 thermostat + air-conditioner + air-fresh 多服务三合一设备,属性跨服务合并解析
- 新增 specResolver.findValListInService/findInService/hasService/serviceShortName 方法
- 移除 KindThermostat,heater/electric-blanket/thermostat 重新归类为 KindAirConditioner

**Cover 窗帘重构**
- CoverProps 结构化:Position/Status/Fault 分离
- SetPosition 优先写 target-position,回退 current-position
- OnPropsChanged/FetchProps 支持多属性同步刷新

**新增设备类型**
- Plug 驱动:插座/插头,支持开合、在线状态、属性变更订阅
- Gateway 驱动:网关设备类型识别
- miot/miot_errors.go:新增 MIoT 云端错误码定义

**MIoT 客户端健壮性**
- SetProp 空结果视为失败;设备被移除或离线时自动刷新设备列表
- GetDevices 的 has_more 改用 parseBool 严格解析
- MIPS SubscribeMany 批量订阅减少往返
- matchURN 改为按段精确匹配,避免 tofan 误匹配 fan

**Bridge**
- 移除 Thermostat case,Cover 改用结构化 CoverProps
- 新增设备创建后自动订阅属性变更

**其他**
- 日志整体 Info → Debug 降噪
- .gitignore 忽略 /.codebuddy/memory
- 更新 devices_test 与 08_adv_filter_classify 示例
This commit is contained in:
2026-08-23 19:42:55 +08:00
parent 04333750ab
commit 86c21ce91c
25 changed files with 1129 additions and 155 deletions
+2 -10
View File
@@ -143,7 +143,7 @@ func newBridge(ctx context.Context, id int64, uuid string, auth xiaomi.AuthInfo,
for _, info := range devList {
d, createErr := devices.Create(client, info)
if createErr != nil {
if createErr != nil || d == nil {
continue
}
b.dv[info.DID] = d
@@ -179,7 +179,6 @@ func deviceTypeName(d interface{}) string {
case devices.Humidifier: return devices.TypeHumidifier
case devices.Vacuum: return devices.TypeVacuum
case devices.WaterHeater: return devices.TypeWaterHeater
case devices.Thermostat: return devices.TypeThermostat
case devices.TempHumiditySensor: return devices.TypeTempHumidSensor
case devices.OccupancySensor: return devices.TypeOccupancySensor
case devices.Speaker: return devices.TypeSpeaker
@@ -195,7 +194,7 @@ func (b *Bridge) AC(did string) (devices.AirConditioner, error) {
d, ok := b.dv[did]
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
ac, ok := d.(devices.AirConditioner)
if !ok { return nil, fmt.Errorf("bridge: device %s is not an AirConditioner", did) }
if !ok { return nil, fmt.Errorf("bridge: device %s is not an AirConditioner (got %T)", did, d) }
return ac, nil
}
func (b *Bridge) Light(did string) (devices.Light, error) {
@@ -247,13 +246,6 @@ func (b *Bridge) WaterHeater(did string) (devices.WaterHeater, error) {
if !ok { return nil, fmt.Errorf("bridge: device %s is not a WaterHeater", did) }
return wh, nil
}
func (b *Bridge) Thermostat(did string) (devices.Thermostat, error) {
d, ok := b.dv[did]
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
t, ok := d.(devices.Thermostat)
if !ok { return nil, fmt.Errorf("bridge: device %s is not a Thermostat", did) }
return t, nil
}
func (b *Bridge) TempHumidity(did string) (devices.TempHumiditySensor, error) {
d, ok := b.dv[did]
if !ok { return nil, fmt.Errorf("bridge: device %s not found", did) }
+4 -12
View File
@@ -55,8 +55,8 @@ func (b *Bridge) subscribeDevice(did string, d interface{}) {
b.fire(did, devType, ch, props)
})
case devices.Cover:
ctrl.OnPropsChanged(func(pos int) {
b.fire(did, devType, devices.PropPosition, pos)
ctrl.OnPropsChanged(func(props devices.CoverProps) {
b.fire(did, devType, devices.PropPosition, props)
})
case devices.Humidifier:
ctrl.OnPropsChanged(func(props devices.HumidifierProps, ch string) {
@@ -70,10 +70,6 @@ func (b *Bridge) subscribeDevice(did string, d interface{}) {
ctrl.OnPropsChanged(func(props devices.HeaterProps, ch string) {
b.fire(did, devType, ch, props)
})
case devices.Thermostat:
ctrl.OnPropsChanged(func(props devices.HeaterProps, ch string) {
b.fire(did, devType, ch, props)
})
case devices.TempHumiditySensor:
ctrl.OnPropsChanged(func(temp, hum float64) {
b.fire(did, devType, "temperature_humidity", map[string]float64{"temperature": temp, "humidity": hum})
@@ -164,13 +160,9 @@ func (b *Bridge) FetchAllStates(ctx context.Context) {
if s, err := ctrl.FetchProps(ctx); err == nil {
b.lgr.Debugf("[bridge] %d Heater: on=%v target=%.1f", b.id, s.On, s.TargetTemp)
}
case devices.Thermostat:
if s, err := ctrl.FetchProps(ctx); err == nil {
b.lgr.Debugf("[bridge] %d Thermostat: target=%.1f current=%.1f", b.id, s.TargetTemp, s.CurrentTemp)
}
case devices.Cover:
if pos, err := ctrl.FetchProps(ctx); err == nil {
b.lgr.Debugf("[bridge] %d Cover: pos=%d", b.id, pos)
if s, err := ctrl.FetchProps(ctx); err == nil {
b.lgr.Debugf("[bridge] %d Cover: pos=%d status=%d fault=%d", b.id, s.Position, s.Status, s.Fault)
}
case devices.TempHumiditySensor:
if t, h, err := ctrl.FetchProps(ctx); err == nil {