diff --git a/include/opendisplay_protocol.h b/include/opendisplay_protocol.h index 770d930..b13c675 100644 --- a/include/opendisplay_protocol.h +++ b/include/opendisplay_protocol.h @@ -10,7 +10,7 @@ * fully-correct client from THIS FILE ALONE, without reading firmware. * * OD_PROTOCOL_VERSION 2.2 (MAJOR.MINOR; see VERSIONING POLICY below) - * LAST CHANGED 2026-07-22 + * LAST CHANGED 2026-07-25 * * -------------------------------------------------------------------------- * VERSIONING POLICY @@ -71,6 +71,11 @@ * silently dropped. Breaking only for peers that wrote >253 bytes in a * single GATT write (none deployed -- HA's GATT client caps at 244 and * rejects larger writes outright). + * - DOC-ONLY: expand the CMD_FIRMWARE_VERSION (0x0043) @response layout to + * spell out the [shaLen:1][sha:shaLen] and optional trailing [patch:1] + * bytes (shaLen = ASCII SHA byte count 0..40; missing patch => 0), matching + * what shipped firmware already emits. Backported from Firmware's vendored + * copy. No wire change, no version bump. * - Add each further wire-spec change here as it lands. On the next version * bump, move these under a new "MAJOR.MINOR (YYYY-MM-DD)" heading. * diff --git a/include/opendisplay_structs.h b/include/opendisplay_structs.h index 5c519b7..0ec81a0 100644 --- a/include/opendisplay_structs.h +++ b/include/opendisplay_structs.h @@ -19,9 +19,9 @@ * type mirrors (see docs/shared-types-plan.md); codegen must carry the prose * through as idiomatic doc comments in each target language. * - * OD_STRUCTS_VERSION 2.0 (MAJOR.MINOR spec marker for THIS file; see + * OD_STRUCTS_VERSION 2.1 (MAJOR.MINOR spec marker for THIS file; see * VERSIONING POLICY below -- NOT sent on the wire) - * LAST CHANGED 2026-07-18 + * LAST CHANGED 2026-08-30 * * The two version schemes carried here are DISTINCT (see §6 Q2 of the plan): * - OD_STRUCTS_VERSION_* : documents this spec file, like protocol.h's @@ -66,7 +66,7 @@ * CHANGELOG (newest first; entries accrue under "Unreleased" and roll into a * new version heading on each bump -- see AGENT INSTRUCTIONS below) * -------------------------------------------------------------------------- - * Unreleased (since 2.0) + * Unreleased (since 2.1) * - LedFlags bit4: OD_LED_FLAG_BUTTON_PRESS (short LED flash on physical * button press / button-wake synthetic click; default off). * - BuzzerFlags bit1: OD_BUZZER_FLAG_BUTTON_PRESS (short chirp on physical @@ -74,6 +74,15 @@ * - MsdStatusBits bit3: OD_MSD_STATUS_ENCRYPTION_ENABLED (1 = app-layer * encryption active). Firmware sets it when encryption_enabled and master * key are non-zero; dynamic[11] is all 0xFF when no display is configured. + * (Folded back from the Firmware side, PR #157 — parity restore.) + * - Add each payload-layout change here as it lands. On the next version bump, + * move these under a new "MAJOR.MINOR (YYYY-MM-DD)" heading. + * + * 2.1 (2026-08-30) + * - Add PanelIC value OD_PANEL_IC_M5PAPERS3_960X540 = 3004 (M5Stack PaperS3, + * ED047TC1-class 960x540 parallel panel, 16-gray capable; FastEPD native + * parallel path). Backward-compatible enum addition (@since 1.5); the + * on-wire config-format minor bumps 4 -> 5 (OD_CONFIG_MINOR_VERSION). * - Doc-only: fixed two comment shapes the codegen parser mis-read and added * the CODEGEN AUTHORING RULES banner section to prevent recurrence. Split the * combined BusFlags/PinBitmap @bits comment into one comment per group; folded @@ -83,8 +92,6 @@ * placeholder macros (no wire change; these bits stay reserved-must-be-0): * TransmissionModes bit5/bit6 -> OD_TRANSMISSION_MODE_RESERVED_5/_6; * MsdStatusBits bit3 -> OD_MSD_STATUS_RESERVED_3. Documentation only. - * - Add each payload-layout change here as it lands. On the next version bump, - * move these under a new "MAJOR.MINOR (YYYY-MM-DD)" heading. * * 2.0 (2026-07-18) * - Initial canonical shared payload contract: the wire-payload counterpart @@ -252,8 +259,8 @@ /* Payload-spec revision, MAJOR.MINOR. Documents THIS file; NOT sent on the wire. * Distinct from the on-wire OD_CONFIG_VERSION pair defined in SECTION 1. */ #define OD_STRUCTS_VERSION_MAJOR 2u -#define OD_STRUCTS_VERSION_MINOR 0u -#define OD_STRUCTS_VERSION_STR "2.0" +#define OD_STRUCTS_VERSION_MINOR 1u +#define OD_STRUCTS_VERSION_STR "2.1" /* -------------------------------------------------------------------------- * Portable compile-time size check. Defined once here; every packed struct is @@ -288,10 +295,10 @@ /* On-wire config-format version. UNLIKE OD_STRUCTS_VERSION / OD_PROTOCOL_VERSION, * these ARE transmitted / negotiated: OuterPacketHeader.version carries the - * MAJOR byte. Frozen at 1.4 by this header (the app's bundled config.yaml is at + * MAJOR byte. At 1.5 as of this header (the app's bundled config.yaml is at * minor 3 and must catch up). */ #define OD_CONFIG_VERSION 1u /* @doc "outer-packet major version byte" */ -#define OD_CONFIG_MINOR_VERSION 4u /* @doc "config-format minor; backward-compatible additions" */ +#define OD_CONFIG_MINOR_VERSION 5u /* @doc "config-format minor; backward-compatible additions" */ /* CRC over the outer packet. CRC16-CCITT, poly 0x1021, init 0xFFFF, computed * over length+version+packets AS IF the 2-byte length field were 0x0000 (the @@ -682,7 +689,8 @@ enum PanelIC { OD_PANEL_IC_ED103TC2_1872X1404 = 3000, /**< @doc "E Ink ED103TC2 + IT8951 (10.3\", 1872x1404, 1bpp; FastEPD IT8951 path, values 3000+)" */ OD_PANEL_IC_ED103TC2_1872X1404_4GRAY = 3001, /**< @doc "same panel as 3000; 4bpp (16-level gray via FastEPD)" */ OD_PANEL_IC_INKPLATE5V2_1280X720 = 3002, /**< @doc "Soldered Inkplate 5 V2 (ED050WROW, 1280x720, 1bpp; FastEPD native parallel path)" */ - OD_PANEL_IC_INKPLATE10_1200X825 = 3003 /**< @doc "Soldered Inkplate 10 (ED097TC2, 1200x825, 1bpp; FastEPD native parallel path)" */ + OD_PANEL_IC_INKPLATE10_1200X825 = 3003, /**< @doc "Soldered Inkplate 10 (ED097TC2, 1200x825, 1bpp; FastEPD native parallel path)" */ + OD_PANEL_IC_M5PAPERS3_960X540 = 3004 /**< @doc "M5Stack PaperS3 (ED047TC1-class, 960x540, 16-gray capable; FastEPD native parallel path)" @since 1.5 */ }; /* DisplayConfig.transmission_modes @bits TransmissionModes (bits 5-6 reserved -- @@ -1235,7 +1243,7 @@ OD_STATIC_ASSERT(sizeof(struct AuthProof) == 32, "AuthProof wire size"); #define OD_MSD_STATUS_REBOOT_FLAG (1u << 1) /* @doc "device rebooted since last read" */ #define OD_MSD_STATUS_CONNECTION_REQUESTED (1u << 2) /* @doc "device is requesting a connection" */ #define OD_MSD_STATUS_ENCRYPTION_ENABLED (1u << 3) /* @doc "application-layer encryption active (encryption_enabled and non-zero master key)" */ -#define OD_MSD_STATUS_RESERVED_3 OD_MSD_STATUS_ENCRYPTION_ENABLED /* legacy doc name */ +#define OD_MSD_STATUS_RESERVED_3 (1u << 3) /* @deprecated @doc "legacy name for OD_MSD_STATUS_ENCRYPTION_ENABLED (same bit)" */ #define OD_MSD_STATUS_MAIN_LOOP_COUNTER_SHIFT 4u /* @doc "bits 4-7: free-running main-loop nibble counter (liveness)" */ #define OD_MSD_STATUS_MAIN_LOOP_COUNTER_MASK 0xF0u /* @doc "mask for the bits 4-7 main-loop counter nibble" */ diff --git a/platformio.ini b/platformio.ini index 36b3bf8..8f71d1d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -198,7 +198,9 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ framework = arduino lib_deps = ${env.lib_deps} - bitbank2/FastEPD@^2.2.0 + ; Exact pin during PaperS3 bring-up: BB_PANEL_* is a positional enum + ; upstream, so a floating 2.x could shift panel behavior. + bitbank2/FastEPD@2.2.0 build_flags = -DTARGET_ESP32 -DOPENDISPLAY_ENABLE_WIFI ; LAN transport + ROM tinfl. Set ONLY on -DBOARD_HAS_PSRAM envs (src/wifi_service.h) diff --git a/src/display_fastepd.cpp b/src/display_fastepd.cpp index 5b7f28b..7c8965e 100644 --- a/src/display_fastepd.cpp +++ b/src/display_fastepd.cpp @@ -29,11 +29,12 @@ class OdFastEPD : public FASTEPD { }; // OpenDisplay panel_ic_type -> FastEPD native parallel panel id (-1 if not a -// parallel panel). FastEPD owns the Inkplate bus/PMIC/IO-expanders internally. +// parallel panel). FastEPD owns the board's bus/PMIC/IO-expanders internally. static int fastepd_parallel_panel(uint16_t panel_ic_type) { switch (panel_ic_type) { case OD_PANEL_IC_INKPLATE5V2_1280X720: return BB_PANEL_INKPLATE5V2; case OD_PANEL_IC_INKPLATE10_1200X825: return BB_PANEL_INKPLATE10; + case OD_PANEL_IC_M5PAPERS3_960X540: return BB_PANEL_M5PAPERS3; default: return -1; } } @@ -112,6 +113,19 @@ static bool fastepd_panel_is_4gray(void) { return d.color_scheme == OD_COLOR_SCHEME_GRAY16; } +// The effective pixel mode follows the runtime config (color_scheme), which +// can change between transfers via CONFIG_WRITE without a reboot. FastEPD's +// mode is sticky per hardware init, so warm transfer paths must re-assert it +// or incoming data is interpreted in the previous config's depth. +static void fastepd_apply_mode(void) { + if (!s_hw_initialized || !g_epd.currentBuffer()) return; + int want = fastepd_panel_is_4gray() ? BB_MODE_4BPP : BB_MODE_1BPP; + if (g_epd.getMode() != want) { + g_epd.setMode(want); + g_epd.setPreviousMode((uint8_t)g_epd.getMode()); + } +} + static size_t fb_byte_size(void) { uint32_t w = globalConfig.displays[0].pixel_width; uint32_t h = globalConfig.displays[0].pixel_height; @@ -229,6 +243,8 @@ void fastepd_epaper_begin(void) { g_epd.setPreviousMode((uint8_t)g_epd.getMode()); } else { g_epd.einkPower(1); + s_hw_initialized = true; // apply_mode requires it; set before the call + fastepd_apply_mode(); } s_hw_initialized = true; return; @@ -264,7 +280,16 @@ void fastepd_epaper_begin(void) { // REFRESH_FULL: parallel panels flash-clear to flush ghosting; IT8951 clears // internally (ignores the mode) so it stays CLEAR_NONE. static void fastepd_full_refresh_impl(void) { - int clear = fastepd_is_parallel() ? CLEAR_FAST : CLEAR_NONE; + int clear = CLEAR_NONE; + if (fastepd_is_parallel()) { + // The PaperS3 needs the library-default 10-pass clear: CLEAR_FAST's + // 8 passes leave residual banding on this panel (verified on + // hardware 2026-08-31, library-only sketch clean with CLEAR_SLOW). + // Inkplates keep the faster clear they were tuned with. + clear = (globalConfig.displays[0].panel_ic_type == OD_PANEL_IC_M5PAPERS3_960X540) + ? CLEAR_SLOW + : CLEAR_FAST; + } g_epd.fullUpdate(clear, true, NULL); } @@ -302,6 +327,7 @@ void fastepd_direct_write_reset(void) { fastepd_epaper_begin(); } else { g_epd.einkPower(1); + fastepd_apply_mode(); } s_direct_offset = 0; uint8_t* p = g_epd.currentBuffer(); @@ -366,6 +392,7 @@ void fastepd_partial_prepare(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { fastepd_epaper_begin(); } else { g_epd.einkPower(1); + fastepd_apply_mode(); } s_partial_x = x; s_partial_y = y; diff --git a/src/display_service.cpp b/src/display_service.cpp index 415befb..7c1c5d5 100644 --- a/src/display_service.cpp +++ b/src/display_service.cpp @@ -852,10 +852,11 @@ bool fastepd_driver_used(void) { // FastEPD IT8951 (SPI) path: E Ink ED103TC2 (Seeed reTerminal). const bool it8951 = (d.panel_ic_type == OD_PANEL_IC_ED103TC2_1872X1404 || d.panel_ic_type == OD_PANEL_IC_ED103TC2_1872X1404_4GRAY); - // FastEPD native parallel path: Soldered Inkplate 5V2 / 10. - const bool inkplate = (d.panel_ic_type == OD_PANEL_IC_INKPLATE5V2_1280X720 || - d.panel_ic_type == OD_PANEL_IC_INKPLATE10_1200X825); - if (!it8951 && !inkplate) return false; + // FastEPD native parallel path: Soldered Inkplate 5V2 / 10, M5Stack PaperS3. + const bool parallel = (d.panel_ic_type == OD_PANEL_IC_INKPLATE5V2_1280X720 || + d.panel_ic_type == OD_PANEL_IC_INKPLATE10_1200X825 || + d.panel_ic_type == OD_PANEL_IC_M5PAPERS3_960X540); + if (!it8951 && !parallel) return false; if (d.display_technology != 0 && d.display_technology != 1) return false; return true; #endif