The Problem
Bleeding‑edge wearable devices often expose a BLE HID stream for low‑power use, but many host devices lack native support for BLE. An adapter that bridges BLE HID and UART to a wired USB HID/CDC interface removes this gap without modifying the wearable firmware.
What This Does
esp/main/main.c and ncs/app/src/main.c implement a dual‑stack firmware that runs on an ESP32‑S3 or an nRF52840. The code:
- Registers a BLE HID client (
ble_hid.c) to connect to the MouthPad’s HID service. - Tunnels the Nordic UART Service (
ble_nus.c) into a USB CDC port. - Exposes a standard HID mouse/consumer device (
ble_hid.c+main.c) and two CDC ports: one for configuration/telemetry, the other for maintenance. - Handles LED status, NeoPixel battery display, and button events (
leds.c,button.c). - Provides DFU and reset commands over the maintenance CDC port (
main.c).
All protobuf traffic is generated with NanoPB (esp/main/mouthpad-proto/nanopb/).
How It Is Wired
Entry point – esp/main/main.c (or ncs/app/src/main.c).
main()callsesp_idf_init()→ prepares the kernel and peripherals.ble_init()→ creates the BLE stack, thenble_hid_init()andble_nus_init()register GATT services and callbacks.usb_init()sets up the USB HID descriptor (inmain.c) and the two CDC interfaces.- The event loop (
app_main()) dispatches BLE events toble_hid.c/ble_nus.c, which forward payloads to the USB endpoints viausb_hid_send()andcdc_write(). - Button presses trigger
button_event()→ updates LEDs (leds.c) and may send a DFU command to the USB side. - Maintenance commands received on CDC port 1 are parsed in
main.cand invokedfu_start(),reset_bonds(), etc.
The most widely used functions are ble_hid_send() and cdc_write(), each called from both the BLE and main loops. The USB stack is the single point of failure; any change to usb_init() or the descriptor files can break compatibility with host drivers.
How To Use It
Setup
# For the nRF52840 build
cd ncs
docker-compose run --rm mouthpad-build
# Flash: copy build/app/zephyr/zephyr.uf2 to the bootloader drive
# For the ESP32‑S3 build
cd esp
source env.sh # Loads ESP‑IDF environment
make xiao # Build for the XIAO ESP32‑S3
make flash # Upload firmware
The Dockerfile and docker-compose.yml live in ncs/. The Makefile and env.sh in esp/ drive the ESP‑IDF build.
Configuration
No external config files are required; the firmware discovers the MouthPad over BLE automatically. For custom USB descriptors, edit esp/main/main.c or ncs/app/src/main.c.
Running It
After flashing the MCU, the device presents two USB interfaces: HID and two CDC ports. Use screen /dev/ttyACM1 115200 (Linux) or a serial terminal to interact with the maintenance console. The device will auto‑reconnect to a previously bonded MouthPad.
Real-World Use
In an assistive‑technology lab, the dongle is plugged into a laptop. The MouthPad sends BLE HID data; the dongle translates this into a standard USB mouse that the OS can use without extra drivers. A technician can upload new firmware via the DFU command on the maintenance CDC port.
# Trigger DFU on the ESP32‑S3
printf "dfu\n" > /dev/ttyACM1
The laptop then reboots into the bootloader, ready for a fresh flash.
Code Health & Issues
- Low – Dependencies declared without a lockfile (
esp/main/mouthpad-proto/nanopb/extra/poetry/pyproject.toml). - Extensive test suite (
971files) suggests good coverage, but CI (.github/workflows/ci.yml) is minimal. - No secrets or sensitive data are committed.
- The project is licensed under an MIT‑style license (
LICENSE).
The Bottom Line
This repository delivers a production‑grade dual‑platform bridge that turns a BLE‑only wearable into a wired USB HID device. The code is well‑structured around clear entry points (main.c), with dedicated modules for BLE, USB, and hardware abstraction. The main risk is the absence of a lockfile for the NanoPB Python dependencies, which can affect reproducibility. Engineers can adopt the firmware on either an nRF52840 or ESP32‑S3 with minimal effort, using the provided Docker or Makefile workflows.