This commit is contained in:
2026-05-22 21:52:50 +03:00
commit be7c60e4dd
1854 changed files with 583428 additions and 0 deletions
@@ -0,0 +1,253 @@
/*
* BLE HID Gamepad Client Example
*
* This example demonstrates how to connect to a BLE HID Gamepad and read its input.
* The ESP32 acts as a BLE Central (client) that connects to a BLE gamepad peripheral.
*
* Features:
* - Scans for BLE HID gamepad devices
* - Connects to the first gamepad found
* - Secure pairing with bonding
* - Subscribes to input report notifications
* - Parses and displays gamepad input (buttons and axes)
* - Automatic reconnection on disconnect
*
* Usage:
* 1. Upload this sketch to your ESP32
* 2. Turn on your BLE gamepad (or run the Server_Gamepad example on another ESP32)
* 3. The ESP32 will scan, connect, and display gamepad input in the serial monitor
*
* Note: This example uses "Just Works" pairing for automatic connection without
* PIN entry or confirmation. The bond is saved for future connections.
*
* Compatible with gamepads using the standard HID Report Descriptor format
*
* Created by lucasssvaz
*/
#include <BLEDevice.h>
#include <BLESecurity.h>
// HID Service UUID (standard UUID for HID over GATT)
static BLEUUID hidServiceUUID((uint16_t)0x1812);
// HID Report characteristic UUID (used for input/output reports)
static BLEUUID reportCharUUID((uint16_t)0x2A4D);
// HID Report Map characteristic UUID
static BLEUUID reportMapUUID((uint16_t)0x2A4B);
static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic *pInputReportCharacteristic = nullptr;
static BLEAdvertisedDevice *myDevice = nullptr;
static BLEClient *pClient = nullptr;
// Gamepad report structure (adjust based on your gamepad's report descriptor)
// This matches the Server_Gamepad example format
struct GamepadReport {
uint8_t reportId; // Report ID
int8_t x; // X axis (-127 to 127)
int8_t y; // Y axis (-127 to 127)
uint8_t buttons; // 8 buttons (bit 0-7)
} __attribute__((packed));
// Callback function to handle gamepad input notifications
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
Serial.printf("Received %d bytes: ", length);
// Check if data length matches our expected gamepad report
if (length == sizeof(GamepadReport)) {
GamepadReport *report = (GamepadReport *)pData;
Serial.printf("ID=%d, X=%4d, Y=%4d, Buttons=0x%02X [", report->reportId, report->x, report->y, report->buttons);
// Display which buttons are pressed
for (int i = 0; i < 8; i++) {
if (report->buttons & (1 << i)) {
Serial.printf("%d ", i + 1);
}
}
Serial.println("]");
} else {
// Unknown format, just display hex dump
Serial.print("Raw data: ");
for (size_t i = 0; i < length; i++) {
Serial.printf("%02X ", pData[i]);
}
Serial.println();
}
}
// Client callbacks to handle connection events
class MyClientCallback : public BLEClientCallbacks {
void onConnect(BLEClient *pclient) {
Serial.println("Connected to gamepad");
}
void onDisconnect(BLEClient *pclient) {
connected = false;
Serial.println("Disconnected from gamepad");
}
};
// Function to connect to the gamepad
bool connectToServer() {
Serial.print("Connecting to gamepad at ");
Serial.println(myDevice->getAddress().toString().c_str());
pClient = BLEDevice::createClient();
Serial.println(" - Created client");
pClient->setClientCallbacks(new MyClientCallback());
// Connect to the gamepad
pClient->connect(myDevice);
Serial.println(" - Connected to server");
pClient->setMTU(185); // Set MTU for larger data transfers
// Obtain a reference to the HID service
BLERemoteService *pRemoteService = pClient->getService(hidServiceUUID);
if (pRemoteService == nullptr) {
Serial.println("Failed to find HID service");
pClient->disconnect();
return false;
}
Serial.println(" - Found HID service");
// Get all characteristics to find input reports
std::map<std::string, BLERemoteCharacteristic *> *pCharMap = pRemoteService->getCharacteristics();
// Look for input report characteristics (UUID 0x2A4D)
for (auto const &entry : *pCharMap) {
BLERemoteCharacteristic *pChar = entry.second;
if (pChar->getUUID().equals(reportCharUUID)) {
// Check if this characteristic has notify property (input report)
if (pChar->canNotify()) {
Serial.printf(" - Found input report characteristic (handle: 0x%04X)\n", pChar->getHandle());
// Try to read Report Reference Descriptor to identify report type and ID
BLERemoteDescriptor *pReportRefDesc = pChar->getDescriptor(BLEUUID((uint16_t)0x2908));
if (pReportRefDesc != nullptr) {
String refValue = pReportRefDesc->readValue();
if (refValue.length() >= 2) {
uint8_t reportId = refValue[0];
uint8_t reportType = refValue[1];
Serial.printf(" Report ID: %d, Type: %d (1=Input, 2=Output, 3=Feature)\n", reportId, reportType);
// We want input reports (type = 1)
if (reportType == 1) {
pInputReportCharacteristic = pChar;
}
}
} else {
// No report reference descriptor, assume it's an input report
pInputReportCharacteristic = pChar;
}
}
}
}
if (pInputReportCharacteristic == nullptr) {
Serial.println("Failed to find input report characteristic");
pClient->disconnect();
return false;
}
// Subscribe to input report notifications
Serial.println(" - Subscribing to input report notifications");
pInputReportCharacteristic->registerForNotify(notifyCallback);
connected = true;
Serial.println("Successfully connected and subscribed to gamepad!");
// Note: Security/encryption will be automatically handled by the BLE stack
// when the HID device requires it (using "Just Works" pairing).
return true;
}
// Scan callback to detect gamepad devices
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Device found: ");
Serial.print(advertisedDevice.toString().c_str());
// Check if device advertises HID service
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(hidServiceUUID)) {
Serial.print(" - HID Device!");
// Check if it's a gamepad by appearance (0x03C4 = HID Gamepad)
if (advertisedDevice.haveAppearance()) {
uint16_t appearance = advertisedDevice.getAppearance();
Serial.printf(" (Appearance: 0x%04X)", appearance);
if (appearance == 0x03C4) {
Serial.print(" - GAMEPAD!");
}
}
Serial.println();
// Stop scanning and connect
BLEDevice::getScan()->stop();
myDevice = new BLEAdvertisedDevice(advertisedDevice);
doConnect = true;
doScan = true;
} else {
Serial.println();
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("\n=== BLE HID Gamepad Client ===");
Serial.println("Scanning for BLE HID gamepads...\n");
BLEDevice::init("ESP32-Gamepad-Client");
// Configure BLE Security for pairing with HID devices
BLESecurity *pSecurity = new BLESecurity();
// Set security capabilities and authentication mode
// HID devices typically use "Just Works" pairing (no MITM) with bonding
// Set IO capability to NONE for "Just Works" pairing
pSecurity->setCapability(ESP_IO_CAP_NONE);
// Bonding, no MITM, secure connections (for "Just Works" pairing)
pSecurity->setAuthenticationMode(true, false, true);
// Set security callbacks (using default implementation)
BLEDevice::setSecurityCallbacks(new BLESecurityCallbacks());
Serial.println("Security configured: Bonding + Secure Connections\n");
// Create scanner and set callbacks
BLEScan *pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setInterval(1349);
pBLEScan->setWindow(449);
pBLEScan->setActiveScan(true);
pBLEScan->start(5, false);
}
void loop() {
// Connect to gamepad if found
if (doConnect == true) {
if (connectToServer()) {
Serial.println("\n*** Ready to receive gamepad input ***\n");
} else {
Serial.println("Failed to connect to gamepad");
}
doConnect = false;
}
// Restart scanning if disconnected
if (!connected && doScan) {
Serial.println("\nScanning for gamepads...");
BLEDevice::getScan()->start(5, false);
delay(1000);
}
delay(100);
}
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y