5 Commits
Author SHA1 Message Date
tfaour a3ab6c5f13 Firmware: OTA client, dual-board build (devkit/XIAO), version reporting, XIAO fixes
Build and push server image / build-and-push (push) Successful in 36s
- version.txt + esp_app_desc_t version reporting (X-Frame-Version header);
  new ota_update.c checks the server's advertised version against the
  running one and streams+applies an update via esp_https_ota, gated by
  bootloader rollback (marks the image valid only after a full successful
  cycle, so a bad update can't brick a wall-mounted frame).
- Dual-OTA partition tables: partitions.csv (8MB dev board, 2MB slots) and
  new partitions_xiao.csv (4MB XIAO, 1.875MB slots -- the dev board's
  table doesn't fit the XIAO's flash). New build_for_board.sh gives each
  board its own build dir + generated sdkconfig via SDKCONFIG_DEFAULTS
  layering, so switching boards never clobbers the other's config.
- fetch_photo_info()/fetch_face_labels() were using the short
  reachability-check timeout even though the manage-menu path can be the
  first (cold, TLS-handshake-paying) request of a wake cycle -- switched
  to the longer fetch timeout to stop spurious ESP_ERR_HTTP_CONNECT
  failures.
- XIAO: the RF switch that selects onboard vs. external antenna
  (GPIO3/14) isn't initialized by plain ESP-IDF the way Seeed's Arduino
  package does it, leaving WiFi unable to reliably reach the antenna at
  all -- new board_antenna.c powers the switch and selects the onboard
  antenna, gated behind FRAME_XIAO_ANTENNA_INIT (on by default in
  sdkconfig.xiao). Also remaps the EPD DC/RST/BUSY pins, since the dev
  board's defaults (GPIO9/10/11) aren't physically exposed on the XIAO.
2026-07-20 22:24:15 -04:00
tfaour 5588ce3e1b HTTPS trust: use the public CA bundle + one missing root, not a pinned cert
Build and push server image / build-and-push (push) Successful in 33s
Root-caused the earlier "No matching trusted root certificate found"
failure properly this time by reading ESP-IDF's actual bundle-matching
code (esp_crt_bundle.c): it looks up a trusted root by the ISSUER name
of whatever certificate it can't otherwise validate, not by matching
the presented certificate itself. The live server's chain ends in a
GTS Root R4 certificate cross-signed by the old GlobalSign Root CA R1
(common Cloudflare/Google Trust Services practice, for compatibility
with older/embedded clients) -- and ESP-IDF's current bundle snapshot
has dropped that old GlobalSign root entirely, so the lookup came up
empty. This was a general gap, not something specific to this one
deployment's cert.

Fix: keep the standard public CA bundle (esp_crt_bundle_attach) as the
trust mechanism -- so any normal reverse-proxy cert (Let's Encrypt,
etc.) works out of the box -- and add the one missing root on top via
ESP-IDF's CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE mechanism
(sdkconfig.defaults), which appends a project-supplied cert file to the
bundle at build time. Fetched GlobalSign's official Root CA R1 cert and
cryptographically verified (openssl verify) it actually validates the
live server's certificate before embedding it -- see
firmware/main/certs/additional_root_ca.pem (replaces the old
tools_server_ca.pem, which pinned one exact certificate directly and
would've broken for anyone else's reverse proxy). Confirmed working
against the real deployment on hardware.
2026-07-19 17:38:39 -04:00
tfaour 1b9226326a Fix hardware-verified bugs: EPD stack overflow and busy-wait spin
Two crashes found flashing to real hardware:

- epd_display_stream's 4KB SPI chunk buffer was a stack local, but the
  default main task stack (3584 bytes) is smaller than that alone --
  Guru Meditation stack protection fault. Made it static instead, and
  bumped CONFIG_ESP_MAIN_TASK_STACK_SIZE to 8192 for headroom in the rest
  of the boot call chain (provisioning -> QR render -> eventually the
  HTTP fetch cycle all run in this one task).

- epd_wait_busy() polled with a 1ms vTaskDelay, which rounds down to 0
  FreeRTOS ticks at the default 100Hz tick rate -- so it never actually
  blocked, tight-spinning the CPU for the panel's real refresh time
  (15-30+s for a full-color pass) and starving the idle task long enough
  to trip the 5s task watchdog. Bumped to 20ms, safely >=1 tick regardless
  of tick rate.

Also updates the EPD pin defaults to the board's actual wiring
(CLK=20 MOSI=19 CS=18 DC=9 RST=10 BUSY=11), confirmed working on hardware.
2026-07-18 14:06:54 -04:00
tfaour 9c855ede47 Pin flash size + a larger app partition in sdkconfig.defaults
The board's flash is 8MB, but neither flash size nor partition table were
pinned in sdkconfig.defaults, so a fresh clone would silently fall back to
IDF's default (2MB flash, ~1MB "single app" partition table) rather than
what this project was actually being built/tested against. Building against
that default left only 4% of the app partition free before the HTTP client
work (task 6) even lands. Adds a custom partitions.csv with a 2MB app
partition and pins CONFIG_ESPTOOLPY_FLASHSIZE_8MB so the committed config
reproduces a working build for anyone else who clones this.
2026-07-18 11:20:19 -04:00
tfaour cb50c99c98 Restructure repo as monorepo: firmware/ + server/ + docs/
Moves the existing ESP-IDF captive_portal example into firmware/ to make
room for the new FastAPI server and project docs, ahead of building out the
full ESPresso Frame project (ESP32-C6 + Immich-backed e-ink photo frame).
2026-07-18 10:20:25 -04:00