diff --git a/README.md b/README.md index 030dc3b..dd686f1 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,16 @@ One ESP32. PS4 controller over Bluetooth Classic, two LEGO Powered Up hubs over BLE, both on the same Bluetooth stack. +Driving a Johnny 5 (Short Circuit) MOC — seven motors across two Technic hubs. + This is the harder of the two routes. If you want the boring one that works first go, see `ps4-lego-bridge` — two boards and a UART between them. -**Build and deploy instructions: [docs/BUILD.md](docs/BUILD.md).** This will not -compile out of the box — the board package ships the BTstack headers but does -not put them on the include path, and that has to be fixed first. +**Build and deploy: [docs/BUILD.md](docs/BUILD.md).** This will not compile out +of the box — the board package ships the BTstack headers but does not put them +on the include path, and that has to be fixed first. + +**Controls and port map: [docs/CONTROLS.md](docs/CONTROLS.md).** ## How it avoids the stack collision @@ -21,9 +25,9 @@ it supports BLE gamepads like the Xbox v5 firmware pads. That machinery is sitting there unused for our purposes, so this sketch borrows it and speaks the LEGO Wireless Protocol directly. No Legoino, no NimBLE. -What you give up: everything Legoino did for you. Sensor callbacks, port device -detection, tacho positioning, hub name reads. What you get back is one board and -about 300 lines. +What you give up: everything Legoino did for you. Sensor callbacks, tacho +positioning, hub name reads. What you get back is one board and about 450 lines. +Port detection we got back cheaply — see below. ## The two rules @@ -33,9 +37,9 @@ anywhere is `btstack_run_loop_execute_on_main_thread()`. So the LEGO side lives entirely inside a repeating BTstack timer, which is scheduled once at boot via that one safe call. `loop()` never touches BTstack — -it reads the sticks and writes three plain ints into `gDesired`, and the timer -picks them up. Any BTstack call added to `loop()` will crash at random places, -which is the worst kind of bug to chase. +it reads the sticks and writes plain ints into `gDesired`, and the timer picks +them up. Any BTstack call added to `loop()` will crash at random places, which +is the worst kind of bug to chase. **2. Boot order.** Gamepad discovery starts *disabled*. Both hubs connect first, then `BP32.enableNewBluetoothConnections(true)` opens up for the pad. Bluepad32's @@ -47,11 +51,12 @@ and the hub connects unreliably or not at all. ``` ps4_lego_onebrain/ps4_lego_onebrain.ino the sketch docs/BUILD.md install, include-path fix, first run +docs/CONTROLS.md port map, control layout, power caps docs/platform.local.txt the include-path override, for restoring after a Bluepad32 package update ``` -## The protocol, in full +## The protocol Everything the motors need is one 8-byte write, no response, to characteristic `00001624-1212-EFDE-1623-785FEABCD123` inside service `...1623...`: @@ -76,44 +81,34 @@ There is no tacho-vs-basic distinction to get wrong here. The trade-off is that you get no closed-loop speed control — power is power, and the motor slows under load. `StartSpeed` (subcommand `0x07`) does regulate, if you want it later. +Going the other way, the sketch subscribes to notifications on the same +characteristic and decodes two inbound messages: + +* **`0x04` Hub Attached I/O** — the hub announces every populated port the + instant notifications go live, so the serial log tells you what is actually + plugged in rather than what you think is. Device type IDs come from + [pybricks/technical-info](https://github.com/pybricks/technical-info/blob/master/assigned-numbers.md). +* **`0x05` Generic Error** — rejected commands used to vanish silently. + Full spec: https://lego.github.io/lego-ble-wireless-protocol-docs/ -## What to expect on the bench - -Verified: the Bluepad32 board-package install, the threading contract and -`btstack_run_loop_execute_on_main_thread()`, the BTstack GATT client call -signatures, the LWP3 message encoding above, and — on Jess's machine — that -package 4.1.0 ships every header this needs under `tools\sdk\esp32\include\`. - -Not verified, because it needs the hardware powered up: - -* **Address type.** LEGO's OUI is `90:84:2B`, a public range, so the sketch uses - `BD_ADDR_TYPE_LE_PUBLIC`. If the connect times out repeatedly with no LE - connection-complete event, switch `HUB_ADDR_TYPE` to `BD_ADDR_TYPE_LE_RANDOM`. -* **Whether BLE is enabled in your Bluepad32 build.** If `gap_connect` never - produces a connection-complete, this is the next thing to check. -* **The LE connection-complete event name.** BTstack moved this from - `HCI_EVENT_LE_META`/`HCI_SUBEVENT_LE_CONNECTION_COMPLETE` to - `HCI_EVENT_META_GAP`/`GAP_SUBEVENT_LE_CONNECTION_COMPLETE`. The sketch handles - both, with the newer one behind `#ifdef`, so whichever your version emits will - land. Guarded by connection state so a BLE gamepad connecting can't be - mistaken for a hub. - -Turn on verbose BTstack logging before you start guessing. Nearly every failure -here shows up as a specific HCI event you can read. - ## Known rough edges -* Commands are throttled to one per port per 60ms (`MOTOR_MIN_GAP_MS`). Push - that lower and the hub's BLE queue backs up and it drops the link. Stops are - exempt and always go out immediately. +* **No end-stop protection.** Every axis except the tracks runs into a + mechanical stop and there is no position feedback, so holding a direction at + a stop stalls the motor. The power caps in `docs/CONTROLS.md` are the only + thing standing between you and stripped gears. Control+ motors have absolute + encoders, so this is fixable properly with Port Input Format Setup (`0x41`). * Write-without-response can fail silently when ACL buffers are full. The tick - retries 25ms later, so a dropped command is invisible in practice, but that is - why you should not treat a single write as a guarantee. -* There is no failsafe timer on the gamepad side beyond `isConnected()`. If the - pad goes out of range the stack can take 20 seconds to notice. The Bluepad32 - FAQ has a `hasData()` plus timeout pattern worth adding if this ends up - driving something that can fall off a table. + retries 25ms later, so a dropped command is invisible in practice, but do not + treat a single write as a guarantee. +* No failsafe timer on the gamepad side beyond `isConnected()`. If the pad goes + out of range the stack can take 20 seconds to notice. The Bluepad32 FAQ has a + `hasData()` plus timeout pattern worth adding. +* Rate limits are tuned by feel, not measurement — see `docs/CONTROLS.md`. + +If something misbehaves, turn on verbose BTstack logging first. Nearly every +failure here shows up as a specific HCI event you can read. ---