Repository navigation
feat(esp32): add ESP32-S3 USB bridge backend - #201
Open
cheetahdotcat wants to merge 7 commits into
Open
cheetahdotcat wants to merge 7 commits into
cheetahdotcat wants to merge 7 commits into
Conversation
Add BackendKind::esp32_bridge. An ESP32-S3 running the bridge firmware enumerates as a composite USB device: a CDC-ACM control port plus two slots that hold an XInput pad, a HID pad (DualSense, DualShock 4, Switch Pro), or a shared keyboard and mouse. The host binds its inbox drivers. - Wire protocol (esp32_bridge_protocol.h) shared with the firmware: COBS frames with CRC-16, layout transactions, input and output reports, keepalive. The bridge re-enumerates on every layout change; the client reopens the port, restores the desired layout after resets, and replays the latest gamepad report. - platform_default switches to an attached bridge (USB serial LVHB-*), LVH_ESP32_PORT, or LVH_BACKEND=esp32_bridge unless Esp32BridgeOptions::autodetect is false; gamepads fall back to the platform backend when an implicitly selected bridge does not answer. - Pairing: the bridge accepts layout and input frames only from hosts that answer an HMAC-SHA256 challenge with a secret it issued after a press of its BOOT button. Secrets are stored per bridge in a file only its owner (and SYSTEM/Administrators on Windows) can read. POSIX ports are opened with TIOCEXCL. - lvh::find_esp32_bridge_ports() and lvh::esp32_bridge_status() report attached bridges and the connection and pairing state without opening a port. - Build lizardbyte-common's env helper on every platform.
Use small structs for the key usage tables instead of std::pair built from int literals (MSVC C4244), and a string_view for the auth context instead of pointer arithmetic on a literal (-Wstring-plus-int).
Build the fixed-size auth message and AUTH payload as arrays and the keyboard/mouse descriptor with a sized vector instead of repeated vector::insert calls.
|
|
9 of 18 tasks
- Replace the C protocol header with a C++ one (constexpr constants, std::span, std::optional frame decoding); the firmware keeps a C copy. - Group BridgeClient state into per-mutex structs and move the frame handlers onto the connection state. - Split frame accumulation out of the reader loop. - Use std::byte for bit manipulation, std::format for messages, and std::array instead of C arrays. - Split the fake firmware's frame handler in the tests and create test directories below the working directory.
A layout change waited for a session that reported the new generation. If the bridge reset again before the waiting thread woke up, the wait ran into the re-enumeration timeout while holding the layout lock, which also delayed restoring the layout after the reset. Record the last generation a usable session reported and wait for that instead.
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Description
Adds
BackendKind::esp32_bridge: devices are created on an ESP32-S3 connected to the host over USB. The board runs separate firmware (esp32s3-hid-bridge) and enumerates as a composite USB device with a CDC-ACM control port and two device slots. The host binds its inbox drivers (xusb22for XInput, the HID class driver otherwise).Device : https://www.seeedstudio.com/XIAO-ESP32S3-p-5627.html
XUSB10compatible ID).src/platform/esp32/esp32_bridge_protocol.h, plain C, shared with the firmware):platform_defaultswitches to an attached bridge (USB serialLVHB-*),LVH_ESP32_PORT, orLVH_BACKEND=esp32_bridge. Consumers that offer the bridge as an explicit choice setEsp32BridgeOptions::autodetect = false. If the bridge was selected implicitly and does not answer, gamepads fall back to the platform backend.TIOCEXCL.Esp32BridgeOptions(RuntimeOptions::esp32_bridge)lvh::find_esp32_bridge_ports()lvh::esp32_bridge_status(): connection and pairing state, without opening a portenv.cppis now compiled on every platform (it was Windows-only) for the environment lookups.docs/esp32-bridge.md, plus a row indocs/platform-support.md.Screenshot
Not applicable: no UI changes.
Issues Fixed or Closed
None.
Roadmap Issues
None.
Testing
New tests:
tests/unit/test_esp32_bridge.cpphas 48 tests against an in-memory firmware model. They cover:stop()during a pending layout, and teardown while output reports arriveTIOCEXCLon a pty (Linux)Runs:
Hardware:
hid-playstation, and lightbar and rumble outputs reach the callback.xusb22binding in the composite device, and multi-monitor absolute pointer mapping.Type of Change
Checklist
AI Usage
See our AI usage policy.
Model: Claude Opus 5.5 (
claude-opus-5-5) via Claude Code, used for implementation, tests, and drafting this description. The author reviewed the changes and tested them on hardware.