package devices import ( "context" "xiaomihome/xiaomi" ) // Fan 风扇统一控制接口 type Fan interface { Device TurnOn(ctx context.Context) error TurnOff(ctx context.Context) error IsOn(ctx context.Context) (bool, error) SetFanLevel(ctx context.Context, level int) error // 0=off, 1-4 档位 GetFanLevel(ctx context.Context) (int, error) SetOscillation(ctx context.Context, on bool) error IsOscillating(ctx context.Context) (bool, error) SetNaturalWind(ctx context.Context, on bool) error IsNaturalWind(ctx context.Context) (bool, error) OnStateChanged(handler func(state FanState, changed string)) (string, error) } // FanState 风扇完整状态(MQTT 缓存) type FanState struct { On bool `json:"on"` FanLevel int `json:"fanLevel"` Oscillation bool `json:"oscillation"` NaturalWind bool `json:"naturalWind"` } type fanDevice struct { BaseDevice propOn siidPiid // "on" propFanLevel siidPiid // "fan-level" propOscillation siidPiid // "horizontal-swing" or "oscillation" propNaturalWind siidPiid // "mode" (natural wind mode) stateCache FanState } // NewFan creates a Fan control for the given device. func NewFan(client *xiaomi.Client, info *xiaomi.DeviceInfo) (Fan, error) { d := &fanDevice{ 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("fan-level"); ok { d.propFanLevel = siidPiid{s, p, true} } if s, p, ok := resolver.find("horizontal-swing"); ok { d.propOscillation = siidPiid{s, p, true} } if s, p, ok := resolver.find("mode"); ok { d.propNaturalWind = siidPiid{s, p, true} } return d, nil } func (d *fanDevice) 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 *fanDevice) 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 *fanDevice) 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 *fanDevice) 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 *fanDevice) 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 *fanDevice) SetOscillation(ctx context.Context, on bool) error { if !d.propOscillation.valid { return xiaomi.ErrNotSupported } return d.SetProp(ctx, d.propOscillation.siid, d.propOscillation.piid, on) } func (d *fanDevice) IsOscillating(ctx context.Context) (bool, error) { if !d.propOscillation.valid { return false, xiaomi.ErrNotSupported } prop, err := d.GetProp(ctx, d.propOscillation.siid, d.propOscillation.piid) if err != nil { return false, err } if b, ok := prop.Value.(bool); ok { return b, nil } return false, nil } func (d *fanDevice) SetNaturalWind(ctx context.Context, on bool) error { if !d.propNaturalWind.valid { return xiaomi.ErrNotSupported } mode := 0 // normal if on { mode = 1 // natural wind } return d.SetProp(ctx, d.propNaturalWind.siid, d.propNaturalWind.piid, mode) } func (d *fanDevice) IsNaturalWind(ctx context.Context) (bool, error) { if !d.propNaturalWind.valid { return false, xiaomi.ErrNotSupported } prop, err := d.GetProp(ctx, d.propNaturalWind.siid, d.propNaturalWind.piid) if err != nil { return false, err } return toInt(prop.Value) == 1, nil } func (d *fanDevice) OnStateChanged(handler func(state FanState, changed string)) (string, error) { type field struct{ siid, piid int; name string; fill func(*FanState, interface{}) } var fields []field if d.propOn.valid { fields = append(fields, field{d.propOn.siid, d.propOn.piid, "on", func(s *FanState, v interface{}) { s.On, _ = v.(bool) }}) } if d.propFanLevel.valid { fields = append(fields, field{d.propFanLevel.siid, d.propFanLevel.piid, "fan-level", func(s *FanState, v interface{}) { s.FanLevel = toInt(v) }}) } if d.propOscillation.valid { fields = append(fields, field{d.propOscillation.siid, d.propOscillation.piid, "swing", func(s *FanState, v interface{}) { s.Oscillation, _ = v.(bool) }}) } if d.propNaturalWind.valid { fields = append(fields, field{d.propNaturalWind.siid, d.propNaturalWind.piid, "mode", func(s *FanState, v interface{}) { s.NaturalWind = toInt(v) == 1 }}) } 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 } } }) }