Students join the hub's Wi-Fi, open a dashboard, and drive ESP32 robots over
Zenoh. This repo is the contract (keys, envelopes, the dashboard) and the
Raspberry Pi hub that hosts it at classroom scale; the robot firmware — and
the ESP32 that can become a hub — lives at
better-robotics/robot.
The room grows; the wire never changes — robots/<name>/… over Zenoh, one
dashboard.html. The robot firmware picks its shape per boot, nothing is
configured:
ISLAND ESP32 HUB PI HUB
solo / home small group · demo full classroom
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ robot-XXXX │ │ hub-XXXX │ │ hub-pi-XXXX │
│ the robot is │ │ any board, │ │ zenohd + │
│ its own hub: │ │ role = hub: │ │ hubd (pi/): │
│ AP + Zenoh │ │ AP + Zenoh │ │ open ACL + │
│ + dashboard │ │ + dashboard │ │ operator │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲
one phone — robots & phones the whole room —
robot.local join hub-XXXX — robots, phones,
hub.local laptops — hub.local
isolation: single driver Wi-Fi perimeter (open) Wi-Fi perimeter (open)
capacity: 1 + a phone ~8–10 Wi-Fi clients room-scale
──────────────────────────────────────────────────────────────────────
a room resizes LIVE: an island yields when any hub-… appears, and every
board prefers the Pi · a board can be locked to ONE hub (the hub pin),
so a rogue hub-… can't absorb it · the Wi-Fi perimeter is the boundary on
every tier — the only gated credential anywhere is operator, for fleet/estop
One self-contained dashboard.html (it speaks a small WS-JSON protocol to the
hub's ws-adapter; also runs from file:// with the hub's address typed once),
two tiers — each enforced by the adapter, not by page logic:
| tier | credential | can |
|---|---|---|
| anyone | none | watch every robot live (telemetry, cameras, per-board settings) and drive any of them — joystick / D-pad, wire log visible (it's a teaching surface) |
| operator | operator:password |
everything anyone can, plus engage/clear the fleet-wide e-stop · Assign: Blink 💡 a board's LED to find it on the desk, then give it a name, hub pin, motor pins |
The hub's own Wi-Fi is the real boundary, not a login — a robot's name in the key is an address, not a credential. Fresh boards arrive in an unassigned pool anyone can drive, same as any other robot.
CONTRACT.md the wire contract — keys, envelopes, identity/ACL model, cmd/* channels
envelopes/ message shapes (imu, pwm, rpc_set_led)
dashboard.html the browser client — CANONICAL copy (the ESP32 hub vendors it;
robot/tools/sync-dashboard.sh --check gates drift)
mcp-bridge/ MCP tool server — drive the fleet from an LLM over the same contract
pi/ the Raspberry Pi hub
├── src/ hubd — dashboard/HTTP chassis + device-served Wi-Fi setup (nmcli)
│ + serves the ide bundle at /ide/ when installed
├── zenoh-router.example.json5 zenohd config + the router ACL (sole drive path)
├── ws-adapter/ the browser edge (WS-JSON ↔ zenohd) + per-owner claiming (hub#10)
├── deploy/ systemd install: hubd · zenohd · ws-adapter · day-zero AP · USB recovery
└── image/ the CI-baked, flash-and-go Pi image (official Lite base + customize-image.sh)
Pi, from the baked image (recommended): take hub.img.xz from Releases
(or run the build-image workflow), flash an SD card, boot. The Pi raises an
open hub-XXXX network with the dashboard at http://hub.local; set its
internet uplink from the dashboard's Wi-Fi panel. A USB-C cable to a laptop is
the headless recovery console.
Pi, onto an existing OS:
cd pi && sudo ./deploy/install.sh # hubd + zenohd + ws-adapter (+ the hub AP on a wlan0 host)Both paths also install the ide
bundle, served at http://hub.local/ide/ — snap blocks together or write
Python, and it runs in the browser and drives a robot over this repo's own
contract. Reachable from any device on the hub's network, phones included
(plain-http origin: no mixed-content wall between the page, the ws-adapter, and
the robots).
ESP32 hub: flash the robot firmware
(browser flasher), flip the board's role
to hub on its robot.local settings page, join its Wi-Fi, open
http://hub.local.
pi/deploy/install.sh seeds a placeholder operator credential into
/etc/hub/operator.env — the only login the classroom has — change it
before a real class (edit the file, then restart the ws-adapter).
robot — the unified robot +
ESP32-hub firmware, and the browser flasher it serves at
/robot/ ·
better-robotics.github.io
— the org's landing URL, redirects to the flasher ·
ide — the blocks-and-Python editor
served at /ide/ · hub-zenoh — archived (the
standalone Zenoh evaluation baseline; the live transport is now Zenoh in-repo,
hub#9) · workbench — a browser dev environment, drifting from the classroom
model.