The Waveshare board exposes PIN_PWR (GPIO 1) specifically so battery
designs can gate the panel rail between refreshes. Before this commit
PIN_PWR was driven HIGH at boot and never released, so the panel's
boost converter kept its quiescent draw (50–500 µA) through every
deep sleep. The e-ink particles are bistable so the displayed image
persists without VDD; dropping the rail is a free win.
Three pieces:
• epd_sleep() drives PIN_PWR LOW after issuing the panel-internal
DEEP_SLEEP command, then gpio_hold_en() latches the level so it
survives the chip's RTC transition.
• normal_operation_impl() calls gpio_deep_sleep_hold_en() just
before esp_deep_sleep_start() so the per-pin hold extends through
the deep sleep period itself (without this the holds release on
the transition and the rail comes back up).
• epd_setup_pins() calls gpio_hold_dis() at the very top to free
PIN_PWR on wake before re-driving it HIGH; no-op on cold boot.
Tests: 47/47 pass. Added test/mocks/driver/gpio.h with no-op stubs so
the native test build links cleanly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adopts the hero treatment Matt picked from the /tmp/setup-mockups
gallery: a 1200×460 harbour photo banner with the WeVisto wordmark at
200pt overlaid centred, then a 70-px accent band carrying the section
title. Replaces the prior 130-tall single band where the 110×110 logo
card couldn't render the Camogli photo recognisably under the 6-colour
palette.
Implementation notes:
- compose_hero_banner() crops from the hi-res IMG_2524.jpg (so we don't
upsample the 900-square version), composites the SVG black-fade
gradient, then Floyd-Steinberg dithers to the Spectra-6 palette so the
photo reads as continuous tone instead of nearest-neighbour colour
fields. Wordmark composited after the dither to keep text edges crisp.
- Compact orientation diagrams + smaller manual QR (box_size=5) so the
AP screen's left column still fits the 4 steps + diagrams + help QR
inside the 1070-px body left below the taller hero.
- Setup QR cell shrunk 16 → 14 (656 → 574 px) so the setup screen fits
the QR + MAC chip + progress bar below the hero.
- Redundant two-line "Scan the QR to link this frame / to your
wevisto.com account." dropped from setup screen — heading + label
above the QR + MAC chip below it cover the same ground without
crowding the post-hero body.
- epd_driver.cpp QR overlay coords updated to match: AP 230→590,
setup (272,490,16) → (313,750,14).
compose_logo() (square card) kept for any future use; not currently
called by gen_ap/gen_setup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three problems surfaced during the first 13.3" end-to-end run:
1) LittleFS IntegerDivideByZero on 200 → write /img.bin. Cause: the
~3.5 MB SPIFFS in default_16MB.csv can't fit three 960 KB setup
screens + a 960 KB cached image (~3.84 MB). Switching to a custom
partitions_13e6.csv with 24 MB LittleFS on the 32 MB flash.
2) Yellow wash across the panel on long SPI bursts. Cause: SPI DMA
from a PSRAM-backed scratch buffer hits a cache-coherency window —
the CPU's writes hadn't reached PSRAM yet when DMA read it. Push
each half in 8 KB chunks through an internal-SRAM (DMA-coherent)
scratch, and drop the bus clock to 4 MHz to match the 7.3"
production speed.
3) Bootstrap window (no image yet) was deep-sleeping for 15 s between
polls — each cycle a ~5 s ROM-boot + Wi-Fi reconnect, so the user
waited ~20 s × N retries between scanning the setup QR and seeing
their first photo land. Now normal_operation_impl returns early
during bootstrap and main.cpp's normal_operation loops with a
2 s delay, keeping Wi-Fi up. Once the first image arrives, the
normal scheduled deep sleep takes over.
Also fixes a related bug Matt called out: a transient TLS hiccup
during bootstrap was hitting the 5xx fallback path and painting a
full yellow fill over the green setup QR, leaving the user with
no claim path. Criterion is now "does /img.bin exist?" (panel has
something worth showing with a border) rather than "is currentImgId
set?", so a fresh device with no cached image preserves the setup
screen through transient network errors.
Diagnostic prints in the panel driver + [op] start/code lines in
normal_operation_impl that proved invaluable during bringup; leaving
them in for now. Tests updated for the new bootstrap semantics
(deep sleep no longer arms on bootstrap-cycle 204/404/5xx); 43/43
native tests pass, 7.3" production build stays byte-identical.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- SPI corruption: lower clock to 4 MHz (matches 7.3" prod) and push from
internal SRAM in 8 KB chunks instead of streaming directly from a PSRAM
scratch buffer. On the S3, Arduino's SPI DMA reads RAM directly — the
CPU's cache can hold writes to PSRAM that the DMA never sees, painting
the panel yellow/garbage. Internal-SRAM chunks are DMA-coherent.
- LittleFS partition: switch the env to default_16MB.csv. The stock
partition table for esp32-s3-devkitc-1 reserves ~1.5 MB for SPIFFS;
three 960 KB setup-screen .bin files need ~2.9 MB + LittleFS metadata.
- Setup screens: redesigned to match the 7.3" information density —
yellow header band, two-column body with vertical divider, "Connect to
WiFi" heading + 5 numbered steps + manual QR + side label on the left,
Step 1 / Step 2 QRs on the right.
- Orientation diagrams: PORTRAIT drawn upright (ribbon-bottom, up-arrow);
LANDSCAPE drawn pre-rotated 90° CCW so it snaps to upright landscape
when the user rotates the frame 90° CW (ribbon-bottom + left-arrow in
portrait view → ribbon-left + up-arrow after rotation). "LANDSCAPE"
label runs vertically up the long edge so it reads horizontally once
the frame is mounted landscape.
- New helper paste_rotated_text() — PIL's text() can't rotate, so render
→ rotate → paste-with-alpha. Used for the vertical LANDSCAPE label.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a second panel target alongside the 7.3":
- src/panels/waveshare13e6/v1/ — full epd.h impl with hardware SPI on
FSPI, dual-CS dispatch (CS_M/CS_S split halves), PSRAM framebuffer
for image/QR/setup-screen render paths
- src/test_display_13e6.cpp + [env:test-display-13e6] — self-contained
first-pixels color-bar smoke test, kept as a hardware diagnostic
- [env:waveshare13e6-v1] — production env: ESP32-S3-WROOM-2 N32R16V
with OPI flash + OPI PSRAM (the WROOM-2 is octal flash; QIO mode
crashes at do_core_init startup.c:328)
- scripts/gen_screens_13e6.py + data/waveshare13e6-v1/ — 1200x1600
portrait setup screens with QR overlay regions matching the driver
- scripts/data_dir.py — extra_scripts shim that routes uploadfs to the
right data/ tree based on $PIOENV (PlatformIO ignores per-env data_dir)
- src/epd.h: epd_setup_pins() abstraction so each panel driver owns its
own pinMode + SPI.begin; main/test_display/sim_border lose all
panel-specific GPIO and call epd_setup_pins() once at boot
- src/operation.h: report PANEL_ID via X-Panel-Id header on every poll
so the server can auto-correct Device.model
7.3" production env stays byte-identical, all 43 native tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>