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

165 lines
4.8 KiB
Go

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
}