Bluetooth LE Gamepad library for the ESP32. Supports three operating modes: Generic HID, SInput (SDL3 native), and XInput (Xbox emulation).
This library depends only on NimBLE-Arduino and has no chip-specific code, so it should work on any Espressif MCU with a BLE radio. The following are currently built and compile-tested in CI (see main.yml):
Other BLE-capable variants (e.g. ESP32-C2, ESP32-H2) are likely to work too, but aren't currently covered by CI. Note that plain ESP32-S2 has no Bluetooth radio at all, so it can't run this library.
| Feature | Generic | SInput | XInput |
|---|---|---|---|
| Buttons | 1-128 | Up to 25 (face, shoulders, stick clicks, triggers, paddles, capture, touchpad clicks, power, misc) | 11 (A/B/X/Y/LB/RB/LS/RS/Select/Start/Home) |
| Thumbsticks | 2 (configurable axes) | 2 (left/right) | 2 (left/right) |
| Triggers | 2 (analog) | 2 (analog) | 2 (analog) |
| D-pad | Up to 4 hat switches | 1 hat switch | 1 hat switch |
| Gyroscope/Accelerometer | Via motion API | Via SInput IMU | No |
| Touchpad | No | 1-2 touchpads | No |
| Rumble | Via Output Report | ERM simulation | Strong/weak motors + trigger vibration |
| Player LED | No | Yes (1-based index) | No |
| RGB LED | No | Yes (24-bit color) | No |
| Battery reporting | Yes (standard BLE) | Yes (SInput power state) | No |
| SDL3 native recognition | No | Yes | No |
| Windows XInput support | No | No | Yes |
| Linux compatibility | Yes | Yes (SDL3 3.4+) | Yes (xpad driver) |
| macOS compatibility | Yes (GCController) | Yes (GCController + SDL3) | Yes (GCController, native Xbox support since Big Sur) |
| Android compatibility | Yes (different mapping) | No | No |
| Configurable VID/PID | Yes | Fixed (0x2E8A/0x10C6) | Fixed (0x045E/0x02FD or 0x0B13) |
| Use case | Custom apps, any OS | SDL3 games, Steam | Xbox-compatible games |
SDL_GameController-aware app. Gets you native recognition, rumble, player LED, RGB, IMU, and touchpad without per-VID/PID driver support.GamepadMode::XInputSeriesX (PID 0x0B13, Share button) for native XInput over BLE — XInputOneS (PID 0x02FD) shows as Generic HID on Win11 22H2+ (WGI only allowlists Series X, One S is for xpad<6.5/broad compat, like Mystfit). Also works natively on macOS (Xbox controllers are supported since macOS Big Sur) and Linux (via xpad driver).Note on VID/PID: SInput and XInput modes automatically set a specific USB Vendor ID and Product ID that host drivers expect. Do not override
setVid()/setPid()in these modes — the host OS identifies the device by VID/PID and loads the matching driver (SDL's SInput driver for0x2E8A:0x10C6, Xbox drivers for0x045E:*). Changing the VID/PID silently breaks driver recognition.setVid()/setPid()are only intended for Generic mode, where any VID/PID is fine.
#include <BleGamepad.h>
BleGamepad bleGamepad;
void setup() {
bleGamepad.begin(); // 16 buttons, all axes, 1 hat
}
void loop() {
if (bleGamepad.isConnected()) {
bleGamepad.press(BUTTON_1);
bleGamepad.sendReport();
delay(500);
bleGamepad.release(BUTTON_1);
bleGamepad.sendReport();
delay(500);
}
}
#include <BleGamepad.h>
BleGamepad bleGamepad;
BleGamepadConfiguration config;
void setup() {
config.setGamepadMode(GamepadMode::SInput);
config.setEnableRumble(true);
bleGamepad.begin(&config);
}
void loop() {
if (bleGamepad.isConnected()) {
bleGamepad.press(BUTTON_1);
bleGamepad.sendReport();
if (bleGamepad.isRumbleReceived()) {
Serial.printf("Rumble: weak=%d strong=%d\n",
bleGamepad.getRumbleLeftAmplitude(),
bleGamepad.getRumbleRightAmplitude());
}
delay(10);
}
}
#include <BleGamepad.h>
BleGamepad bleGamepad;
BleGamepadConfiguration config;
void setup() {
config.setGamepadMode(GamepadMode::XInputSeriesX);
bleGamepad.begin(&config);
}
void loop() {
if (bleGamepad.isConnected()) {
bleGamepad.press(BUTTON_1); // A
bleGamepad.sendReport();
if (bleGamepad.isXInputRumbleReceived()) {
Serial.printf("Rumble: strong=%d weak=%d\n",
bleGamepad.getXInputStrongMotor(),
bleGamepad.getXInputWeakMotor());
}
delay(10);
}
}
examples/NuS/ sketches) Install "NuS-NimBLE-Serial" by afpineda from the Library Manager as well.PlatformIO: add h2zero/NimBLE-Arduino to your lib_deps and esp32:esp32 to your platform.
| Example | Description |
|---|---|
| Gamepad | Basic button presses and axis movement |
| IndividualAxes | Set each axis independently |
| TestAll | Exercise all features |
| FlightControllerTest | Flight controller with simulation controls |
| DrivingControllerTest | Driving controller with steering/brake/accelerator |
| MotionController | Gyroscope and accelerometer |
| PotAsAxis | Map analog pot to axis |
| SpecialButtons | Start, Select, Home, etc. |
| TestFeatureReports | HID Feature Report exchange |
| TestReceivingOutputReport | Receive HID Output Reports |
| MultipleButtons | Multiple simultaneous buttons |
| MultipleButtonsAndHats | Multiple buttons and hat switches |
| CharacteristicsConfiguration | Custom BLE characteristics |
| Keypad4x4 | 4x4 keypad as buttons |
| ForcePairingMode | Force re-pairing |
| GetPeerInfo | Query connected peer |
| SetBatteryLevel | Set battery percentage |
| SetBatteryPowerState | Set battery power state |
| SingleButton | Single button debounce |
| SingleButtonDebounce | Debounced single button |
| MultipleButtonsDebounce | Debounced multiple buttons |
| Fightstick | Fightstick layout |
| Example | Description |
|---|---|
| SInputRumble | Rumble/vibration reception |
| SInputPlayerLED | Player LED assignment |
| SInputRGB | RGB LED via discrete PWM pins |
| SInputRGB_NeoPixel | RGB LED via WS2812/NeoPixel strip |
| SInputIMU | Gyroscope and accelerometer |
| SInputTouchpad | Dual touchpad input |
| SInputBattery | Battery level reporting |
| SInputFullGamepad | All features combined |
| Example | Description |
|---|---|
| XInputOneS | Xbox One S mode with rumble |
| XInputSeriesX | Xbox Series X mode with Share button |
| XInputAllTest | Cycles every Xbox input for 1:1 host comparison |
| Example | Description |
|---|---|
| NuSSerialDiag | Connection diagnostics over BLE serial (replaces removed Diagnostics) |
| NuSGenericBridge | Drive a Generic pad from a BLE terminal (pure defaults) |
| NuSGenericAdvanced | Generic bridge + start/select specials and HID output/feature reports |
| NuSSInputBridge | Drive an SInput pad from a BLE terminal, surface player LED/rumble/RGB |
| NuSXInputBridge | Drive an Xbox pad from a BLE terminal, surface host rumble |
See examples/NuS/README.md and docs/NuSCompatibility.md.
hid-generic. Works in DirectInput-compatible games and any app using hidapi.uhid, creating /dev/hidraw*, /dev/input/js*, and /dev/input/event* nodes. Recognized by jstest, evtest, SDL, and any game using the Linux joystick or evdev subsystems.xpad kernel driver, included in most distributions. The device appears as a standard Xbox controller. Share button requires Linux 6.5+ (Series X PID).For detailed Linux testing (pairing, udev rules, hidapi, monitoring), see LinuxHIDTesting.md.
GCController (GameController framework) and IOHIDManager. Any game or emulator supporting GCController will detect it.GCController. Rumble is supported via GCController haptics. No driver needed.GCController and SDL3 (3.4+) on macOS.| macOS Version | Xbox Controller Support |
|---|---|
| Big Sur (11.0)+ | Xbox One S via Bluetooth |
| Monterey (12.0)+ | GCController framework |
| Ventura (13.0)+ | Improved mapping |
| Sonoma (14.0)+ | Rumble via GCController haptics |
| Sequoia (15.0)+ | Wired Xbox support (USB-C) |
| Tahoe (26.0)+ | Current |
Steam has built-in SDL3 support and recognizes gamepads automatically. Each mode works differently in Steam:
| Mode | Steam Recognition | What You Get |
|---|---|---|
| Generic | Detected as generic gamepad | Basic input; may need manual button mapping in Steam Input |
| SInput | Native SDL_GameController | Automatic mapping, rumble, player LED, IMU, touchpad -- no configuration needed |
| XInput | Recognized as Xbox controller | Automatic mapping on Windows/Linux; macOS via GCController |
Recommendation: Use SInput mode for Steam. It gets you native recognition with full feature support (rumble, IMU, touchpad) without per-game configuration. Steam ships with SDL3 and handles the SInput driver automatically.
If you prefer XInput mode, Steam Input maps Xbox controllers by default -- it will work, but you won't get IMU/touchpad/rumble via the SInput protocol (you'll get Xbox-style rumble instead).
Since version 3 of this library, the more efficient NimBLE library is used instead of the default BLE implementation. Please use the library manager to install it, or get it from here: https://github.com/h2zero/NimBLE-Arduino
Since version 3, this library also supports a configurable HID descriptor, which allows users to customise how the device presents itself to the OS (number of buttons, hats, axes, sliders, simulation controls etc). See the examples for guidance.
This version endeavors to be compatible with the latest released version of NimBLE-Arduino through the Arduino Library Manager.
Published under the MIT license. Please see license.txt.
It would be great however if any improvements are fed back into this version.
The examples/NuS/ sketches and docs/NuSCompatibility.md build on the third-party
NuS-NimBLE-Serial library by Ángel Fernández Pineda,
which is licensed separately under CC BY 4.0
(see the attribution notice in license.txt). That license covers the NuS library itself, not this library.
Troubleshooting guide and suggestions can be found in TroubleshootingGuide
These free tools visualize all gamepad inputs (buttons, axes, triggers, D-pad) in real time and work on Windows, macOS, and Linux:
| Tool | Platform | Source | Notes |
|---|---|---|---|
| HIDTester | Windows, macOS, Linux | Source | Lightweight, no install needed. Shows buttons, axes, D-pad, deadzone analysis, signal curves, polling rate. Built on SDL3. |
| Gamepad_Tester | Windows, macOS, Linux | Source | C++23/SDL3/ImGui. Latency measurement, polling rate analysis, rumble testing, input visualization. Pre-built binaries available. |
| gamepad-tester.net | Browser (all OS) | N/A | Browser-based, no install. Works in Chrome/Edge/Firefox. Shows all buttons/axes/triggers. |
Both HIDTester and Gamepad_Tester are built on SDL3, so they recognize SInput controllers natively (VID 0x2E8A/PID 0x10C6) and map buttons/axes correctly. The browser-based tester works with any mode but shows raw button indices instead of named buttons.
jstest, and evtest.ESP32-BLE-Gamepad-HIL is a hardware-in-the-loop test rig that automates end-to-end validation of the library. An ESP32 runs test firmware; the harness drives it over USB serial and asserts the resulting BLE HID behavior on a Linux host (typically a Raspberry Pi). It tests:
The rig uses a builder/tester split (builder needs PlatformIO; tester needs only esptool + pytest) and runs in CI via GitHub Actions + Tailscale SSH. See the HIL README for setup instructions.
For an explanation of how this library's HID Service and NUS service differ, how SDL/game engines and the Linux input stack actually reach each one, and how to extend it with features like rumble or RGB/player LEDs, see GattVsHid
This library allows you to make the ESP32 act as a Bluetooth Gamepad and control what it does. Relies on NimBLE-Arduino
For Windows testing, use HIDTester or Gamepad_Tester — both are cross-platform and don't require DirectX.
Gamepads designed for Android use a different button mapping. This affects analog triggers, where the standard left and right trigger axes are not detected. Android calls the HID report for right trigger "GAS" and left trigger "BRAKE". Enabling the "Accelerator" and "Brake" simulation controls allows them to be used instead of right and left trigger.
Right thumbstick on Windows is usually z, rz, whereas on Android, this may be z, rx, so you may want to set them separately with setZ and setRX, instead of using setRightThumb(z, rz), or use setRightThumbAndroid(z, rx)
For the most consistent behavior across Windows, macOS, and Linux, use XInput mode (emulates an Xbox controller — universally recognized by games and OSes) or SInput mode (native SDL3 support with full button/axis/rumble/RGB mapping). Generic mode relies on each OS's HID stack to interpret the descriptor, so axis ordering, trigger behavior, and button naming can vary between platforms and drivers. See XInputMode.md and SInputMode.md for setup details.
You might also be interested in:
or the NimBLE versions at
Credits to T-vK as this library is based on his ESP32-BLE-Mouse library (https://github.com/T-vK/ESP32-BLE-Mouse) that he provided.
Credits to chegewara as the ESP32-BLE-Mouse library is based on this piece of code that he provided.
Credits to wakwak-koba for the NimBLE code that he provided.
Credits to LeeNX for the initial SInput research, pull requests, extensive help with GitHub issues, and building the ESP32-BLE-Gamepad-HIL hardware-in-the-loop test rig. Their contributions were instrumental in driving the SInput implementation forward and keeping the project moving.
Credits to Mystfit for the ESP32-BLE-CompositeHID library, which served as a reference for the XInput implementation and Xbox HID descriptors.
46 followers · starred Jul 2024
219 followers · starred Feb 2026
683 followers · starred Dec 2024
27 followers · starred Sep 2021
Bluetooth LE Gamepad library for the ESP32. Supports three operating modes: Generic HID, SInput (SDL3 native), and XInput (Xbox emulation).
This library depends only on NimBLE-Arduino and has no chip-specific code, so it should work on any Espressif MCU with a BLE radio. The following are currently built and compile-tested in CI (see main.yml):
Other BLE-capable variants (e.g. ESP32-C2, ESP32-H2) are likely to work too, but aren't currently covered by CI. Note that plain ESP32-S2 has no Bluetooth radio at all, so it can't run this library.
| Feature | Generic | SInput | XInput |
|---|---|---|---|
| Buttons | 1-128 | Up to 25 (face, shoulders, stick clicks, triggers, paddles, capture, touchpad clicks, power, misc) | 11 (A/B/X/Y/LB/RB/LS/RS/Select/Start/Home) |
| Thumbsticks | 2 (configurable axes) | 2 (left/right) | 2 (left/right) |
| Triggers | 2 (analog) | 2 (analog) | 2 (analog) |
| D-pad | Up to 4 hat switches | 1 hat switch | 1 hat switch |
| Gyroscope/Accelerometer | Via motion API | Via SInput IMU | No |
| Touchpad | No | 1-2 touchpads | No |
| Rumble | Via Output Report | ERM simulation | Strong/weak motors + trigger vibration |
| Player LED | No | Yes (1-based index) | No |
| RGB LED | No | Yes (24-bit color) | No |
| Battery reporting | Yes (standard BLE) | Yes (SInput power state) | No |
| SDL3 native recognition | No | Yes | No |
| Windows XInput support | No | No | Yes |
| Linux compatibility | Yes | Yes (SDL3 3.4+) | Yes (xpad driver) |
| macOS compatibility | Yes (GCController) | Yes (GCController + SDL3) | Yes (GCController, native Xbox support since Big Sur) |
| Android compatibility | Yes (different mapping) | No | No |
| Configurable VID/PID | Yes | Fixed (0x2E8A/0x10C6) | Fixed (0x045E/0x02FD or 0x0B13) |
| Use case | Custom apps, any OS | SDL3 games, Steam | Xbox-compatible games |
SDL_GameController-aware app. Gets you native recognition, rumble, player LED, RGB, IMU, and touchpad without per-VID/PID driver support.GamepadMode::XInputSeriesX (PID 0x0B13, Share button) for native XInput over BLE — XInputOneS (PID 0x02FD) shows as Generic HID on Win11 22H2+ (WGI only allowlists Series X, One S is for xpad<6.5/broad compat, like Mystfit). Also works natively on macOS (Xbox controllers are supported since macOS Big Sur) and Linux (via xpad driver).Note on VID/PID: SInput and XInput modes automatically set a specific USB Vendor ID and Product ID that host drivers expect. Do not override
setVid()/setPid()in these modes — the host OS identifies the device by VID/PID and loads the matching driver (SDL's SInput driver for0x2E8A:0x10C6, Xbox drivers for0x045E:*). Changing the VID/PID silently breaks driver recognition.setVid()/setPid()are only intended for Generic mode, where any VID/PID is fine.
#include <BleGamepad.h>
BleGamepad bleGamepad;
void setup() {
bleGamepad.begin(); // 16 buttons, all axes, 1 hat
}
void loop() {
if (bleGamepad.isConnected()) {
bleGamepad.press(BUTTON_1);
bleGamepad.sendReport();
delay(500);
bleGamepad.release(BUTTON_1);
bleGamepad.sendReport();
delay(500);
}
}
#include <BleGamepad.h>
BleGamepad bleGamepad;
BleGamepadConfiguration config;
void setup() {
config.setGamepadMode(GamepadMode::SInput);
config.setEnableRumble(true);
bleGamepad.begin(&config);
}
void loop() {
if (bleGamepad.isConnected()) {
bleGamepad.press(BUTTON_1);
bleGamepad.sendReport();
if (bleGamepad.isRumbleReceived()) {
Serial.printf("Rumble: weak=%d strong=%d\n",
bleGamepad.getRumbleLeftAmplitude(),
bleGamepad.getRumbleRightAmplitude());
}
delay(10);
}
}
#include <BleGamepad.h>
BleGamepad bleGamepad;
BleGamepadConfiguration config;
void setup() {
config.setGamepadMode(GamepadMode::XInputSeriesX);
bleGamepad.begin(&config);
}
void loop() {
if (bleGamepad.isConnected()) {
bleGamepad.press(BUTTON_1); // A
bleGamepad.sendReport();
if (bleGamepad.isXInputRumbleReceived()) {
Serial.printf("Rumble: strong=%d weak=%d\n",
bleGamepad.getXInputStrongMotor(),
bleGamepad.getXInputWeakMotor());
}
delay(10);
}
}
examples/NuS/ sketches) Install "NuS-NimBLE-Serial" by afpineda from the Library Manager as well.PlatformIO: add h2zero/NimBLE-Arduino to your lib_deps and esp32:esp32 to your platform.
| Example | Description |
|---|---|
| Gamepad | Basic button presses and axis movement |
| IndividualAxes | Set each axis independently |
| TestAll | Exercise all features |
| FlightControllerTest | Flight controller with simulation controls |
| DrivingControllerTest | Driving controller with steering/brake/accelerator |
| MotionController | Gyroscope and accelerometer |
| PotAsAxis | Map analog pot to axis |
| SpecialButtons | Start, Select, Home, etc. |
| TestFeatureReports | HID Feature Report exchange |
| TestReceivingOutputReport | Receive HID Output Reports |
| MultipleButtons | Multiple simultaneous buttons |
| MultipleButtonsAndHats | Multiple buttons and hat switches |
| CharacteristicsConfiguration | Custom BLE characteristics |
| Keypad4x4 | 4x4 keypad as buttons |
| ForcePairingMode | Force re-pairing |
| GetPeerInfo | Query connected peer |
| SetBatteryLevel | Set battery percentage |
| SetBatteryPowerState | Set battery power state |
| SingleButton | Single button debounce |
| SingleButtonDebounce | Debounced single button |
| MultipleButtonsDebounce | Debounced multiple buttons |
| Fightstick | Fightstick layout |
| Example | Description |
|---|---|
| SInputRumble | Rumble/vibration reception |
| SInputPlayerLED | Player LED assignment |
| SInputRGB | RGB LED via discrete PWM pins |
| SInputRGB_NeoPixel | RGB LED via WS2812/NeoPixel strip |
| SInputIMU | Gyroscope and accelerometer |
| SInputTouchpad | Dual touchpad input |
| SInputBattery | Battery level reporting |
| SInputFullGamepad | All features combined |
| Example | Description |
|---|---|
| XInputOneS | Xbox One S mode with rumble |
| XInputSeriesX | Xbox Series X mode with Share button |
| XInputAllTest | Cycles every Xbox input for 1:1 host comparison |
| Example | Description |
|---|---|
| NuSSerialDiag | Connection diagnostics over BLE serial (replaces removed Diagnostics) |
| NuSGenericBridge | Drive a Generic pad from a BLE terminal (pure defaults) |
| NuSGenericAdvanced | Generic bridge + start/select specials and HID output/feature reports |
| NuSSInputBridge | Drive an SInput pad from a BLE terminal, surface player LED/rumble/RGB |
| NuSXInputBridge | Drive an Xbox pad from a BLE terminal, surface host rumble |
See examples/NuS/README.md and docs/NuSCompatibility.md.
hid-generic. Works in DirectInput-compatible games and any app using hidapi.uhid, creating /dev/hidraw*, /dev/input/js*, and /dev/input/event* nodes. Recognized by jstest, evtest, SDL, and any game using the Linux joystick or evdev subsystems.xpad kernel driver, included in most distributions. The device appears as a standard Xbox controller. Share button requires Linux 6.5+ (Series X PID).For detailed Linux testing (pairing, udev rules, hidapi, monitoring), see LinuxHIDTesting.md.
GCController (GameController framework) and IOHIDManager. Any game or emulator supporting GCController will detect it.GCController. Rumble is supported via GCController haptics. No driver needed.GCController and SDL3 (3.4+) on macOS.| macOS Version | Xbox Controller Support |
|---|---|
| Big Sur (11.0)+ | Xbox One S via Bluetooth |
| Monterey (12.0)+ | GCController framework |
| Ventura (13.0)+ | Improved mapping |
| Sonoma (14.0)+ | Rumble via GCController haptics |
| Sequoia (15.0)+ | Wired Xbox support (USB-C) |
| Tahoe (26.0)+ | Current |
Steam has built-in SDL3 support and recognizes gamepads automatically. Each mode works differently in Steam:
| Mode | Steam Recognition | What You Get |
|---|---|---|
| Generic | Detected as generic gamepad | Basic input; may need manual button mapping in Steam Input |
| SInput | Native SDL_GameController | Automatic mapping, rumble, player LED, IMU, touchpad -- no configuration needed |
| XInput | Recognized as Xbox controller | Automatic mapping on Windows/Linux; macOS via GCController |
Recommendation: Use SInput mode for Steam. It gets you native recognition with full feature support (rumble, IMU, touchpad) without per-game configuration. Steam ships with SDL3 and handles the SInput driver automatically.
If you prefer XInput mode, Steam Input maps Xbox controllers by default -- it will work, but you won't get IMU/touchpad/rumble via the SInput protocol (you'll get Xbox-style rumble instead).
Since version 3 of this library, the more efficient NimBLE library is used instead of the default BLE implementation. Please use the library manager to install it, or get it from here: https://github.com/h2zero/NimBLE-Arduino
Since version 3, this library also supports a configurable HID descriptor, which allows users to customise how the device presents itself to the OS (number of buttons, hats, axes, sliders, simulation controls etc). See the examples for guidance.
This version endeavors to be compatible with the latest released version of NimBLE-Arduino through the Arduino Library Manager.
Published under the MIT license. Please see license.txt.
It would be great however if any improvements are fed back into this version.
The examples/NuS/ sketches and docs/NuSCompatibility.md build on the third-party
NuS-NimBLE-Serial library by Ángel Fernández Pineda,
which is licensed separately under CC BY 4.0
(see the attribution notice in license.txt). That license covers the NuS library itself, not this library.
Troubleshooting guide and suggestions can be found in TroubleshootingGuide
These free tools visualize all gamepad inputs (buttons, axes, triggers, D-pad) in real time and work on Windows, macOS, and Linux:
| Tool | Platform | Source | Notes |
|---|---|---|---|
| HIDTester | Windows, macOS, Linux | Source | Lightweight, no install needed. Shows buttons, axes, D-pad, deadzone analysis, signal curves, polling rate. Built on SDL3. |
| Gamepad_Tester | Windows, macOS, Linux | Source | C++23/SDL3/ImGui. Latency measurement, polling rate analysis, rumble testing, input visualization. Pre-built binaries available. |
| gamepad-tester.net | Browser (all OS) | N/A | Browser-based, no install. Works in Chrome/Edge/Firefox. Shows all buttons/axes/triggers. |
Both HIDTester and Gamepad_Tester are built on SDL3, so they recognize SInput controllers natively (VID 0x2E8A/PID 0x10C6) and map buttons/axes correctly. The browser-based tester works with any mode but shows raw button indices instead of named buttons.
jstest, and evtest.ESP32-BLE-Gamepad-HIL is a hardware-in-the-loop test rig that automates end-to-end validation of the library. An ESP32 runs test firmware; the harness drives it over USB serial and asserts the resulting BLE HID behavior on a Linux host (typically a Raspberry Pi). It tests:
The rig uses a builder/tester split (builder needs PlatformIO; tester needs only esptool + pytest) and runs in CI via GitHub Actions + Tailscale SSH. See the HIL README for setup instructions.
For an explanation of how this library's HID Service and NUS service differ, how SDL/game engines and the Linux input stack actually reach each one, and how to extend it with features like rumble or RGB/player LEDs, see GattVsHid
This library allows you to make the ESP32 act as a Bluetooth Gamepad and control what it does. Relies on NimBLE-Arduino
For Windows testing, use HIDTester or Gamepad_Tester — both are cross-platform and don't require DirectX.
Gamepads designed for Android use a different button mapping. This affects analog triggers, where the standard left and right trigger axes are not detected. Android calls the HID report for right trigger "GAS" and left trigger "BRAKE". Enabling the "Accelerator" and "Brake" simulation controls allows them to be used instead of right and left trigger.
Right thumbstick on Windows is usually z, rz, whereas on Android, this may be z, rx, so you may want to set them separately with setZ and setRX, instead of using setRightThumb(z, rz), or use setRightThumbAndroid(z, rx)
For the most consistent behavior across Windows, macOS, and Linux, use XInput mode (emulates an Xbox controller — universally recognized by games and OSes) or SInput mode (native SDL3 support with full button/axis/rumble/RGB mapping). Generic mode relies on each OS's HID stack to interpret the descriptor, so axis ordering, trigger behavior, and button naming can vary between platforms and drivers. See XInputMode.md and SInputMode.md for setup details.
You might also be interested in:
or the NimBLE versions at
Credits to T-vK as this library is based on his ESP32-BLE-Mouse library (https://github.com/T-vK/ESP32-BLE-Mouse) that he provided.
Credits to chegewara as the ESP32-BLE-Mouse library is based on this piece of code that he provided.
Credits to wakwak-koba for the NimBLE code that he provided.
Credits to LeeNX for the initial SInput research, pull requests, extensive help with GitHub issues, and building the ESP32-BLE-Gamepad-HIL hardware-in-the-loop test rig. Their contributions were instrumental in driving the SInput implementation forward and keeping the project moving.
Credits to Mystfit for the ESP32-BLE-CompositeHID library, which served as a reference for the XInput implementation and Xbox HID descriptors.
46 followers · starred Jul 2024
219 followers · starred Feb 2026
683 followers · starred Dec 2024
27 followers · starred Sep 2021