openvibers/openvibe.link Commits

Write the Cozmo bridge bring-up runbook (O31) (#30)

OP OpenVibers committed 5 Oct 2026 · dcbf3b5c05 · 3 files, +95 −2

**Files changed**
- `docs/hardware-cozmo.md` (new): O31 Cozmo bridge bring-up runbook modelled on `hardware-adeept.md` — the two-interface bridge (default route pinned to the internet NIC, `nmcli … never-default`), `openvibe-cozmo[robot]` + `espeak-ng`, the `--driver cozmo` pairing command and config/credential checks, firmware 2381 with the `firmware_unsupported` latch, an on-robot panel checklist (drive/head/lift/say/display/backpack+cube lights/cliff+pickup/battery/camera), and connect/video/nack troubleshooting.
- `docs/HANDOFF.md`: appended a pointer to the runbook at the end of the "Plugin 2: `cozmo`" paragraph (lines 92–101).
- `docs/plugins.md`: extended the hardware-runbook note at line 110–111 to cross-link `hardware-cozmo.md` alongside `hardware-adeept.md`.

**Tests run (one at a time)**
- `PYTHONPATH=/usr/lib/python3/dist-packages /home/workstation/.local/bin/pytest -q plugins/cozmo/tests` → **38 passed in 1.11s**.
- `ov test --all` → `✓ go vet (8.7 s)`, `✓ go test (9.0 s)`, **all green in 18 s**. (Docs reach no Go packages; `--all` ran the full suite since the repo is small.)

**Could not do / notes**
- The brief's `python -m pytest` isn't runnable in this shell: no `python` binary, system `python3` has no pytest, and the available pytest is a uv tool venv that can't see PIL. I ran the suite with `~/.local/bin/pytest` plus `PYTHONPATH=/usr/lib/python3/dist-packages` (where pillow lives) — no packages installed, per instructions. Saved this as a memory for next time.
- No `STATUS.json` or `CHANGELOG.md` exists in the repo, and `README.md` doesn't index the hardware docs (the merged Adeept runbook isn't linked there either), so those were left untouched.
- No test was added: this is a docs-only change with no new behaviour.
- I created a stray memory file under a wrong project path (`…-OpenVibe-Node/memory/`) and the `rm` to remove it was denied by permissions, so it remains; the real copy is under `…-OpenVibers-OpenVibe-Node/memory/` and is indexed.

Changes

