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:
@@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user