BERTH 05 — HARDWARE & E-INK

Tide Display

ESP32-S3 · 2.13" E-Ink · 7-Page Carousel

Always-on coastal conditions dashboard. Tide predictions, swell and wind from a nearby buoy, moon phase, a 7-day beach planning view, and local eBird sightings — refreshed every 10 minutes, readable from across the kitchen without touching a phone.

Production C++ / Arduino ESP32-S3 E-Ink EPD NOAA · NDBC · eBird
7 PAGES — 250×122 PIXEL CANVAS
STACK
HARDWARE
CrowPanel ESP32-S3 (Elecrow)
2.13" B&W e-ink, 250×122px
Elecrow proprietary EPD library
Directional nav switch + buttons
GPIOs 0, 1, 2, 4, 5, 6 (nav)
USB-C power · Li-Po planned
FIRMWARE
C++ · Arduino framework · Arduino IDE 2
ArduinoJson v6 (DynamicJsonDocument)
HTTPClient / WiFiClientSecure
configTime / NTP sync
POSIX time.h for all time math
Page carousel: global g_page int
DATA SOURCES
NOAA Tides & Currents API v1
NDBC buoy 46053 (raw text)
Open-Meteo forecast + marine API
eBird API v2 — geo/recent + notable
No API key required for NOAA, NDBC, Open-Meteo
LOCAL ALGORITHMS
USNO solar position (sunrise/set)
Julian date moon phase + illumination
Parabolic arc for sky clock rendering
Cosine tide interpolation (hi/lo → curve)
spanProgress() / arcPointForProgress()
DATA PIPELINE

fetchAllData() runs at startup and every 10 minutes, calling each source sequentially. JSON is parsed with DynamicJsonDocument allocated on the heap, sized to ~1.5× the observed maximum response length, and freed when each fetch function returns. Seven sequential HTTPS calls on a 512KB target with no async — every sizing and timeout decision compounds.

fetchAllData() every 10 min or on button press NOAA Tides API hi/lo predictions · water level · air temp NDBC Buoy 46053 raw text · strtok parse · wave/wind/pressure Open-Meteo forecast + marine APIs · no auth required eBird API v2 geo/recent + notable · x-ebirdapitoken Sun/Moon · Local algorithms · No API 7-PAGE CAROUSEL drawCurrentPage() → dispatch on g_page beginPage() → memset(ImageBW) → draw → finishPage() nav button → g_page++ % 7 → redraw Dashboard · Surf · Sky · Moon · Week · Birds ×2

NDBC buoy data arrives as raw whitespace-delimited text, not JSON — parsed manually with strtok to extract wave height, period, swell direction, wind speed, gust, pressure, and water temperature from column offsets. The two eBird calls use a two-source merge: Andree Clark Bird Refuge at 1km radius fills available slots first, the broader SB coast at 5km fills any remainder, with species deduplicated by common name across both.

ENGINEERING DECISIONS
NOTABLE PROBLEMS
The GPIO/SPI conflict — weeks of silent failures in one header file

Buttons registered nothing. Multiple pin assignments were tried. Debounce values were retuned. Watchdog resets appeared and disappeared without explanation. The root cause was in a file called spi.h inside an earlier project version — a pinout table showing that GPIOs 9–14 are the exclusive property of the e-ink SPI bus (BUSY, RES, MOSI, SCK, CS, DC). Two of the original button assignments landed on SCK and CS. Setting those to INPUT_PULLUP during button init left the EPD panel unable to signal ready, trapping the busy-wait loop forever. Reading the hardware header file once resolved a multi-week mystery in under five minutes.

Ghost content from the shared pixel buffer

The EPD library uses a single global ImageBW pixel buffer that accumulates all draws until explicitly cleared with memset(ImageBW, 0x00, ALLSCREEN_BYTES). Early versions ghosted content from previous screens because the buffer clear ran in the wrong order — after the page header was drawn rather than before it. The fix is now canonical: beginPage() always zeroes the buffer first, before any draw call.

Fitting six columns into 250 pixels at a readable font size

The Beach Week screen needs seven days of data — morning low time, low height, afternoon high time, high height, and daily temperature — in 250 horizontal pixels. The original design added a weather symbol column at size 8. It was invisible on hardware. That column was cut and replaced with the high-tide time column, which required a second-pass parse over the NOAA prediction array to extract flood events after each morning low. The final layout achieves six columns at size 12 with manually mapped x-coordinates, leaving no pixel to waste.

The parabolic arc — mapping time to pixel position

The Sky Clock page positions sun and moon symbols along an arc representing the sky dome based on their current progress through their rise-to-set window. This required spanProgress() (fractional position in a time span, 0.0–1.0), arcPointForProgress() (mapping that fraction to an x/y coordinate on a parabola defined by three anchor points), and drawArcMarker() (rendering the appropriate symbol at the computed location). The moon symbol changes shape dynamically based on phase and waxing/waning direction.

CURRENT STATUS — V1.16
WORKING All five original pages compile and display on hardware. NOAA tide predictions, NDBC buoy readings, and Open-Meteo forecasts populate reliably. Button navigation cycles pages. Auto-refresh every 10 minutes. Moon phase, sunrise, and sunset compute correctly from local algorithms.
IN TESTING Shore Birds and Rare Birds pages (V1.16) are integrated but not yet confirmed on hardware. Heap diagnostics are instrumented — awaiting a serial monitor run to determine whether eBird calls are reaching the network or failing silently after earlier fetch cycles consume heap.
NEXT Confirm eBird on hardware. Then: deep sleep between refreshes, Li-Po cell and USB-C charging (target: weeks per charge), hardware enclosure (surfboard profile, 3D-printed PETG), Wi-Fi credentials via local config file instead of hardcoded constants.