Просмотр исходного кода

Add invert_mono option for ESPHome ON-color (waveshare) compat

ESPHome's drawing primitives default to COLOR_ON = white, which our literal
color mapping renders as blank - so a default-color it.print() draws
nothing. invert_mono (1bpp-only) flips the mono black/white sense so a
bright color becomes ink, matching the waveshare_epaper convention. Default
false (native, literal RGB); explicit true opts in. No schema default so a
future waveshare model alias can set the default without overriding an
explicit value. No effect on packed (2bpp/4bpp) panels.

Also flesh out dump_config (dimensions, bits/pixel, planes, busy level,
invert_mono). Both 1bpp (Arduino) and 2bpp-packed (IDF) fixtures compile.
ZiB 3 недель назад
Родитель
Сommit
88cd717aea

+ 21 - 0
components/gxepd2/display.py

@@ -27,6 +27,19 @@ from esphome.types import ConfigType
 CODEOWNERS = ["@zib"]
 DEPENDENCIES = ["spi"]
 
+# When true, the 1bpp (mono / BW+accent) draw path treats a *bright* color as
+# ink and a *dark* color as background, instead of the literal RGB mapping
+# (bright = white = blank). This exists only to ease conversion from
+# ESPHome's LCD-rooted color model: its drawing primitives default to
+# COLOR_ON = white, so with the literal mapping a default-color it.print()
+# draws nothing. We can't tell a defaulted COLOR_ON from an explicit white
+# (the default is compiled into the user's lambda as a constant), so the only
+# lever is this wholesale mono inversion - the same thing waveshare_epaper
+# bakes in. Default false (native, literal RGB); a future waveshare model
+# alias will flip the *default* to true, but an explicit setting always wins.
+# No effect on 2bpp/4bpp packed panels - there the palette maps literal RGB.
+CONF_INVERT_MONO = "invert_mono"
+
 gxepd2_ns = cg.esphome_ns.namespace("gxepd2")
 GxEPD2Display = gxepd2_ns.class_("GxEPD2Display", display.DisplayBuffer)
 
@@ -55,6 +68,10 @@ CONFIG_SCHEMA = cv.All(
             cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
             cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
             cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema,
+            # No schema default: absence falls to the alias-derived default in
+            # to_code() (just False today; true for a waveshare alias once that
+            # lands), while an explicit value always wins.
+            cv.Optional(CONF_INVERT_MONO): cv.boolean,
         }
     ),
     # IDF-first, but the runtime C++ uses only framework-portable ESPHome
@@ -81,6 +98,10 @@ async def to_code(config: ConfigType) -> None:
     spi_parent = await cg.get_variable(config[CONF_SPI_ID])
     cg.add(var.set_spi_parent(spi_parent))
 
+    # Alias-derived default is just False for now; the waveshare-compat layer
+    # will compute it from the (aliased) model. An explicit invert_mono wins.
+    cg.add(var.set_invert_mono(config.get(CONF_INVERT_MONO, False)))
+
     # display.register_display() only wires up `pages:` - a bare top-level
     # `lambda:` (what our CONFIG_SCHEMA actually supports) has to be
     # processed and attached via set_writer() by the platform itself, same

+ 14 - 2
components/gxepd2/gxepd2_display.h

@@ -29,6 +29,7 @@ class GxEPD2Display : public display::DisplayBuffer {
   void set_busy_pin(GPIOPin *pin) { this->busy_ = pin; }
   void set_spi_parent(spi::SPIComponent *parent) { this->spi_.set_spi_parent(parent); }
   void set_descriptor(const EpdPanelDescriptor *desc) { this->desc_ = desc; }
+  void set_invert_mono(bool invert) { this->invert_mono_ = invert; }
 
   void setup() override {
     const auto *desc = this->desc_;
@@ -86,7 +87,14 @@ class GxEPD2Display : public display::DisplayBuffer {
   void loop() override { this->engine_.step(); }
 
   void dump_config() override {
-    ESP_LOGCONFIG(TAG, "GxEPD2 Display");
+    ESP_LOGCONFIG(TAG, "GxEPD2 Display:");
+    ESP_LOGCONFIG(TAG, "  Dimensions: %ux%u", (unsigned) this->desc_->width, (unsigned) this->desc_->height);
+    ESP_LOGCONFIG(TAG, "  Bits/pixel: %u (%s)", (unsigned) this->desc_->bits_per_pixel,
+                  this->desc_->bits_per_pixel == 1 ? "mono/plane" : "packed palette");
+    ESP_LOGCONFIG(TAG, "  Planes/regions: %u", (unsigned) this->desc_->plane_count);
+    ESP_LOGCONFIG(TAG, "  Busy active level: %s", this->desc_->busy_active_level ? "HIGH" : "LOW");
+    if (this->desc_->bits_per_pixel == 1)
+      ESP_LOGCONFIG(TAG, "  invert_mono: %s", YESNO(this->invert_mono_));
     LOG_UPDATE_INTERVAL(this);
   }
 
@@ -179,7 +187,10 @@ class GxEPD2Display : public display::DisplayBuffer {
     } else {
       if (this->plane1_ != nullptr)
         this->plane1_[idx] &= ~mask;
-      if (bright) {
+      // plane0 bit set = white/blank. invert_mono_ flips the mono sense so a
+      // bright color (e.g. ESPHome's default COLOR_ON) becomes ink, not blank
+      // - see display.py's CONF_INVERT_MONO. Red classification is unaffected.
+      if (bright ^ this->invert_mono_) {
         this->plane0_[idx] |= mask;
       } else {
         this->plane0_[idx] &= ~mask;
@@ -199,6 +210,7 @@ class GxEPD2Display : public display::DisplayBuffer {
   uint8_t *plane0_{nullptr};
   uint8_t *plane1_{nullptr};
   uint32_t plane_bytes_{0};
+  bool invert_mono_{false};
 };
 
 }  // namespace esphome::gxepd2

+ 1 - 0
tests/test_smoke.esp32-arduino.yaml

@@ -25,6 +25,7 @@ display:
     dc_pin: 17
     reset_pin: 16
     busy_pin: 4
+    invert_mono: true
     update_interval: 60s
     lambda: |-
       it.fill(Color::WHITE);