package devices import ( "context" "xiaomihome/xiaomi" ) // 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, temp float64) error GetTargetTemp(ctx context.Context) (float64, error) GetCurrentTemp(ctx context.Context) (float64, error) OnPropsChanged(handler func(props HeaterProps, changed string)) (string, error) GetProps() HeaterProps FetchProps(ctx context.Context) (HeaterProps, error) } // HeaterProps 热水器状态(MQTT 缓存) type HeaterProps struct { On bool `json:"on"` TargetTemp float64 `json:"targetTemperature"` CurrentTemp float64 `json:"temperature"` } type waterHeaterDevice struct { BaseDevice propOn siidPiid propTargetTemp siidPiid propCurrentTemp siidPiid propsCache HeaterProps } func NewWaterHeater(client *xiaomi.Client, info *xiaomi.DeviceInfo) (WaterHeater, error) { d := &waterHeaterDevice{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} } return d, nil } func (d *waterHeaterDevice) 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 *waterHeaterDevice) 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 *waterHeaterDevice) 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 *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 } prop, err := d.GetProp(ctx, d.propTargetTemp.siid, d.propTargetTemp.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 } 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) 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 *waterHeaterDevice) GetProps() HeaterProps { return d.propsCache } func (d *waterHeaterDevice) 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 }