fix(xiaomi): 空调风速按数字档位映射并增加 MQTT 反向语义解析
klwdz1.airc.001 等设备 fan-level 枚举为 Auto + Level1~Level5,SDK 侧 low/medium/high 在枚举里查不到,导致只有 Auto 能设置成功。 - valMapper 增加 levelFallback 开关(仅 fan-level 启用):精确匹配失败时 low/medium/high 按数字档位相对位置映射(排除 Auto=0) - name() 增加对称的反向映射:设备上报 1/3/5 → low/medium/high, 控制与 MQTT 通知使用同一套语义枚举 - newValMapper 默认关闭 fallback,mode/swing 行为不变 - 新增 TestValMapperLevelFallback 覆盖正反向映射与 mode 无副作用
This commit is contained in:
@@ -2,6 +2,7 @@ package devices
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -17,6 +18,10 @@ type valMapper struct {
|
||||
val2name map[int]string
|
||||
name2val map[string]int
|
||||
names []string
|
||||
|
||||
// levelFallback 允许 low/medium/high 映射到数字档位(仅 fan-level 启用,
|
||||
// 用于枚举为 Level1~LevelN 的设备;mode/swing 枚举语义固定,禁用)。
|
||||
levelFallback bool
|
||||
}
|
||||
|
||||
func newValMapper(vl miot.MIoTSpecValueList) *valMapper {
|
||||
@@ -38,15 +43,82 @@ func newValMapper(vl miot.MIoTSpecValueList) *valMapper {
|
||||
return vm
|
||||
}
|
||||
|
||||
// newValMapperLevel 创建支持 low/medium/high → LevelN 映射的 valMapper(仅 fan-level 使用)。
|
||||
func newValMapperLevel(vl miot.MIoTSpecValueList) *valMapper {
|
||||
vm := newValMapper(vl)
|
||||
vm.levelFallback = true
|
||||
return vm
|
||||
}
|
||||
|
||||
func (vm *valMapper) name(v int) string {
|
||||
if vm == nil { return "" }
|
||||
return vm.val2name[v]
|
||||
if vm == nil {
|
||||
return ""
|
||||
}
|
||||
if s, ok := vm.val2name[v]; ok {
|
||||
if !vm.levelFallback {
|
||||
return s
|
||||
}
|
||||
// levelFallback:数字档位反向映射到语义枚举(与 val() 正向对称)。
|
||||
// 0→auto,最低档→low,中间档→medium,最高档→high;其余返回 spec 描述。
|
||||
levels := vm.levelValues()
|
||||
switch {
|
||||
case v == 0:
|
||||
return "auto"
|
||||
case len(levels) > 0 && v == levels[0]:
|
||||
return "low"
|
||||
case len(levels) > 1 && v == levels[len(levels)-1]:
|
||||
return "high"
|
||||
case len(levels) > 2 && v == levels[len(levels)/2]:
|
||||
return "medium"
|
||||
}
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (vm *valMapper) val(name string) (int, bool) {
|
||||
if vm == nil { return 0, false }
|
||||
v, ok := vm.name2val[strings.ToLower(name)]
|
||||
return v, ok
|
||||
if vm == nil {
|
||||
return 0, false
|
||||
}
|
||||
key := strings.ToLower(name)
|
||||
if v, ok := vm.name2val[key]; ok {
|
||||
return v, true
|
||||
}
|
||||
// fallback:标准档位枚举(low/medium/high)映射到数字档位的相对位置。
|
||||
// 适用于 fan-level 枚举为 Level1~LevelN(或纯数字)的设备,如 klwdz1.airc.001。
|
||||
// 排除 0(通常为 Auto/Off),low→最低档,medium→中间档,high→最高档。
|
||||
// 仅 levelFallback 启用(fan-level)时生效,mode/swing 不受影响。
|
||||
if !vm.levelFallback {
|
||||
return 0, false
|
||||
}
|
||||
levels := vm.levelValues()
|
||||
if len(levels) == 0 {
|
||||
return 0, false
|
||||
}
|
||||
switch key {
|
||||
case "low":
|
||||
return levels[0], true
|
||||
case "high":
|
||||
return levels[len(levels)-1], true
|
||||
case "medium":
|
||||
return levels[len(levels)/2], true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// levelValues 返回非 0 档位的升序值列表(levelFallback 辅助)。
|
||||
func (vm *valMapper) levelValues() []int {
|
||||
var levels []int
|
||||
for v := range vm.val2name {
|
||||
if v != 0 {
|
||||
levels = append(levels, v)
|
||||
}
|
||||
}
|
||||
if len(levels) == 0 {
|
||||
return nil
|
||||
}
|
||||
sort.Ints(levels)
|
||||
return levels
|
||||
}
|
||||
|
||||
func (vm *valMapper) allNames() []string {
|
||||
@@ -282,14 +354,14 @@ func NewAirConditioner(client *xiaomi.Client, info *xiaomi.DeviceInfo) (AirCondi
|
||||
if resolver.hasService("thermostat") {
|
||||
if s, p, ok := resolver.findInService("thermostat", "fan-level"); ok {
|
||||
d.propFanSpeed = siidPiid{s, p, true}
|
||||
d.fanMap = newValMapper(findValListInService("thermostat", "fan-level"))
|
||||
d.fanMap = newValMapperLevel(findValListInService("thermostat", "fan-level"))
|
||||
} else if s, p, ok := resolver.find("fan-level"); ok {
|
||||
d.propFanSpeed = siidPiid{s, p, true}
|
||||
d.fanMap = newValMapper(findValList("fan-level"))
|
||||
d.fanMap = newValMapperLevel(findValList("fan-level"))
|
||||
}
|
||||
} else if s, p, ok := resolver.find("fan-level"); ok {
|
||||
d.propFanSpeed = siidPiid{s, p, true}
|
||||
d.fanMap = newValMapper(findValList("fan-level"))
|
||||
d.fanMap = newValMapperLevel(findValList("fan-level"))
|
||||
}
|
||||
// Try multiple swing mode type names
|
||||
if s, p, ok := resolver.find("swing-mode"); ok {
|
||||
|
||||
Reference in New Issue
Block a user