Spaces:
Running
Running
File size: 8,651 Bytes
8b7ec50 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | # 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
|