refactor: 重大架构升级 — HomeID/RoomID int64化 + 设备驱动重构 + 结构化日志
主要变更: **类型系统重构** - HomeInfo.ID/RoomInfo.ID/HomeID 从 string 改为 int64,json tag 使用 ,string - GetHome/GetRooms/GetRoomDevices 接口同步修改 - 新增 int64FromStr 工具函数,更新 parseHomeInfo 解析逻辑 **新增 Speaker 设备** - 新增 KindSpeaker 类型 - classify 映射覆盖 speaker/bluetooth-speaker/alarm-clock/media-player/playback/music **设备驱动重构** - AC 驱动彻底重写:从硬编码常量改为基于 SPEC ValueList 的动态 valMapper - 所有设备(Fan/Humidifier/Light/OccupancySensor/Switch/TempHumiditySensor/Thermostat/Vacuum/WaterHeater/Cover)新增 GetState() 和 FetchState(ctx) 接口 - base.go 新增 specResolver.format() 方法 **MIoT 模块增强** - MIoTClient 新增 homeIDs 字段,支持按家庭过滤设备 - RefreshDevices 支持 homeIDs 参数 - SpecStdLib 引入 logger.Logger,log.Printf 全部迁移为结构化日志 **其他** - Config 新增 RedirectURL 和 HomeIDs 字段 - AuthInfo.ExpiresIn 类型从 int 改为 int32 - UUID 生成简化为 hex.EncodeToString - 新增 github.com/sirupsen/logrus 依赖 - .gitignore 新增 examples/*/data/ 忽略规则 - 删除已提交的 config.json 敏感配置
This commit is contained in:
+215
-151
@@ -2,89 +2,72 @@ package devices
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"xiaomihome/miot"
|
||||
"xiaomihome/xiaomi"
|
||||
)
|
||||
|
||||
// valMapper maps between device raw values and semantic names. Built from SPEC ValueList.
|
||||
// Different brands assign different raw values for the same mode name,
|
||||
// so this must be per-device. Names are lowercased from SPEC description for consistency.
|
||||
type valMapper struct {
|
||||
val2name map[int]string
|
||||
name2val map[string]int
|
||||
names []string
|
||||
}
|
||||
|
||||
func newValMapper(vl miot.MIoTSpecValueList) *valMapper {
|
||||
vm := &valMapper{
|
||||
val2name: make(map[int]string),
|
||||
name2val: make(map[string]int),
|
||||
}
|
||||
for _, item := range vl {
|
||||
v := toInt(item.Value)
|
||||
name := strings.ToLower(item.Desc)
|
||||
vm.val2name[v] = name
|
||||
vm.name2val[name] = v
|
||||
vm.names = append(vm.names, name)
|
||||
}
|
||||
return vm
|
||||
}
|
||||
|
||||
func (vm *valMapper) name(v int) string {
|
||||
if vm == nil { return "" }
|
||||
return vm.val2name[v]
|
||||
}
|
||||
|
||||
func (vm *valMapper) val(name string) (int, bool) {
|
||||
if vm == nil { return 0, false }
|
||||
v, ok := vm.name2val[name]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
func (vm *valMapper) allNames() []string {
|
||||
if vm == nil { return nil }
|
||||
return vm.names
|
||||
}
|
||||
|
||||
// ========== 空调模式/风速/摆风常量 ==========
|
||||
|
||||
// ACMode 空调模式
|
||||
type ACMode string
|
||||
|
||||
const (
|
||||
ModeCool ACMode = "cool"
|
||||
ModeHeat ACMode = "heat"
|
||||
ModeFan ACMode = "fan"
|
||||
ModeDry ACMode = "dry"
|
||||
ModeAuto ACMode = "auto"
|
||||
ACModeCool = "cool"
|
||||
ACModeHeat = "heat"
|
||||
ACModeFan = "fan"
|
||||
ACModeDry = "dry"
|
||||
ACModeAuto = "auto"
|
||||
|
||||
ACFanLow = "low"
|
||||
ACFanMedium = "medium"
|
||||
ACFanHigh = "high"
|
||||
ACFanAuto = "auto"
|
||||
|
||||
ACSwingOff = "off"
|
||||
ACSwingVertical = "vertical"
|
||||
ACSwingHorizontal = "horizontal"
|
||||
ACSwingBoth = "both"
|
||||
)
|
||||
|
||||
// modeValueMap 模式值映射(uint8 → ACMode)
|
||||
var modeValueMap = map[int]ACMode{
|
||||
0: ModeCool,
|
||||
1: ModeHeat,
|
||||
2: ModeFan,
|
||||
3: ModeDry,
|
||||
4: ModeAuto,
|
||||
}
|
||||
|
||||
var modeReverseMap = map[ACMode]int{
|
||||
ModeCool: 0,
|
||||
ModeHeat: 1,
|
||||
ModeFan: 2,
|
||||
ModeDry: 3,
|
||||
ModeAuto: 4,
|
||||
}
|
||||
|
||||
// FanSpeed 风速
|
||||
type FanSpeed string
|
||||
|
||||
const (
|
||||
SpeedLow FanSpeed = "low"
|
||||
SpeedMedium FanSpeed = "medium"
|
||||
SpeedHigh FanSpeed = "high"
|
||||
SpeedAuto FanSpeed = "auto"
|
||||
)
|
||||
|
||||
var fanSpeedValueMap = map[int]FanSpeed{
|
||||
0: SpeedLow,
|
||||
1: SpeedMedium,
|
||||
2: SpeedHigh,
|
||||
3: SpeedAuto,
|
||||
}
|
||||
|
||||
var fanSpeedReverseMap = map[FanSpeed]int{
|
||||
SpeedLow: 0,
|
||||
SpeedMedium: 1,
|
||||
SpeedHigh: 2,
|
||||
SpeedAuto: 3,
|
||||
}
|
||||
|
||||
// SwingMode 摆风模式
|
||||
type SwingMode string
|
||||
|
||||
const (
|
||||
SwingOff SwingMode = "off"
|
||||
SwingVertical SwingMode = "vertical"
|
||||
SwingHorizontal SwingMode = "horizontal"
|
||||
SwingBoth SwingMode = "both"
|
||||
)
|
||||
|
||||
var swingValueMap = map[int]SwingMode{
|
||||
0: SwingOff,
|
||||
1: SwingVertical,
|
||||
2: SwingHorizontal,
|
||||
3: SwingBoth,
|
||||
}
|
||||
|
||||
var swingReverseMap = map[SwingMode]int{
|
||||
SwingOff: 0,
|
||||
SwingVertical: 1,
|
||||
SwingHorizontal: 2,
|
||||
SwingBoth: 3,
|
||||
}
|
||||
|
||||
// ========== AirConditioner 接口 ==========
|
||||
|
||||
// AirConditioner 空调统一控制接口
|
||||
@@ -102,8 +85,8 @@ type AirConditioner interface {
|
||||
HasEco() bool
|
||||
|
||||
// 模式控制
|
||||
SetMode(ctx context.Context, mode ACMode) error
|
||||
GetMode(ctx context.Context) (ACMode, error)
|
||||
SetMode(ctx context.Context, mode string) error
|
||||
GetMode(ctx context.Context) (string, error)
|
||||
|
||||
// 温度控制
|
||||
SetTargetTemp(ctx context.Context, celsius float64) error
|
||||
@@ -111,12 +94,12 @@ type AirConditioner interface {
|
||||
GetCurrentTemp(ctx context.Context) (float64, error) // 室内温度
|
||||
|
||||
// 风速控制
|
||||
SetFanSpeed(ctx context.Context, speed FanSpeed) error
|
||||
GetFanSpeed(ctx context.Context) (FanSpeed, error)
|
||||
SetFanSpeed(ctx context.Context, speed string) error
|
||||
GetFanSpeed(ctx context.Context) (string, error)
|
||||
|
||||
// 摆风控制
|
||||
SetSwing(ctx context.Context, mode SwingMode) error
|
||||
GetSwing(ctx context.Context) (SwingMode, error)
|
||||
SetSwing(ctx context.Context, mode string) error
|
||||
GetSwing(ctx context.Context) (string, error)
|
||||
|
||||
// 湿度
|
||||
SetTargetHumidity(ctx context.Context, pct int) error
|
||||
@@ -133,20 +116,22 @@ type AirConditioner interface {
|
||||
|
||||
// 订阅
|
||||
OnStateChanged(handler func(state ACState, changed string)) (string, error)
|
||||
GetState() ACState
|
||||
FetchState(ctx context.Context) (ACState, error)
|
||||
}
|
||||
|
||||
// ACState 空调完整状态(字段名对齐 SPEC type)
|
||||
type ACState struct {
|
||||
On bool `json:"on"` // SPEC: "on"
|
||||
Mode ACMode `json:"mode"` // SPEC: "mode"
|
||||
TargetTemperature float64 `json:"targetTemperature"` // SPEC: "target-temperature"
|
||||
Temperature float64 `json:"temperature"` // SPEC: "temperature" (当前温度)
|
||||
TargetHumidity int `json:"targetHumidity"` // SPEC: "target-humidity"
|
||||
CurrentHumidity int `json:"currentHumidity"` // SPEC: "relative-humidity"
|
||||
FanLevel FanSpeed `json:"fanLevel"` // SPEC: "fan-level"
|
||||
Swing SwingMode `json:"swing"` // SPEC: "swing"
|
||||
AuxHeat bool `json:"auxHeat"` // SPEC: "heater"
|
||||
Eco bool `json:"eco"` // SPEC: "eco"
|
||||
On bool `json:"on"`
|
||||
Mode string `json:"mode"` // SPEC ValueList 解析后的名称:cool/heat/fan/dry/auto
|
||||
TargetTemperature float64 `json:"targetTemperature"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
TargetHumidity int `json:"targetHumidity"`
|
||||
CurrentHumidity int `json:"currentHumidity"`
|
||||
FanLevel string `json:"fanLevel"` // SPEC ValueList 解析后的名称:low/medium/high/auto
|
||||
Swing string `json:"swing"` // SPEC ValueList 解析后的名称:off/vertical/horizontal/both
|
||||
AuxHeat bool `json:"auxHeat"`
|
||||
Eco bool `json:"eco"`
|
||||
}
|
||||
|
||||
type acDevice struct {
|
||||
@@ -162,6 +147,10 @@ type acDevice struct {
|
||||
propAuxHeat siidPiid // "aux-heat" or "heater"
|
||||
propEco siidPiid // "eco"
|
||||
|
||||
modeMap *valMapper // SPEC value→name for mode
|
||||
fanMap *valMapper // SPEC value→name for fan-level
|
||||
swingMap *valMapper // SPEC value→name for swing
|
||||
|
||||
stateCache ACState // MQTT 推送维护的本地缓存
|
||||
}
|
||||
|
||||
@@ -171,11 +160,32 @@ func NewAirConditioner(client *xiaomi.Client, info *xiaomi.DeviceInfo) (AirCondi
|
||||
BaseDevice: NewBaseDevice(client, info),
|
||||
}
|
||||
|
||||
dev := d.getMIoTDevice()
|
||||
if dev == nil {
|
||||
return nil, xiaomi.ErrNoSPEC
|
||||
}
|
||||
spec := dev.SpecInstance()
|
||||
if spec == nil {
|
||||
return nil, xiaomi.ErrNoSPEC
|
||||
}
|
||||
|
||||
resolver := d.getSpecResolver()
|
||||
if resolver == nil {
|
||||
return nil, xiaomi.ErrNoSPEC
|
||||
}
|
||||
|
||||
// Helper: find property ValueList from spec by type name
|
||||
findValList := func(propType string) miot.MIoTSpecValueList {
|
||||
for _, svc := range spec.Services {
|
||||
for _, p := range svc.Properties {
|
||||
if propertyShortName(p.Type) == propType {
|
||||
return p.ValueList
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// "on" is required
|
||||
siid, piid, ok := resolver.find("on")
|
||||
if !ok {
|
||||
@@ -186,6 +196,7 @@ func NewAirConditioner(client *xiaomi.Client, info *xiaomi.DeviceInfo) (AirCondi
|
||||
// Optional properties — each independently resolved
|
||||
if s, p, ok := resolver.find("mode"); ok {
|
||||
d.propMode = siidPiid{s, p, true}
|
||||
d.modeMap = newValMapper(findValList("mode"))
|
||||
}
|
||||
if s, p, ok := resolver.find("target-temperature"); ok {
|
||||
d.propTargetTemp = siidPiid{s, p, true}
|
||||
@@ -195,14 +206,18 @@ func NewAirConditioner(client *xiaomi.Client, info *xiaomi.DeviceInfo) (AirCondi
|
||||
}
|
||||
if s, p, ok := resolver.find("fan-level"); ok {
|
||||
d.propFanSpeed = siidPiid{s, p, true}
|
||||
d.fanMap = newValMapper(findValList("fan-level"))
|
||||
}
|
||||
// Try multiple swing mode type names
|
||||
if s, p, ok := resolver.find("swing-mode"); ok {
|
||||
d.propSwing = siidPiid{s, p, true}
|
||||
d.swingMap = newValMapper(findValList("swing-mode"))
|
||||
} else if s, p, ok := resolver.find("horizontal-swing"); ok {
|
||||
d.propSwing = siidPiid{s, p, true}
|
||||
d.swingMap = newValMapper(findValList("horizontal-swing"))
|
||||
} else if s, p, ok := resolver.find("vertical-swing"); ok {
|
||||
d.propSwing = siidPiid{s, p, true}
|
||||
d.swingMap = newValMapper(findValList("vertical-swing"))
|
||||
}
|
||||
if s, p, ok := resolver.find("target-humidity"); ok {
|
||||
d.propTargetHumidity = siidPiid{s, p, true}
|
||||
@@ -256,30 +271,26 @@ func (d *acDevice) HasHumidity() bool { return d.propTargetHumidity.valid }
|
||||
func (d *acDevice) HasAuxHeat() bool { return d.propAuxHeat.valid }
|
||||
func (d *acDevice) HasEco() bool { return d.propEco.valid }
|
||||
|
||||
func (d *acDevice) SetMode(ctx context.Context, mode ACMode) error {
|
||||
func (d *acDevice) SetMode(ctx context.Context, mode string) error {
|
||||
if !d.propMode.valid {
|
||||
return xiaomi.ErrNotSupported
|
||||
}
|
||||
val, ok := modeReverseMap[mode]
|
||||
v, ok := d.modeMap.val(mode)
|
||||
if !ok {
|
||||
val = 4 // default auto
|
||||
return xiaomi.ErrNotSupported
|
||||
}
|
||||
return d.SetProp(ctx, d.propMode.siid, d.propMode.piid, val)
|
||||
return d.SetProp(ctx, d.propMode.siid, d.propMode.piid, v)
|
||||
}
|
||||
|
||||
func (d *acDevice) GetMode(ctx context.Context) (ACMode, error) {
|
||||
func (d *acDevice) GetMode(ctx context.Context) (string, error) {
|
||||
if !d.propMode.valid {
|
||||
return ModeAuto, xiaomi.ErrNotSupported
|
||||
return ACModeAuto, xiaomi.ErrNotSupported
|
||||
}
|
||||
prop, err := d.GetProp(ctx, d.propMode.siid, d.propMode.piid)
|
||||
if err != nil {
|
||||
return ModeAuto, err
|
||||
return ACModeAuto, err
|
||||
}
|
||||
v := toInt(prop.Value)
|
||||
if m, ok := modeValueMap[v]; ok {
|
||||
return m, nil
|
||||
}
|
||||
return ModeAuto, nil
|
||||
return d.modeMap.name(toInt(prop.Value)), nil
|
||||
}
|
||||
|
||||
func (d *acDevice) SetTargetTemp(ctx context.Context, celsius float64) error {
|
||||
@@ -311,56 +322,48 @@ func (d *acDevice) GetCurrentTemp(ctx context.Context) (float64, error) {
|
||||
return toFloat(prop.Value), nil
|
||||
}
|
||||
|
||||
func (d *acDevice) SetFanSpeed(ctx context.Context, speed FanSpeed) error {
|
||||
func (d *acDevice) SetFanSpeed(ctx context.Context, speed string) error {
|
||||
if !d.propFanSpeed.valid {
|
||||
return xiaomi.ErrNotSupported
|
||||
}
|
||||
val, ok := fanSpeedReverseMap[speed]
|
||||
v, ok := d.fanMap.val(speed)
|
||||
if !ok {
|
||||
val = 3 // default auto
|
||||
return xiaomi.ErrNotSupported
|
||||
}
|
||||
return d.SetProp(ctx, d.propFanSpeed.siid, d.propFanSpeed.piid, val)
|
||||
return d.SetProp(ctx, d.propFanSpeed.siid, d.propFanSpeed.piid, v)
|
||||
}
|
||||
|
||||
func (d *acDevice) GetFanSpeed(ctx context.Context) (FanSpeed, error) {
|
||||
func (d *acDevice) GetFanSpeed(ctx context.Context) (string, error) {
|
||||
if !d.propFanSpeed.valid {
|
||||
return SpeedAuto, xiaomi.ErrNotSupported
|
||||
return ACFanAuto, xiaomi.ErrNotSupported
|
||||
}
|
||||
prop, err := d.GetProp(ctx, d.propFanSpeed.siid, d.propFanSpeed.piid)
|
||||
if err != nil {
|
||||
return SpeedAuto, err
|
||||
return ACFanAuto, err
|
||||
}
|
||||
v := toInt(prop.Value)
|
||||
if s, ok := fanSpeedValueMap[v]; ok {
|
||||
return s, nil
|
||||
}
|
||||
return SpeedAuto, nil
|
||||
return d.fanMap.name(toInt(prop.Value)), nil
|
||||
}
|
||||
|
||||
func (d *acDevice) SetSwing(ctx context.Context, mode SwingMode) error {
|
||||
func (d *acDevice) SetSwing(ctx context.Context, mode string) error {
|
||||
if !d.propSwing.valid {
|
||||
return xiaomi.ErrNotSupported
|
||||
}
|
||||
val, ok := swingReverseMap[mode]
|
||||
v, ok := d.swingMap.val(mode)
|
||||
if !ok {
|
||||
val = 0
|
||||
return xiaomi.ErrNotSupported
|
||||
}
|
||||
return d.SetProp(ctx, d.propSwing.siid, d.propSwing.piid, val)
|
||||
return d.SetProp(ctx, d.propSwing.siid, d.propSwing.piid, v)
|
||||
}
|
||||
|
||||
func (d *acDevice) GetSwing(ctx context.Context) (SwingMode, error) {
|
||||
func (d *acDevice) GetSwing(ctx context.Context) (string, error) {
|
||||
if !d.propSwing.valid {
|
||||
return SwingOff, xiaomi.ErrNotSupported
|
||||
return ACSwingOff, xiaomi.ErrNotSupported
|
||||
}
|
||||
prop, err := d.GetProp(ctx, d.propSwing.siid, d.propSwing.piid)
|
||||
if err != nil {
|
||||
return SwingOff, err
|
||||
return ACSwingOff, err
|
||||
}
|
||||
v := toInt(prop.Value)
|
||||
if s, ok := swingValueMap[v]; ok {
|
||||
return s, nil
|
||||
}
|
||||
return SwingOff, nil
|
||||
return d.swingMap.name(toInt(prop.Value)), nil
|
||||
}
|
||||
|
||||
func (d *acDevice) SetTargetHumidity(ctx context.Context, pct int) error {
|
||||
@@ -439,6 +442,7 @@ type propField struct {
|
||||
siid, piid int
|
||||
name string
|
||||
fill func(*ACState, interface{})
|
||||
valType byte // 'b'=bool, 'i'=int, 'f'=float, 's'=string
|
||||
}
|
||||
|
||||
func (d *acDevice) OnStateChanged(handler func(state ACState, changed string)) (string, error) {
|
||||
@@ -446,46 +450,81 @@ func (d *acDevice) OnStateChanged(handler func(state ACState, changed string)) (
|
||||
if len(fields) == 0 {
|
||||
return "", xiaomi.ErrNotSupported
|
||||
}
|
||||
return d.SubProp(0, 0, func(did string, prop *xiaomi.PropertyValue) {
|
||||
for _, f := range fields {
|
||||
if f.siid == prop.SIID && f.piid == prop.PIID {
|
||||
f.fill(&d.stateCache, prop.Value)
|
||||
handler(d.stateCache, f.name)
|
||||
return
|
||||
// 每个属性精确订阅 siid/piid,避免 lumi.acpartner.mcn02 等设备
|
||||
// siid/piid 冲突时把风速 int 路由到 on 槽位
|
||||
var firstID string
|
||||
for i, f := range fields {
|
||||
idx := i
|
||||
subID, err := d.SubProp(f.siid, f.piid, func(did string, prop *xiaomi.PropertyValue) {
|
||||
// lumi.acpartner.mcn02 固件 bug:on 通道会收到 int 值,静默丢弃
|
||||
switch f.valType {
|
||||
case 'b':
|
||||
if _, ok := prop.Value.(bool); !ok {
|
||||
return
|
||||
}
|
||||
case 's':
|
||||
if _, ok := prop.Value.(string); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
fields[idx].fill(&d.stateCache, prop.Value)
|
||||
handler(d.stateCache, f.name)
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
})
|
||||
if firstID == "" {
|
||||
firstID = subID
|
||||
}
|
||||
}
|
||||
return firstID, nil
|
||||
}
|
||||
|
||||
func (d *acDevice) propFields() []propField {
|
||||
r := d.getSpecResolver()
|
||||
var fields []propField
|
||||
add := func(sp siidPiid, name string, fill func(*ACState, interface{})) {
|
||||
add := func(sp siidPiid, name string, specType string, fill func(*ACState, interface{})) {
|
||||
if sp.valid {
|
||||
fields = append(fields, propField{sp.siid, sp.piid, name, fill})
|
||||
vt := byte(0)
|
||||
switch specType {
|
||||
case "bool":
|
||||
vt = 'b'
|
||||
case "uint8", "uint16", "uint32", "int8", "int16", "int32":
|
||||
vt = 'i'
|
||||
case "float":
|
||||
vt = 'f'
|
||||
case "string":
|
||||
vt = 's'
|
||||
}
|
||||
fields = append(fields, propField{sp.siid, sp.piid, name, fill, vt})
|
||||
}
|
||||
}
|
||||
add(d.propOn, "on", func(s *ACState, v interface{}) { s.On, _ = v.(bool) })
|
||||
add(d.propMode, "mode", func(s *ACState, v interface{}) {
|
||||
switch m := v.(type) {
|
||||
case string: s.Mode = ACMode(m)
|
||||
case float64: s.Mode = ACMode(fmt.Sprintf("%.0f", m))
|
||||
}
|
||||
add(d.propOn, "on", specFormat(r, "on"), func(s *ACState, v interface{}) { s.On, _ = v.(bool) })
|
||||
add(d.propMode, "mode", specFormat(r, "mode"), func(s *ACState, v interface{}) {
|
||||
s.Mode = d.modeMap.name(toInt(v))
|
||||
})
|
||||
add(d.propTargetTemp, "target-temperature", func(s *ACState, v interface{}) { s.TargetTemperature = toFloat(v) })
|
||||
add(d.propCurrentTemp, "temperature", func(s *ACState, v interface{}) { s.Temperature = toFloat(v) })
|
||||
add(d.propFanSpeed, "fan-level", func(s *ACState, v interface{}) {
|
||||
if v, ok := v.(float64); ok { s.FanLevel = FanSpeed(fmt.Sprintf("%.0f", v)) }
|
||||
add(d.propTargetTemp, "target-temperature", specFormat(r, "target-temperature"), func(s *ACState, v interface{}) { s.TargetTemperature = toFloat(v) })
|
||||
add(d.propCurrentTemp, "temperature", specFormat(r, "temperature"), func(s *ACState, v interface{}) { s.Temperature = toFloat(v) })
|
||||
add(d.propFanSpeed, "fan-level", specFormat(r, "fan-level"), func(s *ACState, v interface{}) {
|
||||
s.FanLevel = d.fanMap.name(toInt(v))
|
||||
})
|
||||
add(d.propSwing, "swing", func(s *ACState, v interface{}) {
|
||||
if v, ok := v.(float64); ok { s.Swing = SwingMode(fmt.Sprintf("%.0f", v)) }
|
||||
add(d.propSwing, "swing", specFormat(r, "swing-mode"), func(s *ACState, v interface{}) {
|
||||
s.Swing = d.swingMap.name(toInt(v))
|
||||
})
|
||||
add(d.propTargetHumidity, "target-humidity", func(s *ACState, v interface{}) { s.TargetHumidity = int(toFloat(v)) })
|
||||
add(d.propCurrentHumidity, "relative-humidity", func(s *ACState, v interface{}) { s.CurrentHumidity = int(toFloat(v)) })
|
||||
add(d.propAuxHeat, "heater", func(s *ACState, v interface{}) { s.AuxHeat, _ = v.(bool) })
|
||||
add(d.propEco, "eco", func(s *ACState, v interface{}) { s.Eco, _ = v.(bool) })
|
||||
add(d.propTargetHumidity, "target-humidity", specFormat(r, "target-humidity"), func(s *ACState, v interface{}) { s.TargetHumidity = int(toFloat(v)) })
|
||||
add(d.propCurrentHumidity, "relative-humidity", specFormat(r, "relative-humidity"), func(s *ACState, v interface{}) { s.CurrentHumidity = int(toFloat(v)) })
|
||||
add(d.propAuxHeat, "heater", specFormat(r, "heater"), func(s *ACState, v interface{}) { s.AuxHeat, _ = v.(bool) })
|
||||
add(d.propEco, "eco", specFormat(r, "eco"), func(s *ACState, v interface{}) { s.Eco, _ = v.(bool) })
|
||||
return fields
|
||||
}
|
||||
|
||||
func specFormat(r *specResolver, propType string) string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
return r.format(propType)
|
||||
}
|
||||
|
||||
// toFloat converts an interface{} to float64.
|
||||
func toFloat(v interface{}) float64 {
|
||||
switch n := v.(type) {
|
||||
@@ -500,3 +539,28 @@ func toFloat(v interface{}) float64 {
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (d *acDevice) GetState() ACState { return d.stateCache }
|
||||
|
||||
func (d *acDevice) FetchState(ctx context.Context) (ACState, error) {
|
||||
fields := d.propFields()
|
||||
keys := make([]xiaomi.PropKey, len(fields))
|
||||
for i, f := range fields {
|
||||
keys[i] = xiaomi.PropKey{DID: d.info.DID, SIID: f.siid, PIID: f.piid}
|
||||
}
|
||||
props, err := d.client.GetProps(ctx, keys)
|
||||
if err != nil {
|
||||
return d.stateCache, err
|
||||
}
|
||||
state := ACState{}
|
||||
for _, p := range props {
|
||||
for _, f := range fields {
|
||||
if f.siid == p.SIID && f.piid == p.PIID {
|
||||
f.fill(&state, p.Value)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
d.stateCache = state
|
||||
return state, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user