Library / SDK
lemmingDev/ESP32-BLE-Gamepad avatar
lemmingDev/ESP32-BLE-Gamepad

ESP32-BLE-Gamepad: Generic HID, SInput, and XInput Modes

Bluetooth LE Gamepad library for the ESP32

1,603 stars259 forksC++NOASSERTION

At a glance

What is it?
ESP32-BLE-Gamepad turns a BLE-capable Espressif microcontroller into a configurable game controller. Its three modes cover standard HID applications, SDL3-native input, and Windows-oriented XInput emulation, but host recognition depends heavily on the selected mode.
Who is it for?
Adopt ESP32-BLE-Gamepad when an ESP32 project needs BLE controller output and the target host's recognition model is known. Do not choose SInput or XInput as interchangeable emulators or override their fixed identifiers.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 33 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why ESP32-BLE-Gamepad Has Three Controller Modes

ESP32-BLE-Gamepad solves a specific embedded input problem: creating a Bluetooth LE controller from an Espressif MCU without writing the HID report descriptors, connection handling, configuration, and host-facing control logic from scratch. It is aimed at Arduino and PlatformIO developers building handheld controls, motion controllers, driving or flight inputs, and prototypes that need to appear as a gamepad to software or an operating system.

The library exposes three operating modes because a BLE HID controller is not identified by button data alone. Generic mode publishes a conventional HID gamepad and offers the most control over buttons, axes, and vendor and product identifiers. It is the reasonable starting point for a custom application or an operating system that accepts standard HID input. The documented maximum is 128 buttons, two configurable thumbstick axes, two analog triggers, and up to four hat-switch directions.

SInput mode targets software that understands SDL3 controller conventions. The README associates it with SDL3 games, Steam, and applications using `SDL_GameController`. In this mode, button capacity drops to 25, but the protocol exposes two sticks, analog triggers, motion data, one or two touchpads, rumble, player LED, and a 24-bit RGB value. SDL3 3.4 or later is listed for Linux compatibility. Host recognition relies on the fixed `0x2E8A` vendor and `0x10C6` product identifiers.

XInput mode is narrower. It presents 11 familiar Xbox-style buttons, two sticks, two triggers, and one D-pad, with rumble mapped to strong and weak motors plus trigger vibration. Its identifiers are fixed as `0x045E:0x02FD` or `0x0B13`. For Windows, the README specifically recommends `GamepadMode::XInputSeriesX`, whose `0x0B13` product ID represents the Share button variant and supports native XInput over BLE on current Windows releases. This mode is not the correct choice merely because a game uses Xbox-style controls. Host recognition depends on its descriptor and identifier contract.

Report Flow, Connection State, and BLE Constraints

Based on the repository layout and examples, `BleGamepad` is the application-facing controller object, while separate files such as `BleConnectionStatus`, `BleFeatureReport`, `BleOutputReceiver`, `BleSInput`, and `BleXInput` divide connection state and mode-specific protocol work. The example flow is event driven from the sketch's perspective. Code calls `begin()` once during setup, waits for `isConnected()`, mutates the controller state, and then calls `sendReport()`.

Generic input is sent through the normal HID report path. Button calls such as `press(BUTTON_1)` and `release(BUTTON_1)` change controller state before a report is transmitted. Analog values and individual axes can be set independently through the API shown by the Generic examples, although the README does not document every setter signature in its Quick Start. The repository also includes a motion controller example and HID feature-report examples, showing that Generic mode can extend beyond ordinary button polling.

Host-to-device feedback is separate from input transmission. In SInput mode, the sketch polls `isRumbleReceived()` and then reads weak and strong amplitudes. XInput uses `isXInputRumbleReceived()`, `getXInputStrongMotor()`, and `getXInputWeakMotor()`. These APIs tell the application that an output report has arrived; they do not mean the physical device has vibration hardware. An ESP32 board still needs suitable actuators and power circuitry to produce rumble.

