#pragma once #include #include #include "esp_err.h" /* Waveshare 13.3" e-Paper (E) Spectra 6 panel, driven by Seeed's EE02 * board (XIAO ESP32-S3 Plus). The panel is marketed/mounted as a * 1600x1200 landscape rectangle (270.40x202.80mm), but its SPI * controller addresses a native raster of 1200 columns x 1600 rows -- * i.e. the wire format is portrait, rotated 90 degrees from how the * panel physically hangs. Confirmed identically across three independent * vendor sources: Waveshare's RaspberryPi/c and ESP32 reference drivers * for this exact panel (E-paper_Separate_Program/13.3inch_e-Paper_E in * waveshare/e-Paper), and Waveshare's own ESP-IDF example for their * ESP32-S3-ePaper-13.3E6 driver board (a *different* carrier board than * Seeed's EE02, but the same panel+controller, hence the same command * bytes/geometry -- only the GPIO numbers differ, and those come from * EE02-specific sources, see this component's Kconfig). All three define * EPD_WIDTH=1200/EPD_HEIGHT=1600 and split each row into two 600-byte * (300px) halves sent to independent chip-selects: EPD_PIN_CS_MASTER * gets the left half, EPD_PIN_CS_SLAVE the right -- see epd13in3e.c. * * Getting this backwards (assuming the wire raster matches the * 1600x1200 mount/marketing size) doesn't just rotate the image -- 1600 * and 1200 don't share a row stride with 1200 and 1600 the other way * (800 bytes/row x 1200 rows vs 600 bytes/row x 1600 rows), so a mismatch * here slices real image rows at the wrong byte offsets and shreds the * picture into a repeating diagonal garble, not a clean rotation. * server/app/image_pipeline.py's PANEL_WIRE_TRANSPOSE handles the * corresponding rotation server-side before packing bytes for this * panel_type -- this header and that dict must agree on which axis is * native. */ #define EPD_WIDTH 1200 #define EPD_HEIGHT 1600 #define EPD_BYTES_PER_ROW ((EPD_WIDTH + 1) / 2) #define EPD_FRAME_BYTES (EPD_BYTES_PER_ROW * EPD_HEIGHT) /* Same 6-ink Spectra family as the 7.3" panel, and (now confirmed by the * same three vendor sources as the geometry above) the same 4-bit nibble * codes as epd7in3e.h's epd_color_t -- matches * server/app/image_pipeline.py's PANEL_CODES unconditionally, no * panel-specific table needed there. */ typedef enum { EPD_COLOR_BLACK = 0x0, EPD_COLOR_WHITE = 0x1, EPD_COLOR_YELLOW = 0x2, EPD_COLOR_RED = 0x3, EPD_COLOR_BLUE = 0x5, EPD_COLOR_GREEN = 0x6, } epd_color_t; /** Configures SPI + GPIO and runs the panel's power-on register init sequence. */ esp_err_t epd_init(void); /** Fills the whole panel with a single color and refreshes. */ esp_err_t epd_clear(epd_color_t color); /** * Called repeatedly by epd_display_stream() to fill up to chunk_size bytes * into chunk. Must return the number of bytes written, or 0 once exhausted. */ typedef size_t (*epd_read_fn_t)(uint8_t *chunk, size_t chunk_size, void *ctx); /** * Streams a full frame (EPD_FRAME_BYTES bytes, packed 2 pixels/byte) to the * panel via read_fn and refreshes. Pulling from a caller-supplied source * instead of a single buffer lets callers feed the panel directly from an * HTTP response without holding the whole ~960KB frame in RAM. */ esp_err_t epd_display_stream(epd_read_fn_t read_fn, void *ctx); /** * Like epd_display_stream(), but writes the frame into the panel's * internal buffer over SPI WITHOUT triggering the physical refresh (the * visible flash/flicker) -- call epd_turn_on_display() separately to make * it visible. Returns ESP_ERR_INVALID_SIZE if read_fn didn't supply * exactly EPD_FRAME_BYTES, same as epd_display_stream(); either way * nothing is refreshed, so the visible screen is left untouched on * error. * * If out_crc32 is non-NULL, it's set to a CRC32 of the bytes written -- * lets a caller compare against the last-displayed frame's CRC and skip * the refresh entirely when nothing actually changed (e.g. redisplaying * the same photo after a reboot). */ esp_err_t epd_write_frame(epd_read_fn_t read_fn, void *ctx, uint32_t *out_crc32); /** * Triggers the panel's physical refresh cycle (power on, refresh, power * off) -- the visible flash/flicker sequence. Call after epd_write_frame() * to make the written buffer visible. */ esp_err_t epd_turn_on_display(void); /** Convenience wrapper around epd_display_stream() for an in-memory frame buffer. */ esp_err_t epd_display_buffer(const uint8_t *frame, size_t len); /** Puts the panel into deep sleep to minimize power draw between refreshes. */ esp_err_t epd_sleep(void);