Skip to content

Commit e6c1b95

Browse files
committed
Let's do some decoding . . drivers with parsers up next :-)
1 parent 72b9095 commit e6c1b95

File tree

5 files changed

+529
-64
lines changed

5 files changed

+529
-64
lines changed

main/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
idf_component_register(
22
SRCS
33
"badge_hid_host.c"
4+
"badge_hid_drivers.c"
45
"main.c"
56
PRIV_REQUIRES
67
esp_lcd

main/badge_hid_drivers.c

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
/*
2+
* HID host library for gamepad and mouse input devices.
3+
*
4+
* Loosely based on https://github.com/esp32-open-source/usb-components/blob/master/hid-parser/hid_parser.c
5+
*
6+
* SPDX-FileCopyrightText: 2024-2025 chegewara
7+
* SPDX-FileCopyrightText: 2025 Badge.Team
8+
*
9+
* SPDX-License-Identifier: MIT
10+
*/
11+
#include "badge_hid_drivers.h"
12+
#include "esp_log.h"
13+
#include "usb/hid.h"
14+
15+
static char const TAG[] = "BADGE_HID_DRIVER";
16+
17+
static int32_t extract_signed_bits(const uint8_t* data, uint16_t bit_offset, uint8_t bit_size) {
18+
uint32_t raw = 0;
19+
for (int i = 0; i < ((bit_size + 7) / 8) + 1; ++i) {
20+
raw |= data[(bit_offset / 8) + i] << (i * 8);
21+
}
22+
23+
raw >>= (bit_offset % 8);
24+
int32_t value = raw & ((1u << bit_size) - 1);
25+
26+
// Sign extend
27+
if (value & (1u << (bit_size - 1))) {
28+
value |= ~((1u << bit_size) - 1);
29+
}
30+
31+
return value;
32+
}
33+
34+
static bool analyze_mouse_layout(const uint8_t* desc, int desc_len, mouse_field_layout_t* layout_out) {
35+
memset(layout_out, 0, sizeof(*layout_out));
36+
37+
uint16_t bit_offset = 0;
38+
uint8_t usage_page = 0;
39+
uint16_t usages[32] = {0};
40+
int usage_index = 0;
41+
int report_size = 0;
42+
int report_count = 0;
43+
44+
for (int i = 0; i < desc_len;) {
45+
uint8_t b = desc[i++];
46+
if (b == 0xFE || i >= desc_len) break;
47+
48+
int size = b & 0x03;
49+
int type = (b >> 2) & 0x03;
50+
int tag = (b >> 4) & 0x0F;
51+
if (size == 3) size = 4;
52+
if (i + size > desc_len) break;
53+
54+
uint32_t data = 0;
55+
for (int j = 0; j < size; j++) {
56+
data |= desc[i++] << (j * 8);
57+
}
58+
59+
switch (type) {
60+
case 0x1: // Global
61+
if (tag == 0x0)
62+
usage_page = data;
63+
else if (tag == 0x7)
64+
report_size = data;
65+
else if (tag == 0x9)
66+
report_count = data;
67+
break;
68+
69+
case 0x2: // Local
70+
if (tag == 0x0 && usage_index < 32) usages[usage_index++] = data;
71+
break;
72+
73+
case 0x0: // Main
74+
if (tag == 0x8) { // Input
75+
if (usage_page == 0x01 || usage_page == 0x09 || usage_page == 0x0C) {
76+
int fields = report_count;
77+
int bits = report_size;
78+
79+
int used = usage_index;
80+
if (used > fields) used = fields;
81+
82+
for (int u = 0; u < used; u++) {
83+
uint16_t usage = usages[u];
84+
if (usage_page == 0x01) {
85+
if (usage == 0x30) {
86+
layout_out->has_x = true;
87+
layout_out->x_bit_offset = bit_offset;
88+
layout_out->x_bit_size = bits;
89+
} else if (usage == 0x31) {
90+
layout_out->has_y = true;
91+
layout_out->y_bit_offset = bit_offset;
92+
layout_out->y_bit_size = bits;
93+
} else if (usage == 0x38) {
94+
layout_out->has_scroll = true;
95+
layout_out->scroll_bit_offset = bit_offset;
96+
layout_out->scroll_bit_size = bits;
97+
} else if (usage == 0x48) {
98+
layout_out->has_tilt = true;
99+
layout_out->tilt_bit_offset = bit_offset;
100+
layout_out->tilt_bit_size = bits;
101+
}
102+
} else if (usage_page == 0x0C && usage == 0x0238) {
103+
layout_out->has_tilt = true;
104+
layout_out->tilt_bit_offset = bit_offset;
105+
layout_out->tilt_bit_size = bits;
106+
} else if (usage_page == 0x09 && usage >= 0x01 && usage <= 0x10) {
107+
if (!layout_out->has_buttons) layout_out->button_bit_offset = bit_offset;
108+
layout_out->has_buttons = true;
109+
layout_out->button_bit_count += 1;
110+
}
111+
bit_offset += bits;
112+
}
113+
114+
// Account for unused fields (e.g. Button 3-16 with no Usage tags)
115+
if (fields > used) {
116+
bit_offset += (fields - used) * bits;
117+
}
118+
119+
usage_index = 0;
120+
memset(usages, 0, sizeof(usages));
121+
}
122+
}
123+
break;
124+
}
125+
}
126+
127+
return layout_out->has_x && layout_out->has_y;
128+
}
129+
130+
static bool analyze_gamepad_layout(const uint8_t* desc, int desc_len, gamepad_field_layout_t* layout_out) {
131+
memset(layout_out, 0, sizeof(*layout_out));
132+
133+
uint16_t bit_offset = 0;
134+
uint8_t usage_page = 0;
135+
uint16_t usages[32] = {0};
136+
int usage_index = 0;
137+
138+
// Check for Report ID and adjust initial bit offset
139+
for (int pre = 0; pre < desc_len - 1; pre++) {
140+
if ((desc[pre] & 0xFC) == 0x84) {
141+
bit_offset = 8;
142+
break;
143+
}
144+
}
145+
146+
for (int i = 0; i < desc_len;) {
147+
uint8_t b = desc[i++];
148+
if (b == 0xFE || i >= desc_len) break; // long item or overrun
149+
150+
int size = b & 0x03;
151+
int type = (b >> 2) & 0x03;
152+
int tag = (b >> 4) & 0x0F;
153+
if (size == 3) size = 4;
154+
if (i + size > desc_len) break;
155+
156+
uint32_t data = 0;
157+
for (int j = 0; j < size; j++) {
158+
data |= desc[i++] << (j * 8);
159+
}
160+
161+
switch (type) {
162+
case 0x1: // Global
163+
if (tag == 0x0) usage_page = data;
164+
break;
165+
166+
case 0x2: // Local
167+
if (tag == 0x0 && usage_index < (int)(sizeof(usages) / sizeof(usages[0]))) usages[usage_index++] = data;
168+
break;
169+
170+
case 0x0: // Main
171+
if (tag == 0x8) { // Input
172+
int field_count = 1;
173+
int field_size = 0;
174+
175+
// Go backwards to find report count and size
176+
for (int back = i - size - 2; back > 0; back--) {
177+
if ((desc[back] & 0xFC) == 0x94) field_count = desc[back + 1];
178+
if ((desc[back] & 0xFC) == 0x74) field_size = desc[back + 1];
179+
if (field_count && field_size) break;
180+
}
181+
182+
for (int u = 0; u < usage_index; u++) {
183+
uint16_t usage = usages[u];
184+
185+
if (usage_page == 0x01) { // Generic Desktop
186+
if (usage == 0x39) {
187+
layout_out->has_dpad = true;
188+
layout_out->dpad_bit_offset = bit_offset;
189+
layout_out->dpad_bit_size = field_size;
190+
} else if (usage == 0x30) {
191+
layout_out->has_lx = true;
192+
layout_out->lx_bit_offset = bit_offset;
193+
layout_out->lx_bit_size = field_size;
194+
} else if (usage == 0x31) {
195+
layout_out->has_ly = true;
196+
layout_out->ly_bit_offset = bit_offset;
197+
layout_out->ly_bit_size = field_size;
198+
} else if (usage == 0x32) {
199+
layout_out->has_rx = true;
200+
layout_out->rx_bit_offset = bit_offset;
201+
layout_out->rx_bit_size = field_size;
202+
} else if (usage == 0x35) {
203+
layout_out->has_ry = true;
204+
layout_out->ry_bit_offset = bit_offset;
205+
layout_out->ry_bit_size = field_size;
206+
}
207+
} else if (usage_page == 0x09 && usage >= 0x01 && usage <= 0x20) { // Buttons
208+
if (!layout_out->has_buttons) layout_out->button_bit_offset = bit_offset;
209+
layout_out->has_buttons = true;
210+
layout_out->button_bit_count += 1;
211+
} else if (usage_page == 0x02 && (usage == 0xC5 || usage == 0xC4)) { // Triggers
212+
if (!layout_out->has_lt) {
213+
layout_out->has_lt = true;
214+
layout_out->lt_bit_offset = bit_offset;
215+
layout_out->lt_bit_size = field_size;
216+
} else {
217+
layout_out->has_rt = true;
218+
layout_out->rt_bit_offset = bit_offset;
219+
layout_out->rt_bit_size = field_size;
220+
}
221+
}
222+
}
223+
224+
bit_offset += field_count * field_size;
225+
usage_index = 0;
226+
}
227+
break;
228+
}
229+
}
230+
231+
return layout_out->has_dpad && layout_out->has_buttons;
232+
}
233+
234+
esp_err_t decode_descriptor_register_driver(const uint8_t* const desc, const int desc_len, const uint8_t proto) {
235+
if (HID_PROTOCOL_KEYBOARD == proto) {
236+
ESP_LOGI(TAG, "Keyboard uses generic (boot) driver");
237+
return ESP_OK;
238+
}
239+
240+
if (HID_PROTOCOL_MOUSE == proto) {
241+
ESP_LOGI(TAG, "Mouse driver analysing");
242+
ESP_LOG_BUFFER_HEX(TAG, desc, desc_len);
243+
244+
mouse_field_layout_t layout = {0};
245+
246+
if (analyze_mouse_layout(desc, desc_len, &layout)) {
247+
ESP_LOGI(TAG, "Parsed mouse layout:");
248+
ESP_LOGI(TAG, " Buttons: offset %u bits, count %u", layout.button_bit_offset, layout.button_bit_count);
249+
if (layout.has_x)
250+
ESP_LOGI(TAG, " X: offset %u bits, size %u bits", layout.x_bit_offset, layout.x_bit_size);
251+
if (layout.has_y)
252+
ESP_LOGI(TAG, " Y: offset %u bits, size %u bits", layout.y_bit_offset, layout.y_bit_size);
253+
if (layout.has_scroll)
254+
ESP_LOGI(TAG, " Scroll: offset %u bits, size %u bits", layout.scroll_bit_offset,
255+
layout.scroll_bit_size);
256+
if (layout.has_tilt)
257+
ESP_LOGI(TAG, " Tilt: offset %u bits, size %u bits", layout.tilt_bit_offset, layout.tilt_bit_size);
258+
} else {
259+
ESP_LOGW(TAG, "Could not parse mouse layout");
260+
}
261+
} else {
262+
ESP_LOGI(TAG, "Gamepad driver analyusing");
263+
ESP_LOG_BUFFER_HEX(TAG, desc, desc_len);
264+
265+
gamepad_field_layout_t layout;
266+
if (analyze_gamepad_layout(desc, desc_len, &layout)) {
267+
ESP_LOGI(TAG, "Parsed gamepad layout:");
268+
if (layout.has_dpad) {
269+
ESP_LOGI(TAG, " D-Pad: offset %d bits, size %d bits", layout.dpad_bit_offset, layout.dpad_bit_size);
270+
}
271+
272+
if (layout.has_buttons) {
273+
ESP_LOGI(TAG, " Buttons: offset %d bits, count %d", layout.button_bit_offset, layout.button_bit_count);
274+
}
275+
276+
if (layout.has_lx && layout.has_ly) {
277+
ESP_LOGI(TAG, " Left Stick: X@%dbits (%d bits), Y@%dbits (%d bits)", layout.lx_bit_offset,
278+
layout.lx_bit_size, layout.ly_bit_offset, layout.ly_bit_size);
279+
} else {
280+
if (layout.has_lx) {
281+
ESP_LOGI(TAG, " Left Stick X: offset %d bits, size %d bits", layout.lx_bit_offset,
282+
layout.lx_bit_size);
283+
}
284+
if (layout.has_ly) {
285+
ESP_LOGI(TAG, " Left Stick Y: offset %d bits, size %d bits", layout.ly_bit_offset,
286+
layout.ly_bit_size);
287+
}
288+
}
289+
290+
if (layout.has_rx && layout.has_ry) {
291+
ESP_LOGI(TAG, " Right Stick: X@%dbits (%d bits), Y@%dbits (%d bits)", layout.rx_bit_offset,
292+
layout.rx_bit_size, layout.ry_bit_offset, layout.ry_bit_size);
293+
} else {
294+
if (layout.has_rx) {
295+
ESP_LOGI(TAG, " Right Stick X: offset %d bits, size %d bits", layout.rx_bit_offset,
296+
layout.rx_bit_size);
297+
}
298+
if (layout.has_ry) {
299+
ESP_LOGI(TAG, " Right Stick Y: offset %d bits, size %d bits", layout.ry_bit_offset,
300+
layout.ry_bit_size);
301+
}
302+
}
303+
304+
if (layout.has_lt) {
305+
ESP_LOGI(TAG, " Trigger L: offset %d bits, size %d bits", layout.lt_bit_offset, layout.lt_bit_size);
306+
}
307+
308+
if (layout.has_rt) {
309+
ESP_LOGI(TAG, " Trigger R: offset %d bits, size %d bits", layout.rt_bit_offset, layout.rt_bit_size);
310+
}
311+
312+
} else {
313+
ESP_LOGW(TAG, "Could not identify gamepad-compatible descriptor");
314+
}
315+
}
316+
317+
return ESP_OK;
318+
}

0 commit comments

Comments
 (0)