package devices import ( "context" "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 }