package qmkenc import ( "encoding/binary" "fmt" "os" "time" "github.com/sstallion/go-hid" ) const encDevUsage uint16 = 0x61 const encDevUsagePage uint16 = 0xFF60 type QEncDevice struct { path string serial string product string manufacturer string dev *hid.Device bufSize uint8 timeout int retryTimeout int retryWait int lockfile string } func (d *QEncDevice) GetPath() string { return d.path } func (d *QEncDevice) GetSerial() string { return d.serial } func (d *QEncDevice) GetProduct() string { return d.product } func (d *QEncDevice) GetManufacturer() string { return d.manufacturer } func (d *QEncDevice) GetBufSize() uint8 { return d.bufSize } func (d *QEncDevice) Open() (err error) { if d.dev, err = hid.OpenPath(d.path); err != nil { return fmt.Errorf("Error opening device: '%w'", err) } if err = d.setBufSize(); err != nil { d.Close() return err } return nil } func (d *QEncDevice) Close() (err error) { if d.lockfile != "" { os.Remove(d.lockfile) } return d.dev.Close() } func (d *QEncDevice) SetLockfile(lockfile string) { d.lockfile = lockfile } func (d *QEncDevice) SendBuffer(data []byte, msg chan string) (ret []byte, err error) { startTime := time.Now() for { ret = make([]byte, d.bufSize) if len(data) != int(d.bufSize) { return ret, fmt.Errorf("Invalid data size") } if _, err = d.dev.Write(data); err != nil { return ret, fmt.Errorf("Failed to write: '%w'", err) } if _, err = d.dev.ReadWithTimeout(ret, time.Duration(time.Second*time.Duration(d.timeout))); err != nil { return ret, fmt.Errorf("Failed to read: '%w'", err) } var dataLen uint16 ret, err = QEncCheckResponse(ret) if err != nil { qerr := ToQEncError(err) if qerr.errCode != QENC_ERR_RETRY { return ret, err } fmt.Println("RETRY") dataLen = binary.LittleEndian.Uint16(ret[:2]) if int(dataLen) == 5 { if msg != nil { timeout := binary.LittleEndian.Uint32(ret[4:8]) cmd := QEncCmd(ret[3]) msg <- fmt.Sprintf("Allow request for '%s' (timeout: %ds)", cmd, timeout) } } time.Sleep(time.Second * time.Duration(d.retryWait)) } else { return ret, err } if time.Now().Sub(startTime).Seconds() >= float64(d.retryTimeout) { qerr := new(QEncError) qerr.errCode = QENC_ERR_TIMEOUT return ret, qerr } } } func (d *QEncDevice) setBufSize() (err error) { if d.bufSize > 0 { return nil } buf_sizes := []int{8, 16, 32, 64, 128} for _, bs := range buf_sizes { buf_max := bs buf := make([]byte, buf_max) sbuf := make([]byte, buf_max) if copy(sbuf, magicHeader) != 2 { return fmt.Errorf("Invalid magic Header") } sbuf[2] = byte(QENC_CMD_GET_BUFFSIZE) if _, err = d.dev.Write(sbuf); err != nil { return fmt.Errorf("Failed to write to device: '%w'", err) } _, err = d.dev.ReadWithTimeout(buf, time.Duration(time.Second*1)) if err != nil { continue } if buf, err = QEncCheckResponse(buf); err != nil { return err } data_len := binary.LittleEndian.Uint16(buf) if int(data_len) != 1 { return fmt.Errorf("Invalid data") } d.bufSize = buf[2] return nil } return fmt.Errorf("Unsupported device: '%w'", err) } func (d *QEncDevice) Lock() (err error) { if d.lockfile == "" { return nil } for { if _, err := os.Stat(d.lockfile); err == nil { time.Sleep(time.Second * 1) continue } else if os.IsNotExist(err) { file, err := os.Create(d.lockfile) if err != nil { return err } file.Close() return nil } } } func (d *QEncDevice) UnLock() { if d.lockfile == "" { return } os.Remove(d.lockfile) } func QEncNewDevice(path string, bufSize uint8, timeout, retryTimeout, retryWait int, lockfile string) *QEncDevice { return &QEncDevice{ path: path, timeout: timeout, retryTimeout: retryTimeout, retryWait: retryWait, bufSize: bufSize, lockfile: lockfile, } } func QEncScanDevices(ids ...uint16) (ret []*QEncDevice, err error) { vid := uint16(hid.VendorIDAny) pid := uint16(hid.ProductIDAny) if len(ids) > 2 { return ret, fmt.Errorf("To many ids given") } if len(ids) == 1 { vid = ids[0] } if len(ids) == 2 { vid = ids[0] pid = ids[1] } hid.Enumerate(vid, pid, func(info *hid.DeviceInfo) error { if info.Usage == encDevUsage && info.UsagePage == encDevUsagePage { d := QEncDevice{ path: info.Path, serial: info.SerialNbr, product: info.ProductStr, manufacturer: info.MfrStr, timeout: 5, retryTimeout: 10, retryWait: 1, } ret = append(ret, &d) } return nil }) if len(ret) == 0 { return ret, fmt.Errorf("No device found") } return ret, nil } func QEncExit() (err error) { return hid.Exit() }