refactor: 重构 token 管理,新增 NATS adapter 和多示例
按照 TOKEN_REFACTOR.md / FIX_CONFIG.md 方案重构核心层: - miot: Init() 优先读 storage token,新增 RefreshAuthInfo() 和刷新回调 - xiaomi: Config 改用 AuthInfo 结构体(含 UUID/UID/DataDir/OnTokenRefreshed) - xiaomi: NewClient(ctx, Config) 新签名,新增 RefreshToken(),后台自动刷新 - xiaomi/devices: 设备控制接口重构 - 新增示例 12_ac_subscribe,其余示例适配新 Config
This commit is contained in:
@@ -2,6 +2,8 @@ package devices
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"xiaomihome/xiaomi"
|
||||
)
|
||||
|
||||
@@ -130,21 +132,21 @@ type AirConditioner interface {
|
||||
GetEco(ctx context.Context) (bool, error)
|
||||
|
||||
// 订阅
|
||||
OnStateChanged(handler func(state ACState)) (string, error)
|
||||
OnStateChanged(handler func(state ACState, changed string)) (string, error)
|
||||
}
|
||||
|
||||
// ACState 空调完整状态
|
||||
// ACState 空调完整状态(字段名对齐 SPEC type)
|
||||
type ACState struct {
|
||||
Power bool `json:"power"`
|
||||
Mode ACMode `json:"mode"`
|
||||
TargetTemp float64 `json:"target_temp"`
|
||||
CurrentTemp float64 `json:"current_temp"`
|
||||
TargetHumidity int `json:"target_humidity"`
|
||||
CurrentHumidity int `json:"current_humidity"`
|
||||
FanSpeed FanSpeed `json:"fan_speed"`
|
||||
Swing SwingMode `json:"swing"`
|
||||
AuxHeat bool `json:"aux_heat"`
|
||||
Eco bool `json:"eco"`
|
||||
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"
|
||||
}
|
||||
|
||||
type acDevice struct {
|
||||
@@ -159,6 +161,8 @@ type acDevice struct {
|
||||
propCurrentHumidity siidPiid // "relative-humidity"
|
||||
propAuxHeat siidPiid // "aux-heat" or "heater"
|
||||
propEco siidPiid // "eco"
|
||||
|
||||
stateCache ACState // MQTT 推送维护的本地缓存
|
||||
}
|
||||
|
||||
// NewAirConditioner creates an AirConditioner control for the given device.
|
||||
@@ -430,19 +434,58 @@ func (d *acDevice) GetEco(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (d *acDevice) OnStateChanged(handler func(state ACState)) (string, error) {
|
||||
if !d.propOn.valid {
|
||||
// propField maps a SPEC short name to a siid/piid pair and a cache-update function.
|
||||
type propField struct {
|
||||
siid, piid int
|
||||
name string
|
||||
fill func(*ACState, interface{})
|
||||
}
|
||||
|
||||
func (d *acDevice) OnStateChanged(handler func(state ACState, changed string)) (string, error) {
|
||||
fields := d.propFields()
|
||||
if len(fields) == 0 {
|
||||
return "", xiaomi.ErrNotSupported
|
||||
}
|
||||
return d.SubProp(d.propOn.siid, 0, func(did string, prop *xiaomi.PropertyValue) {
|
||||
state := ACState{}
|
||||
if b, ok := prop.Value.(bool); ok {
|
||||
state.Power = b
|
||||
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
|
||||
}
|
||||
}
|
||||
handler(state)
|
||||
})
|
||||
}
|
||||
|
||||
func (d *acDevice) propFields() []propField {
|
||||
var fields []propField
|
||||
add := func(sp siidPiid, name string, fill func(*ACState, interface{})) {
|
||||
if sp.valid {
|
||||
fields = append(fields, propField{sp.siid, sp.piid, name, fill})
|
||||
}
|
||||
}
|
||||
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.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.propSwing, "swing", func(s *ACState, v interface{}) {
|
||||
if v, ok := v.(float64); ok { s.Swing = SwingMode(fmt.Sprintf("%.0f", 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) })
|
||||
return fields
|
||||
}
|
||||
|
||||
// toFloat converts an interface{} to float64.
|
||||
func toFloat(v interface{}) float64 {
|
||||
switch n := v.(type) {
|
||||
|
||||
Reference in New Issue
Block a user