# RemoteRig — ESP32 / ESP8266 Camera Node Firmware > **Platform:** PlatformIO (esp32dev + esp8266dev) | **Framework:** Arduino > **MQTT Contract:** [docs/MQTT_CONTRACT.md](../docs/MQTT_CONTRACT.md) > **Hardware:** [hardware/README.md](../hardware/README.md) ## 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 # Upload to board pio run -e esp32dev --target upload pio run -e esp8266dev --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 ``` ## Platform Differences | 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) | **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. ## Configuration The ESP32 stores configuration in SPIFFS (`data/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_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 ``` ┌──────────────────────────────────────────┐ │ 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) │ └──────────────────────────────────────────┘ ``` ## 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 ## GoPro API Notes (Hero 3 Black/Silver) - **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 ## 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 |