Device-side software for automated image capture on Raspberry Pi, integrated with the R9lab platform.
- Installation Guide - hardware setup, pairing, Docker, standalone mode, and troubleshooting
- Architecture - runtime design, relay flow, auth boundaries, and configuration model
- Contributing - local development, tests, quality checks, and release workflow
- Platform Docs - camera management in R9lab
The plugin runs a lightweight FastAPI server on a Raspberry Pi that:
- captures still images from the connected camera module
- publishes an HLS preview through the local MediaMTX sidecar
- connects to R9lab through an outbound WebSocket relay
- exposes local REST endpoints for setup, diagnostics, and direct integrations
Supported hardware is Raspberry Pi 5/4 with Camera Module 3/v2 on Raspberry Pi OS 64-bit.
flowchart LR
app["R9lab app<br/>(phone or browser)"]
backend["R9lab backend"]
subgraph station["Raspberry Pi"]
plugin["This plugin"]
camera["Camera module"]
end
plugin == "outbound WebSocket relay<br/>(opened by the Pi)" ==> backend
app -- "capture and preview requests" --> backend
backend -. "forwarded over the relay" .-> plugin
plugin --- camera
plugin -- "captures uploaded" --> backend
app <-.->|"LAN direct: HTTP with the local API key<br/>(set up by the backend over the relay)"| plugin
The Pi always opens the connection, so the relay needs no open port on the Pi's network. To pair, the Pi shows a short code on its /setup page, and you enter that code in the R9lab app. After that, the backend sends capture and preview requests down the relay, and the Pi only runs the routes it allows.
When the app and the Pi share a network, the app can skip the relay. The backend asks the Pi over the relay for its local API key and LAN addresses, and passes them to the app. The app then talks to the Pi directly over HTTP, for faster preview and capture, without anyone copying a key by hand.
The platform docs have the full diagram, with LAN direct mode, preview streaming and the pairing sequence. ARCHITECTURE.md covers the plugin's internals.
| Mode | Purpose |
|---|---|
| Paired relay | The Pi opens an outbound WebSocket relay to the R9lab backend. |
| Local direct | LAN/Ethernet clients call the Pi API with X-API-Key. |
| Backend upload | Captures are pushed back to the R9lab backend. |
| S3 upload | Captures are written to a configured S3-compatible bucket. |
- Follow INSTALL.md to prepare the Pi, generate
compose.override.yml, and start the service. - Set
PAIRING_BACKEND_URLin.env. - Read the 6-character pairing code from
/setupor thePAIRING READYlog banner. - Enter the pairing code in the native R9lab app.
- Visit
http://your-pi-ip:8018/setupto check pairing, status, and diagnostics.
Use the generated RPi camera API reference for endpoint-level documentation. The Pi-hosted Swagger/OpenAPI routes are development-only.
The HTTPS-served R9lab web frontend cannot auto-probe the Pi's plain-HTTP local API because browsers block mixed content. Use the native app for pairing and direct-mode setup.
The plugin can run without a R9lab backend by writing captures to an S3-compatible bucket. The default standalone stack uses a loopback-only RustFS sidecar and APP_ENV=development for local HTTP storage. Remote production S3 endpoints require HTTPS.
Captured images, queued retries, dead letters, and preview thumbnails are sensitive local device data. In standalone S3 mode, generated public URLs are reachable wherever the configured bucket, endpoint, or proxy is reachable, so keep image storage private or LAN-only when captures are sensitive.
See INSTALL.md#standalone-mode for the complete .env example and runtime checks.
Local mode is enabled by default. On first boot the plugin generates a persistent local API key. Paired R9lab apps can retrieve that key through the relay for lower-latency LAN access, and custom local clients can use it with X-API-Key.
The local key is an auth gate for the direct interface. It does not register a camera in R9lab; relay pairing still does that. See INSTALL.md#local-direct-mode for key retrieval and network notes.
The plugin writes redacted structured logs and can ship logs, metrics, and traces to an external observability stack. OTLP tracing is opt-in with OTEL_ENABLED=true and OTEL_EXPORTER_OTLP_ENDPOINT; remote production OTLP endpoints require HTTPS.
See INSTALL.md#observability for operator configuration and ARCHITECTURE.md#observability for the internal pipeline.
Use INSTALL.md#troubleshooting for operator issues. Use CONTRIBUTING.md for development setup and test workflow.
| What | Licence |
|---|---|
| Plugin software | AGPL-3.0-or-later |
The published API specification (openapi.r9cam.json) |
Apache-2.0 |
The contract models package (r9cam_models, on PyPI) |
Apache-2.0 |
The specification and the contract models are carved out so that anyone writing a client, an integration or their own device against this camera API does not inherit the plugin's copyleft. This mirrors the same split in the R9lab platform repository, so every published R9lab API schema answers the licensing question the same way.