Development
Requirements
- Python 3.13
- uv package manager
- Node.js 22+ and npm (for the frontend)
- Docker (for integration testing)
- A reachable Redis instance
Setup
# Clone the repo
git clone <repo-url>
cd dive_detector
# Install backend dependencies
uv sync
# Install frontend dependencies
cd frontend && npm install && cd -
Running backend services locally
Start Redis, then run the collector and detector against it:
# Start Redis (in Docker for simplicity)
docker run -d --name redis -p 6379:6379 redis:latest
# Collect navigation data for R/V Rachel Carson
uv run backend/navproc_collector.py \
--ship rcsn --domain rc --redis-host localhost --poll-interval 5
# In another terminal: run the dive detector
OBS_PASSWORD="" \
uv run backend/divedetector.py \
--ship rcsn --redis-host localhost \
--obs-host <your-obs-host> --poll-interval 10
Running the frontend locally
cd frontend
cp .env.example .env # point REDIS_HOST/PORT at a reachable Redis
npm run dev # http://localhost:3000
The dev server hot-reloads Svelte components. The SvelteKit server-side
routes (src/routes/api/.../+server.ts) restart on save automatically.
Inspecting cached data
# Static snapshot
uv run backend/redis_dump.py rcsn
# Live auto-refreshing TUI
uv run backend/redis_dump_textual.py rcsn --refresh-interval 2
See Redis Tools for full options.
Project layout
dive_detector/
├── backend/ # Python services
│ ├── collector_keys.toml # NATS keys the collector mirrors
│ ├── divedetector.py # Detection loop + main entrypoint
│ ├── navproc_collector.py
│ ├── obs_control.py
│ ├── redis_dump.py
│ └── redis_dump_textual.py
├── frontend/ # SvelteKit + Tailwind dashboard
│ ├── src/
│ │ ├── lib/
│ │ │ ├── components/ # ShipCard, MapWidget, StatusPill, ...
│ │ │ ├── server/redis.ts # ioredis singleton + accessors
│ │ │ └── types.ts # Shared TS types
│ │ └── routes/ # Pages + +server.ts API endpoints
│ ├── static/favicon.svg
│ └── package.json
├── docker/ # Dockerfiles
│ ├── dive_detector.dockerfile
│ ├── frontend.dockerfile
│ └── navproc_collector.dockerfile
├── docs/ # This documentation (MkDocs)
├── compose.yaml # Docker Compose stack
├── pyproject.toml
├── mkdocs.yml
└── uv.lock
Dependencies
Backend (pyproject.toml)
| Package | Purpose |
|---|---|
nats-py |
Async NATS JetStream client |
redis |
Redis client |
obs-websocket-py |
OBS WebSocket v5 client |
rich |
Terminal table formatting (redis_dump) |
textual / textual-serve |
Interactive TUI + web serving (redis_dump_textual) |
fastapi / uvicorn |
ASGI web framework (available for future use) |
tomllib (stdlib in Python 3.11+) is used to parse collector_keys.toml
— no extra dependency required.
Frontend (frontend/package.json)
| Package | Purpose |
|---|---|
@sveltejs/kit |
SvelteKit framework |
@sveltejs/adapter-node |
Node server adapter |
svelte |
Svelte 5 |
tailwindcss + @tailwindcss/vite |
Tailwind v4 |
ioredis |
Redis client used in server-side routes |
leaflet + @types/leaflet |
Map widget |
Common changes
Adding a new NATS-sourced metric
- Append the NATS key (UPPERCASE dotted form) to
backend/collector_keys.tomland update the comment block listing consumers. - If a Python service should read it, add the read directly with the normalised Redis key name.
- If the dashboard should display it, add the key helper to
frontend/src/lib/server/redis.ts, extend the pipeline + theShipStatetype, then surface it inShipCard.svelte. - Rebuild the affected containers.
Adding a new ship
- Add a NATS-accessible ship to the system.
- Add two new services to
compose.yaml— onenavproc-collectorand onedive-detector— using the new ship'sSHIP_NAME,SHIP_DOMAIN, andOBS_HOST. - Add the ship to the
SHIPSarray infrontend/src/lib/types.tsand to theShipIdunion. - Verify the new OBS instance has the
MainOnlyandFeedOfflinescenes.
Serving the docs locally
Then open http://127.0.0.1:8000.
To build static HTML:
Output is written to site/.