# ps4-lego-bridge 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)