package devices import ( "context" "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) (VacuumStatus, error) OnStateChanged(handler func(state VacuumStatus, changed string)) (string, error) } // VacuumStatus 扫地机运行状态(字段名对齐 SPEC type) type VacuumStatus 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 stateCache VacuumStatus } 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) (VacuumStatus, 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 VacuumStatus{}, err } status := VacuumStatus{} 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) OnStateChanged(handler func(state VacuumStatus, changed string)) (string, error) { type field struct{ siid, piid int; name string; fill func(*VacuumStatus, interface{}) } var fields []field if d.propOn.valid { fields = append(fields, field{d.propOn.siid, d.propOn.piid, "on", func(s *VacuumStatus, v interface{}) { s.On, _ = v.(bool) }}) } if d.propMode.valid { fields = append(fields, field{d.propMode.siid, d.propMode.piid, "mode", func(s *VacuumStatus, v interface{}) { s.Mode = toInt(v) }}) } if d.propFanLevel.valid { fields = append(fields, field{d.propFanLevel.siid, d.propFanLevel.piid, "fan-level", func(s *VacuumStatus, v interface{}) { s.FanLevel = toInt(v) }}) } if d.propBattery.valid { fields = append(fields, field{d.propBattery.siid, d.propBattery.piid, "battery", func(s *VacuumStatus, 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.stateCache, prop.Value) handler(d.stateCache, f.name) return } } }) }