BLE capability is an absolute board constraint. The CI files compile and test ESP32, ESP32-S3, ESP32-C3, and ESP32-C6 targets. The library says it has no chip-specific code and may work on other BLE-capable variants, but ESP32-C2 and ESP32-H2 are not covered by CI. Plain ESP32-S2 cannot run it because that variant has no Bluetooth radio. This distinction matters before spending time on code: BLE support cannot be added by changing a board setting.

Arduino Installation and a First Generic Controller

The README directs Arduino IDE users to install support for the ESP32, download the latest ESP32-BLE-Gamepad release, add that archive through `Sketch` > `Include Library` > `Add .ZIP Library...`, and install `NimBLE-Arduino` by h2zero from `Tools` > `Manage Libraries...`. `NuS-NimBLE-Serial` by afpineda is optional and applies only to sketches under `examples/NuS/`.

After installation, select an ESP32 BLE Gamepad example from the Arduino IDE's Examples menu. This first sketch uses the default Generic mode and initializes 16 buttons, all axes, and one hat switch. It only sends input after the BLE connection is present.

cpp
#include <BleGamepad.h>

BleGamepad bleGamepad;

void setup() {
  bleGamepad.begin();
}

void loop() {
  if (bleGamepad.isConnected()) {
    bleGamepad.press(BUTTON_1);
    bleGamepad.sendReport();
    delay(500);
    bleGamepad.release(BUTTON_1);
    bleGamepad.sendReport();
    delay(500);
  }
}

The expected observable action is a host-side button press followed by a release, with a 500 millisecond delay after each report. If the controller never connects, check the physical radio variant first. If it connects but the application does not map it correctly, move on to descriptor mode and host compatibility rather than repeatedly changing the button calls.

PlatformIO users do not download the library in the same way. The README says to add `h2zero/NimBLE-Arduino` to `lib_deps` and `esp32:esp32` to the platform. It does not provide a complete `platformio.ini`, so no extra version constraints or configuration keys can be inferred.

SInput Feedback Through SDL3-Aware Hosts

SInput mode is the feature-oriented alternative within ESP32-BLE-Gamepad. It should be selected when the receiving software understands SDL's controller model and the project needs more than plain HID input. The README documents rumble, player LED, RGB, IMU, and touchpad support, but those are host and hardware obligations as well as protocol capabilities. A board without motion sensors cannot produce useful IMU readings merely by enabling the mode.

The documented setup creates a separate configuration object, selects `GamepadMode::SInput`, enables rumble, and passes the configuration to `begin()`.

cpp
#include <BleGamepad.h>

BleGamepad bleGamepad;
BleGamepadConfiguration config;

void setup() {
  config.setGamepadMode(GamepadMode::SInput);
  config.setEnableRumble(true);
  bleGamepad.begin(&config);
}

Once connected, the sketch sends a button report and checks for returned rumble data. The two amplitude values are printed only when `isRumbleReceived()` reports a message, which avoids treating the previous output state as a new host command.

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

Do not call `setVid()` or `setPid()` in this mode. The README states that SDL recognition depends on `0x2E8A:0x10C6`; changing either value can silently defeat driver recognition. Generic mode is the correct place for custom identifiers.

XInput Selection and Windows Recognition Boundaries

XInput mode differs from SInput mainly in its host contract. It emulates an Xbox controller for software and operating systems that recognize the expected descriptor and fixed identity. The README lists native Windows XInput support, Linux support through the `xpad` driver, and macOS recognition through `GCController` with native Xbox support since Big Sur. Android is not listed as compatible for XInput.

A Windows project should select `GamepadMode::XInputSeriesX`. Its `0x0B13` product ID is associated with the Share button controller and is recommended for native BLE XInput on Windows. The alternate `XInputOneS` mode uses `0x02FD`, but the README warns that Windows 11 22H2 and later can expose it as Generic HID because the current Windows Gaming Input path recognizes Series X while broader `xpad` compatibility is associated with older releases.

