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(ch int, on bool)) (string, error) } // switchDevice is the concrete Switch implementation. type switchDevice struct { BaseDevice channels []siidPiid // "on" property per channel (index 0 = channel 1) stateCache []bool // MQTT 推送维护的本地缓存,索引 0 = 通道 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 d.stateCache = 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) OnStateChanged(handler func(ch int, on bool)) (string, error) { if len(d.channels) == 0 { return "", xiaomi.ErrNotSupported } // 构建 siid/piid → channel index 的快速查找表 type chField struct{ siid, piid int } lookup := make(map[chField]int) // key → channel index (0-based) 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.stateCache[idx] = b handler(idx+1, b) // ch = idx+1 } return } } }) } 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 }