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
+56 -64
View File
@@ -5,106 +5,98 @@ import (
"xiaomihome/xiaomi"
)
// WaterHeater 热水器统一控制接口
// WaterHeater 热水器/饮水机统一控制接口
type WaterHeater interface {
Device
TurnOn(ctx context.Context) error
TurnOff(ctx context.Context) error
IsOn(ctx context.Context) (bool, error)
SetTargetTemp(ctx context.Context, celsius float64) error
SetTargetTemp(ctx context.Context, temp float64) error
GetTargetTemp(ctx context.Context) (float64, error)
GetCurrentTemp(ctx context.Context) (float64, error)
OnStateChanged(handler func(state HeaterState, changed string)) (string, error)
}
// HeaterState 热水器状态(MQTT 缓存)
type HeaterState struct {
On bool `json:"on"`
TargetTemp float64 `json:"targetTemperature"`
CurrentTemp float64 `json:"temperature"`
}
type waterHeaterDevice struct {
BaseDevice
propOn siidPiid // "on"
propTargetTemp siidPiid // "target-temperature"
propCurrentTemp siidPiid // "temperature"
propOn siidPiid
propTargetTemp siidPiid
propCurrentTemp siidPiid
stateCache HeaterState
}
// NewWaterHeater creates a WaterHeater control for the given device.
func NewWaterHeater(client *xiaomi.Client, info *xiaomi.DeviceInfo) (WaterHeater, error) {
d := &waterHeaterDevice{
BaseDevice: NewBaseDevice(client, info),
}
d := &waterHeaterDevice{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-temperature"); ok {
d.propTargetTemp = siidPiid{s, p, true}
}
if s, p, ok := resolver.find("temperature"); ok {
d.propCurrentTemp = siidPiid{s, p, true}
}
if s, p, ok := resolver.find("target-temperature"); ok { d.propTargetTemp = siidPiid{s, p, true} }
if s, p, ok := resolver.find("temperature"); ok { d.propCurrentTemp = siidPiid{s, p, true} }
return d, nil
}
func (d *waterHeaterDevice) 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 *waterHeaterDevice) 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 *waterHeaterDevice) 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 *waterHeaterDevice) SetTargetTemp(ctx context.Context, celsius float64) error {
if !d.propTargetTemp.valid {
return xiaomi.ErrNotSupported
}
return d.SetProp(ctx, d.propTargetTemp.siid, d.propTargetTemp.piid, celsius)
func (d *waterHeaterDevice) SetTargetTemp(ctx context.Context, temp float64) error {
if !d.propTargetTemp.valid { return xiaomi.ErrNotSupported }
return d.SetProp(ctx, d.propTargetTemp.siid, d.propTargetTemp.piid, temp)
}
func (d *waterHeaterDevice) GetTargetTemp(ctx context.Context) (float64, error) {
if !d.propTargetTemp.valid {
return 0, xiaomi.ErrNotSupported
}
if !d.propTargetTemp.valid { return 0, xiaomi.ErrNotSupported }
prop, err := d.GetProp(ctx, d.propTargetTemp.siid, d.propTargetTemp.piid)
if err != nil {
return 0, err
}
if err != nil { return 0, err }
return toFloat(prop.Value), nil
}
func (d *waterHeaterDevice) GetCurrentTemp(ctx context.Context) (float64, error) {
if !d.propCurrentTemp.valid { return 0, xiaomi.ErrNotSupported }
prop, err := d.GetProp(ctx, d.propCurrentTemp.siid, d.propCurrentTemp.piid)
if err != nil { return 0, err }
return toFloat(prop.Value), nil
}
func (d *waterHeaterDevice) GetCurrentTemp(ctx context.Context) (float64, error) {
if !d.propCurrentTemp.valid {
return 0, xiaomi.ErrNotSupported
func (d *waterHeaterDevice) OnStateChanged(handler func(state HeaterState, changed string)) (string, error) {
type field struct{ siid, piid int; name string; fill func(*HeaterState, interface{}) }
var fields []field
if d.propOn.valid {
fields = append(fields, field{d.propOn.siid, d.propOn.piid, "on", func(s *HeaterState, v interface{}) { s.On, _ = v.(bool) }})
}
prop, err := d.GetProp(ctx, d.propCurrentTemp.siid, d.propCurrentTemp.piid)
if err != nil {
return 0, err
if d.propTargetTemp.valid {
fields = append(fields, field{d.propTargetTemp.siid, d.propTargetTemp.piid, "target-temperature", func(s *HeaterState, v interface{}) { s.TargetTemp = toFloat(v) }})
}
return toFloat(prop.Value), nil
if d.propCurrentTemp.valid {
fields = append(fields, field{d.propCurrentTemp.siid, d.propCurrentTemp.piid, "temperature", func(s *HeaterState, v interface{}) { s.CurrentTemp = toFloat(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
}
}
})
}