#pragma once #include namespace esphome::gxepd2 { // One command byte plus its (fixed, compile-time) data bytes, as sent by // GxEPD2_EPD::_writeCommand()/_writeData(). delay_before_ms/delay_after_ms // and wait_busy_after mirror the fixed delay()s and _waitWhileBusy() calls // bracketing specific commands in the vendor source (which command gets // which varies per panel - e.g. GxEPD2_213_Z98c delays 10ms on both sides // of its SWRESET, while GxEPD2_154_Z90c busy-waits after its SWRESET // instead). At most one of delay_after_ms/wait_busy_after is expected to be // set on any one command. struct EpdCommand { uint8_t cmd; const uint8_t *data; // nullptr if the command takes no data bytes uint8_t data_len; uint16_t delay_before_ms{0}; uint16_t delay_after_ms{0}; bool wait_busy_after{false}; }; // Everything CycleEngine needs to drive one panel, hand-derived from a // GxEPD2 vendor driver's .cpp/.h (see panel_154_z90c.h). Only what this // first slice's engine actually uses - the RAM-window-set command layout // (0x44/0x45/0x4E/0x4F) is common to every panel inspected so far and is // hardcoded in CycleEngine itself rather than made part of this struct; // revisit if/when the extraction-tooling phase finds a controller family // that lays it out differently. struct EpdPanelDescriptor { uint16_t width; uint16_t height; bool busy_active_level; // digitalRead() level meaning "still busy" uint32_t busy_timeout_ms; // ceiling for every busy wait (init and refresh alike) const EpdCommand *init_sequence; // run once, after the reset pulse uint8_t init_sequence_len; uint8_t plane_count; // 1 (BW) or 2 (BW + one accent color) uint8_t plane_write_cmd[2]; // e.g. {0x24, 0x26} bool plane_invert[2]; // true if the byte must be bit-inverted on write const EpdCommand *refresh_sequence; // e.g. 0x22/0xF7 then trigger 0x20 uint8_t refresh_sequence_len; }; } // namespace esphome::gxepd2