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) OnStateChanged(handler func(on bool)) (string, error) } // switchDevice is the concrete Switch implementation. type switchDevice struct { BaseDevice channels []siidPiid // "on" property per channel (index 0 = channel 1) } 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), } // Use the full SPEC to find all "on" properties (supports multi-channel) 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 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) OnStateChanged(handler func(on bool)) (string, error) { p, ok := d.channel(1) if !ok { return "", xiaomi.ErrNotSupported } return d.SubProp(p.siid, p.piid, func(did string, prop *xiaomi.PropertyValue) { if b, ok := prop.Value.(bool); ok { handler(b) } }) } func (d *switchDevice) channel(ch int) (siidPiid, bool) { i := ch - 1 // channel 1 → index 0 if i < 0 || i >= len(d.channels) { return siidPiid{}, false } return d.channels[i], true }