Esp32 as BT Hid Gamepad
Posted: Sun Mar 23, 2025 6:16 pm
i have tried for a couple of days to get esp32 BT Hid to work but it seems im missing something but i cant quite figure out what. It connects and shows up as a game controller in device manager however pressing buttons / simulating a button press does nothing. Windows doesnt even register it or hid input report could somehow be rejected or ignored. From verbose debug level you can see esp32 sending the report but not sure whats happening after.
if someone can provide a basic BT hid gamepad example or help fix this for Arduino IDE . Id appreciate it. I also know esp idf with vscode has bt hid examples but its only for keyboard, mouse and remote and but still dont know what im missing. Plus im more experienced using arduino ide and wanna learn how to setup bt without relying on a premade solution like the Ble Gamepad library which can break changes at anytime.
the code to reproduce the same issue.
if someone can provide a basic BT hid gamepad example or help fix this for Arduino IDE . Id appreciate it. I also know esp idf with vscode has bt hid examples but its only for keyboard, mouse and remote and but still dont know what im missing. Plus im more experienced using arduino ide and wanna learn how to setup bt without relying on a premade solution like the Ble Gamepad library which can break changes at anytime.
the code to reproduce the same issue.
Code: Select all
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
#include "BLEHIDDevice.h"
#include "HIDTypes.h"
BLEHIDDevice* hid;
BLECharacteristic* inputReport;
BLEServer* server;
BLEAdvertising* advertising;
#define CHARACTERISTIC_UUID_MODEL_NUMBER "2A24" // Characteristic - Model Number String - 0x2A24
#define CHARACTERISTIC_UUID_SOFTWARE_REVISION "2A28" // Characteristic - Software Revision String - 0x2A28
#define CHARACTERISTIC_UUID_SERIAL_NUMBER "2A25" // Characteristic - Serial Number String - 0x2A25
#define CHARACTERISTIC_UUID_FIRMWARE_REVISION "2A26" // Characteristic - Firmware Revision String - 0x2A26
#define CHARACTERISTIC_UUID_HARDWARE_REVISION "2A27" // Characteristic - Hardware Revision String - 0x2A27
uint8_t reportData[3] = { 0, 0, 0};
static uint8_t reportMapSimple[] = {
// Usage Page (Generic Desktop Controls)
0x05, 0x01,
// Usage (Gamepad)
0x09, 0x05,
// Collection (Application)
0xA1, 0x01,
0x85, 0x01, // Report ID 1
// 16 Buttons (2 bytes)
0x19, 0x01, // Usage Minimum (Button 1)
0x29, 0x10, // Usage Maximum (Button 16)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1 bit per button)
0x95, 0x10, // Report Count (16 buttons)
0x81, 0x02, // Input (Data, Variable, Absolute)
0xC0,
};
bool deviceConnected = false;
// Handles Windows BLE HID Disconnection
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println(" Connected!");
}
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("Disconnected! Restarting Advertising...");
advertising->start(); // Restart BLE Advertising
}
};
void taskServer(void*) {
vTaskDelay(1000 / portTICK_PERIOD_MS); // Prevent race conditions
BLEDevice::init("X1 Controller");
server = BLEDevice::createServer();
server->setCallbacks(new MyServerCallbacks());
hid = new BLEHIDDevice(server);
inputReport = hid->inputReport(1);
BLECharacteristic* pCharacteristic_Software_Revision = hid->deviceInfo()->createCharacteristic(
CHARACTERISTIC_UUID_SOFTWARE_REVISION,
BLECharacteristic::PROPERTY_READ
);
pCharacteristic_Software_Revision->setValue("1.0b");
BLECharacteristic* pCharacteristic_Serial_Number = hid->deviceInfo()->createCharacteristic(
CHARACTERISTIC_UUID_SERIAL_NUMBER,
BLECharacteristic::PROPERTY_READ
);
pCharacteristic_Serial_Number->setValue("1234567");
BLECharacteristic* pCharacteristic_Firmware_Revision = hid->deviceInfo()->createCharacteristic(
CHARACTERISTIC_UUID_FIRMWARE_REVISION,
BLECharacteristic::PROPERTY_READ
);
pCharacteristic_Firmware_Revision->setValue("1.0");
BLECharacteristic* pCharacteristic_Hardware_Revision = hid->deviceInfo()->createCharacteristic(
CHARACTERISTIC_UUID_HARDWARE_REVISION,
BLECharacteristic::PROPERTY_READ
);
pCharacteristic_Hardware_Revision->setValue("1.0");
//inputReport->addDescriptor(xBLE2902); // Client Configuration Descriptor
inputReport->setNotifyProperty(true);
inputReport->setReadProperty(true);
inputReport->setWriteProperty(true);
hid->manufacturer()->setValue("Test");
hid->pnp(0x04, 0xe502, 0xa111, 0x0110); // Vendor ID, Product ID
hid->hidInfo(0x00, 0x01); // HID version
//int mode = 1;
// hid->protocolMode()->setValue(mode);
hid->reportMap((uint8_t*)reportMapSimple, sizeof(reportMapSimple));
hid->hidService()->start();
hid->deviceInfo()->start();
hid->startServices();
hid->setBatteryLevel(100); // Fixes Windows HID Driver Issue (Driver Error)
// Initialize the security
BLESecurity* pSecurity = new BLESecurity();
pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND); // Set the bonding mode (pairing required)
pSecurity->setCapability(ESP_IO_CAP_NONE); // No input/output capabilities (use Just Works)
advertising = server->getAdvertising();
advertising->setAppearance(HID_GAMEPAD);
advertising->addServiceUUID(hid->deviceInfo()->getUUID());
advertising->addServiceUUID(hid->hidService()->getUUID());
advertising->start();
Serial.println(" Gamepad Ready. Scan for Controller'");
while (true) {
vTaskDelay(10);
}
}
// Sends Gamepad Data
void sendGamepadReport() {
if (deviceConnected && inputReport) {
reportData[0] = 0x1; //Input report
inputReport->setValue(reportData, sizeof(reportData));
inputReport->notify(true);
/*uint8_t *cccd = inputReport->getDescriptorByUUID(BLEUUID((uint16_t)0x2902))->getValue();
Serial.print("CCCD Value: ");
Serial.println(*cccd, HEX);*/
}
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.setDebugOutput(true);
xTaskCreatePinnedToCore(taskServer, "BT Server", 65536, NULL, tskIDLE_PRIORITY +1, NULL,1);
}
void loop() {
if (deviceConnected) {
// Simulate button press and joystick movement
reportData[1] ^= 1; // Toggle button press
reportData[2] = 1; // Toggle button press
sendGamepadReport(); // Send update to connected device
vTaskDelay(10);
}
else
{
vTaskDelay(200);
}
}