generated from CubeCraft-Creations/Tracehound
feat: dual-board architecture — ESP8266 camera bridge + ESP32 MQTT bridge
Complete rewrite of firmware into two dedicated boards per camera node: ESP8266 (Camera Bridge): - Connects ONLY to GoPro AP — polls status, sends over UART - Zero network switching, zero MQTT - HTTP GET /bacpac/SH for status, start/stop - JSON-per-line UART protocol to ESP32 ESP32 (MQTT Bridge): - Connects ONLY to travel router — MQTT to Pi hub - Reads status from ESP8266 over UART2 (RX16/TX17) - Auto-registration, heartbeat, command forwarding - Zero camera communication UART Protocol: JSON-per-line at 115200 8N1 ESP8266→ESP32: status/ack/pong/error ESP32→ESP8266: cmd (start_recording/stop_recording/ping) Hardware updates: - BOM now includes both boards (~4/node) - 3D case has stacked dual-board compartment - UART wire channel between board recesses - Shared 3.3V power rail for both boards
This commit is contained in:
+93
-96
@@ -1,137 +1,134 @@
|
||||
# RemoteRig — ESP32 / ESP8266 Camera Node Firmware
|
||||
# RemoteRig — Dual-Board Camera Node Firmware
|
||||
|
||||
> **Platform:** PlatformIO (esp32dev + esp8266dev) | **Framework:** Arduino
|
||||
> **Platform:** PlatformIO (esp8266-camera + esp32-mqtt)
|
||||
> **MQTT Contract:** [docs/MQTT_CONTRACT.md](../docs/MQTT_CONTRACT.md)
|
||||
> **Hardware:** [hardware/README.md](../hardware/README.md)
|
||||
|
||||
## Architecture
|
||||
|
||||
Each camera node uses **two boards** connected via UART — zero network switching:
|
||||
|
||||
```
|
||||
┌─────────────────────┐ UART ┌─────────────────────┐
|
||||
│ ESP8266 D1 Mini │ TX──────→RX │ ESP32 Dev Board │
|
||||
│ (Camera Bridge) │ RX←──────TX │ (MQTT Bridge) │
|
||||
│ │ 115200 │ │
|
||||
│ STA → GoPro AP │ 8N1 │ STA → Travel Router │
|
||||
│ HTTP → 10.5.5.1 │ │ MQTT → 192.168.4.10│
|
||||
│ Start/stop/status │ │ Hub registration │
|
||||
└─────────────────────┘ └──────────────────────┘
|
||||
```
|
||||
|
||||
| Board | Job | Network | Protocol |
|
||||
|-------|-----|---------|----------|
|
||||
| ESP8266 | Camera control | GoPro AP only (10.5.5.1) | HTTP → UART JSON |
|
||||
| ESP32 | Hub relay | Travel router only (192.168.4.x) | UART JSON → MQTT |
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Install PlatformIO
|
||||
pip install platformio
|
||||
|
||||
# Build for ESP32 (recommended — dual-STA)
|
||||
cd firmware
|
||||
pio run -e esp32dev
|
||||
|
||||
# Build for ESP8266 D1 Mini (time-shared STA)
|
||||
pio run -e esp8266dev
|
||||
# Build both
|
||||
pio run -e esp8266-camera
|
||||
pio run -e esp32-mqtt
|
||||
|
||||
# Upload to board
|
||||
pio run -e esp32dev --target upload
|
||||
pio run -e esp8266dev --target upload
|
||||
# Upload to boards (connect one at a time via USB)
|
||||
pio run -e esp8266-camera --target upload
|
||||
pio run -e esp32-mqtt --target upload
|
||||
|
||||
# Upload SPIFFS/LittleFS config (first time only)
|
||||
pio run -e esp32dev --target uploadfs
|
||||
pio run -e esp8266dev --target uploadfs
|
||||
|
||||
# Serial monitor
|
||||
pio device monitor
|
||||
# Upload configs (each board needs its own)
|
||||
# ESP8266: copy esp8266-config.json to data/config.json, then:
|
||||
pio run -e esp8266-camera --target uploadfs
|
||||
# ESP32: copy esp32-config.json to data/config.json, then:
|
||||
pio run -e esp32-mqtt --target uploadfs
|
||||
```
|
||||
|
||||
## Platform Differences
|
||||
## UART Protocol (ESP8266 ↔ ESP32)
|
||||
|
||||
| Feature | ESP32 | ESP8266 |
|
||||
|---------|-------|---------|
|
||||
| Wi-Fi | Dual-STA (simultaneous) | Single STA (time-shared) |
|
||||
| Poll latency | 0ms (always connected to both) | ~4s per cycle (switch + poll + switch) |
|
||||
| Power draw | ~80mA active | ~70mA active |
|
||||
| Cost | ~$5 | ~$3 |
|
||||
| Build target | `esp32dev` | `esp8266dev` |
|
||||
| Filesystem | SPIFFS | LittleFS |
|
||||
| LED pin | GPIO 2 (active high) | GPIO 2 / LED_BUILTIN (active low) |
|
||||
JSON-per-line at 115200 8N1. GPIO16 on both boards.
|
||||
|
||||
**ESP8266 workflow:** Every 30s, the ESP8266 switches from the travel router to the GoPro AP (~1s), polls the camera (~2s), switches back to the travel router (~1s), then publishes MQTT. This adds ~4s of latency per cycle but is invisible at 30s poll intervals.
|
||||
|
||||
## Camera Compatibility
|
||||
|
||||
| Camera | IP | Protocol | Status |
|
||||
|--------|-----|----------|--------|
|
||||
| GoPro Hero 3 | `10.5.5.1` | HTTP GET `/bacpac/SH` | ✅ Full support |
|
||||
| GoPro Hero 4 | `10.5.5.1` | HTTP GET `/gp/gpControl` | ⚠️ Different API — needs adaptation |
|
||||
| Akaso Brave 4/7/V50 | `192.168.1.1` or `192.168.42.1` | Varies (HTTP or TCP:7878) | 🔬 Needs testing — config `camera_ip` |
|
||||
|
||||
For Akaso or other cameras: set `camera_ip` in config.json. The firmware will attempt the GoPro-style HTTP API at that IP. If the camera uses a different protocol, the `fetchCameraStatus()` and `sendCameraCommand()` functions in `main.cpp` need to be adapted.
|
||||
| Direction | Type | Format | Purpose |
|
||||
|-----------|------|--------|---------|
|
||||
| ESP8266 → ESP32 | `status` | `{"type":"status","battery_raw":217,...}` | Camera poll result |
|
||||
| ESP8266 → ESP32 | `ack` | `{"type":"ack","cmd":"start_recording"}` | Command confirmation |
|
||||
| ESP8266 → ESP32 | `pong` | `{"type":"pong","uptime_ms":12345}` | Ping response |
|
||||
| ESP8266 → ESP32 | `error` | `{"type":"error","msg":"camera unreachable"}` | Error report |
|
||||
| ESP32 → ESP8266 | `cmd` | `{"type":"cmd","command":"start_recording"}` | Hub command |
|
||||
| ESP32 → ESP8266 | `cmd` | `{"type":"cmd","command":"ping"}` | Link health check |
|
||||
|
||||
## Configuration
|
||||
|
||||
The ESP32 stores configuration in SPIFFS (`data/config.json`):
|
||||
### ESP8266 (`data/esp8266-config.json`)
|
||||
|
||||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `camera_ssid` | `"GOPRO-BP-"` | GoPro Wi-Fi AP name |
|
||||
| `camera_password` | `"goprohero"` | GoPro Wi-Fi password |
|
||||
| `camera_ip` | `"10.5.5.1"` | Camera IP (change for Akaso to 192.168.1.1) |
|
||||
| `poll_interval_sec` | `30` | How often to poll camera |
|
||||
|
||||
### ESP32 (`data/esp32-config.json`)
|
||||
|
||||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `wifi_ssid` | `"RemoteRig"` | Travel router SSID |
|
||||
| `wifi_password` | `""` | Travel router password |
|
||||
| `camera_ssid` | `"GOPRO-BP-"` | GoPro Wi-Fi AP prefix (auto-discovered) |
|
||||
| `camera_password` | `"goprohero"` | GoPro Wi-Fi password |
|
||||
| `mqtt_broker` | `"192.168.4.10"` | Pi Zero 2 W static IP |
|
||||
| `mqtt_broker` | `"192.168.4.10"` | Pi Zero 2 W IP |
|
||||
| `mqtt_port` | `1883` | Mosquitto port |
|
||||
| `camera_id` | `""` | Assigned by hub on first announce (leave empty) |
|
||||
| `poll_interval_sec` | `30` | GoPro status poll frequency |
|
||||
| `heartbeat_interval_sec` | `60` | MQTT heartbeat frequency |
|
||||
|
||||
**First boot:** Leave `camera_id` empty. The ESP32 will auto-announce to the hub, which assigns a `cam-NNN` ID. The assigned ID is saved to SPIFFS automatically.
|
||||
|
||||
## LED Status Codes
|
||||
|
||||
| Pattern | Meaning |
|
||||
|---------|---------|
|
||||
| Slow blink (1s) | Connected to router + MQTT, normal operation |
|
||||
| Fast blink (200ms) | No Wi-Fi connection — reconnecting |
|
||||
| Solid on | Connected but GoPro unreachable |
|
||||
| Off | Boot/shutdown |
|
||||
|
||||
## Architecture
|
||||
## Wiring
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────┐
|
||||
│ ESP32 (Arduino) │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌────────┐ │
|
||||
│ │ WiFi STA │ │ WiFi STA │ │ MQTT │ │
|
||||
│ │ (Router) │ │ (GoPro) │ │ Client │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └───┬────┘ │
|
||||
│ │ │ │ │
|
||||
│ │ ┌────────┘ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ Main Loop │ │
|
||||
│ │ Every 30s: │ │
|
||||
│ │ HTTP GET GoPro status │ │
|
||||
│ │ Parse 60-byte blob │ │
|
||||
│ │ MQTT publish status │ │
|
||||
│ │ Every 60s: │ │
|
||||
│ │ MQTT publish heartbeat │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
│ │
|
||||
│ SPIFFS: /config.json (persistent) │
|
||||
└──────────────────────────────────────────┘
|
||||
ESP8266 D1 Mini ESP32 Dev Board
|
||||
┌────────────┐ ┌────────────┐
|
||||
│ │ │ │
|
||||
│ TX (GPIO1)│──────────→│ RX (GPIO16)│
|
||||
│ RX (GPIO3)│←──────────│ TX (GPIO17)│
|
||||
│ GND │───────────│ GND │
|
||||
│ 3.3V │ │ 3.3V │
|
||||
│ │ │ │
|
||||
└────────────┘ └────────────┘
|
||||
│ │
|
||||
└────────┬─────────────┘
|
||||
│
|
||||
LiPo → 3.3V Buck
|
||||
(shared power)
|
||||
```
|
||||
|
||||
## Boot Sequence
|
||||
|
||||
1. Load config from SPIFFS
|
||||
2. Connect to travel router Wi-Fi (STA mode)
|
||||
3. Connect to GoPro AP Wi-Fi (STA mode — simultaneous)
|
||||
4. Connect to MQTT broker (192.168.4.10)
|
||||
5. If no `camera_id` → publish announce → hub registers us
|
||||
6. Subscribe to `remoterig/cameras/{camera_id}/command`
|
||||
7. Enter main loop
|
||||
1. **ESP8266:** Connect to GoPro AP → wait for UART commands
|
||||
2. **ESP32:** Connect to travel router → connect MQTT → announce if new
|
||||
3. **ESP8266:** Poll camera every 30s → send status over UART
|
||||
4. **ESP32:** Receive status → publish MQTT
|
||||
5. **Hub → MQTT command → ESP32 → UART → ESP8266 → HTTP → GoPro**
|
||||
|
||||
## GoPro API Notes (Hero 3 Black/Silver)
|
||||
## Camera Compatibility
|
||||
|
||||
- **IP:** Always `10.5.5.1` (GoPro's own AP)
|
||||
- **Status endpoint:** `GET /bacpac/SH?t={password}&p=%01`
|
||||
- **Start recording:** `GET /bacpac/SH?t={password}&p=%01` (mode byte = 1)
|
||||
- **Stop recording:** `GET /bacpac/SH?t={password}&p=%00` (mode byte = 0)
|
||||
- **Get password:** `GET /bacpac/sd` (no auth, returns plain text)
|
||||
- **Status blob:** 60 bytes binary — see `parseStatus()` in main.cpp for field offsets
|
||||
| Camera | `camera_ip` | Protocol | Status |
|
||||
|--------|------------|----------|--------|
|
||||
| GoPro Hero 3 | `10.5.5.1` | HTTP GET `/bacpac/SH` | ✅ Full support |
|
||||
| Akaso Brave 7 | `192.168.1.1` | Varies | 🔬 Set `camera_ip`, test |
|
||||
|
||||
For non-GoPro cameras: only the ESP8266 firmware needs changes — the ESP32 stays the same.
|
||||
|
||||
## LED Status (ESP8266)
|
||||
|
||||
| LED | Meaning |
|
||||
|-----|---------|
|
||||
| Solid on | Connected to camera AP, camera responding |
|
||||
| Slow blink (500ms) | Connected to AP but camera not responding |
|
||||
| Off | Wi-Fi disconnected |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Check |
|
||||
|---------|-------|
|
||||
| No serial output | Baud rate: 115200. Hold BOOT, press EN, release BOOT for flash mode |
|
||||
| Can't connect to router | Verify SSID/password in SPIFFS config, check router DHCP range |
|
||||
| GoPro unreachable | GoPro must be ON and Wi-Fi enabled. Password defaults to "goprohero" |
|
||||
| MQTT connect fails | Verify Mosquitto running on Pi: `systemctl status mosquitto` |
|
||||
| Camera never registers | Watch serial for "announce" message, check hub logs for registration |
|
||||
| No UART communication | Verify TX→RX crossover. Both boards at 115200. Shared GND. |
|
||||
| ESP8266 can't connect | GoPro must be ON with Wi-Fi enabled. Default password: `goprohero` |
|
||||
| ESP32 can't connect MQTT | `systemctl status mosquitto` on Pi. Port 1883 open. |
|
||||
| Camera never registers | Watch ESP32 serial for "Announced" message. Check hub logs. |
|
||||
|
||||
Reference in New Issue
Block a user