package devices import ( "context" "git.zeroonesoft.cn/golib/xiaomihome/xiaomi" ) // Vacuum 扫地机统一控制接口 type Vacuum interface { Device Start(ctx context.Context) error Stop(ctx context.Context) error ReturnToDock(ctx context.Context) error SetFanLevel(ctx context.Context, level int) error GetFanLevel(ctx context.Context) (int, error) GetStatus(ctx context.Context) (VacuumProps, error) OnPropsChanged(handler func(props VacuumProps, changed string)) (string, error) GetProps() VacuumProps FetchProps(ctx context.Context) (VacuumProps, error) } // VacuumProps 扫地机运行状态(字段名对齐 SPEC type) type VacuumProps struct { On bool `json:"on"` Mode int `json:"mode"` FanLevel int `json:"fanLevel"` Battery int `json:"battery"` } type vacuumDevice struct { BaseDevice propOn siidPiid propMode siidPiid propFanLevel siidPiid propBattery siidPiid propsCache VacuumProps } func NewVacuum(client *xiaomi.Client, info *xiaomi.DeviceInfo) (Vacuum, error) { d := &vacuumDevice{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("mode"); ok { d.propMode = siidPiid{s, p, true} } if s, p, ok := resolver.find("fan-level"); ok { d.propFanLevel = siidPiid{s, p, true} } if s, p, ok := resolver.find("battery-level"); ok { d.propBattery = siidPiid{s, p, true} } return d, nil } func (d *vacuumDevice) Start(ctx context.Context) error { _, err := d.Action(ctx, d.propOn.siid, 1, nil) return err } func (d *vacuumDevice) Stop(ctx context.Context) error { _, err := d.Action(ctx, d.propOn.siid, 0, nil) return err } func (d *vacuumDevice) ReturnToDock(ctx context.Context) error { _, err := d.Action(ctx, d.propOn.siid, 2, nil) return err } func (d *vacuumDevice) 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 *vacuumDevice) 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 *vacuumDevice) GetStatus(ctx context.Context) (VacuumProps, error) { // 云端实时查询 keys := []xiaomi.PropKey{} add := func(sp siidPiid) { if sp.valid { keys = append(keys, xiaomi.PropKey{DID: d.info.DID, SIID: sp.siid, PIID: sp.piid}) } } add(d.propOn) add(d.propMode) add(d.propFanLevel) add(d.propBattery) props, err := d.client.GetProps(ctx, keys) if err != nil { return VacuumProps{}, err } status := VacuumProps{} for _, p := range props { switch { case d.propOn.valid && p.SIID == d.propOn.siid && p.PIID == d.propOn.piid: status.On, _ = p.Value.(bool) case d.propMode.valid && p.SIID == d.propMode.siid && p.PIID == d.propMode.piid: status.Mode = toInt(p.Value) case d.propFanLevel.valid && p.SIID == d.propFanLevel.siid && p.PIID == d.propFanLevel.piid: status.FanLevel = toInt(p.Value) case d.propBattery.valid && p.SIID == d.propBattery.siid && p.PIID == d.propBattery.piid: status.Battery = toInt(p.Value) } } return status, nil } func (d *vacuumDevice) OnPropsChanged(handler func(props VacuumProps, changed string)) (string, error) { type field struct { siid, piid int name string fill func(*VacuumProps, interface{}) } var fields []field if d.propOn.valid { fields = append(fields, field{d.propOn.siid, d.propOn.piid, "on", func(s *VacuumProps, v interface{}) { s.On, _ = v.(bool) }}) } if d.propMode.valid { fields = append(fields, field{d.propMode.siid, d.propMode.piid, "mode", func(s *VacuumProps, v interface{}) { s.Mode = toInt(v) }}) } if d.propFanLevel.valid { fields = append(fields, field{d.propFanLevel.siid, d.propFanLevel.piid, "fan-level", func(s *VacuumProps, v interface{}) { s.FanLevel = toInt(v) }}) } if d.propBattery.valid { fields = append(fields, field{d.propBattery.siid, d.propBattery.piid, "battery", func(s *VacuumProps, v interface{}) { s.Battery = 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 *vacuumDevice) GetProps() VacuumProps { return d.propsCache } func (d *vacuumDevice) FetchProps(ctx context.Context) (VacuumProps, error) { status, err := d.GetStatus(ctx) if err != nil { return d.propsCache, err } d.propsCache = status return status, nil }