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:
2026-06-30 12:16:49 +08:00
parent a3d94c4b9f
commit 8ffc9e4e44
33 changed files with 1019 additions and 593 deletions
+62 -19
View File
@@ -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) {