Files
remote-rig/docs/CONTEXT.md
T
Otto 02fa6e4d4f
CI/CD / lint-and-typecheck (push) Failing after 0s
CI/CD / test (push) Has been skipped
CI/CD / build (push) Has been skipped
CI/CD / deploy (push) Has been skipped
Build (Dev) / build (push) Failing after 35s
docs: add comprehensive project context file (CONTEXT.md) for agent reference
2026-05-21 13:19:26 -04:00

13 KiB

RemoteRig — Project Context

Last updated: 2026-05-21 Repo: CubeCraft-Creations/remote-rig | Host: code.cubecraftcreations.com Local clone: /mnt/ai-storage/projects/remote-rig | Default branch: dev Discord: DISCORD_DEV_REMOTERIG_CHANNEL_ID Linear Epic: CUB-198


Overview

RemoteRig is a multi-camera remote monitoring system. It provides a camera grid dashboard for monitoring multiple GoPro cameras remotely. Cameras push status via MQTT/HTTP, the UI shows a live grid with SSE updates, and the system supports start/stop recording control.

Target hardware: Raspberry Pi Zero 2 W as the central hub, with ESP32 nodes attached to each GoPro camera for status collection and MQTT communication.

Tech Stack

Layer Technology Notes
Backend Go 1.25+ Chi v5 router, SQLite (modernc.org/sqlite), go-yaml v3
Frontend React 19 + TypeScript 5.7 Vite 6, Tailwind CSS 3.4
State Zustand 5 Client-side camera state store
Icons lucide-react 0.469
Real-time SSE (Server-Sent Events) /api/v1/events/stream
Messaging MQTT Mosquitto broker for ESP32 → hub communication
Database SQLite WAL mode, foreign keys enabled
Auth API key (Bearer token) Middleware, configurable in config.yaml
Testing Vitest 4.1 + testing-library/react Unit tests for React components
Linting ESLint 9 npm run lint
CI/CD Gitea Actions .gitea/workflows/ci.yaml, build-dev.yaml, deploy-dev.yaml

Architecture

ESP32 Node (per camera)
  │
  ├── HTTP POST /api/v1/cameras/:id/status  (status push)
  │
  └── MQTT ──→ Mosquitto Broker ──→ MQTT Subscriber (Go hub)
                                        │
                                        ▼
                         ┌──────────────────────────────┐
                         │  Go Central Hub (Pi Zero 2 W)│
                         │  ┌────────────────────┐      │
                         │  │  SQLite Database    │      │
                         │  └────────────────────┘      │
                         │  ┌────────────────────┐      │
                         │  │  SSE Hub            │      │
                         │  └────────────────────┘      │
                         └──────────────────────────────┘
                                        │
                              SSE /api/v1/events/stream
                                        │
                                        ▼
                              React Dashboard (Browser)

Key Architecture Decisions

  • SQLite over PostgreSQL: Single-node deployment on Pi Zero 2 W — no need for separate DB server
  • SSE over WebSocket: Simpler server-side; unidirectional updates from hub → browser are sufficient
  • MQTT for ESP32 → Hub communication: Lightweight, designed for IoT/embedded use
  • API key auth: Simple bearer token middleware; configurable key in config.yaml
  • Idempotent migrations: DB checks if tables exist before running migrations

Directory Layout

