show-me / docs /dev-guide.md
PierreRouanet's picture
Add developer guide for continuing development
8b7ec50
|
Raw
History Blame Contribute Delete
8.65 kB
# Show Me β€” Developer Guide
## Quick Start
```bash
cd show-me
# Install JS dependencies
npm install
# Start the simulated robot (needs Python venv with reachy-mini)
.venv/bin/python launch_sim_webrtc.py
# In another terminal, start the dev server
npm run dev
```
Open `http://localhost:5174` β€” click Connect to see SMPTE test bars from the sim.
To connect to a real robot instead, change `ROBOT_HOST` in `src/App.svelte` to `"reachy-mini.local"`.
## Project Structure
```
show-me/
β”œβ”€β”€ src/
β”‚ β”œβ”€β”€ main.js # Mounts Svelte app
β”‚ β”œβ”€β”€ App.svelte # Root layout, creates robot store
β”‚ β”œβ”€β”€ lib/
β”‚ β”‚ β”œβ”€β”€ robot/
β”‚ β”‚ β”‚ β”œβ”€β”€ connection.js # ReachyMini class (pure JS, no Svelte)
β”‚ β”‚ β”‚ └── commands.js # High-level command helpers
β”‚ β”‚ └── stores/
β”‚ β”‚ └── robot.svelte.js # Svelte 5 runes reactive adapter
β”‚ β”œβ”€β”€ components/
β”‚ β”‚ β”œβ”€β”€ VideoFeed.svelte # <video> element bound to stream
β”‚ β”‚ └── Controls.svelte # Connect, Wake Up, Sleep, State viewer
β”‚ └── styles/
β”‚ └── global.css # Dark theme, full-viewport reset
β”œβ”€β”€ vendor/
β”‚ └── gstwebrtc-api-3.0.0.tgz # GStreamer WebRTC JS lib (built from source)
β”œβ”€β”€ patches/
β”‚ └── fix_webrtc_asyncio.py # Fix for robot daemon async commands
β”œβ”€β”€ docs/ # Documentation
β”œβ”€β”€ launch_sim_webrtc.py # Desktop simulation launcher
β”œβ”€β”€ index.html # Entry point
β”œβ”€β”€ package.json
β”œβ”€β”€ vite.config.js
└── svelte.config.js
```
### Architecture Layers
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Svelte Components (VideoFeed, Controls, App) β”‚ UI β€” reads from store
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ robot.svelte.js β€” Svelte 5 reactive adapter β”‚ Glue β€” $state() runes
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ connection.js + commands.js β€” pure JS β”‚ Robot β€” framework-agnostic
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ gstwebrtc-api v3 β€” WebRTC signaling + media β”‚ Transport
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```
The robot layer (`src/lib/robot/`) has **zero** Svelte imports. It extends `EventTarget` and emits standard events. This is intentional β€” it's designed for future extraction into an `npm` package (`reachy-mini-js`).
The store layer (`src/lib/stores/`) is a thin adapter that wires robot events to Svelte 5 `$state()` runes.
## Key Technical Details
### gstwebrtc-api v3
Built from [gst-plugins-rs](https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs) `net/webrtc/gstwebrtc-api/`. Stored as `vendor/gstwebrtc-api-3.0.0.tgz`.
**v3 API patterns (different from v2):**
```javascript
// Discovery β€” callback-based, NOT addEventListener
api.registerPeerListener({
producerAdded(producer) { /* producer.id, producer.meta */ },
producerRemoved(producer) { },
});
// Connection listener
api.registerConnectionListener({
connected(clientId) { },
disconnected() { },
});
// Consumer session
const session = api.createConsumerSession(producerId);
session.addEventListener("streamsChanged", () => { /* session.streams */ });
session.addEventListener("stateChanged", () => { /* session.state: 0-3 */ });
session.connect();
// Session states: 0=idle, 1=connecting, 2=streaming, 3=closed
```
**Data channel caveat:** The library only handles channels named `"control"` internally. The robot creates a channel named `"data"` β€” we intercept it via the underlying `RTCPeerConnection`:
```javascript
session.addEventListener("rtcPeerConnectionChanged", () => {
const pc = session.rtcPeerConnection;
pc.addEventListener("datachannel", (event) => {
if (event.channel.label === "data") { /* use it */ }
});
});
```
### Data Channel Protocol
Commands are JSON objects with a single top-level key. Responses include `"status"`, `"error"`, or `"command"` keys.
| Command | Example | Async? |
|---|---|---|
| `get_state` | `{ "get_state": true }` | No |
| `set_target` | `{ "set_target": [[4x4 matrix]] }` | No |
| `set_antennas` | `{ "set_antennas": [right, left] }` | No |
| `set_body_yaw` | `{ "set_body_yaw": 0.5 }` | No |
| `goto_target` | `{ "goto_target": { head, antennas, duration, body_yaw } }` | Yes |
| `wake_up` | `{ "wake_up": true }` | Yes |
| `goto_sleep` | `{ "goto_sleep": true }` | Yes |
| `play_sound` | `{ "play_sound": "file.wav" }` | No |
| `set_motor_mode` | `{ "set_motor_mode": "enabled" }` | No |
| `get_motor_mode` | `{ "get_motor_mode": true }` | No |
Async commands respond only when the motion/animation completes (up to 30s).
All angles are in **radians**. Head poses are **4x4 homogeneous matrices** (row-major).
**State poll vs command response disambiguation:** State poll responses only have a `"state"` key. Command responses have `"status"`, `"error"`, or `"command"` keys. The message handler in `connection.js` uses this to avoid state polls accidentally resolving command promises (FIFO queue).
### Robot Daemon Bug: GStreamer Thread vs asyncio
The daemon's data channel callback fires on a GStreamer thread, not the asyncio event loop. Synchronous commands work fine, but async ones (`wake_up`, `goto_sleep`, `goto_target`) call `asyncio.create_task()` which fails with "no running event loop".
**Fix:** `patches/fix_webrtc_asyncio.py` β€” creates a dedicated `asyncio.new_event_loop()` on a background thread and dispatches all data channel messages there via `call_soon_threadsafe()`.
This patch is applied automatically by `launch_sim_webrtc.py` for local simulation. For a real robot, copy the patch and apply it before the daemon starts (see instructions in the patch file).
The bug exists in reachy-mini v1.3.1. The command handler is in `reachy_mini/daemon/backend/abstract.py` β†’ `_process_webrtc_command()` (line ~874).
### Simulation
`launch_sim_webrtc.py` monkey-patches the daemon to replace hardware-specific GStreamer elements:
| Original (RPi) | Patched (desktop) | Purpose |
|---|---|---|
| `libcamerasrc` | `videotestsrc` (SMPTE bars) | Camera |
| `v4l2h264enc` | handled by `webrtcsink` | Encoding |
| `alsasrc` | `audiotestsrc` (silence) | Microphone |
| `alsasink` | skipped | Speaker |
Requires: Python 3.10+, GStreamer with Rust plugins (`brew install gstreamer` on macOS), and `reachy-mini[gstreamer]` package.
The sim exposes:
- `http://localhost:8000` β€” REST API + dashboard
- `ws://localhost:8443` β€” WebRTC signaling
## Build
```bash
npm run build # β†’ dist/ (static output, ~135KB JS + 2KB CSS)
npm run preview # preview the built app
```
## What's Done (V0)
- Svelte 5 + Vite 7 scaffold
- gstwebrtc-api v3 integration (built from source)
- WebRTC connection with producer discovery
- Video streaming display (full-viewport, mobile-first)
- Data channel for robot commands
- State polling (500ms) with reactive updates
- Basic controls: Connect, Disconnect, Wake Up, Sleep
- Robot state JSON viewer
- Desktop simulation support
- Daemon asyncio bug fix
## What's Next (V1+)
Per the brainstorm (`docs/brainstorm.md`), the roadmap includes:
- **AI models** β€” STT (Whisper via transformers.js), LLM (SmolLM2), TTS (Web Speech API / Kokoro), Vision (YOLO)
- **Tools system** β€” LLM-callable tools (`src/lib/tools/`) for object detection, web search, robot control
- **"Show Me" interaction** β€” the core UX where the robot directs what's on screen
- **Canvas overlay** β€” AR annotations on the video feed (`<canvas>` over `<video>`)
- **Web content display** β€” embedded videos, images, cards
- **Expressive behavior** β€” head tracking, gestures, conversational feedback
- **HF Spaces deployment** β€” static SDK, auto-build on push
- **`reachy-mini-js` npm package** β€” extract `src/lib/robot/` into standalone package