Update for Johnny 5 build: controls doc, notification decoding, end-stop warning
This commit is contained in:
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user