remote-rig/
├── cmd/server/main.go          # Entry point — config load, router setup, graceful shutdown
├── config.yaml                 # Runtime configuration
├── go.mod / go.sum             # Go dependencies
├── internal/
│   ├── api/
│   │   ├── api.go              # Package doc
│   │   ├── cameras.go          # GET /cameras, POST /cameras, GET /cameras/:id
│   │   ├── recording.go        # POST /cameras/:id/start, POST /cameras/:id/stop
│   │   └── status.go           # POST /cameras/:id/status (push from ESP32)
│   ├── auth/
│   │   └── middleware.go        # API key auth middleware
│   ├── db/
│   │   ├── db.go               # Open, migrations, WAL mode
│   │   └── migrations/
│   │       └── 001_create_tables.sql
│   └── events/
│       └── sse.go              # SSE hub (subscribe, broadcast)
├── pkg/models/
│   └── camera.go               # Camera, StatusLog, RecordingEvent, CameraStatus, Settings
├── src/                        # React frontend
│   ├── App.tsx                  # Main app — header, camera grid, footer
│   ├── components/
│   │   ├── CameraCard.tsx       # Single camera status card
│   │   ├── CameraCard.test.tsx  # Unit tests
│   │   └── index.ts
│   ├── hooks/
│   │   ├── useSSE.ts            # SSE connection hook
│   │   ├── useCameraStatus.ts   # Camera status hook
│   │   ├── useSystemHealth.ts   # System health hook
│   │   └── index.ts
│   ├── services/
│   │   └── api.ts               # API client
│   ├── store/
│   │   ├── useCameraStore.ts    # Zustand store
│   │   └── index.ts
│   ├── types/
│   │   └── index.ts             # TypeScript interfaces
│   ├── utils/
│   │   └── index.ts
│   └── main.tsx
├── docs/
│   ├── CONTEXT.md               # ← this file
│   └── plans/
│       └── 2026-05-21-cub-196-cameracard.md  # CameraCard implementation plan
├── .gitea/workflows/
│   ├── ci.yaml                 # PR CI: lint → typecheck → test → build
│   ├── build-dev.yaml          # Go binary build on dev push
│   └── deploy-dev.yaml         # SCP + SSH deploy with rollback
├── .env.example                # VITE_API_URL=http://localhost:8080/api
├── package.json
├── vite.config.ts
├── tailwind.config.js
└── tsconfig.json

Database Schema (SQLite)

cameras

Column Type Notes
camera_id TEXT PK Unique camera identifier
friendly_name TEXT NOT NULL Human-readable name
mac_address TEXT UNIQUE MAC address (optional)
created_at DATETIME Default now
updated_at DATETIME Default now

status_logs

Column Type Notes
id INTEGER PK AUTO
camera_id TEXT FK → cameras
recorded_at DATETIME Default now
battery_pct INTEGER Nullable
video_remaining_sec INTEGER Nullable
recording_state INTEGER 0=idle, 1=recording
mode TEXT e.g. "video"
resolution TEXT e.g. "1080p"
fps INTEGER
online INTEGER 0=offline, 1=online
raw_battery_pct REAL Float precision

Index: (camera_id, recorded_at DESC)

recording_events

Column Type Notes
id INTEGER PK AUTO
camera_id TEXT FK → cameras
started_at DATETIME NOT NULL
stopped_at DATETIME Null while recording
reason TEXT e.g. "manual"
duration INTEGER Seconds

Index: (camera_id, started_at DESC)

settings

Column Type Notes
key TEXT PK
value TEXT NOT NULL
updated_at DATETIME Default now

Default seeds: poll_interval_sec=30, low_battery_threshold=15, low_storage_alert_sec=300

API Endpoints

Method Path Auth Description
GET /health No Health check → {"status":"ok"}
GET /api/v1/cameras Yes List all cameras with latest status
POST /api/v1/cameras Yes Register a new camera
GET /api/v1/cameras/:id Yes Camera detail + latest status + 24h history
POST /api/v1/cameras/:id/start Yes Start recording + MQTT publish
POST /api/v1/cameras/:id/stop Yes Stop recording
POST /api/v1/cameras/:id/status Yes Push status from ESP32 node
GET /api/v1/events/stream No (SSE) Real-time camera status stream

Configuration (config.yaml)

db_path: "remoterig.db"         # SQLite database path
api_key: "changeme"             # Bearer token for API auth
port: 8080                      # HTTP listen port
read_timeout: 5s
write_timeout: 10s
idle_timeout: 120s
mqtt:
  broker: "localhost:1883"
  client_id: "remoterig-hub"
platform:
  type: "pi-zero-2w"
  max_cameras: 16

Frontend Component Tree

App
├── Header
│   ├── Logo + Title ("RemoteRig Dashboard")
│   └── Stats bar (online count, recording count)
├── CameraGrid
│   └── CameraCard[] (responsive: 1→2→3→4 columns)
│       ├── Camera name + online/offline badge
│       ├── Resolution + FPS display
│       ├── Recording indicator (pulsing dot + REC/IDLE badge)
│       ├── Battery bar (color-coded: green/yellow/red)
│       └── Footer (Live/Last seen + video remaining)
└── Footer
    └── "RemoteRig v0.1.0 — Multi-Camera Remote Monitoring System"

Empty state: "Waiting for cameras..." with pulsing radio icon when no cameras connected. Offline state: Camera card dimmed with dashed border, shows "Last seen Xm ago".

