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
+55 -85
View File
@@ -5,136 +5,106 @@ import (
"xiaomihome/xiaomi"
)
// Thermostat 温控器/电热毯统一控制接口
// Thermostat 温控器统一控制接口
type Thermostat 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)
SetHeating(ctx context.Context, on bool) error
IsHeating(ctx context.Context) (bool, error)
OnStateChanged(handler func(state HeaterState, changed string)) (string, error)
}
type thermostatDevice struct {
BaseDevice
propOn siidPiid // "on"
propTargetTemp siidPiid // "target-temperature"
propCurrentTemp siidPiid // "temperature"
propHeating siidPiid // "heating" or "mode"
propOn siidPiid
propTargetTemp siidPiid
propCurrentTemp siidPiid
propHeating siidPiid
stateCache HeaterState
}
// NewThermostat creates a Thermostat control for the given device.
func NewThermostat(client *xiaomi.Client, info *xiaomi.DeviceInfo) (Thermostat, error) {
d := &thermostatDevice{
BaseDevice: NewBaseDevice(client, info),
}
d := &thermostatDevice{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("heating"); ok {
d.propHeating = siidPiid{s, p, true}
} else if s, p, ok := resolver.find("heater"); ok {
d.propHeating = 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} }
if s, p, ok := resolver.find("heater"); ok { d.propHeating = siidPiid{s, p, true} }
return d, nil
}
func (d *thermostatDevice) 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 *thermostatDevice) 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 *thermostatDevice) 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 *thermostatDevice) 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 *thermostatDevice) 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 *thermostatDevice) 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 *thermostatDevice) GetCurrentTemp(ctx context.Context) (float64, error) {
if !d.propCurrentTemp.valid {
return 0, xiaomi.ErrNotSupported
}
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
}
if err != nil { return 0, err }
return toFloat(prop.Value), nil
}
func (d *thermostatDevice) SetHeating(ctx context.Context, on bool) error {
if !d.propHeating.valid {
return xiaomi.ErrNotSupported
}
if !d.propHeating.valid { return xiaomi.ErrNotSupported }
return d.SetProp(ctx, d.propHeating.siid, d.propHeating.piid, on)
}
func (d *thermostatDevice) IsHeating(ctx context.Context) (bool, error) {
if !d.propHeating.valid {
return false, xiaomi.ErrNotSupported
}
if !d.propHeating.valid { return false, xiaomi.ErrNotSupported }
prop, err := d.GetProp(ctx, d.propHeating.siid, d.propHeating.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 *thermostatDevice) 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) }})
}
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) }})
}
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
}
}
})
}