Skip to content

Latest commit

 

History

529 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ESP32-BLE-Gamepad

Build PlatformIO

Bluetooth LE Gamepad library for the ESP32. Supports three operating modes: Generic HID, SInput (SDL3 native), and XInput (Xbox emulation).

Supported Boards

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):

  • ESP32
  • ESP32-S3
  • ESP32-C3
  • ESP32-C6

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.

Mode Comparison

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

Which mode should I use?

  • Generic -- Default. Works everywhere as a standard HID gamepad. Use this if you're building a custom app or need maximum configurability.
  • SInput -- Use this for SDL3 games, Steam, or any SDL_GameController-aware app. Gets you native recognition, rumble, player LED, RGB, IMU, and touchpad without per-VID/PID driver support.
  • XInput -- Use this for Windows games that expect an Xbox controller. Windows users should use 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 for 0x2E8A:0x10C6, Xbox drivers for 0x045E:*). Changing the VID/PID silently breaks driver recognition. setVid()/setPid() are only intended for Generic mode, where any VID/PID is fine.

Quick Start

Generic Mode (default)

#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);
  }
}

SInput Mode

#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);
  }
}

XInput Mode

#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);
  }
}

Installation

  1. Make sure you can use the ESP32 with the Arduino IDE. Instructions can be found here.
  2. Download the latest release of this library from the release page.
  3. In the Arduino IDE go to "Sketch" -> "Include Library" -> "Add .ZIP Library..." and select the file you just downloaded.
  4. In the Arduino IDE go to "Tools" -> "Manage Libraries..." -> Filter for "NimBLE-Arduino" by h2zero and install.
  5. (Optional, only for the examples/NuS/ sketches) Install "NuS-NimBLE-Serial" by afpineda from the Library Manager as well.
  6. You can now go to "File" -> "Examples" -> "ESP32 BLE Gamepad" and select an example to get started.

PlatformIO: add h2zero/NimBLE-Arduino to your lib_deps and esp32:esp32 to your platform.

Examples

Generic Examples

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

SInput Examples

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

XInput Examples

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

NuS Examples (require NuS-NimBLE-Serial)

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.

OS Compatibility

Windows

  • Generic mode: Recognized as a standard HID gamepad via hid-generic. Works in DirectInput-compatible games and any app using hidapi.
  • XInput mode: Recognized natively as an Xbox controller. Works in all XInput-compatible games (virtually every modern Windows game with controller support). Shows as "Xbox Wireless Controller" in Settings > Bluetooth & devices > Controllers.
  • SInput mode: Recognized as a HID gamepad. Works in Steam via SDL3.

Linux

  • Generic mode: BlueZ bridges the device into the kernel via 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.
  • XInput mode: Works via the xpad kernel driver, included in most distributions. The device appears as a standard Xbox controller. Share button requires Linux 6.5+ (Series X PID).
  • SInput mode: Recognized via SDL3's HIDAPI SInput driver (SDL 3.4+). Steam uses SDL3 and recognizes the device natively.

For detailed Linux testing (pairing, udev rules, hidapi, monitoring), see LinuxHIDTesting.md.

macOS

  • Generic mode: Recognized as a Bluetooth HID gamepad. Works via Apple's GCController (GameController framework) and IOHIDManager. Any game or emulator supporting GCController will detect it.
  • XInput mode: macOS natively supports Xbox Wireless Controllers with Bluetooth (since macOS Big Sur 11.0). The device appears as "Xbox Wireless Controller" and works via GCController. Rumble is supported via GCController haptics. No driver needed.
  • SInput mode: Recognized as a Bluetooth HID gamepad. Works via 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

Android

  • Generic mode only: Works as a HID gamepad. Triggers are mapped to GAS/BRAKE instead of standard trigger axes. Right thumbstick may use z/rx instead of z/rz. See GenericMode.md for details.
  • SInput and XInput modes: Not supported on Android.

Steam (All Platforms)

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).

Deep Dives

NimBLE

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.

License

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

Troubleshooting guide and suggestions can be found in TroubleshootingGuide

Testing Your Gamepad

Cross-Platform Gamepad Testers

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.

Platform-Specific Testing

  • Linux: See LinuxHIDTesting.md for testing Input/Output/Feature Reports via hidraw/hidapi, jstest, and evtest.
  • macOS: Gamepad appears in System Settings > Bluetooth. Use Chrome or Firefox for browser-based testing (Safari has partial Gamepad API support).
  • Windows: Gamepad appears in Settings > Bluetooth & devices > Controllers. Use HIDTester or Gamepad_Tester for detailed input visualization.

Hardware-in-the-Loop Testing (HIL)

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:

  • Buttons — every configured button produces one distinct evdev key event
  • Axes — each axis maps to the correct ABS code with exact min/centre/max endpoints
  • Hats — 8 directions + centre
  • HID descriptor — golden file comparison per profile
  • Device Information / PnP / Battery — GATT service validation
  • Feature / Output reports — bidirectional round-trip
  • Latency / throughput — ~18.6ms median button press, burst testing

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.

GATT vs HID-over-GATT

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

Notes

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

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.

About

Bluetooth LE Gamepad library for the ESP32

Resources

Contributing

Stars

1.6k stars

Watchers

47 watching

Forks

Releases

Packages

Contributors

Languages