diff --git a/data/ap_bg.bin b/data/waveshare73-v1/ap_bg.bin similarity index 100% rename from data/ap_bg.bin rename to data/waveshare73-v1/ap_bg.bin diff --git a/data/ap_bg_preview.png b/data/waveshare73-v1/ap_bg_preview.png similarity index 100% rename from data/ap_bg_preview.png rename to data/waveshare73-v1/ap_bg_preview.png diff --git a/data/img.bin b/data/waveshare73-v1/img.bin similarity index 100% rename from data/img.bin rename to data/waveshare73-v1/img.bin diff --git a/data/img_portrait.bin b/data/waveshare73-v1/img_portrait.bin similarity index 100% rename from data/img_portrait.bin rename to data/waveshare73-v1/img_portrait.bin diff --git a/data/setup_bg.bin b/data/waveshare73-v1/setup_bg.bin similarity index 100% rename from data/setup_bg.bin rename to data/waveshare73-v1/setup_bg.bin diff --git a/data/setup_bg_preview.png b/data/waveshare73-v1/setup_bg_preview.png similarity index 100% rename from data/setup_bg_preview.png rename to data/waveshare73-v1/setup_bg_preview.png diff --git a/platformio.ini b/platformio.ini index 4952afc..56f7f98 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,66 +1,86 @@ -[env:esp32dev] +; pictureFrame firmware build environments. +; +; Layout: shared sources at src/ root + panel-specific implementations at +; src/panels/{vendor}/v{N}/. Each panel/version gets its own [env:...] block +; that sets dimensions, pins, and panel-id via build_flags, and selects its +; driver via build_src_filter. New panel = new env block + new +; src/panels/{vendor}/v{N}/ folder + new data/{vendor}-v{N}/ folder. No +; surgery to existing envs. +; +; Old envs preserved as historical snapshots — re-flashing units in the +; field stays a one-line `pio run -e --target upload` command. + +; ── Production firmware: Waveshare 7.3" Spectra-6 + ESP32 dev breakout ── +[env:waveshare73-v1] platform = espressif32 board = esp32dev framework = arduino -monitor_speed = 115200 upload_port = /dev/ttyUSB0 monitor_port = /dev/ttyUSB0 +monitor_speed = 115200 board_build.filesystem = littlefs +data_dir = data/waveshare73-v1 +build_src_filter = + + + + +build_flags = + -DEPD_WIDTH=800 + -DEPD_HEIGHT=480 + -DMAX_PANEL_WIDTH=800 + -DPIN_SCK=18 + -DPIN_MOSI=23 + -DPIN_CS=5 + -DPIN_DC=17 + -DPIN_RST=16 + -DPIN_BUSY=4 + -DPANEL_ID=\"waveshare-7.3-spectra6\" lib_deps = ricmoo/QRCode@^0.0.1 -; Flash a single image from firmware/data/img.bin — no WiFi, no server needed. -; 1. pio run -e test-display --target uploadfs (upload the image) -; 2. pio run -e test-display --target upload (upload the sketch) +; ── Hardware visual test: flash a single image from data/waveshare73-v1/img.bin ── +; No WiFi, no server. +; 1. pio run -e test-display --target uploadfs (upload data/) +; 2. pio run -e test-display --target upload (upload sketch) [env:test-display] -platform = espressif32 -board = esp32dev -framework = arduino -monitor_speed = 115200 -upload_port = /dev/ttyUSB0 -monitor_port = /dev/ttyUSB0 -board_build.filesystem = littlefs -build_flags = -DENV_TEST_DISPLAY -build_src_filter = + + -lib_deps = - ricmoo/QRCode@^0.0.1 +extends = env:waveshare73-v1 +build_src_filter = + + + + +build_flags = + ${env:waveshare73-v1.build_flags} + -DENV_TEST_DISPLAY +; ── Visual hardware tests for the sync-fail / no-WiFi border ── +; Reads the cached /img.bin from LittleFS and draws it with a yellow / red +; border. Sets NVS err_border=1 so the next normal-firmware boot exercises +; the recovery redraw via 304. Flow: +; 1. pio run -e sim-yellow --target upload (see yellow border) +; 2. pio run -e sim-red --target upload (see red border) +; 3. pio run -e waveshare73-v1 --target upload (back to normal; verifies 304 recovery) +[env:sim-yellow] +extends = env:waveshare73-v1 +build_src_filter = + + + + +build_flags = + ${env:waveshare73-v1.build_flags} + -DSIM_BORDER + -DSIM_BORDER_COLOR=COLOR_YELLOW + +[env:sim-red] +extends = env:waveshare73-v1 +build_src_filter = + + + + +build_flags = + ${env:waveshare73-v1.build_flags} + -DSIM_BORDER + -DSIM_BORDER_COLOR=COLOR_RED + +; ── Native unit tests — no hardware, uses test/mocks/ ── [env:native-test] platform = native lib_deps = throwtheswitch/Unity@^2.6 build_flags = -DUNIT_TEST -std=c++17 -iquote test/mocks -iquote test -Itest/mocks -Itest test_build_src = no - -; Visual hardware tests for the sync-fail / no-WiFi border. Reads the cached -; /img.bin from LittleFS and draws it with a yellow / red border. Sets -; NVS err_border=1 so the next normal-firmware boot exercises the recovery -; redraw via 304. Flow: -; 1. pio run -e sim-yellow --target upload (see yellow border on hardware) -; 2. pio run -e sim-red --target upload (see red border on hardware) -; 3. pio run -e esp32dev --target upload (back to normal; verifies 304 recovery) -[env:sim-yellow] -platform = espressif32 -board = esp32dev -framework = arduino -monitor_speed = 115200 -upload_port = /dev/ttyUSB0 -monitor_port = /dev/ttyUSB0 -board_build.filesystem = littlefs -build_flags = -DSIM_BORDER -DSIM_BORDER_COLOR=COLOR_YELLOW -build_src_filter = + + -lib_deps = - ricmoo/QRCode@^0.0.1 - -[env:sim-red] -platform = espressif32 -board = esp32dev -framework = arduino -monitor_speed = 115200 -upload_port = /dev/ttyUSB0 -monitor_port = /dev/ttyUSB0 -board_build.filesystem = littlefs -build_flags = -DSIM_BORDER -DSIM_BORDER_COLOR=COLOR_RED -build_src_filter = + + -lib_deps = - ricmoo/QRCode@^0.0.1 diff --git a/scripts/gen_screens.py b/scripts/gen_screens.py index ad36c22..19d1442 100644 --- a/scripts/gen_screens.py +++ b/scripts/gen_screens.py @@ -329,13 +329,20 @@ def save_bin(img, path, preview_path): print(f"Preview → {os.path.abspath(preview_path)}") if __name__ == "__main__": - out_dir = os.path.join(os.path.dirname(__file__), "../data") + # Per-panel output directory: data/{vendor}-v{N}/. Defaults to the V1 + # 7.3" panel since that's the only one in production; pass --panel to + # target a different one once new panels exist. + panel = "waveshare73-v1" + if "--panel" in sys.argv: + panel = sys.argv[sys.argv.index("--panel") + 1] + + out_dir = os.path.join(os.path.dirname(__file__), f"../data/{panel}") os.makedirs(out_dir, exist_ok=True) - print("Generating AP screen…") + print(f"Generating AP screen for {panel}…") save_bin(gen_ap(), f"{out_dir}/ap_bg.bin", f"{out_dir}/ap_bg_preview.png") print() - print("Generating setup screen…") + print(f"Generating setup screen for {panel}…") save_bin(gen_setup(), f"{out_dir}/setup_bg.bin", f"{out_dir}/setup_bg_preview.png") print() print("QR overlay constants for epd.cpp:") diff --git a/scripts/gen_setup_bg.py b/scripts/gen_setup_bg.py index 545b0d8..8232f47 100644 --- a/scripts/gen_setup_bg.py +++ b/scripts/gen_setup_bg.py @@ -116,7 +116,7 @@ for y in range(H): lo = nearest(*pixels[x+1, y]) out.append((hi << 4) | lo) -out_path = os.path.join(os.path.dirname(__file__), "../data/setup_bg.bin") +out_path = os.path.join(os.path.dirname(__file__), "../data/waveshare73-v1/setup_bg.bin") with open(out_path, "wb") as f: f.write(out) diff --git a/src/config.h b/src/config.h index 8b92a8f..3b1c888 100644 --- a/src/config.h +++ b/src/config.h @@ -1,21 +1,99 @@ #pragma once // repo: pictureFrame-firmware +// +// Hardware specifics (panel dimensions + GPIO pinout) come from the build +// env's -D flags in platformio.ini, not from this file. That keeps the +// shared sources panel-agnostic and lets each `src/panels/{vendor}/v{N}/` +// subtree stay focused on its own driver. +// +// Strict: missing a -D flag is a compile error here, not a silent fallback +// to V1 values. A bad env config fails fast at build time. Native tests +// (UNIT_TEST defined) get harmless defaults — they don't drive real +// hardware, so the values never matter beyond satisfying the compiler. -// ── EPD pins (Waveshare 7.3" ACeP) ────────────────────────────────────────── -#define EPD_WIDTH 800 -#define EPD_HEIGHT 480 -#define PIN_SCK 18 -#define PIN_MOSI 23 -#define PIN_CS 5 -#define PIN_DC 17 -#define PIN_RST 16 -#define PIN_BUSY 4 +// ── EPD pixel dimensions ───────────────────────────────────────────────── +#ifndef EPD_WIDTH +# ifdef UNIT_TEST +# define EPD_WIDTH 800 +# else +# error "EPD_WIDTH not defined — set -DEPD_WIDTH=… in platformio.ini build_flags" +# endif +#endif +#ifndef EPD_HEIGHT +# ifdef UNIT_TEST +# define EPD_HEIGHT 480 +# else +# error "EPD_HEIGHT not defined — set -DEPD_HEIGHT=… in platformio.ini build_flags" +# endif +#endif -// ── Reset button (BOOT button = GPIO 0 on most dev boards) ────────────────── +// Largest panel width any build env may target. Static buffers in the +// driver use this to size themselves at compile time so we don't pay for +// heap allocation on every draw. Default: assume the active env's panel +// (so V1 builds get an 800-wide buffer; V2 builds will set it to 1872). +#ifndef MAX_PANEL_WIDTH +#define MAX_PANEL_WIDTH EPD_WIDTH +#endif + +// ── GPIO pinout ────────────────────────────────────────────────────────── +// Same UNIT_TEST escape: native tests get the V1 dev-board pinout so +// shared sources compile, even though no real GPIO is touched. +#ifndef PIN_SCK +# ifdef UNIT_TEST +# define PIN_SCK 18 +# else +# error "PIN_SCK not defined — set -DPIN_SCK=… in platformio.ini build_flags" +# endif +#endif +#ifndef PIN_MOSI +# ifdef UNIT_TEST +# define PIN_MOSI 23 +# else +# error "PIN_MOSI not defined — set -DPIN_MOSI=… in platformio.ini build_flags" +# endif +#endif +#ifndef PIN_CS +# ifdef UNIT_TEST +# define PIN_CS 5 +# else +# error "PIN_CS not defined — set -DPIN_CS=… in platformio.ini build_flags" +# endif +#endif +#ifndef PIN_DC +# ifdef UNIT_TEST +# define PIN_DC 17 +# else +# error "PIN_DC not defined — set -DPIN_DC=… in platformio.ini build_flags" +# endif +#endif +#ifndef PIN_RST +# ifdef UNIT_TEST +# define PIN_RST 16 +# else +# error "PIN_RST not defined — set -DPIN_RST=… in platformio.ini build_flags" +# endif +#endif +#ifndef PIN_BUSY +# ifdef UNIT_TEST +# define PIN_BUSY 4 +# else +# error "PIN_BUSY not defined — set -DPIN_BUSY=… in platformio.ini build_flags" +# endif +#endif + +// ── Panel telemetry strings ────────────────────────────────────────────── +// Reported to the server at provisioning. PANEL_ID identifies the hardware +// model (vendor + size); PANEL_FW_VERSION lives in src/panels/{…}/v{N}/version.h +// and ticks when the panel-specific driver code changes. +#ifndef PANEL_ID +#define PANEL_ID "unknown" +#endif + +// ── Reset button (BOOT button = GPIO 0 on most dev boards) ────────────── #define PIN_BTN_RESET 0 #define RESET_HOLD_MS 5000 -// ── EPD color nibbles ──────────────────────────────────────────────────────── +// ── EPD color nibbles ──────────────────────────────────────────────────── // Verified on Waveshare 7.3" hardware. Same map on 13.3" Spectra 6. #define COLOR_BLACK 0x0 #define COLOR_WHITE 0x1 @@ -24,7 +102,7 @@ #define COLOR_BLUE 0x5 #define COLOR_GREEN 0x6 -// ── NVS ────────────────────────────────────────────────────────────────────── +// ── NVS ────────────────────────────────────────────────────────────────── #define NVS_NAMESPACE "pf" #define NVS_KEY_SSID "ssid" #define NVS_KEY_PASS "pass" @@ -40,7 +118,7 @@ // Width of the sync-fail / no-WiFi border, in pixels. #define BORDER_THICKNESS_PX 4 -// ── Network ────────────────────────────────────────────────────────────────── +// ── Network ────────────────────────────────────────────────────────────── #define APP_BASE_URL "https://pictureframe.edholm.me" #define AP_IP "192.168.4.1" #define WIFI_TIMEOUT_MS 30000 diff --git a/src/panels/waveshare73/v1/README.md b/src/panels/waveshare73/v1/README.md new file mode 100644 index 0000000..f4b6768 --- /dev/null +++ b/src/panels/waveshare73/v1/README.md @@ -0,0 +1,48 @@ +# waveshare73 / v1 + +Driver for the Waveshare 7.3" 6-color e-ink panel (800×480, Spectra-6) on a +discrete ESP32 dev-board breakout. This is the original V1 firmware that +shipped on Matt's prototype unit. + +## What lives here + +- `epd_driver.cpp` — implements the `epd.h` interface for this panel: + `epd_init`, `epd_sleep`, `epd_fill`, `epd_draw_image_from_file`, + `epd_draw_image_with_border`, `epd_draw_qr`, `epd_draw_ap_screen`, + `epd_draw_setup_screen`. The init byte sequence at the top of `epd_init()` + is verified for this specific controller; do not blind-paste from another + panel. +- `version.h` — `PANEL_FW_VERSION` string transmitted during provisioning. + Bump when the driver changes in a way the server should know about. + +## Per-panel data (lives elsewhere) + +Pre-rendered LittleFS payloads for this panel size live at +`data/waveshare73-v1/`: + +- `ap_bg.bin`, `setup_bg.bin` — captive-portal and account-binding + background screens at 800×480 +- `img.bin`, `img_portrait.bin` — test images used by the `test-display` + build env + +Regenerate with `scripts/gen_screens.py --width 800 --height 480` after +changing the source artwork. + +## Build env + +```bash +pio run -e waveshare73-v1 --target upload # production firmware +pio run -e test-display --target upload # single-image hardware test +pio run -e sim-yellow --target upload # sync-fail border test +pio run -e sim-red --target upload # no-WiFi border test +``` + +The build flags supplied by `platformio.ini` parametrize `src/config.h` — +this folder doesn't redefine pins or dimensions itself. + +## QR positions + +`epd_draw_ap_screen` and `epd_draw_setup_screen` have hardcoded QR +coordinates (cell size, x, y) tuned for 800×480 + the bg screens at +`data/waveshare73-v1/`. If the bg art changes, the matching constants +in `gen_screens.py` and the driver must be re-tuned together. diff --git a/src/epd.cpp b/src/panels/waveshare73/v1/epd_driver.cpp similarity index 100% rename from src/epd.cpp rename to src/panels/waveshare73/v1/epd_driver.cpp diff --git a/src/panels/waveshare73/v1/version.h b/src/panels/waveshare73/v1/version.h new file mode 100644 index 0000000..05a0995 --- /dev/null +++ b/src/panels/waveshare73/v1/version.h @@ -0,0 +1,10 @@ +#pragma once + +// Panel-specific firmware version. Bump when the V1 7.3" driver changes +// in a way the server should know about. Reported to the server during +// provisioning so support can correlate field-reported issues with the +// exact panel-driver build a device is running. +// +// Independent of the shared firmware version (HTTP / NVS / sleep / etc.) +// — that's tracked by git SHA, not this string. +#define PANEL_FW_VERSION "v1.0"