/* * gpro-battery-openbsd.c * * Minimal OpenBSD userland tool: Logitech G PRO X Wireless battery via /dev/uhidX * heavily inspired by * https://github.com/Sapd/HeadsetControl/blob/master/src/devices/logitech_gpro.c * * TX (19 bytes): ff 06 0d 00... * RX (19 bytes): on this device, voltage/status are at: * voltage_raw = (rx[3] << 8) | rx[4] * charging = (rx[5] == 0x03) * * Prints: * voltage_raw= * percent_est=<0..100> (simple linear approximation) * charging=yes|no */ #include #include #include #include #include #include #include static int read_timeout(int fd, uint8_t *buf, size_t len, int ms) { struct pollfd pfd = { .fd = fd, .events = POLLIN }; int r = poll(&pfd, 1, ms); if (r == -1) err(1, "poll"); if (r == 0) return 0; ssize_t n = read(fd, buf, len); if (n == -1) err(1, "read"); return (int)n; } static double percent_estimate(uint16_t vraw) { /* Linear clamp: 3300 -> 0%, 4200 -> 100% (approximation) */ const double vmin = 3300.0, vmax = 4200.0; if ((double)vraw <= vmin) return 0.0; if ((double)vraw >= vmax) return 100.0; return ((double)vraw - vmin) * 100.0 / (vmax - vmin); } int main(int argc, char **argv) { const char *dev = (argc > 1) ? argv[1] : "/dev/uhid0"; int fd = open(dev, O_RDWR | O_NONBLOCK); if (fd == -1) err(1, "open %s", dev); uint8_t tx[19]; memset(tx, 0, sizeof(tx)); tx[0] = 0xff; tx[1] = 0x06; tx[2] = 0x0d; if (write(fd, tx, sizeof(tx)) != (ssize_t)sizeof(tx)) err(1, "write"); uint8_t rx[19]; int n = read_timeout(fd, rx, sizeof(rx), 500); if (n <= 0) errx(1, "no response"); if (n != (int)sizeof(rx)) errx(1, "short read: %d bytes", n); uint16_t voltage_raw = (uint16_t)((rx[3] << 8) | rx[4]); int charging = (rx[5] == 0x03); double pct = percent_estimate(voltage_raw); printf("voltage_raw=%u\n", voltage_raw); printf("percent_est=%.1f\n", pct); printf("charging=%s\n", charging ? "yes" : "no"); close(fd); return 0; }