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) GetState() VacuumStatus FetchState(ctx context.Context) (VacuumStatus, 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 } } }) } func (d *vacuumDevice) GetState() VacuumStatus { return d.stateCache } func (d *vacuumDevice) FetchState(ctx context.Context) (VacuumStatus, error) { status, err := d.GetStatus(ctx) if err != nil { return d.stateCache, err } d.stateCache = status return status, nil }