https://github.com/dmatking/esp32-ble-kbd-host
BLE HID keyboard host for ESP-IDF (NimBLE) with reconnect, re-pair, and callback API
https://github.com/dmatking/esp32-ble-kbd-host
Last synced: 11 days ago
JSON representation
BLE HID keyboard host for ESP-IDF (NimBLE) with reconnect, re-pair, and callback API
- Host: GitHub
- URL: https://github.com/dmatking/esp32-ble-kbd-host
- Owner: dmatking
- License: other
- Created: 2026-04-14T23:37:29.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-05-16T01:31:15.000Z (28 days ago)
- Last Synced: 2026-05-16T03:43:20.421Z (28 days ago)
- Language: C
- Size: 86.9 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# esp32-ble-kbd-host
BLE HID keyboard host component for ESP-IDF (NimBLE stack).
Handles pairing, reconnection, and key decoding with a clean callback API.
No display or GPIO coupling — works headless or with any UI layer.
## Features
- **Automatic reconnection** — reconnects to the last bonded keyboard without re-pairing
- **Reliable re-pair** — `ble_kbd_host_start_pairing()` escapes from any state, including
the reconnect loop (fixes a common watchdog bug where BOOT hold was ignored mid-retry)
- **Single persistent task** — one `kbd_main_task` state machine replaces the
scan-task → watchdog hand-off pattern that caused missed BOOT presses
- **Callback API** — on_key, on_status, on_passkey, on_scan_updated; no queue returned,
no display pointer, no GPIO in the component
- **Bundled modified esp_hid** — includes `esp_ble_hidh_dev_reconnect()` (lightweight
reconnect without full GATT rediscovery), passkey handler, enc-change handler,
device kept alive on disconnect
- **ESP32-P4 support** — BLE via ESP32-C6 co-processor through `esp_hosted` / vHCI
## Quick start
In your project's `main/idf_component.yml`:
```yaml
dependencies:
dmatking/esp32-ble-kbd-host: ">=1.0.0"
```
For local development, use a path dependency instead:
```yaml
dependencies:
esp32-ble-kbd-host:
path: /path/to/esp32-ble-kbd-host
```
```c
#include "esp_ble_kbd_host.h"
static QueueHandle_t s_key_queue;
static void on_key(char ascii, void *ctx) {
if (ascii == '\0') return; // synthetic wakeup — discard
xQueueSend(s_key_queue, &ascii, 0);
}
static void on_status(ble_kbd_state_t state,
const char *line1, const char *line2, void *ctx) {
ESP_LOGI("kbd", "state %d: %s / %s", state, line1, line2);
}
void app_main(void) {
s_key_queue = xQueueCreate(64, sizeof(char));
ble_kbd_host_config_t cfg = {
.callbacks = {
.on_key = on_key,
.on_status = on_status,
.ctx = NULL,
},
};
ESP_ERROR_CHECK(ble_kbd_host_init(&cfg));
// Block until keyboard connects (optional)
ble_kbd_host_wait_connected();
char c;
while (1) {
if (xQueueReceive(s_key_queue, &c, portMAX_DELAY)) {
printf("key: %c\n", c);
}
}
}
```
## API
```c
// Initialise — starts the internal kbd_main_task
esp_err_t ble_kbd_host_init(const ble_kbd_host_config_t *cfg);
// Trigger re-pair from any state (safe to call from any task)
void ble_kbd_host_start_pairing(void);
// Select device by index during SCANNING state
void ble_kbd_host_select_device(int index);
// Query state
bool ble_kbd_host_is_connected(void);
ble_kbd_state_t ble_kbd_host_get_state(void);
// Block until CONNECTED (useful in app_main before starting SSH etc.)
void ble_kbd_host_wait_connected(void);
```
## Config (`menuconfig`)
| Key | Default | Description |
|-----|---------|-------------|
| `BLE_KBD_HOST_TASK_STACK` | 8192 | kbd_main_task stack bytes |
| `BLE_KBD_HOST_TASK_PRIORITY` | 2 | kbd_main_task priority |
| `BLE_KBD_HOST_RECONNECT_INTERVAL_MS` | 3000 | Delay between reconnect attempts |
| `BLE_KBD_HOST_SCAN_TIMEOUT_MS` | 20000 | BLE scan window before restart |
## State machine
```
IDLE
└─ has bonds? ──yes──► RECONNECTING ─── reconnect succeeds ───► CONNECTED
│ │ │
│ └── CMD_START_PAIRING ──► │
no │ disconnect
│ ▼ │
└─────────────────────────────────► SCANNING │
│ │
device selected │
▼ │
CONNECTING │
│ │
open succeeds │
└──────────────┘
```
`CMD_START_PAIRING` (from `ble_kbd_host_start_pairing()`) clears bonds and forces
a scan regardless of current state. This is what makes BOOT hold reliable from
the RECONNECTING state — the previous watchdog implementation polled BOOT only in
the scan loop, so holding BOOT while retrying a reconnect had no effect.
## License
Apache-2.0. Bundled `esp_hid/` sources are from Espressif Systems
(Unlicense / Apache-2.0); see file headers for details.