Spaces:
Running
Running
PanGalactic Claude Opus 4.6 commited on
Commit ·
78b0f5a
1
Parent(s): 1e98f5f
Add HuggingFace Spaces live demo infrastructure
Browse filesDocker-based demo that runs the full dashboard with reachy-mini-daemon
in simulation mode. Supervisor manages daemon + app lifecycle. Includes
graceful fallbacks for missing hardware (GStreamer, Bluetooth) so the
app runs cleanly in containers without audio/BT hardware.
New files: Dockerfile.demo, supervisord.demo.conf, demo-start.sh,
README.demo.md, .dockerignore
Modified: audio_output.py (GStreamer availability check),
bluetooth.py (bluetoothctl availability check),
index.html (Try Live Demo button)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- .dockerignore +26 -0
- Dockerfile.demo +48 -0
- README.demo.md +36 -0
- demo-start.sh +48 -0
- hello_world/api/audio_output.py +7 -0
- hello_world/api/bluetooth.py +3 -0
- index.html +4 -1
- supervisord.demo.conf +31 -0
.dockerignore
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Landing page assets (not needed in demo container)
|
| 2 |
+
demo_video.mp4
|
| 3 |
+
frames/
|
| 4 |
+
screenshots/
|
| 5 |
+
docs.html
|
| 6 |
+
style.css
|
| 7 |
+
README.md
|
| 8 |
+
index.html
|
| 9 |
+
README.demo.md
|
| 10 |
+
|
| 11 |
+
# Git
|
| 12 |
+
.git/
|
| 13 |
+
.gitattributes
|
| 14 |
+
.gitignore
|
| 15 |
+
|
| 16 |
+
# Python
|
| 17 |
+
__pycache__/
|
| 18 |
+
*.pyc
|
| 19 |
+
*.pyo
|
| 20 |
+
*.egg-info/
|
| 21 |
+
|
| 22 |
+
# Media (runtime-generated, not needed in image)
|
| 23 |
+
media/
|
| 24 |
+
|
| 25 |
+
# Settings (runtime-generated on Reachy, not needed in image)
|
| 26 |
+
settings.json
|
Dockerfile.demo
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# HuggingFace Spaces demo — runs the full dashboard with a simulated robot.
|
| 2 |
+
#
|
| 3 |
+
# Build: docker build -f Dockerfile.demo -t hw-demo .
|
| 4 |
+
# Run: docker run -p 8042:8042 hw-demo
|
| 5 |
+
# Browse: http://localhost:8042
|
| 6 |
+
|
| 7 |
+
FROM python:3.12-slim
|
| 8 |
+
|
| 9 |
+
# ── System dependencies ──────────────────────────────────────────
|
| 10 |
+
# libosmesa6-dev — headless OpenGL for MuJoCo (no GPU needed)
|
| 11 |
+
# libgl1-mesa-glx — GL runtime (MuJoCo shared libs expect it)
|
| 12 |
+
# tmux — shell tab uses shared tmux sessions
|
| 13 |
+
# curl — startup script health checks
|
| 14 |
+
# supervisor — manages daemon + app lifecycle
|
| 15 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 16 |
+
libosmesa6-dev \
|
| 17 |
+
libgl1-mesa-glx \
|
| 18 |
+
tmux \
|
| 19 |
+
curl \
|
| 20 |
+
supervisor \
|
| 21 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 22 |
+
|
| 23 |
+
# MuJoCo headless rendering — OSMesa software renderer, no GPU required
|
| 24 |
+
ENV MUJOCO_GL=osmesa
|
| 25 |
+
|
| 26 |
+
# HuggingFace Spaces requires non-root user with uid 1000
|
| 27 |
+
RUN useradd -m -u 1000 user
|
| 28 |
+
USER user
|
| 29 |
+
ENV PATH="/home/user/.local/bin:${PATH}"
|
| 30 |
+
|
| 31 |
+
WORKDIR /home/user/app
|
| 32 |
+
|
| 33 |
+
# Install MuJoCo + reachy-mini SDK (provides reachy-mini-daemon command)
|
| 34 |
+
RUN pip install --no-cache-dir --user mujoco "reachy-mini"
|
| 35 |
+
|
| 36 |
+
# Copy the hello_world app and install as editable package
|
| 37 |
+
# (editable so the daemon discovers it via entry points)
|
| 38 |
+
COPY --chown=user:user . .
|
| 39 |
+
RUN pip install --no-cache-dir --user -e .
|
| 40 |
+
|
| 41 |
+
# Make startup script executable
|
| 42 |
+
RUN chmod +x demo-start.sh
|
| 43 |
+
|
| 44 |
+
# The app serves on port 8042 (HF proxy forwards to this)
|
| 45 |
+
EXPOSE 8042
|
| 46 |
+
|
| 47 |
+
# Supervisor manages: (1) daemon in sim mode, (2) startup script
|
| 48 |
+
CMD ["supervisord", "-n", "-c", "/home/user/app/supervisord.demo.conf"]
|
README.demo.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Hello World Demo
|
| 3 |
+
emoji: 🤖
|
| 4 |
+
colorFrom: purple
|
| 5 |
+
colorTo: cyan
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 8042
|
| 8 |
+
pinned: false
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# Hello World Demo
|
| 12 |
+
|
| 13 |
+
Live interactive demo of the **Hello World** dashboard running with a simulated Reachy Mini robot. No physical robot needed — the daemon runs in simulation mode with full MuJoCo kinematics.
|
| 14 |
+
|
| 15 |
+
**[View documentation and source code on the main space](https://huggingface.co/spaces/panny247/hello_world)**
|
| 16 |
+
|
| 17 |
+
## What works
|
| 18 |
+
|
| 19 |
+
- 3D MuJoCo simulation with 20 scenes and 21 skins
|
| 20 |
+
- Joystick head control (moves the simulated robot)
|
| 21 |
+
- 81 emotions and 20 dances with 3D preview
|
| 22 |
+
- 6 oscillation patterns (sway, nod, circle, breathe, figure-8, look around)
|
| 23 |
+
- AI conversation (bring your own API key in Settings)
|
| 24 |
+
- YOLO WebGPU vision (uses your browser camera)
|
| 25 |
+
- System telemetry charts
|
| 26 |
+
- Settings persistence
|
| 27 |
+
- Scratchpad (generative UI)
|
| 28 |
+
- Timers and alarms
|
| 29 |
+
- Shell terminal
|
| 30 |
+
|
| 31 |
+
## Hardware-only features (unavailable in demo)
|
| 32 |
+
|
| 33 |
+
- Robot speaker and microphone (no USB audio hardware)
|
| 34 |
+
- Bluetooth A2DP (no BlueZ stack)
|
| 35 |
+
- AuraBox LED display (no peripheral)
|
| 36 |
+
- Smart home integrations (no local network access)
|
demo-start.sh
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Startup script for HuggingFace Spaces demo.
|
| 3 |
+
#
|
| 4 |
+
# Waits for the daemon to be healthy, wakes it up (transitions from
|
| 5 |
+
# not_initialized → ready), then starts the hello_world app.
|
| 6 |
+
|
| 7 |
+
set -e
|
| 8 |
+
|
| 9 |
+
DAEMON_URL="http://localhost:8000"
|
| 10 |
+
APP_URL="http://localhost:8042"
|
| 11 |
+
|
| 12 |
+
# ── Wait for daemon health endpoint ──────────────────────────────
|
| 13 |
+
echo "[demo] Waiting for daemon to start..."
|
| 14 |
+
for i in $(seq 1 60); do
|
| 15 |
+
if curl -sf "${DAEMON_URL}/api/daemon/status" > /dev/null 2>&1; then
|
| 16 |
+
echo "[demo] Daemon is up (took ${i}s)"
|
| 17 |
+
break
|
| 18 |
+
fi
|
| 19 |
+
if [ "$i" -eq 60 ]; then
|
| 20 |
+
echo "[demo] ERROR: Daemon did not start within 60s"
|
| 21 |
+
exit 1
|
| 22 |
+
fi
|
| 23 |
+
sleep 1
|
| 24 |
+
done
|
| 25 |
+
|
| 26 |
+
# ── Wake up the daemon (not_initialized → ready) ─────────────────
|
| 27 |
+
echo "[demo] Waking up daemon..."
|
| 28 |
+
curl -sf -X POST "${DAEMON_URL}/api/daemon/start?wake_up=true" || true
|
| 29 |
+
sleep 3
|
| 30 |
+
|
| 31 |
+
# ── Start the hello_world app ────────────────────────────────────
|
| 32 |
+
echo "[demo] Starting Hello World app..."
|
| 33 |
+
curl -sf -X POST "${DAEMON_URL}/api/apps/start-app/hello_world" || true
|
| 34 |
+
|
| 35 |
+
# ── Wait for the app to be serving ────────────────────────────────
|
| 36 |
+
echo "[demo] Waiting for app to respond..."
|
| 37 |
+
for i in $(seq 1 30); do
|
| 38 |
+
if curl -sf "${APP_URL}/api/settings" > /dev/null 2>&1; then
|
| 39 |
+
echo "[demo] Hello World is live on port 8042"
|
| 40 |
+
break
|
| 41 |
+
fi
|
| 42 |
+
if [ "$i" -eq 30 ]; then
|
| 43 |
+
echo "[demo] WARNING: App did not respond within 30s (may still be starting)"
|
| 44 |
+
fi
|
| 45 |
+
sleep 1
|
| 46 |
+
done
|
| 47 |
+
|
| 48 |
+
echo "[demo] Startup complete."
|
hello_world/api/audio_output.py
CHANGED
|
@@ -36,11 +36,15 @@ __all__ = ['AudioRouter']
|
|
| 36 |
import asyncio
|
| 37 |
import logging
|
| 38 |
import os
|
|
|
|
| 39 |
import subprocess
|
| 40 |
import tempfile
|
| 41 |
|
| 42 |
import numpy as np
|
| 43 |
|
|
|
|
|
|
|
|
|
|
| 44 |
logger = logging.getLogger("reachy_mini.app.hello_world.audio_output")
|
| 45 |
|
| 46 |
# Bluetooth A2DP sample rate (full quality)
|
|
@@ -158,6 +162,9 @@ class AudioRouter:
|
|
| 158 |
hardware with the daemon. This is the same approach as the reference
|
| 159 |
app and ensures the XMOS DSP sees the reference signal for AEC.
|
| 160 |
"""
|
|
|
|
|
|
|
|
|
|
| 161 |
cmd = [
|
| 162 |
"gst-launch-1.0", "-q",
|
| 163 |
"filesrc", f"location={filepath}", "!",
|
|
|
|
| 36 |
import asyncio
|
| 37 |
import logging
|
| 38 |
import os
|
| 39 |
+
import shutil
|
| 40 |
import subprocess
|
| 41 |
import tempfile
|
| 42 |
|
| 43 |
import numpy as np
|
| 44 |
|
| 45 |
+
# Check once at import time — avoids noisy error logs in containers without GStreamer
|
| 46 |
+
_HAS_GSTREAMER = shutil.which("gst-launch-1.0") is not None
|
| 47 |
+
|
| 48 |
logger = logging.getLogger("reachy_mini.app.hello_world.audio_output")
|
| 49 |
|
| 50 |
# Bluetooth A2DP sample rate (full quality)
|
|
|
|
| 162 |
hardware with the daemon. This is the same approach as the reference
|
| 163 |
app and ensures the XMOS DSP sees the reference signal for AEC.
|
| 164 |
"""
|
| 165 |
+
if not _HAS_GSTREAMER:
|
| 166 |
+
logger.debug("GStreamer not available, skipping robot audio playback")
|
| 167 |
+
return
|
| 168 |
cmd = [
|
| 169 |
"gst-launch-1.0", "-q",
|
| 170 |
"filesrc", f"location={filepath}", "!",
|
hello_world/api/bluetooth.py
CHANGED
|
@@ -22,6 +22,7 @@ import json
|
|
| 22 |
import logging
|
| 23 |
import math
|
| 24 |
import re
|
|
|
|
| 25 |
import subprocess
|
| 26 |
import urllib.request
|
| 27 |
|
|
@@ -44,6 +45,8 @@ _UUID_CAPABILITIES = {
|
|
| 44 |
|
| 45 |
def _run_bluetoothctl(args: list, timeout: int = 10) -> subprocess.CompletedProcess:
|
| 46 |
"""Run a bluetoothctl command with timeout."""
|
|
|
|
|
|
|
| 47 |
return subprocess.run(
|
| 48 |
["bluetoothctl"] + args,
|
| 49 |
capture_output=True, text=True, timeout=timeout
|
|
|
|
| 22 |
import logging
|
| 23 |
import math
|
| 24 |
import re
|
| 25 |
+
import shutil
|
| 26 |
import subprocess
|
| 27 |
import urllib.request
|
| 28 |
|
|
|
|
| 45 |
|
| 46 |
def _run_bluetoothctl(args: list, timeout: int = 10) -> subprocess.CompletedProcess:
|
| 47 |
"""Run a bluetoothctl command with timeout."""
|
| 48 |
+
if not shutil.which("bluetoothctl"):
|
| 49 |
+
raise FileNotFoundError("bluetoothctl not found")
|
| 50 |
return subprocess.run(
|
| 51 |
["bluetoothctl"] + args,
|
| 52 |
capture_output=True, text=True, timeout=timeout
|
index.html
CHANGED
|
@@ -524,7 +524,10 @@
|
|
| 524 |
<div class="stat-label">Robot Skins</div>
|
| 525 |
</div>
|
| 526 |
</div>
|
| 527 |
-
<
|
|
|
|
|
|
|
|
|
|
| 528 |
</div>
|
| 529 |
</section>
|
| 530 |
|
|
|
|
| 524 |
<div class="stat-label">Robot Skins</div>
|
| 525 |
</div>
|
| 526 |
</div>
|
| 527 |
+
<div style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;">
|
| 528 |
+
<a href="https://huggingface.co/spaces/panny247/hello_world-demo" class="hero-btn" style="background: linear-gradient(135deg, rgba(124, 92, 252, 0.25), rgba(92, 224, 216, 0.15));">Try Live Demo <span class="arrow">→</span></a>
|
| 529 |
+
<a href="docs.html" class="hero-btn">Read the Full Documentation <span class="arrow">→</span></a>
|
| 530 |
+
</div>
|
| 531 |
</div>
|
| 532 |
</section>
|
| 533 |
|
supervisord.demo.conf
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
; Supervisor config for HuggingFace Spaces demo.
|
| 2 |
+
;
|
| 3 |
+
; Manages two processes:
|
| 4 |
+
; 1. reachy-mini-daemon --sim — simulated robot server on port 8000
|
| 5 |
+
; 2. demo-start.sh — waits for daemon, wakes it, starts the app
|
| 6 |
+
|
| 7 |
+
[supervisord]
|
| 8 |
+
nodaemon=true
|
| 9 |
+
logfile=/tmp/supervisord.log
|
| 10 |
+
pidfile=/tmp/supervisord.pid
|
| 11 |
+
childlogdir=/tmp
|
| 12 |
+
|
| 13 |
+
[program:daemon]
|
| 14 |
+
command=reachy-mini-daemon --sim
|
| 15 |
+
autostart=true
|
| 16 |
+
autorestart=true
|
| 17 |
+
stdout_logfile=/dev/stdout
|
| 18 |
+
stdout_logfile_maxbytes=0
|
| 19 |
+
stderr_logfile=/dev/stderr
|
| 20 |
+
stderr_logfile_maxbytes=0
|
| 21 |
+
|
| 22 |
+
[program:startup]
|
| 23 |
+
command=/home/user/app/demo-start.sh
|
| 24 |
+
autostart=true
|
| 25 |
+
autorestart=false
|
| 26 |
+
startsecs=0
|
| 27 |
+
exitcodes=0
|
| 28 |
+
stdout_logfile=/dev/stdout
|
| 29 |
+
stdout_logfile_maxbytes=0
|
| 30 |
+
stderr_logfile=/dev/stderr
|
| 31 |
+
stderr_logfile_maxbytes=0
|