Renames the user-facing description of the BOOT-button factory reset
across the codebase. The threshold remains 5 s (RESET_HOLD_MS) but
"hold for 5 seconds" misled users: total wall-clock time-to-visible-
change includes ~20 s of e-ink redraw after the threshold fires, and
a too-short press now wakes the device into a normal poll cycle (a
side effect of the EXT0 wakeup we just added). "Until the screen
starts to flash" matches what the user actually sees.
- Remove-this-frame modal gains a small aside describing the
physical reset for the new owner, with the new terminology and
a callout that a brief tap just refreshes the image.
- CLAUDE.md and the operation.h comment near the EXT0 wake call
use the same phrasing.
- feedback_reset_terminology.md memory locks the rule for future
edits — never write "hold for 5 seconds" in user copy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.1 KiB
pictureFrame — web app
Handcrafted e-ink digital picture frame ecosystem — built as a meaningful gift. ESP32 firmware pulls pre-rendered images from a Symfony web app over WiFi; the companion web app handles image management, device configuration, and family sharing.
Project goal
Build gifted e-ink frames that stay personal and current over time, with no ongoing effort required from the recipient. One image per configured interval, cycling through a curated pool of family uploads and shared photos. Setup: scan two QR codes. Ongoing: nothing unless the recipient wants it.
Stack
- Firmware: PlatformIO + Arduino framework (C/C++), ESP32 dev board
- Web app: Symfony 8.0 (PHP 8.4+), PostgreSQL 16, Nginx-FPM
- Local dev: DDEV — mirrors
~/src/aqua-iqsetup - Git: git.edholm.me (self-hosted Gitea/Forgejo)
- Domain: pictureframe.edholm.me
Hardware
Dev / V1 hardware (in hand)
- ESP32 dev board (
esp32dev), dual-core 240MHz, 4MB flash - Waveshare 7.3" 6-color e-ink (800×480)
- SPI pinout: SCK=18, MOSI=23, CS=5, DC=17, RST=16, BUSY=4
- 4bpp packed, palette: BLACK=0x0, WHITE=0x1, YELLOW=0x2, RED=0x3, BLUE=0x5, GREEN=0x6 (same map as Spectra 6; 0x4 unused)
Target / V2 hardware (on order)
- Waveshare ESP32-S3-ePaper-13.3E6 — 13.3" Spectra 6 (6-color), ESP32-S3 onboard
- Spectra 6 palette: same as above — BLACK=0x0, WHITE=0x1, YELLOW=0x2, RED=0x3, BLUE=0x5, GREEN=0x6
- Battery or plugin at recipient's choice
Design decisions
- Server pre-renders all images to display-ready 4bpp per device model/orientation — ESP32 never transforms images
- Device pull model:
GET /api/device/{mac}/image→ 200 (binary), 204 (no ready image), 404 (unknown MAC) - Atomic image write: display only refreshes after complete confirmed transfer; last good image persists through outages
- Deep sleep between pull cycles (see ESP32 deep sleep memory)
- Status via border color: yellow = sync fail, red = no WiFi
- Holding the BOOT button until the screen starts to flash triggers re-provisioning (config wipe + AP mode). Threshold is 5 s in firmware (RESET_HOLD_MS), but user-facing terminology is "hold until the screen flashes" because total wall-clock time-to-visible-change includes a ~20 s e-ink redraw, and a too-short press just wakes the device into a normal poll cycle.
- Two-phase provisioning: AP mode (WiFi credentials) → STA mode (QR to account setup page)
- Async image processing: Symfony Messenger (Doctrine transport),
max_retries: 1 - Image storage:
storage/images/{id}/{model}_{orientation}.bin, relative paths in DB - PHP 8.1 backed enums for
RenderStatus,TokenType,Orientation - Imagick for Floyd-Steinberg dithering (not GD)
- No OTA firmware updates in V1 — API contract must not break without reflash
Infrastructure reference
For Docker/DDEV config, server location, and SSH details: ~/src/aqua-iq
Full spec
See _bmad-output/planning-artifacts/ — PRD, architecture, epics all complete as of 2026-04-27. Symfony web app scaffold + Vue SPA frontend scaffolded. Firmware fully rewritten with WiFi, provisioning, and deep sleep (firmware/src/).