Files
xiaomihome/xiaomi/devices/humidifier.go
T
4566704 cdc459b5ff fix(xiaomihome): 模块路径改为 git.zeroonesoft.cn/golib/xiaomihome
- 改动:go.mod module 声明与全部内部导入路径补全为完整域名路径,README 同步
- 原因:裸模块名无法被 go get 解析,发版前必须修正为 GOPRIVATE 可拉取路径
2026-10-05 08:53:23 +08:00

177 lines
5.5 KiB
Go

package devices
import (
"context"
"git.zeroonesoft.cn/golib/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
}