Skip to content

22 Home Camera Architecture

Anubha Parashar edited this page Oct 10, 2026 · 1 revision

The Home Activity Camera is an optional, local-first add-on that turns a home RTSP, ONVIF or HTTP camera into suggested activity events for Daily Health (internal code name: BabyCare). A worker process on a machine inside the home network reads the stream, runs a coarse motion and presence analyser, and sends only derived metadata (kind, time window, confidence, a few ratios) to the API. By default raw video stays on the home network. Every event starts as ai_observed and counts for nothing until a caregiver confirms or corrects it.

Responsibilities

  • Store camera configuration with the stream URL and credentials application-encrypted, and never return them to the app.
  • Allow private-network stream addresses by default, and strip credentials embedded in URLs.
  • Authenticate the local worker with a per-camera key. Only the key's hash is stored.
  • Accept derived events only, deduplicated and filtered by analysis mode and enabled state.
  • Provide review (confirm, correct, dismiss), add-to-journal and analytics that use parent-confirmed events only.
  • Offer privacy controls: instant disable, retention setting, purge recordings, delete camera.

Inputs and outputs

Endpoint Auth Purpose
GET/POST /api/patients/{pid}/cameras user (add: primary guardian) list (redacted) and privacy notes, or add a camera
PUT/PATCH/DELETE /api/cameras/{cid} user (PUT/DELETE: primary guardian) edit, toggle settings, delete
POST /api/cameras/{cid}/disable user instant off switch
POST /api/cameras/{cid}/test user RTSP OPTIONS/DESCRIBE probe (Basic/Digest) or HTTP HEAD
POST /api/cameras/{cid}/worker-key primary guardian issue or rotate the worker key, shown once
GET /api/cameras/{cid}/preview user one JPEG frame, only if OpenCV is installed on the API host
GET /api/cameras/{cid}/worker-config X-Camera-Worker-Key stream URL and credentials for the worker only
POST /api/cameras/{cid}/worker/ingest X-Camera-Worker-Key heartbeat, state and up to 500 derived events
GET /api/patients/{pid}/camera-observations user events with AI OBSERVED / PARENT CONFIRMED / PARENT CORRECTED / DISMISSED labels
POST /api/camera-observations/{oid}/review user confirm, correct (with corrected_kind) or dismiss
POST /api/camera-observations/{oid}/add-to-journal user create an activity or sleep entry with source=camera_confirmed
GET /api/patients/{pid}/camera-analytics user confirmed minutes by day, pending count
POST /api/cameras/{cid}/purge-recordings user delete stored clip blobs

Architecture

flowchart LR
  subgraph HOME["Home network"]
    CAM["Local RTSP or ONVIF camera"] -->|"stream read locally"| WK["Local worker<br/>api.camera_worker"]
    WK --> INF["Activity and movement inference<br/>MotionAnalyser on 64x36 grayscale"]
    INF --> META["Derived metadata<br/>kind, window, confidence, ratios"]
    RAW["Raw video and frames<br/>stay local by default"]
    WK -.-> RAW
  end
  META -->|"HTTPS with worker key"| API["FastAPI<br/>worker ingest"]
  API --> OBS["Camera observation<br/>review_status ai_observed"]
  OBS --> REV{"Caregiver review"}
  REV -->|"confirm or correct"| CONF["Confirmed observation"]
  REV -->|"dismiss"| DIS["Dismissed<br/>hidden from timeline"]
  CONF -->|"add to journal"| REC["Confirmed record<br/>activity or sleep entry<br/>source camera_confirmed"]
  REC --> DH["Daily Health timeline<br/>and calendar"]
Loading

Worker and keys

python -m api.camera_worker --api <url> --camera <id> --key <key> (or BABYCARE_API_URL, BABYCARE_CAMERA_ID, BABYCARE_CAMERA_KEY) runs on a home machine. The worker:

  1. Fetches worker-config with its key. If the camera is disabled or the analysis mode is off, it reports disconnected and idles, checking again every 30 s.
  2. Opens the stream with opencv-python-headless (required for real cameras) or --simulate N synthetic frames, and downsamples each frame to 64×36 grayscale.
  3. Feeds frames to MotionAnalyser and sends closed segments plus a heartbeat at least every 60 s.
  4. Stops when the ingest response says enabled: false. On errors it reports reconnecting and retries with exponential back-off up to 120 s.
  5. Refuses a non-local API that is not https://.

camera.new_worker_key() makes a token_urlsafe(32) key. Only sha256(key) is kept in worker_key_hash. Issuing a new key invalidates the old one. worker_state takes the values never_connected, connected, reconnecting, disconnected and disabled.

Encrypted camera credentials

stream_url, username, password, name and room are EncryptedText columns. split_credentials() moves any user:pass@ in the URL into separate fields. API responses carry only stream_display (via redact(), which removes credentials and query strings) and a has_credentials flag. Credentials are returned in clear only by worker-config, and only to the holder of the worker key. Edit snapshots in record_revisions exclude the URL and credentials.

Analysis modes and confidence

Mode Effect
off ingest accepts no events
motion (default) MotionAnalyser events are accepted
activity stored and returned to the worker. The bundled worker runs the same MotionAnalyser

MotionAnalyser emits movement (motion ratio ≥ 0.025) and low_activity (≤ 0.004) segments of at least 20 s (5 s in simulation). Confidence is 0.4 + 0.4·signal strength + 0.2·min(1, length/300 s), so it always lies between 0.4 and 1.0. Ingest accepts the full OBSERVATION_KINDS vocabulary (movement, active, low_activity, presence, no_presence, rest, standing, sitting, walking_like, crawling_like). Posture classes with confidence below 0.6 are skipped, and duplicates are dropped by (camera_id, dedupe_key).

Review states

ai_observed → parent_confirmed | parent_corrected (with corrected_kind and note) | dismissed. Add-to-journal creates an ActivityEntry or SleepEntry (rest is suggested as sleep) with client_key=camera-{id}, so it cannot be added twice (409), and confirms an ai_observed event. The month calendar counts only parent-confirmed events. camera-analytics sums minutes from confirmed and corrected events and reports AI-only events separately as pending.

Privacy defaults

Setting Default
enabled false
local_network_only true: a non-private host returns 422 unless explicitly allowed
audio_enabled false. The worker never captures audio
record_clips false
cloud_upload_allowed false
retention_days 7 (0–90)

Disabling sets worker_state=disabled, after which ingest accepts nothing. Deleting a camera deletes its clip blobs and its observations (cascade). The preview endpoint is the one path where a frame passes through the API host: it is on-demand, authenticated, and only works where that host has OpenCV and can reach the camera.

What it never infers

The ingest schema is a closed Literal list, so a fever event is rejected with 422. The camera never creates symptom, fever, medicine or stool records (test_camera_api_hides_credentials_and_handles_review_states checks that symptoms stays empty). The UI and API warnings state that events describe movement and presence only and are "never used to infer fever, illness, pain or development". walking_like and crawling_like need a pose model that this build does not ship, so the bundled analyser never emits them.

Implementation

api/camera.py, api/camera_api.py, api/camera_worker.py, api/care_models.py (Camera, CameraObservation), api/care.py (timeline and calendar integration), mobile/CameraScreen.tsx, web/care.js, tests/test_camera.py.

Related pages

Daily Health and Care Calendar · Encryption and Private Storage · Security and Privacy Model · Design Decisions · Limitations · Roadmap

Clone this wiki locally