refactor(firmware): per-panel folder layout + parametrized config.h
Reorganizes the tree so adding a new panel is purely additive — drop in a
new src/panels/{vendor}/v{N}/ folder and a new platformio.ini env block,
no surgery to existing files.
Layout:
src/ shared across all panels
src/panels/waveshare73/v1/ V1 driver, version, README
data/waveshare73-v1/ LittleFS payload at this panel's size
src/config.h still defines the panel-agnostic bits (NVS keys, color
palette, network, sync-fail border) but EPD_WIDTH / EPD_HEIGHT / pin
assignments now come from each env's -D flags. Strict #error guards in
production builds; native tests get the V1 defaults via UNIT_TEST.
build_src_filter per env picks the right driver:
waveshare73-v1 main + panels/waveshare73/v1/
test-display test_display + panels/waveshare73/v1/
sim-yellow sim_border + panels/waveshare73/v1/
sim-red sim_border + panels/waveshare73/v1/
native-test unchanged
When V2 hardware lands, the diff is a new env block, a new
src/panels/waveshare133/v1/epd_driver.cpp, and regenerated screens at
data/waveshare133-v1/. Existing V1 envs stay frozen — re-flashing old
units remains a one-liner.
scripts/gen_screens.py takes --panel to target the correct
data/{panel}/ subfolder; defaults to waveshare73-v1.
29/29 native tests pass. All four hardware envs build clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
+69
-49
@@ -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 <env> --target upload` command.
|
||||||
|
|
||||||
|
; ── Production firmware: Waveshare 7.3" Spectra-6 + ESP32 dev breakout ──
|
||||||
|
[env:waveshare73-v1]
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
framework = arduino
|
framework = arduino
|
||||||
monitor_speed = 115200
|
|
||||||
upload_port = /dev/ttyUSB0
|
upload_port = /dev/ttyUSB0
|
||||||
monitor_port = /dev/ttyUSB0
|
monitor_port = /dev/ttyUSB0
|
||||||
|
monitor_speed = 115200
|
||||||
board_build.filesystem = littlefs
|
board_build.filesystem = littlefs
|
||||||
|
data_dir = data/waveshare73-v1
|
||||||
|
build_src_filter =
|
||||||
|
+<main.cpp>
|
||||||
|
+<panels/waveshare73/v1/>
|
||||||
|
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 =
|
lib_deps =
|
||||||
ricmoo/QRCode@^0.0.1
|
ricmoo/QRCode@^0.0.1
|
||||||
|
|
||||||
; Flash a single image from firmware/data/img.bin — no WiFi, no server needed.
|
; ── Hardware visual test: flash a single image from data/waveshare73-v1/img.bin ──
|
||||||
; 1. pio run -e test-display --target uploadfs (upload the image)
|
; No WiFi, no server.
|
||||||
; 2. pio run -e test-display --target upload (upload the sketch)
|
; 1. pio run -e test-display --target uploadfs (upload data/)
|
||||||
|
; 2. pio run -e test-display --target upload (upload sketch)
|
||||||
[env:test-display]
|
[env:test-display]
|
||||||
platform = espressif32
|
extends = env:waveshare73-v1
|
||||||
board = esp32dev
|
build_src_filter =
|
||||||
framework = arduino
|
+<test_display.cpp>
|
||||||
monitor_speed = 115200
|
+<panels/waveshare73/v1/>
|
||||||
upload_port = /dev/ttyUSB0
|
build_flags =
|
||||||
monitor_port = /dev/ttyUSB0
|
${env:waveshare73-v1.build_flags}
|
||||||
board_build.filesystem = littlefs
|
-DENV_TEST_DISPLAY
|
||||||
build_flags = -DENV_TEST_DISPLAY
|
|
||||||
build_src_filter = +<epd.cpp> +<test_display.cpp>
|
|
||||||
lib_deps =
|
|
||||||
ricmoo/QRCode@^0.0.1
|
|
||||||
|
|
||||||
|
; ── 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 =
|
||||||
|
+<sim_border.cpp>
|
||||||
|
+<panels/waveshare73/v1/>
|
||||||
|
build_flags =
|
||||||
|
${env:waveshare73-v1.build_flags}
|
||||||
|
-DSIM_BORDER
|
||||||
|
-DSIM_BORDER_COLOR=COLOR_YELLOW
|
||||||
|
|
||||||
|
[env:sim-red]
|
||||||
|
extends = env:waveshare73-v1
|
||||||
|
build_src_filter =
|
||||||
|
+<sim_border.cpp>
|
||||||
|
+<panels/waveshare73/v1/>
|
||||||
|
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]
|
[env:native-test]
|
||||||
platform = native
|
platform = native
|
||||||
lib_deps =
|
lib_deps =
|
||||||
throwtheswitch/Unity@^2.6
|
throwtheswitch/Unity@^2.6
|
||||||
build_flags = -DUNIT_TEST -std=c++17 -iquote test/mocks -iquote test -Itest/mocks -Itest
|
build_flags = -DUNIT_TEST -std=c++17 -iquote test/mocks -iquote test -Itest/mocks -Itest
|
||||||
test_build_src = no
|
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 = +<epd.cpp> +<sim_border.cpp>
|
|
||||||
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 = +<epd.cpp> +<sim_border.cpp>
|
|
||||||
lib_deps =
|
|
||||||
ricmoo/QRCode@^0.0.1
|
|
||||||
|
|||||||
+10
-3
@@ -329,13 +329,20 @@ def save_bin(img, path, preview_path):
|
|||||||
print(f"Preview → {os.path.abspath(preview_path)}")
|
print(f"Preview → {os.path.abspath(preview_path)}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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)
|
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")
|
save_bin(gen_ap(), f"{out_dir}/ap_bg.bin", f"{out_dir}/ap_bg_preview.png")
|
||||||
print()
|
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")
|
save_bin(gen_setup(), f"{out_dir}/setup_bg.bin", f"{out_dir}/setup_bg_preview.png")
|
||||||
print()
|
print()
|
||||||
print("QR overlay constants for epd.cpp:")
|
print("QR overlay constants for epd.cpp:")
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ for y in range(H):
|
|||||||
lo = nearest(*pixels[x+1, y])
|
lo = nearest(*pixels[x+1, y])
|
||||||
out.append((hi << 4) | lo)
|
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:
|
with open(out_path, "wb") as f:
|
||||||
f.write(out)
|
f.write(out)
|
||||||
|
|
||||||
|
|||||||
+89
-11
@@ -1,21 +1,99 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
// repo: pictureFrame-firmware
|
// 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) ──────────────────────────────────────────
|
// ── EPD pixel dimensions ─────────────────────────────────────────────────
|
||||||
|
#ifndef EPD_WIDTH
|
||||||
|
# ifdef UNIT_TEST
|
||||||
# define EPD_WIDTH 800
|
# 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
|
# define EPD_HEIGHT 480
|
||||||
#define PIN_SCK 18
|
# else
|
||||||
#define PIN_MOSI 23
|
# error "EPD_HEIGHT not defined — set -DEPD_HEIGHT=… in platformio.ini build_flags"
|
||||||
#define PIN_CS 5
|
# endif
|
||||||
#define PIN_DC 17
|
#endif
|
||||||
#define PIN_RST 16
|
|
||||||
#define PIN_BUSY 4
|
|
||||||
|
|
||||||
// ── 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 PIN_BTN_RESET 0
|
||||||
#define RESET_HOLD_MS 5000
|
#define RESET_HOLD_MS 5000
|
||||||
|
|
||||||
// ── EPD color nibbles ────────────────────────────────────────────────────────
|
// ── EPD color nibbles ────────────────────────────────────────────────────
|
||||||
// Verified on Waveshare 7.3" hardware. Same map on 13.3" Spectra 6.
|
// Verified on Waveshare 7.3" hardware. Same map on 13.3" Spectra 6.
|
||||||
#define COLOR_BLACK 0x0
|
#define COLOR_BLACK 0x0
|
||||||
#define COLOR_WHITE 0x1
|
#define COLOR_WHITE 0x1
|
||||||
@@ -24,7 +102,7 @@
|
|||||||
#define COLOR_BLUE 0x5
|
#define COLOR_BLUE 0x5
|
||||||
#define COLOR_GREEN 0x6
|
#define COLOR_GREEN 0x6
|
||||||
|
|
||||||
// ── NVS ──────────────────────────────────────────────────────────────────────
|
// ── NVS ──────────────────────────────────────────────────────────────────
|
||||||
#define NVS_NAMESPACE "pf"
|
#define NVS_NAMESPACE "pf"
|
||||||
#define NVS_KEY_SSID "ssid"
|
#define NVS_KEY_SSID "ssid"
|
||||||
#define NVS_KEY_PASS "pass"
|
#define NVS_KEY_PASS "pass"
|
||||||
@@ -40,7 +118,7 @@
|
|||||||
// Width of the sync-fail / no-WiFi border, in pixels.
|
// Width of the sync-fail / no-WiFi border, in pixels.
|
||||||
#define BORDER_THICKNESS_PX 4
|
#define BORDER_THICKNESS_PX 4
|
||||||
|
|
||||||
// ── Network ──────────────────────────────────────────────────────────────────
|
// ── Network ──────────────────────────────────────────────────────────────
|
||||||
#define APP_BASE_URL "https://pictureframe.edholm.me"
|
#define APP_BASE_URL "https://pictureframe.edholm.me"
|
||||||
#define AP_IP "192.168.4.1"
|
#define AP_IP "192.168.4.1"
|
||||||
#define WIFI_TIMEOUT_MS 30000
|
#define WIFI_TIMEOUT_MS 30000
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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"
|
||||||
Reference in New Issue
Block a user