package via import ( "errors" "fmt" ) // Channel is a VIA lighting channel. The values are QMK's id_qmk_*_channel // constants from quantum/via.h. type Channel uint8 const ( ChannelBacklight Channel = 1 ChannelRgblight Channel = 2 ChannelRgbMatrix Channel = 3 ChannelAudio Channel = 4 ChannelLedMatrix Channel = 5 ) // AssignedChannelMax is the highest channel QMK currently assigns. The probe // asks beyond it on purpose, see probeChannelMax. const AssignedChannelMax = ChannelLedMatrix // probeChannelMax is the highest channel the probe asks about. The QMK enum // stops at 5; the extra reads cost one round trip each and let a future QMK // that claims a higher channel be found without a code change. const probeChannelMax = 15 // probeValueID is the brightness value ID. It is one byte and valid on every // lighting subsystem, so one request per channel finds any kind of channel. const probeValueID = 0x01 // Subsystem returns the QMK name of the channel's lighting subsystem. The name // follows from the channel number, so no board file stores it and it is always // available for a channel the keyboard has. func (c Channel) Subsystem() string { switch c { case ChannelBacklight: return "backlight" case ChannelRgblight: return "rgblight" case ChannelRgbMatrix: return "rgb_matrix" case ChannelAudio: return "audio" case ChannelLedMatrix: return "led_matrix" default: return "" } } // DetectChannels asks the keyboard which lighting channels it has, in ascending // order. // // The 0xFF answer is the discriminator, not the payload: QMK rejects a channel // it does not compile in with id_unhandled, whereas an unknown value ID on a // known channel is mirrored back with a zero payload. A read that fails for any // other reason aborts the probe, because a channel list missing entries is // indistinguishable from a complete one and would silently under-report. func (p *Protocol) DetectChannels() ([]Channel, error) { var present []Channel for c := Channel(1); c <= probeChannelMax; c++ { if _, err := p.GetValue(c, probeValueID); err != nil { if errors.Is(err, errUnhandled) { continue } return nil, fmt.Errorf("probe channel %d: %w", c, err) } present = append(present, c) } return present, nil }