Files
remote-rig/docs/CONTEXT.md
T

323 lines
13 KiB
Markdown
Raw Normal View History

# 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](https://linear.app/cubecraft-creations/issue/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`)
```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
```bash
# 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 | |