Add BLE HID output, Windows fixes and status LED states #1

Merged
selu merged 8 commits from ble-hid into master 2026-10-28 14:04:07 +00:00
Owner

Summary

BLE HID output to the TV, plus the groundwork and fixes found while testing it against Windows.

  • Clippy pedantic lints enabled across the workspace and firmware, warnings fixed.
  • BLE HID over NimBLE (SPEC.md §8 Phase 3): advertises as "IR Bridge", bonds Just Works, and sends keyboard and consumer-control reports. irbt status shows the connection and an event goes to the USB host.
  • Windows fixes:
    • The GAP Device Name is now set, so hosts no longer show "nimble" after connecting.
    • Connection state comes from the encryption-complete callback. On ESP-IDF 5.5, esp32-nimble skips on_connect when the delayed CONNECT event has a non-zero feature-read status, so status stayed "not connected".
    • press() checks the report's subscriber list instead of connected_count, which missed the same connections and dropped every key.
  • Status LED: blinks blue twice a second while waiting for a host, stays dim green once connected, and flashes purple once for each key sent.
  • README: dev setup, build, flashing, usage, and the LED meanings.

Testing

  • Tested against Windows: pairing, the device name, irbt status, HID keys through irbt send-hid and the IR remote, and all LED states.
  • Not yet tested against the TV.

🤖 Generated with Claude Code

## Summary BLE HID output to the TV, plus the groundwork and fixes found while testing it against Windows. - **Clippy pedantic** lints enabled across the workspace and firmware, warnings fixed. - **BLE HID over NimBLE** (SPEC.md §8 Phase 3): advertises as "IR Bridge", bonds Just Works, and sends keyboard and consumer-control reports. `irbt status` shows the connection and an event goes to the USB host. - **Windows fixes:** - The GAP Device Name is now set, so hosts no longer show "nimble" after connecting. - Connection state comes from the encryption-complete callback. On ESP-IDF 5.5, esp32-nimble skips `on_connect` when the delayed CONNECT event has a non-zero feature-read status, so status stayed "not connected". - `press()` checks the report's subscriber list instead of `connected_count`, which missed the same connections and dropped every key. - **Status LED:** blinks blue twice a second while waiting for a host, stays dim green once connected, and flashes purple once for each key sent. - **README:** dev setup, build, flashing, usage, and the LED meanings. ## Testing - Tested against Windows: pairing, the device name, `irbt status`, HID keys through `irbt send-hid` and the IR remote, and all LED states. - Not yet tested against the TV. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Root workspace: `[workspace.lints.clippy] pedantic = warn`, inherited
  by proto, irbt and irbt-cli. The firmware is excluded from that
  workspace, so it carries its own copy of the setting.
- Replace truncating/sign-losing `as` casts with `From`/`try_from`
  (keymap counts go through a small `count()` helper in control.rs;
  uptime saturates instead of wrapping).
- `Control::handle` takes `&Command`.
- Fill in `# Errors`/`# Panics` docs on the irbt public API, add
  `#[must_use]` where clippy asks, and fix doc markdown nits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the logging placeholder in hid_loop with a real BLE HID
peripheral (SPEC.md §8 Phase 3).

- bt_hid.rs: advertises as "IR Bridge" (appearance 0x0180, HID service),
  bonds Just Works (NoInputNoOutput, bond+MITM+SC) and sends a boot-style
  keyboard report (ID 1) for navigation keys and a 16-bit consumer
  control report (ID 2) for system/media keys, as press, 20ms, release.
  Advertising restarts on disconnect.
- Control tracks the connection: Status.ble_connected is live and
  Event::BleConnection is sent to the USB host on connect/disconnect.
- esp32-nimble 0.12 (matches esp-idf-svc 0.52; 0.13 needs 0.53).
- sdkconfig: enable NimBLE with NVS_PERSIST so bonds survive reboots.

Builds; not yet tested against a TV.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Covers installing the esp toolchain (espup, espflash, ldproxy), the
RUSTUP_TOOLCHAIN override pitfall, building host tools and firmware,
CI-equivalent checks, flashing with cargo run / espflash, using irbt
instead of espflash monitor, and serial access on Linux and WSL2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Set the GAP Device Name (code and sdkconfig) to "IR Bridge": it was
  only in the advertising data, so hosts showed NimBLE's default
  "nimble" after connecting. GAP appearance now matches too (0x0180).
- Report "connected" from the encryption-complete callback instead of
  on_connect. On ESP-IDF 5.5 NimBLE sends CONNECT only after reading
  the peer's features, carrying that read's status; esp32-nimble skips
  on_connect and its connection list on a non-zero status even though
  the link is up. Windows hits this, so status stayed "not connected".
- press() checks the report's subscriber list instead of
  BLEServer::connected_count, which had the same blind spot and
  dropped every key.

Tested against Windows: name, status and HID keys work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Control::ble_connected() exposes the connection flag, used by both the
LED loop and GetStatus. The colour is chosen at the start of each blink.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Not connected: dark blue blink, now a 500ms cycle (was 2s).
- Connected: steady dim green (0, 15, 0) instead of blinking; dimmer
  than before since it stays on.
- The connection is re-checked every 250ms toggle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- BtHid::press returns whether the key went out (false when no host
  has subscribed to its report).
- hid_loop signals the LED thread over a channel for each sent key;
  the LED loop waits on it instead of sleeping, so the 80ms purple
  flash starts immediately. Keys queued during a flash (held buttons)
  are merged into one flash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Document the status LED meanings in the README
Some checks failed
Continuous Integration / Firmware-1 (pull_request) Failing after 27s
Continuous Integration / Firmware (pull_request) Failing after 27s
Continuous Integration / Firmware-2 (pull_request) Failing after 3s
Continuous Integration / Host tools (pull_request) Failing after 3s
Continuous Integration / Host tools-2 (pull_request) Failing after 3s
Continuous Integration / Host tools-1 (pull_request) Failing after 3s
62e1a81c47
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
selu merged commit 2cad127d7e into master 2026-10-28 14:04:07 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
selu/ir-bt-bridge!1
No description provided.