package devices import ( "context" "xiaomihome/xiaomi" ) // 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, 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) OnPropsChanged(handler func(props HeaterProps, changed string)) (string, error) GetProps() HeaterProps FetchProps(ctx context.Context) (HeaterProps, error) } type thermostatDevice struct { BaseDevice propOn siidPiid propTargetTemp siidPiid propCurrentTemp siidPiid propHeating siidPiid propsCache HeaterProps } func NewThermostat(client *xiaomi.Client, info *xiaomi.DeviceInfo) (Thermostat, error) { d := &thermostatDevice{BaseDevice: NewBaseDevice(client, info)} resolver := d.getSpecResolver() if resolver == nil { return nil, xiaomi.ErrNoSPEC } siid, piid, ok := resolver.find("on") 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("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 } 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 } 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 } 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 } return false, nil } 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 } prop, err := d.GetProp(ctx, d.propTargetTemp.siid, d.propTargetTemp.piid) 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 } prop, err := d.GetProp(ctx, d.propCurrentTemp.siid, d.propCurrentTemp.piid) 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 } 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 } 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 } return false, nil } func (d *thermostatDevice) OnPropsChanged(handler func(props HeaterProps, changed string)) (string, error) { type field struct { siid, piid int name string fill func(*HeaterProps, interface{}) } var fields []field if d.propOn.valid { fields = append(fields, field{d.propOn.siid, d.propOn.piid, "on", func(s *HeaterProps, v interface{}) { s.On, _ = v.(bool) }}) } if d.propTargetTemp.valid { fields = append(fields, field{d.propTargetTemp.siid, d.propTargetTemp.piid, "target-temperature", func(s *HeaterProps, v interface{}) { s.TargetTemp = toFloat(v) }}) } if d.propCurrentTemp.valid { fields = append(fields, field{d.propCurrentTemp.siid, d.propCurrentTemp.piid, "temperature", func(s *HeaterProps, 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.propsCache, prop.Value) handler(d.propsCache, f.name) return } } }) } func (d *thermostatDevice) GetProps() HeaterProps { return d.propsCache } func (d *thermostatDevice) FetchProps(ctx context.Context) (HeaterProps, error) { keys := []xiaomi.PropKey{} if d.propOn.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propOn.siid, PIID: d.propOn.piid}) } if d.propTargetTemp.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propTargetTemp.siid, PIID: d.propTargetTemp.piid}) } if d.propCurrentTemp.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propCurrentTemp.siid, PIID: d.propCurrentTemp.piid}) } props, err := d.client.GetProps(ctx, keys) if err != nil { return d.propsCache, err } result := HeaterProps{} for _, p := range props { switch { case d.propOn.valid && p.SIID == d.propOn.siid && p.PIID == d.propOn.piid: result.On, _ = p.Value.(bool) case d.propTargetTemp.valid && p.SIID == d.propTargetTemp.siid && p.PIID == d.propTargetTemp.piid: result.TargetTemp = toFloat(p.Value) case d.propCurrentTemp.valid && p.SIID == d.propCurrentTemp.siid && p.PIID == d.propCurrentTemp.piid: result.CurrentTemp = toFloat(p.Value) } } d.propsCache = result return result, nil }