From 706811790015bbb3e1fbf316a170248e54e4388a Mon Sep 17 00:00:00 2001 From: jpk Date: Wed, 5 Jan 2022 13:00:14 +0100 Subject: [PATCH 01/11] Logitech PRO battery status support --- src/devices/logitech_gpro.c | 79 ++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/src/devices/logitech_gpro.c b/src/devices/logitech_gpro.c index 9d840c5a..7d666abf 100644 --- a/src/devices/logitech_gpro.c +++ b/src/devices/logitech_gpro.c @@ -2,8 +2,14 @@ #include "../utility.h" #include "logitech.h" +#include +#include + +#include #include +#define MSG_SIZE 20 + static struct device device_gpro; #define ID_LOGITECH_PRO 0x0aa7 @@ -17,6 +23,7 @@ static const uint16_t PRODUCT_IDS[] = { }; static int gpro_send_sidetone(hid_device* device_handle, uint8_t num); +static int gpro_request_battery(hid_device* device_handle); void gpro_init(struct device** device) { @@ -26,8 +33,9 @@ void gpro_init(struct device** device) strncpy(device_gpro.device_name, "Logitech G PRO Series", sizeof(device_gpro.device_name)); - device_gpro.capabilities = B(CAP_SIDETONE); - device_gpro.send_sidetone = &gpro_send_sidetone; + device_gpro.capabilities = B(CAP_SIDETONE | B(CAP_BATTERY_STATUS)); + device_gpro.send_sidetone = &gpro_send_sidetone; + device_gpro.request_battery = &gpro_request_battery; *device = &device_gpro; } @@ -40,3 +48,70 @@ static int gpro_send_sidetone(hid_device* device_handle, uint8_t num) return hid_write(device_handle, sidetone_data, sizeof(sidetone_data) / sizeof(sidetone_data[0])); } + +#ifdef DEBUG +static void printhexdump(uint8_t* buf, int len) +{ + printf("┌────────┬─────────────────────────────────────────────────┬──────────────────┐\n"); + for (int offset = 0; offset < MSG_SIZE; offset += 16) { + printf("│ \e[1m%06X\e[m │", offset); + printf("\e[36m"); + for (int i = offset; i < offset + 16; i++) { + if (i < MSG_SIZE) { + printf(" %02X", buf[i]); + } else { + printf(" "); + } + } + printf("\e[m"); + printf(" │ "); + printf("\e[33m"); + for (int i = offset; i < offset + 16; i++) { + if (buf[i] > 31 && buf[i] < 127) { + printf("%c", buf[i]); + } else { + printf("."); + } + } + printf("\e[m"); + printf(" │\n"); + } + printf("└────────┴─────────────────────────────────────────────────┴──────────────────┘\n"); +} +#endif /* DEBUG */ + +static float estimate_battery_level(uint16_t voltage) +{ + return voltage * 100.0 / 0x0fff; +} + +static int gpro_request_battery(hid_device* device_handle) +{ + /* + CREDIT GOES TO https://github.com/ashkitten/ for the project + https://github.com/ashkitten/g933-utils/ + I've simply ported that implementation to this project! + */ + + int r = 0; + // request battery voltage + uint8_t buf[HIDPP_LONG_MESSAGE_LENGTH] = { HIDPP_LONG_MESSAGE, HIDPP_DEVICE_RECEIVER, 0x06, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + r = hid_write(device_handle, buf, sizeof(buf) / sizeof(buf[0])); + if (r < 0) + return r; + + r = hid_read_timeout(device_handle, buf, HIDPP_LONG_MESSAGE_LENGTH, hsc_device_timeout); + if (r < 0) + return r; + + if (r == 0) + return HSC_READ_TIMEOUT; + +#ifdef DEBUG + printhexdump(buf, HIDPP_LONG_MESSAGE_LENGTH); +#endif /* DEBUG */ + + const uint16_t voltage = (buf[4] << 8) | buf[5]; + return (int)(roundf(estimate_battery_level(voltage))); +} From 01fc55860a89f1c9eacd0f1b2808d7a085eb4dc6 Mon Sep 17 00:00:00 2001 From: jpk Date: Wed, 5 Jan 2022 15:06:48 +0100 Subject: [PATCH 02/11] Added Support for setting the inactivity timeout --- src/devices/logitech_gpro.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/devices/logitech_gpro.c b/src/devices/logitech_gpro.c index 7d666abf..473d239a 100644 --- a/src/devices/logitech_gpro.c +++ b/src/devices/logitech_gpro.c @@ -24,6 +24,7 @@ static const uint16_t PRODUCT_IDS[] = { static int gpro_send_sidetone(hid_device* device_handle, uint8_t num); static int gpro_request_battery(hid_device* device_handle); +static int gpro_send_inactive_time(hid_device* device_handle, uint8_t num); void gpro_init(struct device** device) { @@ -33,9 +34,10 @@ void gpro_init(struct device** device) strncpy(device_gpro.device_name, "Logitech G PRO Series", sizeof(device_gpro.device_name)); - device_gpro.capabilities = B(CAP_SIDETONE | B(CAP_BATTERY_STATUS)); + device_gpro.capabilities = B(CAP_SIDETONE | B(CAP_BATTERY_STATUS) | B(CAP_INACTIVE_TIME)); device_gpro.send_sidetone = &gpro_send_sidetone; device_gpro.request_battery = &gpro_request_battery; + device_gpro.send_inactive_time = &gpro_send_inactive_time; *device = &device_gpro; } @@ -115,3 +117,23 @@ static int gpro_request_battery(hid_device* device_handle) const uint16_t voltage = (buf[4] << 8) | buf[5]; return (int)(roundf(estimate_battery_level(voltage))); } + +static int gpro_send_inactive_time(hid_device* device_handle, uint8_t num) +{ + int r = 0; + // request new inactivity timeout + uint8_t buf[HIDPP_LONG_MESSAGE_LENGTH] = { HIDPP_LONG_MESSAGE, HIDPP_DEVICE_RECEIVER, 0x06, 0x2d, num, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + r = hid_write(device_handle, buf, sizeof(buf) / sizeof(buf[0])); + if (r < 0) + return r; + + r = hid_read_timeout(device_handle, buf, HIDPP_LONG_MESSAGE_LENGTH, hsc_device_timeout); + if (r < 0) + return r; + + if (r == 0) + return HSC_READ_TIMEOUT; + + return r; +} From d63a432efc0fb918814061a1cd4b2639f31cc512 Mon Sep 17 00:00:00 2001 From: jpk Date: Wed, 5 Jan 2022 15:17:43 +0100 Subject: [PATCH 03/11] Fine tuned battery status --- src/devices/logitech_gpro.c | 51 +++++++++++-------------------------- 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/src/devices/logitech_gpro.c b/src/devices/logitech_gpro.c index 473d239a..73feef14 100644 --- a/src/devices/logitech_gpro.c +++ b/src/devices/logitech_gpro.c @@ -2,13 +2,13 @@ #include "../utility.h" #include "logitech.h" -#include #include #include #include #define MSG_SIZE 20 +#define MAX_BATTERY 4200 // max seen 0x10e6, but seems to report Vdc static struct device device_gpro; @@ -51,40 +51,11 @@ static int gpro_send_sidetone(hid_device* device_handle, uint8_t num) return hid_write(device_handle, sidetone_data, sizeof(sidetone_data) / sizeof(sidetone_data[0])); } -#ifdef DEBUG -static void printhexdump(uint8_t* buf, int len) -{ - printf("┌────────┬─────────────────────────────────────────────────┬──────────────────┐\n"); - for (int offset = 0; offset < MSG_SIZE; offset += 16) { - printf("│ \e[1m%06X\e[m │", offset); - printf("\e[36m"); - for (int i = offset; i < offset + 16; i++) { - if (i < MSG_SIZE) { - printf(" %02X", buf[i]); - } else { - printf(" "); - } - } - printf("\e[m"); - printf(" │ "); - printf("\e[33m"); - for (int i = offset; i < offset + 16; i++) { - if (buf[i] > 31 && buf[i] < 127) { - printf("%c", buf[i]); - } else { - printf("."); - } - } - printf("\e[m"); - printf(" │\n"); - } - printf("└────────┴─────────────────────────────────────────────────┴──────────────────┘\n"); -} -#endif /* DEBUG */ - static float estimate_battery_level(uint16_t voltage) { - return voltage * 100.0 / 0x0fff; + if (voltage > 4200) + return 100.0; + return voltage * 100.0 / MAX_BATTERY; } static int gpro_request_battery(hid_device* device_handle) @@ -110,9 +81,17 @@ static int gpro_request_battery(hid_device* device_handle) if (r == 0) return HSC_READ_TIMEOUT; -#ifdef DEBUG - printhexdump(buf, HIDPP_LONG_MESSAGE_LENGTH); -#endif /* DEBUG */ + if (buf[6] == 0x03) + return BATTERY_CHARGING; + + // Headset offline + if (buf[2] == 0xff) + return HSC_ERROR; + + // 6th byte is state; 0x1 for discharging, 0x3 for charging + if (buf[6] == 0x03) + return BATTERY_CHARGING; + const uint16_t voltage = (buf[4] << 8) | buf[5]; return (int)(roundf(estimate_battery_level(voltage))); From 15b8808e7873be16b1486e2b8527daefe2e7f3c6 Mon Sep 17 00:00:00 2001 From: jpk Date: Wed, 5 Jan 2022 15:20:24 +0100 Subject: [PATCH 04/11] Same error handling for gpro_send_inactive_time 0xff device offline --- src/devices/logitech_gpro.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/devices/logitech_gpro.c b/src/devices/logitech_gpro.c index 73feef14..b6f39547 100644 --- a/src/devices/logitech_gpro.c +++ b/src/devices/logitech_gpro.c @@ -114,5 +114,8 @@ static int gpro_send_inactive_time(hid_device* device_handle, uint8_t num) if (r == 0) return HSC_READ_TIMEOUT; + if (buf[2] == 0xff) + return HSC_ERROR; + return r; } From d1aa59bb6c0849b1fc3c69ceccb79303eef26f5b Mon Sep 17 00:00:00 2001 From: jpk Date: Wed, 5 Jan 2022 15:49:58 +0100 Subject: [PATCH 05/11] Formatting code to match format guidelines --- src/devices/logitech_gpro.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/devices/logitech_gpro.c b/src/devices/logitech_gpro.c index b6f39547..9f0964c1 100644 --- a/src/devices/logitech_gpro.c +++ b/src/devices/logitech_gpro.c @@ -7,7 +7,7 @@ #include #include -#define MSG_SIZE 20 +#define MSG_SIZE 20 #define MAX_BATTERY 4200 // max seen 0x10e6, but seems to report Vdc static struct device device_gpro; @@ -34,9 +34,9 @@ void gpro_init(struct device** device) strncpy(device_gpro.device_name, "Logitech G PRO Series", sizeof(device_gpro.device_name)); - device_gpro.capabilities = B(CAP_SIDETONE | B(CAP_BATTERY_STATUS) | B(CAP_INACTIVE_TIME)); - device_gpro.send_sidetone = &gpro_send_sidetone; - device_gpro.request_battery = &gpro_request_battery; + device_gpro.capabilities = B(CAP_SIDETONE | B(CAP_BATTERY_STATUS) | B(CAP_INACTIVE_TIME)); + device_gpro.send_sidetone = &gpro_send_sidetone; + device_gpro.request_battery = &gpro_request_battery; device_gpro.send_inactive_time = &gpro_send_inactive_time; *device = &device_gpro; @@ -92,7 +92,6 @@ static int gpro_request_battery(hid_device* device_handle) if (buf[6] == 0x03) return BATTERY_CHARGING; - const uint16_t voltage = (buf[4] << 8) | buf[5]; return (int)(roundf(estimate_battery_level(voltage))); } From 714e5eb12428a5b0637c123e6bb65a2673cc5f69 Mon Sep 17 00:00:00 2001 From: jpk Date: Wed, 5 Jan 2022 16:46:58 +0100 Subject: [PATCH 06/11] Removed debug left over define MSG_SIZE --- src/devices/logitech_gpro.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/devices/logitech_gpro.c b/src/devices/logitech_gpro.c index 9f0964c1..fd772e1a 100644 --- a/src/devices/logitech_gpro.c +++ b/src/devices/logitech_gpro.c @@ -7,7 +7,6 @@ #include #include -#define MSG_SIZE 20 #define MAX_BATTERY 4200 // max seen 0x10e6, but seems to report Vdc static struct device device_gpro; From 6750eb0f5ff138db341ed246765614013c044c74 Mon Sep 17 00:00:00 2001 From: jpk Date: Fri, 7 Jan 2022 08:51:36 +0100 Subject: [PATCH 07/11] Remove duplicate charging check It would not be reached if it is charging. --- src/devices/logitech_gpro.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/devices/logitech_gpro.c b/src/devices/logitech_gpro.c index fd772e1a..53eb8bbf 100644 --- a/src/devices/logitech_gpro.c +++ b/src/devices/logitech_gpro.c @@ -87,10 +87,6 @@ static int gpro_request_battery(hid_device* device_handle) if (buf[2] == 0xff) return HSC_ERROR; - // 6th byte is state; 0x1 for discharging, 0x3 for charging - if (buf[6] == 0x03) - return BATTERY_CHARGING; - const uint16_t voltage = (buf[4] << 8) | buf[5]; return (int)(roundf(estimate_battery_level(voltage))); } From a83f401248a92001b668b1fb989ce001d4e96c27 Mon Sep 17 00:00:00 2001 From: jpk Date: Fri, 7 Jan 2022 16:53:41 +0100 Subject: [PATCH 08/11] [WIP] Support Windows MSYS2 environment Adding support for Windows with MSYS2. Helps observing the percentage in G Hub and the raw voltage readings. --- src/devices/logitech_gpro.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/devices/logitech_gpro.c b/src/devices/logitech_gpro.c index 53eb8bbf..802f9c21 100644 --- a/src/devices/logitech_gpro.c +++ b/src/devices/logitech_gpro.c @@ -34,6 +34,10 @@ void gpro_init(struct device** device) strncpy(device_gpro.device_name, "Logitech G PRO Series", sizeof(device_gpro.device_name)); device_gpro.capabilities = B(CAP_SIDETONE | B(CAP_BATTERY_STATUS) | B(CAP_INACTIVE_TIME)); + + device_gpro.capability_details[CAP_BATTERY_STATUS] = (struct capability_detail) { .usagepage = 0xff43, .usageid = 0x0202 }; + device_gpro.capability_details[CAP_INACTIVE_TIME] = (struct capability_detail) { .usagepage = 0xff43, .usageid = 0x0202 }; + device_gpro.send_sidetone = &gpro_send_sidetone; device_gpro.request_battery = &gpro_request_battery; device_gpro.send_inactive_time = &gpro_send_inactive_time; From acbdff096250dccf3e4f749fc1b88b2a93c43faa Mon Sep 17 00:00:00 2001 From: jpk Date: Sat, 8 Jan 2022 14:01:06 +0100 Subject: [PATCH 09/11] Added documentation to estimate_battery_level --- src/devices/logitech_gpro.c | 41 ++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/devices/logitech_gpro.c b/src/devices/logitech_gpro.c index 802f9c21..a87b141e 100644 --- a/src/devices/logitech_gpro.c +++ b/src/devices/logitech_gpro.c @@ -7,8 +7,6 @@ #include #include -#define MAX_BATTERY 4200 // max seen 0x10e6, but seems to report Vdc - static struct device device_gpro; #define ID_LOGITECH_PRO 0x0aa7 @@ -54,11 +52,44 @@ static int gpro_send_sidetone(hid_device* device_handle, uint8_t num) return hid_write(device_handle, sidetone_data, sizeof(sidetone_data) / sizeof(sidetone_data[0])); } +/** + * @brief This function calculates the estimate batttery level in percent. + * + * Battery stats: + * - Voltage capacity: 4200 (100%) + * - Charging voltage: 4700 + * - Power off voltage: 3300 ( 0%) + * + * @param voltage readings + * @return battery level in percent + */ static float estimate_battery_level(uint16_t voltage) { - if (voltage > 4200) - return 100.0; - return voltage * 100.0 / MAX_BATTERY; + double terms[] = { + -6.1964487179274298e+005, + 6.5828721046922783e+002, + -2.2633136783613775e-001, + 1.1433822915597668e-005, + 8.9940712231305347e-009, + -1.5410267634516437e-012, + -8.1338998403017992e-018, + 1.1289501838578214e-020 + }; + + size_t csz = sizeof terms / sizeof *terms; + + double t = 1; + double percent = 0; + for (int i = 0; i < csz; i++) { + percent += terms[i] * t; + t *= voltage; + } + + if (percent > 100) + percent = 100; + if (percent < 0) + percent = 0; + return percent; } static int gpro_request_battery(hid_device* device_handle) From 0913cbaf706618065ba51809a6dca0869fece6fb Mon Sep 17 00:00:00 2001 From: jpk Date: Sat, 8 Jan 2022 20:24:45 +0100 Subject: [PATCH 10/11] Battery discharge polynom The estimate_battery_level should return a precentage most equal to G Hub. --- src/devices/logitech_gpro.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/devices/logitech_gpro.c b/src/devices/logitech_gpro.c index a87b141e..6f678515 100644 --- a/src/devices/logitech_gpro.c +++ b/src/devices/logitech_gpro.c @@ -66,14 +66,12 @@ static int gpro_send_sidetone(hid_device* device_handle, uint8_t num) static float estimate_battery_level(uint16_t voltage) { double terms[] = { - -6.1964487179274298e+005, - 6.5828721046922783e+002, - -2.2633136783613775e-001, - 1.1433822915597668e-005, - 8.9940712231305347e-009, - -1.5410267634516437e-012, - -8.1338998403017992e-018, - 1.1289501838578214e-020 + -1.7790085824253613e+006, + 2.3692153307344888e+003, + -1.2580905041506840e+000, + 3.3295201187388395e-004, + -4.3913981733597497e-008, + 2.3093121045539907e-012 }; size_t csz = sizeof terms / sizeof *terms; From 93bed031e3e20526f306da2fd11393239d44a4cc Mon Sep 17 00:00:00 2001 From: jpk Date: Sun, 9 Jan 2022 11:31:34 +0100 Subject: [PATCH 11/11] Satisfying format checker --- src/devices/logitech_gpro.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/devices/logitech_gpro.c b/src/devices/logitech_gpro.c index 6f678515..d5a4db2a 100644 --- a/src/devices/logitech_gpro.c +++ b/src/devices/logitech_gpro.c @@ -31,10 +31,10 @@ void gpro_init(struct device** device) strncpy(device_gpro.device_name, "Logitech G PRO Series", sizeof(device_gpro.device_name)); - device_gpro.capabilities = B(CAP_SIDETONE | B(CAP_BATTERY_STATUS) | B(CAP_INACTIVE_TIME)); + device_gpro.capabilities = B(CAP_SIDETONE | B(CAP_BATTERY_STATUS) | B(CAP_INACTIVE_TIME)); device_gpro.capability_details[CAP_BATTERY_STATUS] = (struct capability_detail) { .usagepage = 0xff43, .usageid = 0x0202 }; - device_gpro.capability_details[CAP_INACTIVE_TIME] = (struct capability_detail) { .usagepage = 0xff43, .usageid = 0x0202 }; + device_gpro.capability_details[CAP_INACTIVE_TIME] = (struct capability_detail) { .usagepage = 0xff43, .usageid = 0x0202 }; device_gpro.send_sidetone = &gpro_send_sidetone; device_gpro.request_battery = &gpro_request_battery; @@ -76,7 +76,7 @@ static float estimate_battery_level(uint16_t voltage) size_t csz = sizeof terms / sizeof *terms; - double t = 1; + double t = 1; double percent = 0; for (int i = 0; i < csz; i++) { percent += terms[i] * t;