Quellcode durchsuchen

Dedupe generalized engine facts out of generated panel headers

The vendor "always-reset-every-cycle means a driver's own _hibernating ||
_initial_write reset-gating needs no accommodation" fact was restated nearly
verbatim in three separate panel_*.h files. It's a property of
CycleEngine::request_full_update(), true for every panel - moved to one spot
in CONTRIBUTING.md's Execution engine section instead.

panel_579c_gdey0579f51.h's "history worth not repeating" narrative duplicated
the gxepd2_579_retest_plan project memory almost exactly; trimmed to a
pointer, keeping only the facts a maintainer of this descriptor still needs
(PAIRED_EDGES rationale, region mapping).

Dropped panel_579c_gdey0579f51_master_only.h entirely: a diagnostic-only
variant from before the real dual-region descriptor worked, whose header was
stale speculation (wrong PSR values, mirroring theories) already contradicted
by the resolved real panel. Removed its display.py/gxepd2_display.h wiring
and the now-dead MASTER_ONLY example in check_descriptors.py/README.md.

check_descriptors.py: 61 reproduced, 0 known-diff, 0 skipped (was 1 skipped).
ZiB vor 2 Wochen
Ursprung
Commit
cd1730f91c

+ 8 - 1
CONTRIBUTING.md

@@ -168,6 +168,14 @@ Every command and data byte is its own SPI transaction with CS toggled around
 it — wasteful versus a burst transfer, but deliberate: matching the vendor
 `_writeCommand()`/`_writeData()` cycle byte-for-byte is the point.
 
