ST7789 External Display Component Won't Build since end of 2023 esphome version

Hi,

I am unable to use my custom external display for my ST7789 based display.

Display type: ZJY-IPS130-V2.0, 240x240, ST7789, SPI

I have made optimizations for that implementation to use a line buffer instead of a full frame buffer so that it takes less memory and to be able to use it with an ESP8266.

Here is the error I get recently when I try to update it:

INFO ESPHome 2023.12.9
INFO Reading configuration /config/esphome/growtent-03.yaml...
WARNING GPIO5 is a strapping PIN and should only be used for I/O with care.
Attaching external pullup/down resistors to strapping pins can cause unexpected failures.
See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins
WARNING GPIO15 is a strapping PIN and should only be used for I/O with care.
Attaching external pullup/down resistors to strapping pins can cause unexpected failures.
See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins
WARNING GPIO12 is a strapping PIN and should only be used for I/O with care.
Attaching external pullup/down resistors to strapping pins can cause unexpected failures.
See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins
INFO Generating C++ source...
Traceback (most recent call last):
  File "/usr/local/bin/esphome", line 33, in <module>
    sys.exit(load_entry_point('esphome', 'console_scripts', 'esphome')())
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/esphome/esphome/__main__.py", line 1041, in main
    return run_esphome(sys.argv)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/esphome/esphome/__main__.py", line 1028, in run_esphome
    rc = POST_CONFIG_ACTIONS[args.command](args, config)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/esphome/esphome/__main__.py", line 458, in command_run
    exit_code = write_cpp(config)
                ^^^^^^^^^^^^^^^^^
  File "/esphome/esphome/__main__.py", line 192, in write_cpp
    generate_cpp_contents(config)
  File "/esphome/esphome/__main__.py", line 204, in generate_cpp_contents
    CORE.flush_tasks()
  File "/esphome/esphome/core/__init__.py", line 679, in flush_tasks
    self.event_loop.flush_tasks()
  File "/esphome/esphome/coroutine.py", line 246, in flush_tasks
    next(task.iterator)
  File "/esphome/esphome/__main__.py", line 184, in wrapped
    await coro(conf)
  File "/config/esphome/Components/st7789/display.py", line 68, in to_code
    await display.register_display(var, config)
  File "/esphome/esphome/components/display/__init__.py", line 119, in register_display
    await cg.register_component(var, config)
  File "/esphome/esphome/cpp_helpers.py", line 56, in register_component
    raise ValueError(
ValueError: Component ID lcd was not declared to inherit from Component, or was registered twice. Please create a bug report with your configuration.

This is my device definition:

esphome:
  name: growtent-03
  platform: ESP32
  board: nodemcu-32s

external_components:
  # use all components from a local folder
  - source:
      type: local
      path: Components

# Example configuration entry
debug:
      
# Enable logging
logger:
  level: debug

# Enable Home Assistant API
api:

ota:
  password: "***"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  id: wificomp

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Growtent-03 Fallback Hotspot"
    password: "***"

captive_portal:

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: true
  id: bus_a
  setup_priority: -100.0

sensor:
  - platform: htu21d
    id: htu_component
    temperature:
      name: "Growtent-03 Temperature"
      id: temperature
      filters:
        - offset: -1.7
    humidity:
      name: "Growtent-03 Humidity"
      id: humidity
      filters:
      - calibrate_linear:
         method: least_squares
         datapoints:
          # Map 0.0 (from sensor) to 1.0 (true value)
          - 27.1 -> 31.8
          - 48.5 -> 52
    update_interval: 10s
    setup_priority: -100.0
    
switch:
  - platform: gpio
    pin: GPIO5
    inverted: true
    name: "Growtent-03 Heater"
  - platform: gpio
    pin: GPIO15
    inverted: true
    name: "Growtent-03 Humidifier"

spi:
  clk_pin: GPIO18
  mosi_pin: GPIO23

font:
  - file: "fonts/Lato-Medium.ttf"
    id: lato_20
    size: 20
  - file: "fonts/Lato-Medium.ttf"
    id: lato_14
    size: 14

display:
  - platform: st7789
    backlight_pin: GPIO13
    cs_pin: GPIO12
    dc_pin: GPIO33
    reset_pin: GPIO32
    width: 240
    height: 240
    id: lcd
    lambda: |-
      if (id(htu_component)->is_failed())
      {
        id(bus_a)->setup();
        App.reboot();
      }
      auto red = Color(255, 0, 0);
      auto blue = Color(0, 0, 255);
      it.printf(0, 0, id(lato_20), "IP: %s", id(wificomp).get_ip_address().str().c_str());
      //it.printf(0, 30, id(lato_20), red,  "Temperature: %.1f°C", id(temperature).state);
      //it.printf(0, 50, id(lato_20), blue, "Humidity:    %.1f%%", id(humidity).state);
      // Draw the graph at position [x=10,y=20]
      it.graph(0, 25, id(multi_temperature_graph), my_yellow);
      it.legend(0, 175, id(multi_temperature_graph), my_yellow);

graph:
  # Show multi-trace graph
  - id: multi_temperature_graph
    duration: 1h
    x_grid: 10min
    y_grid: 5.0     # degC/div
    width: 240
    height: 150
    legend:
      name_font: lato_14
      value_font: lato_14
      show_values: 'BESIDE'
      show_lines: true
      show_units: true
      width: 240
      height: 65
      border: true
    traces:
      - sensor: temperature
        line_type: DASHED
        line_thickness: 2
        color: my_red
      - sensor: humidity
        line_type: SOLID
        line_thickness: 3
        color: my_blue

color:
  - id: my_red
    red: 100%
    green: 0%
    blue: 0%
  - id: my_green
    red: 0%
    green: 100%
    blue: 0%
  - id: my_blue
    red: 0%
    green: 0%
    blue: 100%
  - id: my_yellow
    red: 100%
    green: 100%
    blue: 0%

interval:
  - interval: 5s
    then:
      - component.update: lcd








Any help or insight as to why it does not build anymore would be appreciated.
Thanks,

OK, I have found two issues with the newer codebase. The following fixes my problems:

  1. in my display.py, I had this:

async def to_code(config):
    var = cg.new_Pvariable(config[CONF_ID])
->    await cg.register_component(var, config) <-
    await display.register_display(var, config)
    await spi.register_spi_device(var, config)

    dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])
    cg.add(var.set_dc_pin(dc))

it seems that the register_component should not be called anymore or it generates the aforementionned problem. This is abreaking change IMHO and should have been documented? If it is the case, I obviously missed it.

  1. Custom display components should remove the inheritance from PollingComponent since this generates an error as well.

Thanks,
Hope this can help other.