The setup and feedback path are explicit:

cpp
#include <BleGamepad.h>

BleGamepad bleGamepad;
BleGamepadConfiguration config;

void setup() {
  config.setGamepadMode(GamepadMode::XInputSeriesX);
  bleGamepad.begin(&config);
}

The loop presses `BUTTON_1`, described by the README as A, sends the report, and checks for motor feedback.

cpp
if (bleGamepad.isConnected()) {
  bleGamepad.press(BUTTON_1);
  bleGamepad.sendReport();

  if (bleGamepad.isXInputRumbleReceived()) {
    Serial.printf("Rumble: strong=%d weak=%d\n",
                  bleGamepad.getXInputStrongMotor(),
                  bleGamepad.getXInputWeakMotor());
  }
  delay(10);
}

As with SInput, the fixed `0x045E` vendor and selected product identifier must not be overridden. The failure mode here is not poor button timing. It is a host refusing to bind the expected controller driver because the project changed the identity that triggers that driver.

Release State, Custom Licence, and Upgrade Work

The repository is not archived, and its last push was on 2026-09-21. Releases include v0.8.0 on 2026-09-17, v0.7.5 on 2026-09-06, and v0.7.5-rc0 on 2026-08-27. This shows current repository activity, but it does not remove integration work from an upgrade. Mode selection, host recognition, and fixed identifiers should be checked against the target application after changing releases.

The dependency surface is comparatively constrained. The library depends on NimBLE-Arduino, and the README states that it contains no chip-specific code. That design may simplify porting between BLE-capable variants, but it also places confidence in untested combinations. ESP32-C2 and ESP32-H2 are described as likely, not CI-verified, so their status should not be presented as supported without qualification.

The repository identifies its licence as a custom licence under `license.txt`. The README does not reproduce its terms, so redistribution rights, modification rights, and obligations cannot be summarized as permissions. Anyone incorporating the source into a product must read `license.txt` directly. That is a compliance check, not legal advice.

Compared with a platform-neutral Bluetooth controller library, ESP32-BLE-Gamepad's main advantage is that it packages Generic HID, SInput, and XInput handling behind one ESP32-oriented API. The trade-off is that host identity is part of the design. A project cannot freely change identifiers or assume all modes will be mapped by the same software.

Editorial conclusion

Adopt ESP32-BLE-Gamepad when an ESP32 project needs BLE controller output and the target host's recognition model is known. Do not choose SInput or XInput as interchangeable emulators or override their fixed identifiers. Verify first that the selected ESP32 variant has BLE support, that the target application expects the corresponding device type, and that the Arduino or PlatformIO build resolves NimBLE-Arduino correctly.

Frequently asked questions

Which ESP32-BLE-Gamepad mode should Windows projects use?

The README recommends `GamepadMode::XInputSeriesX`, which uses PID `0x0B13` for native XInput over BLE on current Windows releases. It warns that `XInputOneS` with PID `0x02FD` can appear as Generic HID on Windows 11 22H2 or later.

Can ESP32-BLE-Gamepad run on every ESP32 board?

No. Plain ESP32-S2 has no Bluetooth radio, so it cannot run the library. ESP32, ESP32-S3, ESP32-C3, and ESP32-C6 are compile-tested, while ESP32-C2 and ESP32-H2 are described as likely but are not covered by CI.

Can setVid() or setPid() be changed in SInput and XInput modes?

No. SInput relies on fixed identifiers `0x2E8A:0x10C6`, while XInput uses `0x045E` with a fixed supported product ID. The README says these values drive host recognition and should not be overridden outside Generic mode.

Official sources

  1. Issues
  2. lemmingDev/ESP32-BLE-Gamepad on GitHub
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lemmingdev-esp32-ble-gamepad.svg)](https://hysenlabs.com/projects/lemmingdev-esp32-ble-gamepad)

Was this useful?