docs: align hardware and MQTT architecture notes
CI/CD / lint-and-typecheck (pull_request) Successful in 7s
CI/CD / test (pull_request) Successful in 10m3s
CI/CD / build (pull_request) Failing after 4m58s
CI/CD / deploy (pull_request) Has been skipped

This commit is contained in:
2026-05-22 17:08:11 -04:00
parent c5cbeabd92
commit 0e2e94a4cf
3 changed files with 58 additions and 50 deletions
+17 -17
View File
@@ -45,31 +45,31 @@ RemoteRig is a **multi-camera remote monitoring system**. It provides a camera g
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ ESP32 #1 │ │ ESP32 #N │ │ Pi Zero 2 W │ │ ESP32 #1 │ │ ESP32 #N │ │ Pi Zero 2 W │
│ DHCP addr │ │ DHCP addr │ │ 10.60.1.56 │ │ DHCP addr │ │ DHCP addr │ │ 10.60.1.56 │
│ │ │ │ (static IP) │ STA→Router │ │ STA→Router │ │ (static IP) │
STA→GoPro AP │ │ STA→GoPro AP │ │ MQTT→:1883 │ │ MQTT→:1883 │ Mosquitto :1883
STA→Router │ │ STA→Router │ │ Mosquitto :1883 UART relay │ │ UART relay │ │ Go API :8080
│ │ │ │ │ Go API :8080 │ │ │ │ │ React UI
│ MQTT→:1883 │ │ MQTT→:1883 │ │ React UI │
└──────┬───────┘ └──────┬───────┘ │ SQLite DB │ └──────┬───────┘ └──────┬───────┘ │ SQLite DB │
└──────────────────┘ UARTUART └──────────────────┘
▼ ▼ │ ▼ ▼ │
┌──────────────┐ ┌──────────────┐ │ ┌──────────────┐ ┌──────────────┐ │
GoPro Hero 3 │ │ GoPro Hero 3 │ SSE /api/v1/events/stream ESP8266 #1 │ │ ESP8266 #N │ SSE /api/v1/events/stream
AP: 10.5.5.1 │ │ AP: 10.5.5.1 │ │ STA→GoPro AP │ │ STA→GoPro AP │ │
Wi-Fi only │ │ Wi-Fi only │ ▼ HTTP→10.5.5.1│ │ HTTP→10.5.5.1│ ▼
└─────────────┘ └─────────────┘ ┌──────────────────┐ └─────────────┘ └─────────────┘ ┌──────────────────┐
│ User Device │ │ User Device │
│ (laptop/kiosk) │ ┌──────────────┐ ┌──────────────┐ │ (laptop/kiosk) │
│ http://.4.10 │ GoPro Hero 3 │ │ GoPro Hero 3 │ │ 10.60.1.56:8080
└──────────────────┘ └──────────────┘ └──────────────┘ └──────────────────┘
``` ```
**Network is fully self-contained — no internet dependency.** The travel router creates the LAN. All devices connect to it. The Pi runs all services (Mosquitto, Go API, React UI, SQLite). ESP32s bridge the GoPro's AP to the LAN via MQTT. **Network is fully self-contained — no internet dependency.** The travel router creates the LAN. All devices connect to it. The Pi runs all services (Mosquitto, Go API, React UI, SQLite). ESP8266 boards talk to the GoPro AP over HTTP, then relay camera status/commands over UART to ESP32 boards. ESP32 boards stay on the travel-router LAN and bridge UART messages to MQTT.
### Key Architecture Decisions (revised) ### Key Architecture Decisions (revised)
- **Closed travel router network** — No venue Wi-Fi dependency. User brings their own router. All devices on `10.60.1.0/24`. - **Closed travel router network** — No venue Wi-Fi dependency. User brings their own router. All devices on `10.60.1.0/24`.
- **ESP32 dual-STA** — One STA to GoPro AP (10.5.5.1), one STA to travel router. No channel-hopping concerns on closed network. - **Two-board camera node** — ESP8266 handles GoPro AP/HTTP; ESP32 stays on the travel-router LAN for MQTT. This avoids ESP32 dual-STA/channel switching complexity.
- **ESP32 → GoPro over Wi-Fi** — Bacpac I²C route rejected (30-pin Herobus connector too complex). HTTP to GoPro AP is proven and reliable. - **ESP8266 → GoPro over Wi-Fi** — Bacpac I²C route rejected (30-pin Herobus connector too complex). HTTP to GoPro AP is proven and reliable.
- **UART bridge between boards** — ESP8266 reports GoPro status and receives commands over UART; ESP32 relays those messages to/from MQTT.
- **MQTT for ESP32 → Hub** — Lightweight, designed for IoT. Mosquitto on Pi. QoS 1 for status, QoS 2 for commands. Full contract: [docs/MQTT_CONTRACT.md](./docs/MQTT_CONTRACT.md) - **MQTT for ESP32 → Hub** — Lightweight, designed for IoT. Mosquitto on Pi. QoS 1 for status, QoS 2 for commands. Full contract: [docs/MQTT_CONTRACT.md](./docs/MQTT_CONTRACT.md)
- **SQLite over PostgreSQL** — Single-node Pi Zero 2 W deployment. WAL mode for concurrent read/write. - **SQLite over PostgreSQL** — Single-node Pi Zero 2 W deployment. WAL mode for concurrent read/write.
- **SSE over WebSocket** — Unidirectional hub → browser updates. Simpler, sufficient for status dashboard. - **SSE over WebSocket** — Unidirectional hub → browser updates. Simpler, sufficient for status dashboard.
+29 -22
View File
@@ -15,22 +15,26 @@
▼ ▼ ▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ ESP32 #1 │ │ ESP32 #2 │ │ Pi Zero 2 W │ │ ESP32 #1 │ │ ESP32 #2 │ │ Pi Zero 2 W │
│ 10.60.1.101 │ 10.60.1.102 │ 10.60.1.56 │ │ 10.60.1.101 │ 10.60.1.102 │ 10.60.1.56 │
│ │ STA→Router │ │ STA→Router │ │ Mosquitto
STA→GoPro AP STA→GoPro AP │ │ Mosquitto MQTT relay │ │ MQTT relay │ │ Go backend
│ STA→Router │ │ STA→Router │ │ Go backend │
└──────┬───────┘ └──────┬───────┘ │ React UI │ └──────┬───────┘ └──────┬───────┘ │ React UI │
└──────────────┘ UARTUART └──────────────┘
▼ ▼
┌──────────────┐ ┌──────────────┐
│ ESP8266 #1 │ │ ESP8266 #2 │
│ STA→GoPro AP │ │ STA→GoPro AP │
│ HTTP→10.5.5.1│ │ HTTP→10.5.5.1│
└──────┬───────┘ └──────┬───────┘
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ GoPro Hero 3 │ │ GoPro Hero 3 │ │ GoPro Hero 3 │ │ GoPro Hero 3 │
│ AP: 10.5.5.1 │ │ AP: 10.5.5.1 │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
``` ```
- **Travel router:** Self-contained, no internet. Gateway `10.60.1.1`. DHCP pool: `10.60.1.100-200` - **Travel router:** Self-contained, no internet. Gateway `10.60.1.1`. DHCP pool: `10.60.1.100-200`
- **Pi Zero 2 W:** Static IP `10.60.1.56`. Runs Mosquitto (port 1883), Go backend (port 8080), serves React UI - **Pi Zero 2 W:** Static IP `10.60.1.56`. Runs Mosquitto (port 1883), Go backend (port 8080), serves React UI
- **ESP32s:** DHCP from router. Each has dual STA: one to GoPro AP, one to router - **ESP32s:** DHCP from router. Each stays on the travel-router LAN, relays MQTT to/from its paired ESP8266 over UART
- **User device:** Connects to router, opens `http://10.60.1.56:8080` for dashboard - **User device:** Connects to router, opens `http://10.60.1.56:8080` for dashboard
## MQTT Broker ## MQTT Broker
@@ -61,7 +65,7 @@ remoterig/
**Direction:** ESP32 → Hub **Direction:** ESP32 → Hub
**QoS:** 1 | **Retain:** true | **Interval:** 30 seconds **QoS:** 1 | **Retain:** true | **Interval:** 30 seconds
Published by the ESP32 every 30s with the latest GoPro status. Published by the ESP32 every 30s using the latest GoPro status received from the paired ESP8266 over UART.
```json ```json
{ {
@@ -143,8 +147,9 @@ Commands sent from the dashboard to individual cameras.
| `stop_recording` | Stop GoPro recording | status (updated on next poll) | | `stop_recording` | Stop GoPro recording | status (updated on next poll) |
| `reboot` | Reboot the ESP32 | — (ESP32 reconnects after boot) | | `reboot` | Reboot the ESP32 | — (ESP32 reconnects after boot) |
**ESP32 behavior:** **ESP32 / ESP8266 behavior:**
- On receipt, execute command against GoPro - ESP32 receives the MQTT command and forwards it over UART to the paired ESP8266
- ESP8266 executes the corresponding HTTP command against the GoPro AP
- Next status publish will reflect the new state - Next status publish will reflect the new state
- If command fails (GoPro unreachable), publish status with `online: false` - If command fails (GoPro unreachable), publish status with `online: false`
@@ -221,16 +226,17 @@ ESP32 boots
├── Publishes announce (retained) on cameras/<id>/announce ├── Publishes announce (retained) on cameras/<id>/announce
┌─────────────────────────────────────────┐ ┌───────────────────────────────────────────────
│ Main loop (every 30s): │ │ Main loop (every 30s): │
│ 1. HTTP GET GoPro status (10.5.5.1) │ 1. ESP32 requests/receives status via UART
│ 2. Parse 60-byte status blob │ 2. ESP8266 polls GoPro HTTP (10.5.5.1)
│ 3. Publish status (retained) │ 3. ESP8266 returns parsed status over UART
│ 4. Every 60s: publish heartbeat │ 4. ESP32 publishes MQTT status (retained)
└─────────────────────────────────────────┘ │ 5. Every 60s: ESP32 publishes heartbeat │
└───────────────────────────────────────────────┘
├── On MQTT disconnect → reconnect with 1s/2s/4s/8s/16s/30s backoff ├── On MQTT disconnect → reconnect with 1s/2s/4s/8s/16s/30s backoff
├── On GoPro unreachable → publish status with online: false ├── On ESP8266/GoPro unreachable → publish status with online: false
├── On Wi-Fi loss → buffer status locally, replay on reconnect (CUB-230) ├── On Wi-Fi loss → buffer status locally, replay on reconnect (CUB-230)
@@ -243,11 +249,12 @@ ESP32 shutdown / watchdog reboot
1. User clicks "Start" on dashboard 1. User clicks "Start" on dashboard
2. Browser → HTTP POST /api/v1/cameras/cam-001/start → Go backend 2. Browser → HTTP POST /api/v1/cameras/cam-001/start → Go backend
3. Go backend → MQTT publish remoterig/cameras/cam-001/command {command: "start_recording"} 3. Go backend → MQTT publish remoterig/cameras/cam-001/command {command: "start_recording"}
4. ESP32 receives command, sends HTTP GET to 10.5.5.1/bacpac/SH?t=<password>&p=%01 4. ESP32 receives command and forwards it to ESP8266 over UART
5. GoPro starts recording 5. ESP8266 sends HTTP GET to 10.5.5.1/bacpac/SH?t=<password>&p=%01
6. Next 30s poll: ESP32 publishes status with recording: true 6. GoPro starts recording
7. Go backend receives status, updates SQLite, fans out via SSE 7. Next 30s poll: ESP8266 reports status over UART; ESP32 publishes status with recording: true
8. Dashboard updates with pulsing REC indicator 8. Go backend receives status, updates SQLite, fans out via SSE
9. Dashboard updates with pulsing REC indicator
``` ```
## Offline Buffering (future — CUB-230) ## Offline Buffering (future — CUB-230)
+5 -4
View File
@@ -105,7 +105,8 @@ The ESP8266 and GoPro talk over Wi-Fi — **no data cable between them**. The on
| | W × D × H (mm) | | | W × D × H (mm) |
|---|---| |---|---|
| Case external | ~64 × 38 × 27 | | Case body external | ~56.8 × 36.9 × 19.0 |
| Case internal | ~57 × 31 × 18 | | Lid external | ~56.8 × 32.8 × 4.0 |
| Fits poles | 20-35mm diameter | | Tripod clamp | ~43.0 × 54.3 × 16.0 |
| Total weight | ~50g (case + boards, without power bank) | | Clamp pole fit | Nominal 35mm; smaller poles TBD / may need inserts |
| Total weight | TBD after prototype print |