ddiff --git a/docs/HANDOFF.md b/docs/HANDOFF.md
iindex c1302e4..c8c9eac 100644
---- a/docs/HANDOFF.md
++++ b/docs/HANDOFF.md
@@@ -98,7 +98,9 @@ Cozmo runs closed firmware and makes its own Wi-Fi access point with no internet
PCM), `display_image` (text/faces rendered with Pillow to 128×32/64), backpack and cube lights, camera frames
(`enable_camera`, 320×240, grey or colour) handed to the core as JPEG, battery voltage, **cliff and pick-up events that
stop the wheels at once**. Check the robot's firmware at connect and report `nack` with a clear fault if PyCozmo does not
--support it. Map commands so Cozmo's head and lift appear as ptz-like actuators. Tests with a fake PyCozmo client.
++support it. Map commands so Cozmo's head and lift appear as ptz-like actuators. Tests with a fake PyCozmo client. The
++on-robot bridge pass — the two interfaces, pairing, firmware and the panel checklist — is in
++[hardware-cozmo.md](hardware-cozmo.md).
## Plugin 0: `dryrun`
ddiff --git a/docs/hardware-cozmo.md b/docs/hardware-cozmo.md
nnew file mode 100644
iindex 0000000..c495bfb
---- /dev/null
++++ b/docs/hardware-cozmo.md
@@@ -0,0 +1,90 @@
++# Anki / Digital Dream Labs Cozmo — bridge bring-up (O31)
++
++Cozmo runs closed firmware, makes its own Wi-Fi access point with no internet, and cannot run the Node itself. The
++device side is therefore a **bridge**: `openvibe-node` with the `cozmo` plugin runs on a computer next to the robot —
++a Raspberry Pi or a laptop — and drives it over Wi-Fi through PyCozmo. The bridge joins Cozmo's network on one
++interface and reaches OpenVibe on another. Bot serves the one-paste installer; the panel is
++`openvibe.bot/panel/<rob_…>`.
++
++## 1. Two interfaces (the bridge computer)
++
++Cozmo is an access point (`Cozmo_XXXXXX`, robot at `172.31.1.1`) and has no internet, so the bridge computer needs
++**two network interfaces**:
++
++| interface | joined to | used for |
++|-----------|-----------|----------|
++| `wlan0` (or the built-in Wi-Fi) | Cozmo's Wi-Fi | PyCozmo's UDP link to the robot |
++| Ethernet, or a second Wi-Fi (USB dongle) | your home network | the Node's link to OpenVibe.Bot and video |
++
++1. Put Cozmo on its charger and turn it on. Raise and lower its lift: its face shows the network name and password.
++2. Join one interface to that network. Make sure the **default route stays on the internet interface**: Cozmo's network
++ has no internet, and some systems prefer the newest Wi-Fi. With NetworkManager:
++
++ ```sh
++ nmcli connection modify Cozmo_XXXXXX ipv4.never-default yes ipv6.never-default yes
++ ```
++
++3. Close the Cozmo app on any phone: the robot accepts one controller at a time.
++4. The installer `pip install`s the plugin with the robot extra (`openvibe-cozmo[robot]`, which pulls PyCozmo) into the
++ Node's plugin virtual environment. It only *tips* about speech — install a TTS engine yourself:
++
++ ```sh
++ sudo apt install espeak-ng # or libttspico-utils (pico2wave)
++ ```
++
++## 2. Pair (one pasted command from openvibe.bot)
++
++```sh
++curl -fsSL https://openvibe.bot/install | sh -s -- --robot rob_… --code XXXX-XXXX --driver cozmo
++```
++
++Network pairing, if openvibe.bot offers the Network form:
++
++```sh
++curl -fsSL https://openvibe.bot/install | sh -s -- --network https://openvibe.network --pairing pair_… --code XXXX-XXXX --driver cozmo
++```
++
++Then check the written config (`cat /etc/openvibe-node/config.json`): `device_kind` is `bridge`, `plugins` names
++`cozmo`, and `video.source` is `auto` (the plugin supplies JPEG camera frames). The config has no `video.whip_url`: the
++ingest URL + publish key the pairing returned is stored in `/etc/openvibe-node/credential.json`, and video publishes
++there unless a `video.whip_url` in the config overrides it. Setup waits `connect_timeout_s` (default 10 s) for the
++robot; if it is unreachable the plugin reports fault `not_connected`. `openvibe-node status --json` should show
++`link.connected: true`, `sensors` (`head_deg`, `lift_mm`, `cliff`, `picked_up`, `cubes`) and `video.state` at
++`connecting` or `live`.
++
++## 3. Firmware
++
++PyCozmo 0.8 speaks the protocol of Cozmo firmware **2381** (the last release of the Cozmo app). At connect the plugin
++reads the firmware signature. On another version, or on factory/recovery firmware (`build: FACTORY`), it stays up but
++**latches fault `firmware_unsupported`**: it emits an `event` with the version found and the supported ones, lists the
++fault in telemetry, and answers every command except `halt` with that nack. Update the robot with the Cozmo app. If you
++know a version works, allow it with `allow_firmware: [2381, <version>]` in the plugin config.
++
++## 4. Panel checklist
++
++Drive from openvibe.bot; watch `openvibe-node status --json` for telemetry.
++
++| Subsystem | Expected | Pass |
++|---|---|---|
++| Drive (`drive` `{"throttle": -1..1, "steer": -1..1}`) | left = throttle + steer, right = throttle − steer (steer > 0 turns right), × `max_wheel_mmps`; wheels stop at the deadline and on `openvibe-node stop` | |
++| Head (`ptz` `{"tilt": -1..1}` / actuator `head`) | −1 = 25° down → +1 = 44.5° up (no pan: turn with `drive`) | |
++| Lift (actuator `lift` 0..1) | 0 = 32 mm down → 1 = 92 mm up | |
++| Say (`say` `{"text"}`) | espeak-ng / pico2wave played on Cozmo's speaker (ack = queued) | |
++| Display (`display` text / `{"face": …}` / `{"image_png_b64"}`) | text, any of the six faces, and a PNG on the 128 × 32 1-bit screen | |
++| Backpack lights (actuator `backpack_lights` `{"r","g","b"}`, `null` = off) | all five LEDs change | |
++| Cube lights (actuator `cube_lights` `{"r","g","b"}, "cube": id?`) | every connected cube, or one by object id | |
++| Cliff (`sensors.cliff`, event `cliff` / `cliff_cleared`) | wheels stop in the callback; forward drives nack `cliff`, backing away allowed | |
++| Pick-up (`sensors.picked_up`, event `picked_up` / `picked_up_cleared`) | every drive nack `picked_up`; all motors stopped | |
++| Battery (`battery.volts`, `percent` 3.5–4.2 V) | sane pack voltage; `low_battery` event once below `low_battery_v` | |
++| Camera (320 × 240 JPEG files) | panel shows video; `color: true` for colour (grey by default) | |
++
++## 5. If it will not connect or shows no video
++
++- **No telemetry / `not_connected`**: confirm the second interface is joined to `Cozmo_XXXXXX`, the default route is
++ still on the internet NIC (`ip route`), the Cozmo app is closed everywhere, and the robot is on its charger.
++- **No video**: the plugin writes JPEG frames to `frame_dir` (a temp dir by default) and announces each as
++ `{"op":"video","format":"jpeg","path":…,"seq":n}`; the core publishes them. Check `openvibe-node status --json` for
++ `video.state` / `video.last_error` and journalctl for `openvibe-node`. To check the WHIP path without the camera, set
++ `"video": {"source": "test"}` in `/etc/openvibe-node/config.json` and restart: the panel then shows the test pattern.
++- **A command answers `nack`**: read the fault — `firmware_unsupported` is the firmware latch above; `cliff` and
++ `picked_up` are the safety stops in the checklist.
ddiff --git a/docs/plugins.md b/docs/plugins.md
iindex 3c58691..da93877 100644
---- a/docs/plugins.md
++++ b/docs/plugins.md
@@@ -108,7 +108,8 @@ the panel (ADR-043 decision 7: names are open strings checked against a registry
| `battery`, `sensors`, `lights`, `bridge` | informational | — |
For the Adeept 4WD Smart Car's on-Pi pass — wiring, pairing, the panel checklist and camera troubleshooting — see
--[hardware-adeept.md](hardware-adeept.md).
++[hardware-adeept.md](hardware-adeept.md). For Cozmo's bridge pass — the two interfaces, pairing, firmware and the panel
++checklist — see [hardware-cozmo.md](hardware-cozmo.md).
## The bundled drivers