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

185 lines
4.7 KiB
Go

package devices
import (
"context"
"git.zeroonesoft.cn/golib/xiaomihome/xiaomi"
)
// Switch 开关统一控制接口。
// 单通道:TurnOn/TurnOff/Toggle/IsOn 操作唯一通道。
// 多通道:ChannelCount > 1 时,TurnOn 等操作通道 1,或使用带 channel 参数的方法。
type Switch interface {
Device
TurnOn(ctx context.Context) error
TurnOff(ctx context.Context) error
Toggle(ctx context.Context) error
IsOn(ctx context.Context) (bool, error)
// 多通道开关支持(单通道时自动回退到上述方法)
ChannelCount() int
TurnOnChannel(ctx context.Context, ch int) error
TurnOffChannel(ctx context.Context, ch int) error
ToggleChannel(ctx context.Context, ch int) error
IsChannelOn(ctx context.Context, ch int) (bool, error)
OnPropsChanged(handler func(ch int, on bool)) (string, error)
GetProps() SwitchProps
FetchProps(ctx context.Context) (SwitchProps, error)
}
// SwitchProps 多键开关完整状态。
// JSON: {"on":[true,false,true]},索引 0 = 通道 1
type SwitchProps struct {
On []bool `json:"on"`
}
// switchDevice is the concrete Switch implementation.
type switchDevice struct {
BaseDevice
channels []siidPiid // "on" property per channel (index 0 = channel 1)
propsCache SwitchProps
}
type siidPiid struct {
siid, piid int
valid bool
}
// NewSwitch creates a Switch control for the given device.
func NewSwitch(client *xiaomi.Client, info *xiaomi.DeviceInfo) (Switch, error) {
d := &switchDevice{
BaseDevice: NewBaseDevice(client, info),
}
miotDev := d.getMIoTDevice()
if miotDev == nil || miotDev.SpecInstance() == nil {
return nil, xiaomi.ErrNoSPEC
}
spec := miotDev.SpecInstance()
var channels []siidPiid
for _, svc := range spec.Services {
for _, prop := range svc.Properties {
if propertyShortName(prop.Type) == "on" {
channels = append(channels, siidPiid{prop.SIID, prop.PIID, true})
}
}
}
if len(channels) == 0 {
return nil, xiaomi.ErrNotSupported
}
d.channels = channels
d.propsCache.On = make([]bool, len(channels))
return d, nil
}
func (d *switchDevice) ChannelCount() int {
return len(d.channels)
}
func (d *switchDevice) TurnOn(ctx context.Context) error { return d.TurnOnChannel(ctx, 1) }
func (d *switchDevice) TurnOff(ctx context.Context) error { return d.TurnOffChannel(ctx, 1) }
func (d *switchDevice) Toggle(ctx context.Context) error { return d.ToggleChannel(ctx, 1) }
func (d *switchDevice) IsOn(ctx context.Context) (bool, error) {
return d.IsChannelOn(ctx, 1)
}
func (d *switchDevice) TurnOnChannel(ctx context.Context, ch int) error {
p, ok := d.channel(ch)
if !ok {
return xiaomi.ErrNotSupported
}
return d.SetProp(ctx, p.siid, p.piid, true)
}
func (d *switchDevice) TurnOffChannel(ctx context.Context, ch int) error {
p, ok := d.channel(ch)
if !ok {
return xiaomi.ErrNotSupported
}
return d.SetProp(ctx, p.siid, p.piid, false)
}
func (d *switchDevice) ToggleChannel(ctx context.Context, ch int) error {
on, err := d.IsChannelOn(ctx, ch)
if err != nil {
return err
}
if on {
return d.TurnOffChannel(ctx, ch)
}
return d.TurnOnChannel(ctx, ch)
}
func (d *switchDevice) IsChannelOn(ctx context.Context, ch int) (bool, error) {
p, ok := d.channel(ch)
if !ok {
return false, xiaomi.ErrNotSupported
}
prop, err := d.GetProp(ctx, p.siid, p.piid)
if err != nil {
return false, err
}
if b, ok := prop.Value.(bool); ok {
return b, nil
}
return false, nil
}
func (d *switchDevice) OnPropsChanged(handler func(ch int, on bool)) (string, error) {
if len(d.channels) == 0 {
return "", xiaomi.ErrNotSupported
}
type chField struct{ siid, piid int }
lookup := make(map[chField]int)
for i, c := range d.channels {
lookup[chField{c.siid, c.piid}] = i
}
return d.SubProp(0, 0, func(did string, prop *xiaomi.PropertyValue) {
for k, idx := range lookup {
if k.siid == prop.SIID && k.piid == prop.PIID {
if b, ok := prop.Value.(bool); ok {
d.propsCache.On[idx] = b
handler(idx+1, b)
}
return
}
}
})
}
func (d *switchDevice) channel(ch int) (siidPiid, bool) {
i := ch - 1
if i < 0 || i >= len(d.channels) {
return siidPiid{}, false
}
return d.channels[i], true
}
func (d *switchDevice) GetProps() SwitchProps { return d.propsCache }
func (d *switchDevice) FetchProps(ctx context.Context) (SwitchProps, error) {
keys := make([]xiaomi.PropKey, len(d.channels))
for i, c := range d.channels {
keys[i] = xiaomi.PropKey{DID: d.info.DID, SIID: c.siid, PIID: c.piid}
}
props, err := d.client.GetProps(ctx, keys)
if err != nil {
return d.propsCache, err
}
states := make([]bool, len(d.channels))
for i, c := range d.channels {
for _, p := range props {
if p.SIID == c.siid && p.PIID == c.piid {
states[i], _ = p.Value.(bool)
break
}
}
}
d.propsCache.On = states
return d.propsCache, nil
}