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

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 net/webrtc/gstwebrtc-api/. Stored as vendor/gstwebrtc-api-3.0.0.tgz.

v3 API patterns (different from v2):

// 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:

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

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