Skip to content

Commit 4f7839e

Browse files
committed
fix(ble): Fix HID BLE permissions, add examples and fix char handle
1 parent bc82942 commit 4f7839e

File tree

10 files changed

+530
-28
lines changed

10 files changed

+530
-28
lines changed
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/*
2+
* BLE HID Gamepad Client Example
3+
*
4+
* This example demonstrates how to connect to a BLE HID Gamepad and read its input.
5+
* The ESP32 acts as a BLE Central (client) that connects to a BLE gamepad peripheral.
6+
*
7+
* Features:
8+
* - Scans for BLE HID gamepad devices
9+
* - Connects to the first gamepad found
10+
* - Subscribes to input report notifications
11+
* - Parses and displays gamepad input (buttons and axes)
12+
* - Automatic reconnection on disconnect
13+
*
14+
* Usage:
15+
* 1. Upload this sketch to your ESP32
16+
* 2. Turn on your BLE gamepad (or run the Server_Gamepad example on another ESP32)
17+
* 3. The ESP32 will scan, connect, and display gamepad input in the serial monitor
18+
*
19+
* Compatible with gamepads using the standard HID Report Descriptor format
20+
*
21+
* Created by lucasssvaz
22+
*/
23+
24+
#include <BLEDevice.h>
25+
26+
// HID Service UUID (standard UUID for HID over GATT)
27+
static BLEUUID hidServiceUUID((uint16_t)0x1812);
28+
// HID Report characteristic UUID (used for input/output reports)
29+
static BLEUUID reportCharUUID((uint16_t)0x2A4D);
30+
// HID Report Map characteristic UUID
31+
static BLEUUID reportMapUUID((uint16_t)0x2A4B);
32+
33+
static boolean doConnect = false;
34+
static boolean connected = false;
35+
static boolean doScan = false;
36+
static BLERemoteCharacteristic *pInputReportCharacteristic = nullptr;
37+
static BLEAdvertisedDevice *myDevice = nullptr;
38+
static BLEClient *pClient = nullptr;
39+
40+
// Gamepad report structure (adjust based on your gamepad's report descriptor)
41+
// This matches the Server_Gamepad example format
42+
struct GamepadReport {
43+
uint8_t reportId; // Report ID
44+
int8_t x; // X axis (-127 to 127)
45+
int8_t y; // Y axis (-127 to 127)
46+
uint8_t buttons; // 8 buttons (bit 0-7)
47+
} __attribute__((packed));
48+
49+
// Callback function to handle gamepad input notifications
50+
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
51+
Serial.printf("Received %d bytes: ", length);
52+
53+
// Check if data length matches our expected gamepad report
54+
if (length == sizeof(GamepadReport)) {
55+
GamepadReport *report = (GamepadReport *)pData;
56+
57+
Serial.printf("ID=%d, X=%4d, Y=%4d, Buttons=0x%02X [",
58+
report->reportId, report->x, report->y, report->buttons);
59+
60+
// Display which buttons are pressed
61+
for (int i = 0; i < 8; i++) {
62+
if (report->buttons & (1 << i)) {
63+
Serial.printf("%d ", i + 1);
64+
}
65+
}
66+
Serial.println("]");
67+
} else {
68+
// Unknown format, just display hex dump
69+
Serial.print("Raw data: ");
70+
for (size_t i = 0; i < length; i++) {
71+
Serial.printf("%02X ", pData[i]);
72+
}
73+
Serial.println();
74+
}
75+
}
76+
77+
// Client callbacks to handle connection events
78+
class MyClientCallback : public BLEClientCallbacks {
79+
void onConnect(BLEClient *pclient) {
80+
Serial.println("Connected to gamepad");
81+
}
82+
83+
void onDisconnect(BLEClient *pclient) {
84+
connected = false;
85+
Serial.println("Disconnected from gamepad");
86+
}
87+
};
88+
89+
// Function to connect to the gamepad
90+
bool connectToServer() {
91+
Serial.print("Connecting to gamepad at ");
92+
Serial.println(myDevice->getAddress().toString().c_str());
93+
94+
pClient = BLEDevice::createClient();
95+
Serial.println(" - Created client");
96+
97+
pClient->setClientCallbacks(new MyClientCallback());
98+
99+
// Connect to the gamepad
100+
pClient->connect(myDevice);
101+
Serial.println(" - Connected to server");
102+
pClient->setMTU(185); // Set MTU for larger data transfers
103+
104+
// Obtain a reference to the HID service
105+
BLERemoteService *pRemoteService = pClient->getService(hidServiceUUID);
106+
if (pRemoteService == nullptr) {
107+
Serial.println("Failed to find HID service");
108+
pClient->disconnect();
109+
return false;
110+
}
111+
Serial.println(" - Found HID service");
112+
113+
// Get all characteristics to find input reports
114+
std::map<std::string, BLERemoteCharacteristic *> *pCharMap = pRemoteService->getCharacteristics();
115+
116+
// Look for input report characteristics (UUID 0x2A4D)
117+
for (auto const &entry : *pCharMap) {
118+
BLERemoteCharacteristic *pChar = entry.second;
119+
120+
if (pChar->getUUID().equals(reportCharUUID)) {
121+
// Check if this characteristic has notify property (input report)
122+
if (pChar->canNotify()) {
123+
Serial.printf(" - Found input report characteristic (handle: 0x%04X)\n", pChar->getHandle());
124+
125+
// Try to read Report Reference Descriptor to identify report type and ID
126+
BLERemoteDescriptor *pReportRefDesc = pChar->getDescriptor(BLEUUID((uint16_t)0x2908));
127+
if (pReportRefDesc != nullptr) {
128+
String refValue = pReportRefDesc->readValue();
129+
if (refValue.length() >= 2) {
130+
uint8_t reportId = refValue[0];
131+
uint8_t reportType = refValue[1];
132+
Serial.printf(" Report ID: %d, Type: %d (1=Input, 2=Output, 3=Feature)\n", reportId, reportType);
133+
134+
// We want input reports (type = 1)
135+
if (reportType == 1) {
136+
pInputReportCharacteristic = pChar;
137+
}
138+
}
139+
} else {
140+
// No report reference descriptor, assume it's an input report
141+
pInputReportCharacteristic = pChar;
142+
}
143+
}
144+
}
145+
}
146+
147+
if (pInputReportCharacteristic == nullptr) {
148+
Serial.println("Failed to find input report characteristic");
149+
pClient->disconnect();
150+
return false;
151+
}
152+
153+
// Subscribe to input report notifications
154+
Serial.println(" - Subscribing to input report notifications");
155+
pInputReportCharacteristic->registerForNotify(notifyCallback);
156+
157+
connected = true;
158+
Serial.println("Successfully connected and subscribed to gamepad!");
159+
return true;
160+
}
161+
162+
// Scan callback to detect gamepad devices
163+
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
164+
void onResult(BLEAdvertisedDevice advertisedDevice) {
165+
Serial.print("BLE Device found: ");
166+
Serial.print(advertisedDevice.toString().c_str());
167+
168+
// Check if device advertises HID service
169+
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(hidServiceUUID)) {
170+
Serial.print(" - HID Device!");
171+
172+
// Check if it's a gamepad by appearance (0x03C4 = HID Gamepad)
173+
if (advertisedDevice.haveAppearance()) {
174+
uint16_t appearance = advertisedDevice.getAppearance();
175+
Serial.printf(" (Appearance: 0x%04X)", appearance);
176+
if (appearance == 0x03C4) {
177+
Serial.print(" - GAMEPAD!");
178+
}
179+
}
180+
Serial.println();
181+
182+
// Stop scanning and connect
183+
BLEDevice::getScan()->stop();
184+
myDevice = new BLEAdvertisedDevice(advertisedDevice);
185+
doConnect = true;
186+
doScan = true;
187+
} else {
188+
Serial.println();
189+
}
190+
}
191+
};
192+
193+
void setup() {
194+
Serial.begin(115200);
195+
Serial.println("\n=== BLE HID Gamepad Client ===");
196+
Serial.println("Scanning for BLE HID gamepads...\n");
197+
198+
BLEDevice::init("ESP32-Gamepad-Client");
199+
200+
// Create scanner and set callbacks
201+
BLEScan *pBLEScan = BLEDevice::getScan();
202+
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
203+
pBLEScan->setInterval(1349);
204+
pBLEScan->setWindow(449);
205+
pBLEScan->setActiveScan(true);
206+
pBLEScan->start(5, false);
207+
}
208+
209+
void loop() {
210+
// Connect to gamepad if found
211+
if (doConnect == true) {
212+
if (connectToServer()) {
213+
Serial.println("\n*** Ready to receive gamepad input ***\n");
214+
} else {
215+
Serial.println("Failed to connect to gamepad");
216+
}
217+
doConnect = false;
218+
}
219+
220+
// Restart scanning if disconnected
221+
if (!connected && doScan) {
222+
Serial.println("\nScanning for gamepads...");
223+
BLEDevice::getScan()->start(5, false);
224+
delay(1000);
225+
}
226+
227+
delay(100);
228+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fqbn_append: PartitionScheme=huge_app
2+
3+
requires_any:
4+
- CONFIG_SOC_BLE_SUPPORTED=y
5+
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y

0 commit comments

Comments
 (0)