Device-side software for automated image capture on Raspberry Pi, integrated with the Reverse Engineering Lab platform.
- Installation Guide — Hardware, setup, and configuration
- Contributing — Development workflow and testing
- Platform Docs — Camera management in RELab
The plugin runs a lightweight FastAPI server on your Raspberry Pi that:
- Captures images from the connected camera module
- Publishes a low-latency LL-HLS preview through the local MediaMTX sidecar
- Connects to the RELab platform via WebSocket relay
- Exposes a REST API for manual testing and integration
Supports Raspberry Pi 5/4 with Camera Module 3/v2, running on Raspberry Pi OS (64-bit) with Python 3.13+.
The app uses a feature-first layout. Each HTTP feature (camera/, pairing/, auth/, system/, frontend/) owns its router, schemas, dependencies, and services in one folder. Infra packages (backend/, relay/, media/, upload/, image_sinks/) live as peers at app/ root. A small AppRuntime in app/core/ owns the long-lived process services (camera manager, relay, pairing, preview pipeline, workers).
Config precedence: env-backed Settings → persisted credentials file (~/.config/relab/relay_credentials.json) → generated first-boot defaults. The device contract lives in the separately published relab_rpi_cam_models package.
See CONTRIBUTING.md for a deeper tour.
| Mode | What it does |
|---|---|
| Paired relay | Outbound WebSocket relay to the RELab backend |
| Local direct | Ethernet/LAN access with X-API-Key auth |
| Backend upload | Captures pushed back to the RELab backend |
| S3 upload | Captures written to a configured S3-compatible bucket |
- Install the plugin on your Pi.
- Set
PAIRING_BACKEND_URLin.envand start the plugin (docker compose up -dorjust dev). - Enter the 6-character pairing code (shown on
/setupor in thePAIRING READYlog banner) in the RELab app. - Visit
http://your-pi-ip:8018to check:/setup— pairing and status/docs— API reference (Swagger UI)/preview/hls/cam-preview/index.m3u8— LL-HLS live preview (MediaMTX sidecar on:8888)/camera/controls— discover/set autofocus, exposure, etc./captures— trigger a still capture
Need a fresh code? Click Generate a new pairing code on /setup. It rotates the code without unpairing the camera.
Docker device mappings: generate compose.override.yml with ./scripts/generate_compose_override.py.
The native RELab app drives pairing end-to-end. In the HTTPS-served web frontend, browsers block plain-HTTP calls to the Pi as mixed content, so direct-mode auto-probing doesn't apply — use the native app or call the Pi manually.
Ship structured logs to an external Loki-compatible collector via the observability-ship Compose profile:
COMPOSE_PROFILES=observability-ship
LOKI_PUSH_URL=http://your-observability-host:3100/loki/api/v1/push
OBSERVABILITY_INSTANCE=pi-01Tracing is opt-in via OTEL_ENABLED=true + OTEL_EXPORTER_OTLP_ENDPOINT. Neither Loki/Grafana nor an OTLP collector is bundled — point at your central stack.
For platform-side operation, see the RELab camera guide.
The plugin can also run fully standalone, writing captures straight to a local S3-compatible bucket (RustFS by default) instead of pushing them to the RELab backend. This is useful for hobbyist / bench / offline-first setups.
Everything lives in the same compose.yml. Standalone mode uses a separate
build target that includes the S3 client (aioboto3); the default paired build
does not. Set the following in .env and rebuild:
# Select the standalone build target and start the RustFS sidecar
APP_BUILD_TARGET=runtime-standalone
COMPOSE_PROFILES=standalone
# S3 sink and RustFS credentials — see .env.example for the full list
IMAGE_SINK=s3
S3_ENDPOINT_URL=http://host.docker.internal:9000
S3_BUCKET=rpi-cam
S3_ACCESS_KEY_ID=rustfsadmin
S3_SECRET_ACCESS_KEY=change-me-to-a-strong-password
RUSTFS_SECRET_KEY=change-me-to-a-strong-passworddocker compose build && docker compose up -dOnce up:
- Camera API at
http://<pi-lan-ip>:8018(same as paired mode) - Live LL-HLS preview at the same URL shape as paired mode (proxied through
the Pi's own
/hlsendpoint; no RELab backend needed) - RustFS console at
http://<pi-lan-ip>:9001— log in withRUSTFS_ACCESS_KEY/RUSTFS_SECRET_KEY - Captures browsable under
http://<pi-lan-ip>:9000/rpi-cam/
To point the plugin at an external S3-compatible service (Backblaze B2,
Cloudflare R2, Wasabi, AWS S3, …), update S3_ENDPOINT_URL, credentials, and
S3_PUBLIC_URL_TEMPLATE in .env and rebuild. No code changes required.
Set COMPOSE_PROFILES= (empty) to skip the RustFS sidecar when using a managed
bucket.
Profiles combine freely: COMPOSE_PROFILES=standalone,observability-ship
runs the RustFS sidecar and ships logs to your central Loki.
Local mode is enabled by default. On first boot the plugin generates a local API key and persists it. Two use-cases:
- RELab app latency boost (after pairing) — the app fetches the key through the relay and switches to Ethernet-direct on the same LAN, dropping preview latency from ~2 s to ~0.4 s. No user action.
- Standalone / custom clients (no pairing needed) — call the API directly with
X-API-Key: <key>.
The local key is a latency optimization for paired cameras and an auth gate for custom clients. It does not replace relay pairing for registering a camera in the RELab app.
Disable with LOCAL_MODE_ENABLED=false.
On startup the plugin logs a banner showing the current mode + how to fetch the key. From an SSH session:
just show-key
# or directly from the credentials file:
python3 -c "import json,pathlib; print(json.loads((pathlib.Path.home()/'.config/relab/relay_credentials.json').read_text()).get('local_api_key',''))"- mDNS (optional): install
avahi-daemonand advertise_relab-rpi-cam._tcpon port 8018 to reach the Pi at<hostname>.local. See INSTALL.md for the service-file snippet. - Ethernet: any port works on any RPi model; link-local (
169.254.x.x) is negotiated automatically if no DHCP. - USB gadget mode (USB-C data): only on RPi Zero 2W and some RPi 4 revisions — not RPi 5.
- Contract: any device implementing
GET /camera,POST /captures, andGET /preview/hls/*withX-API-Keyauth works with the RELab frontend's local mode.
The local API key is the only authentication gate on the direct interface; physical access to the cable is the trust boundary.
See INSTALL.md — Troubleshooting. For development setup, see CONTRIBUTING.md.