camera

Hub API

The hub runs a FastAPI backend that manages cameras and users. Endpoints require a bearer token obtained from /api/auth/login unless noted. See ../communication/README.md for a diagram of how the hub fits into the system.

Configuration

python-dotenv loads variables from .env automatically. On first run the hub generates a random CAMMY_SECRET_KEY, saves it to .env and cammy.secret, and prints the value. Once persisted, providing a different key via the environment causes the hub to exit. Optional CAMMY_ADMIN_USER and CAMMY_ADMIN_PASSWORD create the initial admin when no users exist. If omitted the hub defaults to an admin account with password cammy-cam.

Local HTTPS

The hub supports local HTTPS with mDNS discovery. Certificates are generated per hostname using mkcert:

mkcert -install
CAMMY_NAME=cammy.local mkcert "$CAMMY_NAME"
mkdir -p certs
mv "$CAMMY_NAME.pem" "certs/$CAMMY_NAME.pem"
mv "$CAMMY_NAME-key.pem" "certs/$CAMMY_NAME-key.pem"

start-local.sh and start-docker.sh look for certificates under certs/<CAMMY_NAME>.pem and will generate them automatically if they are missing. Inside the container the working directory is /app, so paths remain relative.

To build the hub container with the compiled UI and fresh certificates, run the Python helper:

python tools/build_docker.py

Set CAMMY_PORT in your .env file to choose the HTTPS port (default 443). The Dockerfile exposes the port defined by the build argument HUB_PORT (also 443 by default). Run start-local.sh or the container to launch HTTPS on that port. The startup script prints the login URL (ending in /login) so you can sign in right away. The optional CAMMY_TZ variable controls the time zone for token expiration timestamps (default America/New_York). The container advertises _https._tcp via avahi-publish using the CAMMY_NAME environment variable (for example cammy-92ab.local). Make sure avahi-daemon, avahi-utils, and dbus are installed on your host (the latter provides avahi-publish). Each container on the same network should have a unique CAMMY_NAME to avoid mDNS collisions. The hub also performs periodic mDNS discovery for _cammycam._tcp.local services and records the IP addresses of ESP32 cameras that announce themselves.

Authentication

Method Path Description
POST /api/auth/login Obtain a token pair (access & refresh).
POST /api/users Provision a new user (admin or super only).
GET /api/users List all users (admin or super only).
GET /api/integrations List integration accounts (admin or super).
POST /api/integrations Create an integration account.
DELETE /api/integrations/{username} Delete an integration account.
POST /api/auth/change-password Change the current user’s password.
POST /api/auth/reset-request Create a password reset token.
POST /api/auth/reset-confirm Confirm password reset with the token.
POST /api/auth/refresh Refresh an expired access token.
POST /api/auth/unlock Unlock a locked account using OTP.

Token Expiration

Account Lockout

User accounts temporarily lock after 5 failed logins for 5 minutes. If another 5 failures occur afterwards, the account is permanently locked. Admin accounts can be recovered with an OTP generated during the first login.

Integration Role

Integration accounts cannot log in to the UI. They are created by admins and receive scoped API permissions such as viewer, admin, or super. When authenticating, they receive the same JSON response as normal users but with the longer token lifetime.

Cameras

Method Path Description
POST /api/cameras/register Register or update a camera record.
POST /api/key-exchange ECDH handshake for encrypted streams.
GET /api/cameras List all cameras.
GET /api/cameras/{camera_id} Get details for a camera.
PUT /api/cameras/{camera_id} Update a camera’s info.
DELETE /api/cameras/{camera_id} Remove a camera entry.
GET /api/cameras/{camera_id}/status Connection and client counts.

Streaming

Type Path Notes
WebSocket /ws/camera/{camera_id} Camera connects and pushes frames.
WebSocket /ws/client/{camera_id} Clients receive frames.
HTTP /mjpeg/{camera_id} MJPEG fallback for browsers.

Diagnostics

Method Path Description
GET /api/speedtest Returns latency for a small transfer.
GET /api/system-metrics CPU, memory, disk, network usage.
POST /api/upload-clip-part Save a recording chunk from a camera.
POST /api/upload-clip-meta Finalize and merge recorded chunks.

Events

Method Path Description
POST /api/event/motion Receive a motion detection event.

Example payload:

{
  "camera_id": "abcd1234",
  "active": true
}

After logging in you can visit /api-docs for the interactive API documentation. The underlying OpenAPI schema is available at /openapi.json and also requires authentication.

Devices

Method Path Description
POST /api/devices/{camera_id}/format-sd Format the camera SD card
GET /api/devices/{camera_id}/sd-status Query SD card usage
POST /api/devices/{camera_id}/backup-config Request config backup
POST /api/devices/{camera_id}/reboot Reboot the camera

Database Models

The hub uses SQLite via SQLAlchemy. The main tables are shown below.

classDiagram
    class User {
        string username
        string password
        string salt
        string role
        bool must_reset
    }

    class Setting {
        string key
        string value
    }

    class AuthHistory {
        int id
        string username
        bool success
        datetime timestamp
    }

    class ConfigChange {
        int id
        string user
        string change
        datetime timestamp
    }

MQTT Integration

CammyCam publishes camera status and motion events over MQTT so Home Assistant or other services can react. Enable it by setting CAMMY_MQTT_HOST (and optional credentials) in your .env file.

Environment Variables

Topics

The hub also publishes Home Assistant discovery messages on homeassistant/binary_sensor/<camera_id>_motion/config and homeassistant/binary_sensor/<camera_id>_status/config so cameras appear automatically with binary motion and status sensors.