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
+53 -64
View File
@@ -11,108 +11,97 @@ type Humidifier interface {
TurnOn(ctx context.Context) error
TurnOff(ctx context.Context) error
IsOn(ctx context.Context) (bool, error)
SetTargetHumidity(ctx context.Context, pct int) error
GetTargetHumidity(ctx context.Context) (int, error)
SetFanLevel(ctx context.Context, level int) error
GetFanLevel(ctx context.Context) (int, error)
OnStateChanged(handler func(state HumidifierState, changed string)) (string, error)
}
// HumidifierState 加湿器状态(MQTT 缓存)
type HumidifierState struct {
On bool `json:"on"`
TargetHumidity int `json:"targetHumidity"`
FanLevel int `json:"fanLevel"`
}
type humidifierDevice struct {
BaseDevice
propOn siidPiid // "on"
propTargetHumidity siidPiid // "target-humidity"
propFanLevel siidPiid // "fan-level"
propOn siidPiid
propTargetHumidity siidPiid
propFanLevel siidPiid
stateCache HumidifierState
}
// NewHumidifier creates a Humidifier control for the given device.
func NewHumidifier(client *xiaomi.Client, info *xiaomi.DeviceInfo) (Humidifier, error) {
d := &humidifierDevice{
BaseDevice: NewBaseDevice(client, info),
}
d := &humidifierDevice{BaseDevice: NewBaseDevice(client, info)}
resolver := d.getSpecResolver()
if resolver == nil {
return nil, xiaomi.ErrNoSPEC
}
if resolver == nil { return nil, xiaomi.ErrNoSPEC }
siid, piid, ok := resolver.find("on")
if !ok {
return nil, xiaomi.ErrNotSupported
}
if !ok { return nil, xiaomi.ErrNotSupported }
d.propOn = siidPiid{siid, piid, true}
if s, p, ok := resolver.find("target-humidity"); ok {
d.propTargetHumidity = siidPiid{s, p, true}
}
if s, p, ok := resolver.find("fan-level"); ok {
d.propFanLevel = siidPiid{s, p, true}
}
if s, p, ok := resolver.find("target-humidity"); ok { d.propTargetHumidity = siidPiid{s, p, true} }
if s, p, ok := resolver.find("fan-level"); ok { d.propFanLevel = siidPiid{s, p, true} }
return d, nil
}
func (d *humidifierDevice) TurnOn(ctx context.Context) error {
if !d.propOn.valid {
return xiaomi.ErrNotSupported
}
if !d.propOn.valid { return xiaomi.ErrNotSupported }
return d.SetProp(ctx, d.propOn.siid, d.propOn.piid, true)
}
func (d *humidifierDevice) TurnOff(ctx context.Context) error {
if !d.propOn.valid {
return xiaomi.ErrNotSupported
}
if !d.propOn.valid { return xiaomi.ErrNotSupported }
return d.SetProp(ctx, d.propOn.siid, d.propOn.piid, false)
}
func (d *humidifierDevice) IsOn(ctx context.Context) (bool, error) {
if !d.propOn.valid {
return false, xiaomi.ErrNotSupported
}
if !d.propOn.valid { return false, xiaomi.ErrNotSupported }
prop, err := d.GetProp(ctx, d.propOn.siid, d.propOn.piid)
if err != nil {
return false, err
}
if b, ok := prop.Value.(bool); ok {
return b, nil
}
if err != nil { return false, err }
if b, ok := prop.Value.(bool); ok { return b, nil }
return false, nil
}
func (d *humidifierDevice) SetTargetHumidity(ctx context.Context, pct int) error {
if !d.propTargetHumidity.valid {
return xiaomi.ErrNotSupported
}
if !d.propTargetHumidity.valid { return xiaomi.ErrNotSupported }
return d.SetProp(ctx, d.propTargetHumidity.siid, d.propTargetHumidity.piid, pct)
}
func (d *humidifierDevice) GetTargetHumidity(ctx context.Context) (int, error) {
if !d.propTargetHumidity.valid {
return 0, xiaomi.ErrNotSupported
}
if !d.propTargetHumidity.valid { return 0, xiaomi.ErrNotSupported }
prop, err := d.GetProp(ctx, d.propTargetHumidity.siid, d.propTargetHumidity.piid)
if err != nil {
return 0, err
}
if err != nil { return 0, err }
return toInt(prop.Value), nil
}
func (d *humidifierDevice) SetFanLevel(ctx context.Context, level int) error {
if !d.propFanLevel.valid {
return xiaomi.ErrNotSupported
}
if !d.propFanLevel.valid { return xiaomi.ErrNotSupported }
return d.SetProp(ctx, d.propFanLevel.siid, d.propFanLevel.piid, level)
}
func (d *humidifierDevice) GetFanLevel(ctx context.Context) (int, error) {
if !d.propFanLevel.valid {
return 0, xiaomi.ErrNotSupported
}
if !d.propFanLevel.valid { return 0, xiaomi.ErrNotSupported }
prop, err := d.GetProp(ctx, d.propFanLevel.siid, d.propFanLevel.piid)
if err != nil {
return 0, err
}
if err != nil { return 0, err }
return toInt(prop.Value), nil
}
func (d *humidifierDevice) OnStateChanged(handler func(state HumidifierState, changed string)) (string, error) {
type field struct{ siid, piid int; name string; fill func(*HumidifierState, interface{}) }
var fields []field
if d.propOn.valid {
fields = append(fields, field{d.propOn.siid, d.propOn.piid, "on", func(s *HumidifierState, v interface{}) { s.On, _ = v.(bool) }})
}
if d.propTargetHumidity.valid {
fields = append(fields, field{d.propTargetHumidity.siid, d.propTargetHumidity.piid, "target-humidity", func(s *HumidifierState, v interface{}) { s.TargetHumidity = toInt(v) }})
}
if d.propFanLevel.valid {
fields = append(fields, field{d.propFanLevel.siid, d.propFanLevel.piid, "fan-level", func(s *HumidifierState, v interface{}) { s.FanLevel = toInt(v) }})
}
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
}
}
})
}