Files
remote-rig/docs/design/esp01s-uart-ota.md
T
Joshua King 403e1d9edd
Build (Dev) / build (push) Failing after 9s
CI/CD / lint-and-typecheck (push) Successful in 9m28s
CI/CD / test (push) Successful in 9m27s
CI/CD / build (push) Failing after 4m53s
CI/CD / deploy (push) Has been skipped
firmware: no-reflash config updates for ESP-01S + UART-OTA groundwork
Updating the buried ESP-01S currently means a USB-UART adapter and a
GPIO0 jumper. Add a path to change its settings without reflashing, and
lay the groundwork for full firmware updates over the existing UART.

set_config (no reflash for settings):
- ESP-01S: add saveConfig() + a set_config command — updates GoPro
  SSID/password/IP and poll interval, persists to LittleFS, acks, and
  re-associates Wi-Fi if creds changed
- XIAO: forward an MQTT set_camera_config down to the ESP-01S over UART
  (hub -> MQTT -> XIAO -> UART -> ESP-01S/LittleFS)

UART-OTA groundwork ("XIAO as flasher"):
- reserve XIAO GPIOs ESP01_RST_PIN=D8, ESP01_PGM_PIN=D10 for driving the
  ESP-01S serial bootloader (not driven yet)
- docs/design/esp01s-uart-ota.md: full design (why Wi-Fi OTA doesn't fit
  the 1MB ESP-01S on the GoPro AP, bootloader entry, ROM flash protocol,
  HTTP-pull delivery, scope)
- hardware/README.md: fix stale ESP32-C3 -> XIAO ESP32-C6 wiring, add the
  two control lines (Notion wiring diagram updated to match)

Both firmwares build clean and are flashed; set_config round-trip needs
the broker to exercise end-to-end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 19:11:34 -04:00

101 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ESP-01S firmware updates without a USB-UART adapter
Status: **design / not yet implemented.** Interim mitigations (config-over-UART,
GPIO reservation) are shipped; see "Shipped now" below.
## Problem
The ESP-01S camera bridge has no native USB. Today it is flashed with an external
3.3 V USB-UART adapter and a `GPIO0 → GND` jumper held during reset. On an assembled
field node that is impractical — we want to update it over the network.
## Why not Wi-Fi OTA on the ESP-01S itself
1. **Network topology.** The ESP-01S joins the *GoPro* AP (`10.5.5.1`), not the hub /
travel-router network. The hub cannot reach it to push an OTA.
2. **1 MB flash.** Standard ESP8266 OTA stages a second copy of the sketch alongside the
running one. Our sketch is ~333 KB; a 1 MB module has no room for two copies plus FS
and reserved areas.
So updates must arrive **through the XIAO**, which is already UART-connected to the
ESP-01S and sits on the hub network.
## Approach: XIAO ESP32-C6 as the flasher (UART OTA)
The XIAO plays the role the USB-UART adapter plays today, driving the ESP-01S's ROM
serial bootloader over the existing UART.
### Hardware — two added control lines
| XIAO pin | → ESP-01S | purpose |
|----------|-----------|---------|
| `D8` (`ESP01_RST_PIN`) | `RST` | pulse low to reset the ESP-01S |
| `D10` (`ESP01_PGM_PIN`) | `GPIO0` | hold low across reset → enter bootloader |
| `D6` (TX) / `D7` (RX) | `RX` / `TX` | existing `Serial1` link (crossed) |
| GND | GND | common ground |
> **Confirm before committing the PCB/wiring:** verify `D8`/`D10` on the actual XIAO
> ESP32-C6 variant do **not** map to ESP32-C6 strapping pins (`GPIO8`, `GPIO9`, `GPIO15`)
> or the USB-JTAG pins. Pins are reserved in firmware (`ESP01_RST_PIN`, `ESP01_PGM_PIN`)
> but not yet driven.
### Bootloader entry
`GPIO0 = LOW`, pulse `RST` low→high → ESP-01S enters the serial bootloader on the UART.
After writing: `GPIO0 = HIGH`, pulse `RST` → run the new firmware. Always restore
`GPIO0 = HIGH` on give-up so the ESP can boot normally.
### Flash protocol
Implement enough of the ESP8266 ROM bootloader / esptool SLIP protocol on the XIAO over
`Serial1`:
- `SYNC`, then `FLASH_BEGIN` / `FLASH_DATA` (≈1 KB blocks) / `FLASH_END` to write the app
at offset `0x0`.
- Start at 115200 baud; optionally raise after sync.
- Verify with the ROM `SPI_FLASH_MD5` against the expected MD5.
### Firmware delivery (hub → XIAO)
Greenfield on the Go hub (only a `firmware_version` field exists today). Recommended:
- **HTTP pull.** Hub exposes `GET /firmware/esp01s/<version>.bin` (+ MD5). XIAO is
triggered by an MQTT command, e.g.
`{"command":"update_esp01s","url":"http://<hub>/firmware/esp01s/0.4.0.bin","md5":"…"}`,
fetches the `.bin` in chunks, and streams each chunk straight into a `FLASH_DATA` block.
- Avoid buffering the whole image in RAM — stream HTTP chunk → flash block → repeat.
- MQTT chunked transfer is possible but heavier on the broker; prefer HTTP.
### Sequencing / safety
- Pause the UART JSON status/command protocol while flashing (the link is busy with the
bootloader protocol).
- On failure leave the ESP recoverable and retry; report progress/result to the hub over
MQTT.
### XIAO self-update (separate, easy)
The XIAO (4 MB flash, on the hub network) can use standard ESP32 OTA (`ArduinoOTA` or
`httpUpdate`). No gymnastics required. Split: **XIAO = native OTA; ESP-01S = flashed by
the XIAO over UART.**
## Scope estimate
- **XIAO firmware:** ESP8266 ROM-loader client over `Serial1` + `GPIO0`/`RST` control +
HTTP fetch + MQTT trigger. Mediumlarge.
- **Hub (Go):** firmware store + HTTP endpoint + MQTT trigger command. Smallmedium.
- **Hardware:** two control wires + confirm non-strapping pins. Small.
## Shipped now (interim)
- **Config-over-UART (`set_config`)** — change GoPro SSID/password/IP and poll interval
with no reflash. Hub → `set_camera_config` (MQTT) → XIAO → `set_config` (UART) →
ESP-01S persists to LittleFS.
- **GPIO reservation** — `ESP01_RST_PIN = D8`, `ESP01_PGM_PIN = D10` reserved in the XIAO
firmware for the flasher.
## References
- ESP8266 ROM serial bootloader protocol (esptool).
- Wiring: Notion "XIAO ESP32-C6 Pin-to-Pin Wiring Diagram".