protocol.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. package via
  2. import (
  3. "errors"
  4. "fmt"
  5. "netdome.biz/paul/qmk-rgb/internal/device"
  6. "netdome.biz/paul/qmk-rgb/internal/hid"
  7. )
  8. type transport interface {
  9. SendReport(reportID byte, report []byte) (int, error)
  10. Read(buf []byte) (int, error)
  11. Close() error
  12. }
  13. // Protocol implements the VIA protocol for communication with a keyboard.
  14. type Protocol struct {
  15. handle transport
  16. }
  17. // New creates a new VIA protocol handler for a connected device.
  18. func New(dev device.Device) (*Protocol, error) {
  19. h, err := hid.OpenPath(dev.Path)
  20. if err != nil {
  21. return nil, fmt.Errorf("open device: %w", err)
  22. }
  23. return &Protocol{handle: h}, nil
  24. }
  25. // Close releases the device handle.
  26. func (p *Protocol) Close() error {
  27. if p.handle != nil {
  28. return p.handle.Close()
  29. }
  30. return nil
  31. }
  32. // Message defines VIA message types.
  33. type Message uint8
  34. const (
  35. CustomSet Message = 0x07
  36. CustomGet Message = 0x08
  37. Unhandled Message = 0xff
  38. )
  39. // errUnhandled reports a channel or value ID the firmware does not implement.
  40. // It is a sentinel so a caller can tell "this does not exist here" from a
  41. // transport failure, which the two are otherwise indistinguishable in.
  42. var errUnhandled = errors.New("unhandled response")
  43. // SetValue sends a set value command for the given channel and parameter.
  44. func (p *Protocol) SetValue(ch Channel, param uint8, value uint8) error {
  45. report := make([]byte, 32)
  46. report[0] = byte(CustomSet)
  47. report[1] = byte(ch)
  48. report[2] = param
  49. report[3] = value
  50. if _, err := p.handle.SendReport(0x00, report); err != nil {
  51. return err
  52. }
  53. _, err := p.readResponse(CustomSet, byte(ch), param)
  54. return err
  55. }
  56. func (p *Protocol) SetColor(ch Channel, hue uint8, saturation uint8) error {
  57. report := make([]byte, 32)
  58. report[0] = byte(CustomSet)
  59. report[1] = byte(ch)
  60. report[2] = 0x04
  61. report[3] = hue
  62. report[4] = saturation
  63. if _, err := p.handle.SendReport(0x00, report); err != nil {
  64. return err
  65. }
  66. _, err := p.readResponse(CustomSet, byte(ch), 0x04)
  67. return err
  68. }
  69. // GetValue sends a get value request and reads the response.
  70. func (p *Protocol) GetValue(ch Channel, param uint8) ([]byte, error) {
  71. report := make([]byte, 32)
  72. report[0] = byte(CustomGet)
  73. report[1] = byte(ch)
  74. report[2] = param
  75. _, err := p.handle.SendReport(0x00, report)
  76. if err != nil {
  77. return nil, fmt.Errorf("send get request: %w", err)
  78. }
  79. buf, err := p.readResponse(CustomGet, byte(ch), param)
  80. if err != nil {
  81. return nil, err
  82. }
  83. valueSize := 1
  84. if param == 0x04 {
  85. valueSize = 2
  86. }
  87. return append([]byte(nil), buf[3:3+valueSize]...), nil
  88. }
  89. func (p *Protocol) readResponse(command Message, ch, param byte) ([]byte, error) {
  90. buf := make([]byte, 32)
  91. n, err := p.handle.Read(buf)
  92. if err != nil {
  93. return nil, fmt.Errorf("read response: %w", err)
  94. }
  95. if n != len(buf) {
  96. return nil, fmt.Errorf("short response: got %d bytes, want 32", n)
  97. }
  98. if buf[0] == byte(Unhandled) {
  99. // A 0xFF for a different channel is not absence, it is a stale answer
  100. // still sitting in the read buffer. QMK echoes the channel it answers,
  101. // so the mismatch is what tells the two apart.
  102. if buf[1] != ch {
  103. return nil, fmt.Errorf("stale response for channel 0x%02x while reading channel 0x%02x", buf[1], ch)
  104. }
  105. return nil, errUnhandled
  106. }
  107. if buf[0] != byte(command) {
  108. return nil, fmt.Errorf("unexpected response command: 0x%02x", buf[0])
  109. }
  110. if buf[1] != ch || buf[2] != param {
  111. return nil, fmt.Errorf("response value mismatch: got channel 0x%02x value 0x%02x", buf[1], buf[2])
  112. }
  113. return buf, nil
  114. }