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) OnStateChanged(handler func(state HumidifierState, changed string)) (string, error) GetState() HumidifierState FetchState(ctx context.Context) (HumidifierState, error) } // HumidifierState 加湿器状态(MQTT 缓存) type HumidifierState struct { On bool `json:"on"` TargetHumidity int `json:"targetHumidity"` FanLevel int `json:"fanLevel"` } type humidifierDevice struct { BaseDevice propOn siidPiid propTargetHumidity siidPiid propFanLevel siidPiid stateCache HumidifierState } 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) OnStateChanged(handler func(state HumidifierState, changed string)) (string, error) { type field struct{ siid, piid int; name string; fill func(*HumidifierState, interface{}) } var fields []field if d.propOn.valid { fields = append(fields, field{d.propOn.siid, d.propOn.piid, "on", func(s *HumidifierState, v interface{}) { s.On, _ = v.(bool) }}) } if d.propTargetHumidity.valid { fields = append(fields, field{d.propTargetHumidity.siid, d.propTargetHumidity.piid, "target-humidity", func(s *HumidifierState, v interface{}) { s.TargetHumidity = toInt(v) }}) } if d.propFanLevel.valid { fields = append(fields, field{d.propFanLevel.siid, d.propFanLevel.piid, "fan-level", func(s *HumidifierState, 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.stateCache, prop.Value) handler(d.stateCache, f.name) return } } }) } func (d *humidifierDevice) GetState() HumidifierState { return d.stateCache } func (d *humidifierDevice) FetchState(ctx context.Context) (HumidifierState, 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.stateCache, err } state := HumidifierState{} for _, p := range props { switch { case d.propOn.valid && p.SIID == d.propOn.siid && p.PIID == d.propOn.piid: state.On, _ = p.Value.(bool) case d.propTargetHumidity.valid && p.SIID == d.propTargetHumidity.siid && p.PIID == d.propTargetHumidity.piid: state.TargetHumidity = toInt(p.Value) case d.propFanLevel.valid && p.SIID == d.propFanLevel.siid && p.PIID == d.propFanLevel.piid: state.FanLevel = toInt(p.Value) } } d.stateCache = state return state, nil }