Color Palette (Tailwind — dark dashboard theme)

Custom theme in tailwind.config.js:

  • rig-dark-900 (background), rig-dark-800, rig-dark-700 (cards), etc.
  • rig-accent (accent color)
  • rig-success (green — battery ≥50%, online)
  • rig-warning (yellow — battery 15-49%)
  • rig-danger (red — battery <15%, offline, recording)

Linear Issue Map

CUB Title Status
198 Epic: Multi-camera remote monitoring system Backlog
178 UX/UI design: camera monitoring dashboard mockups Backlog
179 Logging & persistence strategy In Review
180 Risk mitigation checklist Done
181 Scaffold Go module, directory layout, config, main.go Done
182 Scaffold Go module (dup of 181) Backlog
183 SQLite schema migration + DB init Backlog
184 API key auth middleware Backlog
185 Camera/StatusLog/RecordingEvent Go models Backlog
186 GET /api/v1/cameras (list with live status) Backlog
187 POST /api/v1/cameras/:id/start + MQTT publish Done
188 POST /api/v1/cameras/:id/stop recording Backlog
189 POST /api/v1/cameras (register new camera) Backlog
190 GET /api/v1/cameras/:id (detail + history) Backlog
191 POST /api/v1/cameras/:id/push-status (HTTP ingestion) Backlog
192 MQTT subscriber for status ingestion + fan-out Backlog
193 SSE /api/v1/events/stream endpoint Backlog
194 Scaffold Vite + React + TypeScript + Tailwind project Done
195 React SSE hook (useSSE.ts) In Review
196 React CameraCard component with status display Backlog
197 UX wireframe — main dashboard camera grid In Review

CI/CD Pipeline

ci.yaml (PR gate)

Runs on push/PR to dev and main:

  1. lint-and-typecheck: npm ci → eslint → tsc --noEmit
  2. test (needs lint): npm test (vitest)
  3. build (needs test): npm run build → upload dist artifact

build-dev.yaml

Triggered by repository_dispatch: dev-build-success:

  • Checks out, downloads artifact, builds Go binary

deploy-dev.yaml

Triggered by workflow_dispatch:

  • SCP binary + deploy script to dev host
  • Deploy with backup/rollback, systemctl restart
  • Failure notification

Key Design Decisions

  1. SQLite chosen over PostgreSQL — Single-node Pi Zero 2 W deployment; no need for a separate DB server. WAL mode for concurrent read/write.
  2. SSE chosen over WebSocket — Unidirectional updates (hub → browser) are sufficient for status dashboard; SSE is simpler to implement and maintain.
  3. Chi router — Lightweight, idiomatic Go HTTP router with middleware support.
  4. Zustand over Redux — Minimal boilerplate for the camera status store.
  5. API key auth — Simple bearer token; hub is on a local network, not internet-facing.
  6. MQTT — Standard IoT protocol for ESP32 communication; Mosquitto broker runs locally on the Pi.
  7. Recording state tracking — Status push handler detects recording state changes and automatically opens/closes recording_events rows.

Known Limitations

  • MQTT subscriber implementation is still in Backlog (CUB-192). Currently, only HTTP status push is wired up.
  • SSE endpoint implementation is in Backlog (CUB-193) — frontend SSE hook exists but backend SSE hub is placeholder.
  • CameraCard component plan exists (docs/plans/2026-05-21-cub-196-cameracard.md) but implementation is pending.
  • CUB-182 is essentially a duplicate of CUB-181 (both scaffold Go module).
  • remoterig.db (SQLite DB file) is currently committed to the repo — should be in .gitignore for a production config.

Getting Started

# Clone
cd /mnt/ai-storage/projects/remote-rig
git checkout dev
git pull origin dev

# Backend
go run cmd/server/main.go
# → runs on :8080

# Frontend
cp .env.example .env    # edit if needed
npm install
npm run dev             # → Vite dev server with API proxy

Default Agent Assignments

Area Agent Notes
Backend (Go API, MQTT, SSE) Dex gitea-dex MCP
Database (SQLite schema/migrations) Hex gitea-hex MCP
Frontend (React, Tailwind) Rex gitea-rex MCP
Hardware (ESP32 firmware, GPIO) Pip gitea-pip MCP
Design (wireframes, UX) Sketch