Stack — Qué Corre Dónde
┌─────────────────────────────────────────────────────┐
│ RPi 3B — Yocto Poky 4.3.4 │
│ └─ Docker (Bookworm container) │
│ │
│ systemd │
│ ├── murmurd.service (:64738) ← ya existente │
│ ├── ustreamer.service (:8080) ← NUEVO │
│ └── oasis.service (:3000) ← ya existente │
│ └── audio-bridge (interno) │
│ └── node-datachannel (interno) │
└─────────────────────────────────────────────────────┘
systemd Units — Copy-Paste Ready
ustreamer.service (NUEVO)
[Unit]
Description=µStreamer MJPEG server
After=network.target
[Service]
Type=simple
User=video
ExecStartPre=/sbin/modprobe bcm2835-v4l2
ExecStart=/usr/bin/ustreamer \
--host 127.0.0.1 \
--port 8080 \
--encoder=m2m-image \
--workers=3 \
--resolution=1280x720 \
--desired-fps=15 \
--drop-same-frames=20
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Para cámara USB: quitar ExecStartPre, cambiar --encoder a cpu, añadir --device=/dev/video0
Habilitar y arrancar
sudo systemctl daemon-reload
sudo systemctl enable --now ustreamer.service
sudo systemctl status ustreamer.service
Docker — Integración con Container Bookworm
El SNH corre Yocto Poky 4.3.4 como host, con un container Docker Debian Bookworm. µStreamer corre dentro del container pero necesita acceso a /dev/video* del host.
services:
snh:
image: snh-bookworm:latest
devices:
- /dev/video0:/dev/video0 # cámara
- /dev/video11:/dev/video11 # M2M encoder (si CSI)
ports:
- "3000:3000" # Oasis (único puerto público)
# µStreamer :8080 y murmurd :64738 son solo internos
volumes:
- mumble-data:/var/lib/mumble
cap_add:
- SYS_RAWIO # para V4L2 M2M si es necesario
No exponer :8080 ni :64738 en ports. Solo :3000 sale al exterior. Los servicios internos se comunican via localhost dentro del container.
Verificar devices dentro del container
docker exec -it snh ls -la /dev/video*
docker exec -it snh v4l2-ctl --list-devices
Monitoring — Health Checks
Script de health check (crontab cada 5 min)
curl -sf http://127.0.0.1:8080/state > /dev/null \
|| systemctl restart ustreamer.service
ss -tlnp | grep -q :64738 \
|| systemctl restart mumble-server.service
curl -sf http://127.0.0.1:3000/ > /dev/null \
|| systemctl restart oasis.service
Crontab entry
crontab -e
*/5 * * * * /usr/local/bin/snh-health.sh 2>&1 | logger -t snh-health
RAM monitoring (alerta si <100 MB libre)
FREE_MB=$(free -m | awk '/^Mem:/{print $7}')
if [ "$FREE_MB" -lt 100 ]; then
logger -t snh-health -p warning "LOW RAM: ${FREE_MB}MB free"
fi
Pre-Flight Checklist
| # | Check | Comando | Esperado |
| 1 | Cámara detectada | v4l2-ctl --list-devices | /dev/video0 aparece |
| 2 | M2M encoder (CSI) | ls /dev/video1* | /dev/video11 (bcm2835-codec) |
| 3 | User en grupo video | groups | "video" aparece |
| 4 | µStreamer instalado | which ustreamer | /usr/bin/ustreamer |
| 5 | µStreamer arranca | curl localhost:8080/state | JSON con fps > 0 |
| 6 | MJPEG stream | curl -I localhost:8080/stream | 200 + multipart/* |
| 7 | murmurd activo | ss -tlnp | grep 64738 | LISTEN |
| 8 | Oasis activo | curl -I localhost:3000 | 200 |
| 9 | Proxy video | curl -I localhost:3000/video | 200 + multipart/* |
| 10 | RAM available | free -m | awk '/Mem/{print $7}' | > 200 MB |
Boot Order — Dependencias
[kernel modules]
│ modprobe bcm2835-v4l2 + bcm2835-codec
▼
[murmurd] ← ya existente, sin deps nuevas
│
[ustreamer] ← After=network.target
│
[oasis] ← After=ustreamer.service mumble-server.service
├── audio-bridge (child process)
└── node-datachannel (in-process)
Añadir After=ustreamer.service mumble-server.service en oasis.service para que el proxy no falle al arrancar antes que los upstreams.
Logs — Comandos Útiles
journalctl -u ustreamer -f --no-pager
journalctl -u mumble-server -f --no-pager
journalctl -u oasis -f --no-pager
journalctl -t snh-health --since "1 hour ago"
free -m && ps aux --sort=-%mem | head -10
Keywords
systemd
ExecStart
Docker
devices
modprobe
crontab
journalctl
health check
V4L2
Yocto
Bookworm
free -m