From 24f4dcc08bbe201321f17b5ab3aabd86d1138022 Mon Sep 17 00:00:00 2001 From: jessikitty Date: Thu, 27 Aug 2026 19:33:26 +1000 Subject: [PATCH] Document architecture, setup and known gotchas --- README.md | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d71031..2da9f71 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,116 @@ # ps4-lego-bridge -PS4 controller to two LEGO Powered Up hubs via two ESP32s (Bluepad32 + Legoino, split across boards) \ No newline at end of file +PS4 controller -> two LEGO Powered Up / Technic hubs, using two ESP32 boards. + +## Why two boards + +The obvious sketch — Bluepad32 for the gamepad, Legoino for the hubs, one ESP32 — +does not work, and it is not a library-version problem. + +* **Bluepad32** needs Bluetooth Classic (BR/EDR), because a DualShock 4 is a + BR/EDR device. It gets that from **BTstack**, and it ships as a whole + replacement ESP32 board package rather than a normal library. +* **Legoino** talks to the hubs over BLE using **NimBLE-Arduino**. + +BTstack and NimBLE are both Bluetooth *host* stacks. There is one radio and one +VHCI interface on the chip, and whichever stack registers second wins. You +cannot run both in one firmware image. Every mature project in this space +(Anton's Mindstorms LMS-ESP32, the Pybricks bridges) solves it the same way: +one chip owns the gamepad, and it hands the data to something else over a wire. + +So: board A runs Bluepad32 and nothing else. Board B runs Legoino and nothing +else. Three jumper wires between them. + +## Hardware + +* 2x ESP32 dev boards. Both must be the **original ESP32** (WROOM/WROVER). + S3, C3, C6 and H2 have no Bluetooth Classic, so a PS4 pad will not pair. +* 3 jumper wires. + +``` + Board A (Bluepad32) Board B (Legoino) + GPIO17 TX -------------> GPIO16 RX + GPIO16 RX <------------- GPIO17 TX + GND ------------- GND <- do not skip this one +``` + +Power both from the same supply if you can. Separate USB bricks are fine as +long as the grounds are tied together. + +## Software setup + +### Board A — transmitter + +1. Arduino IDE -> Preferences -> Additional board manager URLs, add: + `https://raw.githubusercontent.com/ricardoquesada/esp32-arduino-lib-builder/master/bluepad32_files/package_esp32_bluepad32_index.json` +2. Boards Manager -> install **esp32_bluepad32**. +3. Tools -> Board -> **esp32_bluepad32** -> ESP32 Dev Module. + Selecting the board from the normal `esp32` package is what produces + `fatal error: Bluepad32.h: No such file or directory`. Bluepad32 is not in + the Library Manager and installing it as a ZIP library will not help. +4. Flash `transmitter/transmitter.ino`. + +### Board B — receiver + +1. Boards Manager -> the standard **esp32** package by Espressif. +2. Library Manager -> **NimBLE-Arduino**. Pin this to **1.4.x**. Legoino has + not moved to the NimBLE 2.x API, so 2.x gives a wall of compile errors. +3. Library Manager -> **Legoino**. +4. Flash `tools/hub_scanner/hub_scanner.ino` first, note both hub addresses, + paste them into `receiver.ino`, then flash `receiver/receiver.ino`. + +## Controls as shipped + +| Input | Action | +| ----------------- | ------------------------------- | +| Left stick Y | Left track, hub 1 port A | +| Right stick Y | Right track, hub 1 port B | +| Left stick X | Head rotation, hub 2 port A | + +Change the mapping in the frame-handling block of `receiver.ino`. + +## Link protocol + +ASCII, newline terminated, 115200 8N1: + +``` +G,,,,,*\n +``` + +* axes are Bluepad32 raw values, -512..511 +* `buttons` is the 16-bit mask as a decimal number +* `XX` is a two-digit hex XOR checksum of everything before the `*` + +Being plain text means you can watch the link with a USB-serial adapter when +something misbehaves. + +## Things that bite + +* **One hub connects, the other does not.** Legoino shares a single NimBLE + scanner. `receiver.ino` connects them strictly one at a time for this reason. + Do not "optimise" that into two parallel `init()` calls. +* **Motors stutter or the hub drops out.** You are sending commands faster than + the hub can chew. `MOTOR_MIN_INTERVAL_MS` throttles per port; raise it before + blaming the radio. +* **More than 3 hubs later on.** Edit `CONFIG_BT_NIMBLE_MAX_CONNECTIONS` in + `NimBLE-Arduino/src/nimconfig.h`, then restart the IDE to force a rebuild. +* **Wrong motor command.** Technic/Control+ motors want `setTachoMotorSpeed`; + train and simple PU motors want `setBasicMotorSpeed`. Toggle + `USE_TACHO_MOTORS` in `receiver.ino`. +* **Bluepad32 API drift.** v4 renamed `GamepadPtr` to `ControllerPtr` and + `BP32_MAX_GAMEPADS` to `BP32_MAX_CONTROLLERS`. This code uses the v4 names. + +## If you really want one board + +Two options, both more work than a second ESP32: + +* Write the LEGO Wireless Protocol GATT client directly against BTstack inside + the Bluepad32 sketch. Bluepad32 v4 does support BLE, so the radio can do it — + you would just be reimplementing the parts of Legoino you need. The motor + command is only eight bytes. +* Put Pybricks on the hubs and drive them from an LMS-ESP32 running the + prebuilt Bluepad32 LPF2 firmware over the LEGO wire. + +--- + +Created by: Jess Rogerson (yelling commands at Claude.AI)