package devices import ( "context" "xiaomihome/xiaomi" ) // Humidifier 加湿器/除湿机统一控制接口 type Humidifier interface { Device TurnOn(ctx context.Context) error TurnOff(ctx context.Context) error IsOn(ctx context.Context) (bool, error) SetTargetHumidity(ctx context.Context, pct int) error GetTargetHumidity(ctx context.Context) (int, error) SetFanLevel(ctx context.Context, level int) error GetFanLevel(ctx context.Context) (int, error) OnPropsChanged(handler func(props HumidifierProps, changed string)) (string, error) GetProps() HumidifierProps FetchProps(ctx context.Context) (HumidifierProps, error) } // HumidifierProps 加湿器状态(MQTT 缓存) type HumidifierProps struct { On bool `json:"on"` TargetHumidity int `json:"targetHumidity"` FanLevel int `json:"fanLevel"` } type humidifierDevice struct { BaseDevice propOn siidPiid propTargetHumidity siidPiid propFanLevel siidPiid propsCache HumidifierProps } func NewHumidifier(client *xiaomi.Client, info *xiaomi.DeviceInfo) (Humidifier, error) { d := &humidifierDevice{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-humidity"); ok { d.propTargetHumidity = siidPiid{s, p, true} } if s, p, ok := resolver.find("fan-level"); ok { d.propFanLevel = siidPiid{s, p, true} } return d, nil } func (d *humidifierDevice) 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 *humidifierDevice) 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 *humidifierDevice) 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 *humidifierDevice) SetTargetHumidity(ctx context.Context, pct int) error { if !d.propTargetHumidity.valid { return xiaomi.ErrNotSupported } return d.SetProp(ctx, d.propTargetHumidity.siid, d.propTargetHumidity.piid, pct) } func (d *humidifierDevice) GetTargetHumidity(ctx context.Context) (int, error) { if !d.propTargetHumidity.valid { return 0, xiaomi.ErrNotSupported } prop, err := d.GetProp(ctx, d.propTargetHumidity.siid, d.propTargetHumidity.piid) if err != nil { return 0, err } return toInt(prop.Value), nil } func (d *humidifierDevice) SetFanLevel(ctx context.Context, level int) error { if !d.propFanLevel.valid { return xiaomi.ErrNotSupported } return d.SetProp(ctx, d.propFanLevel.siid, d.propFanLevel.piid, level) } func (d *humidifierDevice) GetFanLevel(ctx context.Context) (int, error) { if !d.propFanLevel.valid { return 0, xiaomi.ErrNotSupported } prop, err := d.GetProp(ctx, d.propFanLevel.siid, d.propFanLevel.piid) if err != nil { return 0, err } return toInt(prop.Value), nil } func (d *humidifierDevice) OnPropsChanged(handler func(props HumidifierProps, changed string)) (string, error) { type field struct { siid, piid int name string fill func(*HumidifierProps, interface{}) } var fields []field if d.propOn.valid { fields = append(fields, field{d.propOn.siid, d.propOn.piid, "on", func(s *HumidifierProps, v interface{}) { s.On, _ = v.(bool) }}) } if d.propTargetHumidity.valid { fields = append(fields, field{d.propTargetHumidity.siid, d.propTargetHumidity.piid, "target-humidity", func(s *HumidifierProps, v interface{}) { s.TargetHumidity = toInt(v) }}) } if d.propFanLevel.valid { fields = append(fields, field{d.propFanLevel.siid, d.propFanLevel.piid, "fan-level", func(s *HumidifierProps, v interface{}) { s.FanLevel = toInt(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 *humidifierDevice) GetProps() HumidifierProps { return d.propsCache } func (d *humidifierDevice) FetchProps(ctx context.Context) (HumidifierProps, 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.propTargetHumidity.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propTargetHumidity.siid, PIID: d.propTargetHumidity.piid}) } if d.propFanLevel.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: d.propFanLevel.siid, PIID: d.propFanLevel.piid}) } props, err := d.client.GetProps(ctx, keys) if err != nil { return d.propsCache, err } result := HumidifierProps{} 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.propTargetHumidity.valid && p.SIID == d.propTargetHumidity.siid && p.PIID == d.propTargetHumidity.piid: result.TargetHumidity = toInt(p.Value) case d.propFanLevel.valid && p.SIID == d.propFanLevel.siid && p.PIID == d.propFanLevel.piid: result.FanLevel = toInt(p.Value) } } d.propsCache = result return result, nil }