Add session notes: Johnny 5 PS4 control build
This commit is contained in:
1 parent
8fac5c893e
commit
8824f807eb
1 file changed
+151
@@ -0,0 +1,151 @@
|
||||
# PS4 Controller to Two LEGO Hubs — Johnny 5 Build
|
||||
|
||||
**Date:** 2026-09-23
|
||||
**Project:** ps4-lego-bridge (also ps4-lego-onebrain)
|
||||
**Status:** ongoing
|
||||
**Tags:** esp32, bluepad32, legoino, nimble, btstack, lwp3, lego-powered-up, ps4, arduino
|
||||
|
||||
## Context
|
||||
Goal was to drive a Johnny 5 (Short Circuit) LEGO MOC — seven motors across two
|
||||
Technic (Control+) hubs — from a PS4 DualShock 4 via ESP32. The starting sketch
|
||||
combined Bluepad32 and Legoino on one ESP32 and failed to compile
|
||||
(`Bluepad32.h: No such file or directory`). The session went through a
|
||||
single-board attempt, then settled on two boards, then iterated the control
|
||||
scheme on a separate branch.
|
||||
|
||||
## Key Decisions
|
||||
- **Two ESP32 boards, not one.** Bluepad32 uses BTstack (needed for BR/EDR —
|
||||
the DS4 is Bluetooth Classic); Legoino uses NimBLE. Two host stacks cannot
|
||||
share one radio/VHCI. Board A runs Bluepad32 only, board B runs Legoino only,
|
||||
linked by UART.
|
||||
- *Rejected:* single-board via hand-rolled LWP3 GATT client on BTstack
|
||||
(`ps4-lego-onebrain`). It compiled and connected, but was far more code, an
|
||||
unresolved routing fault appeared, and the pivot made that moot. Kept as a
|
||||
separate repo, not maintained.
|
||||
- *Rejected:* PS4-esp32 (Bluedroid) + BLE on one board — PS4-esp32 depends
|
||||
on internal Bluedroid symbols and is broken on modern cores.
|
||||
- **ASCII UART protocol with XOR checksum** over binary — debuggable with any
|
||||
USB-serial adapter. Receiver drops bad frames and stops all motors after
|
||||
400ms of silence.
|
||||
- **Tank drive, one input per motor.** Arcade drive was tried first and
|
||||
rejected: mixing put one stick on both track motors.
|
||||
- **Direction via `DIR_*` constants** rather than editing signs inline —
|
||||
mirrored mountings are normal and flips should be one-line changes.
|
||||
- **Board B pinned to arduino-esp32 core 2.0.17.** Core 3.x breaks Legoino.
|
||||
Downgrading chosen over patching the library (patch is the first of several
|
||||
layers and gets reverted by Library Manager updates).
|
||||
- **Arms moved to position control** on the evolved branch: trigger position =
|
||||
arm angle, via `setAbsoluteMotorPosition` with `BrakingStyle::HOLD`.
|
||||
*Rejected:* power-based arm control with separate lower buttons — works, but
|
||||
the user wanted angle-proportional behaviour.
|
||||
- **Arm encoders zeroed once per boot, not per reconnect.** The hub retains its
|
||||
encoder preset; re-zeroing mid-session would redefine zero wherever the arms
|
||||
happened to be.
|
||||
- **Separate branch `johnny5-evolved`** for the new control scheme so `main`
|
||||
keeps the proven configuration.
|
||||
|
||||
## Technical Details
|
||||
**Toolchain**
|
||||
| Board | Package / library | Version |
|
||||
| --- | --- | --- |
|
||||
| A | esp32_bluepad32 | 4.1.0 |
|
||||
| B | esp32 (Espressif) | 2.0.17 |
|
||||
| B | NimBLE-Arduino | 1.4.x |
|
||||
| B | Legoino | latest |
|
||||
|
||||
- Bluepad32 board manager URL:
|
||||
`https://raw.githubusercontent.com/ricardoquesada/esp32-arduino-lib-builder/master/bluepad32_files/package_esp32_bluepad32_index.json`
|
||||
- Selecting the `esp32_bluepad32` package starts BTstack **before `setup()`**.
|
||||
Any NimBLE sketch compiled under it aborts with `ESP_ERR_INVALID_STATE` at
|
||||
`esp_bt_controller_init`. Board B must use the plain `esp32` package.
|
||||
- Core 3.x Legoino errors: `'string' in namespace 'std' does not name a type`
|
||||
and `no declaration matches ... ReadUInt32LE`. Cause: ESP-IDF 5 made
|
||||
`uint32_t` `unsigned long`.
|
||||
|
||||
**Wiring:** A GPIO17 TX → B GPIO16 RX; A GPIO16 RX ← B GPIO17 TX; common GND
|
||||
(mandatory). Optional 5V→5V daisy-chain for single supply — never with both USB
|
||||
cables connected. LEDs on board B: GPIO25 and GPIO26, each via resistor to
|
||||
board B GND (not tapped off the UART ground wire).
|
||||
|
||||
**Link frame:** `G,<lx>,<ly>,<rx>,<ry>,<buttons>,<dpad>,<l2>,<r2>*<XX>\n`,
|
||||
115200 8N1. Axes −512..511, triggers 0..1023, XX = XOR of payload.
|
||||
|
||||
**Port map (verified on hardware)**
|
||||
- hub0 `90:84:2b:61:f2:d7` — A right track, B left track, C free, D body lift
|
||||
- hub1 `90:84:2b:61:e6:8c` — A left arm, B head tilt, C head turn, D right arm
|
||||
- Note: the two addresses were originally the wrong way round, and hub1 A/C and
|
||||
B/C were both mislabelled at points. All found by observation, not code.
|
||||
|
||||
**Arm calibration:** travel ≈ +280 (left) / −275 (right) motor degrees — under
|
||||
one revolution, so the single-turn absolute encoder is unambiguous. Spans set
|
||||
to ±250 to keep backlash clear of the end stop.
|
||||
|
||||
**Rate limits:** `MOTOR_MIN_GAP_MS` 100 (per port), `HUB_MIN_GAP_MS` 25 (per
|
||||
hub, ~40 cmd/s), `ARM_MIN_GAP_MS` 60. Hub1 carries four motors, hence the
|
||||
per-hub cap.
|
||||
|
||||
**Arduino gotchas hit this session**
|
||||
- The IDE injects generated prototypes before the *first function definition*.
|
||||
Types used in signatures (`HubLink`, `PortState`, `ArmState`, `Frame`) must
|
||||
be declared above it — broke the build twice.
|
||||
- `Lpf2Hub::init()` starts an **asynchronous** scan. Guarding it on
|
||||
`!isConnected() && !isConnecting()` re-enters `NimBLEDevice::init()`
|
||||
continuously and crashes. Use a one-shot `initialised` flag.
|
||||
- A scan that finds nothing ends silently; without a timeout the hub sits dead
|
||||
until power cycle. Fixed with `scanExpiresAt` / `SCAN_TIMEOUT_MS` (12s).
|
||||
- Bluepad32 keeps pairing keys in NVS; reflashing doesn't clear them.
|
||||
`BP32.forgetBluetoothKeys()` must be commented out after first pairing.
|
||||
|
||||
**Startup order:** wake both hubs (green button, blinking) → arms physically
|
||||
down → power board B. hub1 is only serviced after hub0 connects.
|
||||
|
||||
## Code Changes
|
||||
- `transmitter/transmitter.ino` — frame extended with d-pad and analog
|
||||
triggers; `DEBUG_FRAMES` flag.
|
||||
- `receiver/receiver.ino` (main) — seven motors, tank drive, `DIR_*`
|
||||
constants, swapped hub addresses, right-arm button swap, types hoisted,
|
||||
core 2.0.17 noted in header.
|
||||
- `receiver/receiver.ino` (johnny5-evolved) — evolved scheme, position-
|
||||
controlled arms, GPIO LED toggles, head tilt/turn port swap, once-per-boot
|
||||
arm zeroing, scan timeout.
|
||||
- `tools/arm_calibrate/arm_calibrate.ino` — measures arm span in motor degrees
|
||||
(delivered as a file; Gitea push timed out — verify it's on the branch).
|
||||
- `README.md` (main) — build guide, toolchain table, power section,
|
||||
power-loss test, gotchas.
|
||||
|
||||
**Evolved control scheme (johnny5-evolved)**
|
||||
| Input | Action |
|
||||
| --- | --- |
|
||||
| Left / right stick Y | Left / right track |
|
||||
| L2 / R2 analog | Left / right arm angle |
|
||||
| L1 / R1 | Head turn left / right |
|
||||
| D-pad ↑/↓ | Body lift |
|
||||
| D-pad ←/→ | Head tilt |
|
||||
| Square | All stop (arms go limp) |
|
||||
| Cross | Toggle 50/100% track speed |
|
||||
| Circle / Triangle | Toggle LED pair 1 / 2 |
|
||||
|
||||
## Open Questions / Next Steps
|
||||
- **Head direction untested.** `DIR_HEAD_TURN` and `DIR_HEAD_TILT` are +1 by
|
||||
default; flip if L1/R1 or d-pad ←/→ run backwards.
|
||||
- **Scan timeout tuning.** 12s is a guess; don't go below ~6s.
|
||||
- **Power-loss test not yet run.** The 400ms failsafe only fires if board B is
|
||||
alive. Cut board B's power with a track running and confirm whether the hub
|
||||
stops on BLE disconnect.
|
||||
- **Heavy-load test.** Both arms held mid-travel plus head swinging is the peak
|
||||
hub1 command load — check it doesn't drop.
|
||||
- **Arm calibrate sketch** — confirm it landed on `johnny5-evolved`.
|
||||
- **Merge decision** — `johnny5-evolved` into `main` once verified.
|
||||
- **Boot track speed** defaults to 50%; user may want 100%.
|
||||
|
||||
## Artifacts
|
||||
- Repo: https://gitea.hideawaygaming.com.au/jessikitty/ps4-lego-bridge
|
||||
(branches `main`, `johnny5-evolved`)
|
||||
- Single-board attempt: https://gitea.hideawaygaming.com.au/jessikitty/ps4-lego-onebrain
|
||||
- Bluepad32 docs: https://bluepad32.readthedocs.io/
|
||||
- LEGO Wireless Protocol 3.0: https://lego.github.io/lego-ble-wireless-protocol-docs/
|
||||
- Device type IDs: https://github.com/pybricks/technical-info/blob/master/assigned-numbers.md
|
||||
- Legoino: https://github.com/corneliusmunz/legoino
|
||||
|
||||
---
|
||||
Created by: Jess Rogerson (yelling commands at Claude.AI)
|
||||
Reference in new issue
Block a user