+A vendor driver often gates its own reset pulse on `_hibernating ||
+_initial_write` (real RST only on the first cycle or after a hibernate,
+re-issuing the command sequence with no reset otherwise) - this engine's
+`RESET_LOW` is unconditional every single cycle instead, a superset of that
+first-cycle behavior, so a panel descriptor never needs to special-case it.
+Don't re-derive or re-document this per panel_*.h; it's a property of
+`request_full_update()`, true for every panel.
+
 `power_off_sequence`/`hibernate_sequence` are a separate, standalone entry
 point (`CycleEngine::request_power_off()`/`request_hibernate()`, exposed as
 `GxEPD2Display::power_off()`/`hibernate()` for a user's own lambda) - no reset
@@ -309,7 +317,6 @@ from it, and a non-verified panel logs a warning in its boot `dump_config`.
 | `GxEPD2_750_GDEY075T7` | GDEY075T7 | UC8179 | mono | ✅ verified |
 | `GxEPD2_290c_GDEY029F51` | GDEY029F51 | JD79667 | 4-color | ⚪ structural |
 | `GxEPD2_579c_GDEY0579F51` | GDEY0579F51 | HX8717 | 4-color | ✅ verified |
-| `GxEPD2_579c_GDEY0579F51_MASTER_ONLY` | GDEY0579F51 | HX8717 | 4-color | 🔧 diagnostic |
 
 ✅ rendered correctly on real hardware · ⚪ captured, not yet bench-confirmed · 🔧 diagnostic variant.
 

+ 0 - 3
components/gxepd2/display.py

@@ -126,9 +126,6 @@ PANELS = {
     "GxEPD2_583_T8": Panel("PANEL_583_T8", "GDEW0583T8", "GD7965", "mono", "structural"),
     "GxEPD2_750": Panel("PANEL_750", "TODO", "IL0371", "mono", "structural"),
     "GxEPD2_750_T7": Panel("PANEL_750_T7", "GDEW075T7", "GD7965", "mono", "structural"),
-    # Diagnostic variant: the 396x272 master region as a self-contained panel.
-    "GxEPD2_579c_GDEY0579F51_MASTER_ONLY": Panel(
-        "PANEL_579C_GDEY0579F51_MASTER_ONLY", "GDEY0579F51", "HX8717", "4-color", "diagnostic"),
 }
 
 

+ 0 - 1
components/gxepd2/gxepd2_display.h

@@ -62,7 +62,6 @@
 #include "panel_420.h"
 #include "panel_426_gdeq0426t82.h"
 #include "panel_579c_gdey0579f51.h"
-#include "panel_579c_gdey0579f51_master_only.h"
 #include "panel_583.h"
 #include "panel_583_gdeq0583t31.h"
 #include "panel_730c_gdep073e01.h"

+ 0 - 7
components/gxepd2/panel_290c_gdey029f51.h

@@ -10,13 +10,6 @@
 // (black=0x00, white=0x01, yellow=0x02, red=0x03 per GxEPD2_4C.h color4()),
 // not 1-bit-per-pixel planes - see EpdPanelDescriptor::bits_per_pixel /
 // palette and GxEPD2Display's draw_packed_() for the buffer-side half.
-//
-// _InitDisplay()'s reset pulse is itself gated on `_hibernating ||
-// _initial_write` in the vendor driver (steady-state cycles skip the
-// physical RST and go straight to re-issuing the command sequence) - this
-// engine always does a real hardware reset every cycle regardless (see
-// CycleEngine::request_full_update()), a superset of what the vendor
-// driver does on its own first cycle, so no special-casing needed here.
 
 #include "epd_command.h"
 

+ 6 - 17
components/gxepd2/panel_579c_gdey0579f51.h

@@ -13,18 +13,11 @@
 // panel correctly when run natively, so a byte-faithful replay is correct by
 // construction.
 //
-// History worth not repeating: an earlier revision carried hand-"fixes" from
-// Waveshare's epd5in79g.cpp (swapped PSR, power-on under select=Both, changed
-// BTST) plus, on 2026-10-18, a PSR scan-direction swap + window-order swap to
-// chase a left-right mirror and swapped halves. The bench showed those were
-// ALL compensating for two separate real issues, neither a driver bug:
-//   - the "slave never converges" symptom was DRIVE-CURRENT STARVATION on the
-//     bench MCU (same panel + firmware renders on an adequately-powered
-//     board), so power-on-select and BTST are back to stock;
-//   - the mirror + swapped halves were the engine sending ONE line per row
-//     where GxEPD2 sends TWO (line_order below), so the PSR/window swaps are
-//     gone too.
-// Only busy_timeout_ms is tuned (engine hard-aborts where GxEPD2 polls on).
+// Every value below is stock (PSR/BTST/power-on included) - earlier hand-
+// "fixes" chasing a mirror/convergence bug turned out to be compensating for
+// drive-current starvation and the missing line_order, not a driver bug; see
+// project memory (gxepd2_579_retest_plan) for that history. Only
+// busy_timeout_ms is tuned (engine hard-aborts where GxEPD2 polls on).
 //
 // Dual-region layer assembled by hand from the driver source, per
 // CONTRIBUTING.md "Limits - True dual-region command structure": the
@@ -49,11 +42,7 @@ inline const uint8_t PANEL_579C_GDEY0579F51_SELECT_MASTER[] = {0x01};
 inline const uint8_t PANEL_579C_GDEY0579F51_SELECT_SLAVE[] = {0x02};
 inline const uint8_t PANEL_579C_GDEY0579F51_SELECT_BOTH[] = {0x00};
 // Stock PSR values, matching GxEPD2's _InitDisplay() exactly: master
-// {0x07,0x29}, slave {0x03,0x29}. (A brief detour on 2026-10-18 swapped
-// these to "fix" a left-right mirror, but the mirror was an artifact of the
-// engine sending one line per row instead of GxEPD2's two - see line_order
-// below. With the line order faithful, the stock PSR is correct and the
-// swap is gone.)
+// {0x07,0x29}, slave {0x03,0x29}.
 inline const uint8_t PANEL_579C_GDEY0579F51_PSR_MASTER[] = {0x07, 0x29};
 inline const uint8_t PANEL_579C_GDEY0579F51_PSR_SLAVE[] = {0x03, 0x29};
 inline const uint8_t PANEL_579C_GDEY0579F51_BTST[] = {0x32, 0x32, 0x32, 0x32};

+ 0 - 127
components/gxepd2/panel_579c_gdey0579f51_master_only.h

@@ -1,127 +0,0 @@
-#pragma once
-
-// Hand-derived from third_party/GxEPD2/src/epd4c/GxEPD2_579c_GDEY0579F51.{h,cpp}
-// (GDEY0579F51, HX8717 controller, 792x272, same JD79667-family 2bpp/4-color
-// packing as GDEY029F51 - see panel_290c_gdey029f51.h). This is the actual
-// dual-CS-like split panel from the original bench list: NOT two physical
-// CS pins, but two logical regions ("master"/"slave", left/right halves)
-// selected over one bus via an 0xA2 command, each with its own window-set
-// and its own 0x10 plane-write burst.
-//
-// Deliberate scope cut (2026-09-19, per user): CycleEngine's state machine
-// only knows one window-set + one plane-write per update cycle, and
-// extending it to loop over two regions is real work, not just descriptor
-// data - so this descriptor addresses the "master" half ONLY, as if it
-// were a self-contained 396x272 single-region panel, to validate the 2bpp
-// packing and this controller family's init/refresh shape before building
-// the master+slave loop.
-//
-// First hardware run (2026-09-19) showed real content, correctly decoded,
-// but compressed/duplicated within roughly half the driven region, plus
-// noise/black bands on the untouched half - not yet fully explained. One
-// finding so far: don't assume "master" = the physically-left/near-
-// connector half. GxEPD2_579_GDEY0579T93.cpp (SSD1683, same 792x272
-// panel family, comments explicit) maps "near connector" -> SLAVE and
-// "opposite to connector" -> MASTER - the reverse of what might be
-// guessed. GDEY0579F51's own driver has no such comment, so which
-// physical side "master" actually addresses here is unverified, not
-// assumed-correct. A slave-only variant of this same descriptor (swap
-// every SELECT_MASTER for SELECT_MASTER->0x02) would empirically test
-// whether content moves to the other half - not yet done.
-//
-// The refresh sequence originally used select=Both (0x00) before the
-// 0x12 trigger, matching the vendor's own refresh(bool)'s
-// _setPartialRamAreaBoth() call - but 0x12 appears to fire a real
-// electrical refresh across the WHOLE physical panel regardless of which
-// side's RAM was ever written, which explains the noise/black bands as
-// slave's never-initialized RAM being "developed" onto glass. Changed to
-// select=Master only, since we only ever want to touch the side we wrote.
-// Does not yet explain the duplication/compression artifact.
-//
-// _InitDisplay()'s reset pulse is gated the same way GDEY029F51's is
-// (`_hibernating || _initial_write`) - this engine always does a real
-// hardware reset every cycle regardless, a superset of the vendor
-// driver's own first-cycle behavior, so no special-casing needed.
-
-#include "epd_command.h"
-
-namespace esphome::gxepd2 {
-
-inline const uint8_t PANEL_579C_GDEY0579F51_MO_SELECT_MASTER[] = {0x01};
-inline const uint8_t PANEL_579C_GDEY0579F51_MO_SELECT_BOTH[] = {0x00};
-// 0x03, not 0x07 - see panel_579c_gdey0579f51.h's header comment: this
-// value's SHL bit was backwards relative to Waveshare's own reference
-// driver for this panel (2026-09-19).
-inline const uint8_t PANEL_579C_GDEY0579F51_MO_PSR_MASTER[] = {0x03, 0x29};
-inline const uint8_t PANEL_579C_GDEY0579F51_MO_BTST[] = {0x32, 0x32, 0x32, 0x32};
-inline const uint8_t PANEL_579C_GDEY0579F51_MO_TRES[] = {0x01, 0x8C, 0x01, 0x10};  // 396x272
-inline const uint8_t PANEL_579C_GDEY0579F51_MO_CDI_INIT[] = {0x97};
-inline const uint8_t PANEL_579C_GDEY0579F51_MO_E9[] = {0x01};
-inline const uint8_t PANEL_579C_GDEY0579F51_MO_E0[] = {0x01};
-
-inline const EpdCommand PANEL_579C_GDEY0579F51_MO_INIT[] = {
-    {0xA2, PANEL_579C_GDEY0579F51_MO_SELECT_MASTER, 1},
-    {0x00, PANEL_579C_GDEY0579F51_MO_PSR_MASTER, 2},
-    {0xA2, PANEL_579C_GDEY0579F51_MO_SELECT_BOTH, 1},
-    {0x06, PANEL_579C_GDEY0579F51_MO_BTST, 4},
-    {0x61, PANEL_579C_GDEY0579F51_MO_TRES, 4},
-    {0x50, PANEL_579C_GDEY0579F51_MO_CDI_INIT, 1},
-    {0xE9, PANEL_579C_GDEY0579F51_MO_E9, 1},
-    {0xE0, PANEL_579C_GDEY0579F51_MO_E0, 1},
-    {0xA2, PANEL_579C_GDEY0579F51_MO_SELECT_MASTER, 1},
-    {0x04, nullptr, 0, /*delay_before_ms=*/0, /*delay_after_ms=*/0, /*wait_busy_after=*/true},  // power on
-};
-
-// _setPartialRamAreaMaster(0, 0, 396, 272), default partial_mode=true.
-inline const uint8_t PANEL_579C_GDEY0579F51_MO_WINDOW[] = {
-    0x00, 0x00,  // x/256, x%256   = 0
-    0x01, 0x8B,  // xe/256, xe%256 = 395
-    0x00, 0x00,  // y/256, y%256   = 0
-    0x01, 0x0F,  // ye/256, ye%256 = 271
-    0x01,
-};
-
-inline const EpdCommand PANEL_579C_GDEY0579F51_MO_WINDOW_SEQ[] = {
-    {0xA2, PANEL_579C_GDEY0579F51_MO_SELECT_MASTER, 1},
-    {0x83, PANEL_579C_GDEY0579F51_MO_WINDOW, 9},
-};
-
-// refresh(false): vendor's own refresh(bool) uses _setPartialRamAreaBoth()
-// here, but see file header - deliberately using select=Master instead so
-// the 0x12 trigger's real electrical refresh never touches slave's
-// never-written RAM (that Both selection is what produced the noise/black
-// bands on the untouched half in the first hardware run).
-inline const uint8_t PANEL_579C_GDEY0579F51_MO_CDI_REFRESH[] = {0x37};
-inline const uint8_t PANEL_579C_GDEY0579F51_MO_REFRESH_TRIGGER[] = {0x00};
-
-inline const EpdCommand PANEL_579C_GDEY0579F51_MO_REFRESH[] = {
-    {0xA2, PANEL_579C_GDEY0579F51_MO_SELECT_MASTER, 1},
-    {0x83, PANEL_579C_GDEY0579F51_MO_WINDOW, 9},
-    {0x50, PANEL_579C_GDEY0579F51_MO_CDI_REFRESH, 1},
-    {0x12, PANEL_579C_GDEY0579F51_MO_REFRESH_TRIGGER, 1, /*delay_before_ms=*/0, /*delay_after_ms=*/0,
-     /*wait_busy_after=*/true},
-};
-
-inline const EpdPanelDescriptor PANEL_579C_GDEY0579F51_MASTER_ONLY = {
-    .width = 396,   // WIDTH/2 of the real 792-wide panel - master half only
-    .height = 272,
-    .busy_active_level = false,  // GxEPD2_EPD(..., /*busy_level=*/LOW, ...)
-    .busy_timeout_ms = 25000,    // full_refresh_time is 21000ms
-    .init_sequence = PANEL_579C_GDEY0579F51_MO_INIT,
-    .init_sequence_len = sizeof(PANEL_579C_GDEY0579F51_MO_INIT) / sizeof(PANEL_579C_GDEY0579F51_MO_INIT[0]),
-    .window_sequence = {PANEL_579C_GDEY0579F51_MO_WINDOW_SEQ, nullptr},
-    .window_sequence_len = {sizeof(PANEL_579C_GDEY0579F51_MO_WINDOW_SEQ) / sizeof(PANEL_579C_GDEY0579F51_MO_WINDOW_SEQ[0]),
-                             0},
-    .plane_count = 1,
-    .plane_write_cmd = {0x10, 0x00},
-    .plane_invert = {false, false},
-    .line_order = LineOrder::PAIRED_EDGES,
-    .bits_per_pixel = 2,
-    .palette = GXEPD2_PALETTE_4C,
-    .palette_len = GXEPD2_PALETTE_4C_LEN,
-    .blank_code = 0x01,
-    .refresh_sequence = PANEL_579C_GDEY0579F51_MO_REFRESH,
-    .refresh_sequence_len = sizeof(PANEL_579C_GDEY0579F51_MO_REFRESH) / sizeof(PANEL_579C_GDEY0579F51_MO_REFRESH[0]),
-};
-
-}  // namespace esphome::gxepd2

+ 2 - 1
tools/check_descriptors.py

@@ -20,7 +20,8 @@ review it). Exit non-zero on any unexpected mismatch.
 Panels the emitter does not yet fully model (dual-region, see KNOWN_DIFF) are
 expected to differ and don't fail; if one of them starts MATCHING, that's an
 emitter improvement - remove it from KNOWN_DIFF. Panels with no GxEPD2 driver
-source (e.g. the synthetic MASTER_ONLY variant) are skipped.
+source (a synthetic model with no third_party/GxEPD2/ .cpp of its own) are
+skipped.
 """
 
 import argparse

+ 2 - 2
tools/gxepd2_harness/README.md

@@ -86,8 +86,8 @@ vendor bump changed that driver — review the panel). Any panel the emitter
 can't yet segment is listed in its `KNOWN_DIFF` and reported as `XFAIL` (none
 at present — the dual-region GDEY0579F51 was promoted out once the emitter
 modelled the region-select layer; an `XPASS` means a `KNOWN_DIFF` entry now
-reproduces and should be removed). Panels with no GxEPD2 driver source (the
-synthetic `MASTER_ONLY` variant) are skipped. Run it after any
+reproduces and should be removed). Panels with no GxEPD2 driver source of
+their own (a synthetic model) are skipped. Run it after any
 `third_party/GxEPD2/` update.
 
 ## Other programs