Multiply ESPs but e.g. same switch name, still wrong old name and/or ID in HomeAssistant

Hi,
I am programming an ESPHome but will later on have more than one device of the same type, but for sure with different names.

In my esphome YAML it looks like this I want to configure the devices in one spot

substitutions:
  # !!! ALL LOWER CASE, NO dashes (-) because prohibited by ESPHome IDs and no underscores (_) because prohibited in DNS Host Names
  # NO CAPITAL Letters!
  # this is only a file, that contains the variables for each individual ESPHome device.
  #  the major programming code for the ESPHome is n the .yaml that you include below.
  rmgr_device_name: "framery3lilygo"

# --------------
# NO CHANGE BELOW THIS LINE in the TEMPLATE - must be the same for all devices.
# after copy use the right include file below. Only one include is needed in
# the packages: section.
# 
# --------------

packages:
  base: !include
#    file: common/esp_display_waveshare.yaml
    file: common/esp_display_lilygo-t547-touch.yaml
    vars:
      rmgr_device_name: "${rmgr_device_name}"



# Core Configuration Section: https://esphome.io/components/esphome.html
esphome:

  # https://esphome.io/components/esphome.html#changing-esphome-node-name
  name: "${rmgr_device_name}"

and in the lilygo I use

switch:
  - platform: template
    name: "Trigger Sleep"
    restore_mode: ALWAYS_OFF
    turn_on_action:
      - script.execute: go_to_sleep

In my HomeAssistant YAML packages for each device I have e.g.

  - id: "framery3lilygo_sleep_1724151868216"
    alias: framery3lilygo_touch_sleep
    description: "Put the device to sleep if button touched"
    triggers:
      - trigger: state
        entity_id:
          - binary_sensor.framery3lilygo_bottom_sleep_touch
        to: "on"
    conditions: []
    actions:
      - action: esphome.framery3lilygo_set_sleep_duration
        data:
          duration: 3600 # 1 hour
      - action: switch.turn_on
        metadata: {}
        data: {}
        target:
          entity_id: switch.framery3lilygo_trigger_sleep
    mode: single

For testing I renamed a device which was called framery2lilygo now to framery3lilygo.

I recompiled the ESPHome and the new ESPHome YAML has everywhere a framery3lilygo where a 2 was before.

After rebooting everything, when I want to add a new button in my lovelace frontend, I see this

So “somewhere” still the “2” is used, which breaks all automations. To me it seems, as if I have to add an “id” somewhere, which I seem not to have done before, so a “default” ID was used instead.

Any idea?
many thanks
Juergen

Can You share common/esp_display_lilygo-t547-touch.yaml too - to see switch configuration.
At least not sure - is friendly_name is used or not.

As well - when device name changed: can try to delete device in HA and add it again.

1 Like

I do not know how to upload yaml files, so here comes the code:

# substitution fort ESPHome and recalculate in lambda for drawing
# rectangle (top_x, top_y, x_width, y_width) in the drawing


substitutions:
# Button Bottom Right
  button_br_x1: "680"
  button_br_y1: "425"
  button_br_x2: "890"
  button_br_y2: "500"
# Button Bottom Left
  button_bl_x1: "420" # 680 - length - padding = 680 - (button_br_x2 + button_br_x1) - 50 
  button_bl_y1: "${button_br_y1}" # same height as br but used for clarity below
  button_bl_x2: "630" # button_bl_x1 - (button_br_x2 + button_br_x1)
  button_bl_y2: "${button_br_y2}" # same height as br
# Button Bottom sleep
  button_bs_x1: "60" # 
  button_bs_y1: "${button_br_y1}" # same height as br but used for clarity below
  button_bs_x2: "270" # 
  button_bs_y2: "${button_br_y2}" # same height as br
# MDI fonts
# https://pictogrammers.com/library/mdi/
# WIFI
  mdi_wifi: "\U000F05A9" # mdi-wifi
  mdi_wifi_strength_4: "\U000F0928" # mdi-wifi-strength-4
  mdi_wifi_strength_3: "\U000F0925" # mdi-wifi-strength-3 
  mdi_wifi_strength_2: "\U000F0922" # mdi-wifi-strength-2
  mdi_wifi_strength_1: "\U000F091F" # mdi-wifi-strength-1
  mdi_wifi_strength_alert_outline: "\U000F092B" # mdi-wifi-strength-alert-outline
  mdi_access_point: "\U000F0003" # mdi-access-point
# battery
  mdi_battery_100: "\U000F0079" # mdi-battery
  mdi_battery_80: "\U000F0081" # mdi-battery-80
  mdi_battery_60: "\U000F007F" # mdi-battery-60
  mdi_battery_40: "\U000F007D" # mdi-battery-40
  mdi_battery_20: "\U000F007B" # mdi-battery-20
  mdi_battery_10: "\U000F007A" # mdi-battery-10
  mdi_battery_charging_100: "\U000F0085" # mdi-battery-charging-100
  mdi_battery_charging_80: "\U000F008A" # mdi-battery-charging-80
  mdi_battery_charging_60: "\U000F0089" # mdi-battery-charging-60
  mdi_battery_charging_40: "\U000F0088" # mdi-battery-charging-40
  mdi_battery_charging_20: "\U000F0086" # mdi-battery-charging-20
  mdi_battery_charging_10: "\U000F089C" # mdi-battery-charging-10
  mdi_battery_unknown: "\U000F0091" # mdi-battery-unknown
# Misc
  mdi_book_outline: "\U000F0B64" # mdi-book-outline


esphome:
  name: "${rmgr_device_name}"
  friendly_name: "${rmgr_device_name}"
  platformio_options:
    board_build.f_flash: 80000000L
    board_build.flash_mode: qio
    board_build.psram_type: opi
    board_build.partitions: default_16MB.csv
    board_build.arduino.memory_type: qio_opi
    build_flags:  # the first three defines are required for the screen library to function.
      - "-DBOARD_HAS_PSRAM" #OK
      - "-DARDUINO_USB_MODE=1" #OK
      - "-DARDUINO_USB_CDC_ON_BOOT=1" #OK
  libraries:
    - SPI
  on_boot:
    - priority: -100
      then:
        - component.update: wifisignal
        - component.update: battery_voltage
    - priority: -800  # run as early as possible (must be <1000)
      then:
        - component.update: t5_display  # show "Booting..." immediately


esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: arduino
  variant: esp32s3
  flash_size: 16MB

i2c:
  - id: bus_a
    sda: GPIO18
    scl: GPIO17
    frequency: 100khz
    # There is some problems with i2c scan so turn scan off if problem appear on your board
    scan: False


touchscreen:
    platform: gt911
    id: lilygo_touchscreen
    interrupt_pin: GPIO47
    address: 0x5D
    i2c_id: bus_a
    setup_priority: -100
    transform:
    # because we use the display widescreen, buttons on top we have to transfer the touch
      mirror_x: false
      mirror_y: true
      swap_xy: true
    on_update:
        - lambda: |-
              for (auto touch: touches)  {
                  if (touch.state <= 2) {
                    ESP_LOGI("Touch points:", "id=%d x=%d, y=%d", touch.id, touch.x, touch.y);
                  }
              }


# Have Buttons in HomeAssistant for actions
# defined above and substitutions used also in lambda for drawing the rectangle on screen !!!

binary_sensor:
  - platform: status
    name: "API Status"
    id: api_status
  - platform: touchscreen
    name: "bottom left touch"
    id: bl_touch
    x_min: ${button_bl_x1}
    y_min: ${button_bl_y1}
    x_max: ${button_bl_x2}
    y_max: ${button_bl_y2}
  - platform: touchscreen
    name: "bottom right touch"
    id: br_touch
    x_min: ${button_br_x1}
    y_min: ${button_br_y1}
    x_max: ${button_br_x2}
    y_max: ${button_br_y2}
  - platform: touchscreen
    name: "bottom sleep touch"
    id: bs_touch
    x_min: ${button_bs_x1}
    y_min: ${button_bs_y1}
    x_max: ${button_bs_x2}
    y_max: ${button_bs_y2}
#  - platform: gpio
#    pin: 
#      number: GPIO21 # SENS OP_VN
#      inverted: true
#      allow_other_uses: true
#    name: "Button 1"
#    on_press:
#      - logger.log: "GPIO21 pressed"

text_sensor:
  - platform: homeassistant
    name: "Charging Status"
    id: ha_charging_status # internal here in ESPHome YAML
    entity_id: sensor.${rmgr_device_name}_battery_charging_status # ID in HA

globals:
  - id: sleep_duration
    type: int
    restore_value: no
    initial_value: '10'  # default sleep duration in seconds
  - id: overlay_text
    type: std::string
    restore_value: no
    initial_value: ""
  - id: is_booting
    type: bool
    restore_value: no
    initial_value: 'true'
  - id: is_going_to_sleep
    type: bool
    restore_value: no
    initial_value: 'false'


# Not used in lilygo: but kept for compatibility to waveshare different display use (so we can use the same Perl)
  - id: initial_data_received
    type: bool
    restore_value: no
    initial_value: 'false'
  - id: rmgr_booking_slot_length_minutes
    type: std::string
    restore_value: no
  - id: last_display_refresh
    type: std::string
    restore_value: false

# used for both waveshare and lilygo
# Display globals:
  - id: headline_text
    type: std::string
    restore_value: false
    initial_value: ""
  - id: subheadline_text
    type: std::string
    restore_value: false
    initial_value: ""
# ready made times and summary from calendar, e.g. 10:05 - 13:15 whatever
  - id: esp_drawing_events
    type: std::vector<std::string>
    restore_value: false
    initial_value: 'std::vector<std::string>()'
# Enable logging
logger:
#  level: NONE
#  level: ERROR
#  level: WARN
#  level: INFO
  level: DEBUG
#  level: VERBOSE
#  level: VERY_VERBOSE

# we need time :-)
time:
  - platform: homeassistant
    id: ha_time

# Enable Home Assistant API
api:
  encryption:
    key: !secret api_encryption_key
  services:
    - service: set_sleep_duration
      variables:
        duration: int
      then:
        - lambda: |-
            id(sleep_duration) = duration;
            ESP_LOGI("Sleep", "Sleep duration set to %d seconds", duration);

# MUSS Angepasst werden an das LilyGo!
    - service: "draw_schedule"
      variables:
        param_headline_text: string
        param_subheadline_text: string
        param_rmgr_booking_slot_length_minutes: string
        param_last_display_refresh: string
        param_esp_drawing_events: string[]



      then:
        - lambda: 'id(initial_data_received) = true;'
        - lambda: 'id(is_booting) = false;'
        - lambda: 'id(is_going_to_sleep) = false;'
        - lambda: 'id(overlay_text) = "";' 
        - lambda: 'id(headline_text) = param_headline_text;'
#        - lambda: 'id(subheadline_text) = param_subheadline_text;'
        - lambda: |-
            id(subheadline_text) = param_subheadline_text;
            ESP_LOGI("INFO", "subheadline_text: [%s]", id(subheadline_text).c_str());
#        - lambda: 'id(esp_drawing_events) = param_esp_drawing_events;'
        - lambda: |-
            id(esp_drawing_events) = param_esp_drawing_events;
            std::string joined;
            for (const auto &item : param_esp_drawing_events) {
              joined += item + ", ";
            }
            ESP_LOGI("INFO","esp_drawing_events: [%s]", joined.c_str());
        - lambda: 'id(rmgr_booking_slot_length_minutes) = param_rmgr_booking_slot_length_minutes;'
        - lambda: 'id(last_display_refresh) = param_last_display_refresh;'
        - logger.log: ".-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. calfreebusy_draw_schedule Data"
        - logger.log:
            format: "param_headline_text has value >%s<"
            args: [ 'id(headline_text).c_str()' ]
        - logger.log:
            format: "param_subheadline_text has value >%s<"
            args: [ 'id(subheadline_text).c_str()' ]
        - component.update: t5_display


  on_client_connected:
    then:
      - logger.log: "Connected to HomeAssistant. -> booted"
      - homeassistant.event:
          event: esphome.rmgr_device_booted
          data:
            device_name: "${rmgr_device_name}"
            boot_time: !lambda |-
              return id(ha_time).now().strftime("%Y-%m-%d %H:%M:%S");
      - lambda: 'id(is_booting) = false;'
      - lambda: 'id(is_going_to_sleep) = false;'

#      - logger.log: "simulate refresh to refresh the screen on first connect"
#      - homeassistant.event:
#          event: esphome.${rmgr_device_name}





switch:
  - platform: template
    name: "Trigger Sleep"
    id: ${rmgr_device_name}_trigger_sleep
    restore_mode: ALWAYS_OFF
    turn_on_action:
      - script.execute: go_to_sleep

# scripts for putting ESP into sleep and informing the User on ePaper

script:
# to print on screen until when we sleep
  - id: go_to_sleep
    mode: restart
    then:
      - lambda: |-
          auto now = id(ha_time).now();
          auto timestamp = now.timestamp - 10; // seems to have 10 sec offset 
          auto nowtime = esphome::ESPTime::from_epoch_local(timestamp);
          timestamp += id(sleep_duration);
          // timestamp = now.timestamp + id(sleep_duration) - 10;  seems to have 10 sec offset 
          auto future = esphome::ESPTime::from_epoch_local(timestamp);

          // Map day_of_week (0 = saturday) to 3-letter name
          const char* days[] = {"SAT", "SUN", "MON", "TUE", "WED", "THU", "FRI" };
          int dow_future = future.day_of_week % 7;  // ensure it's in 0..6
          int dow = nowtime.day_of_week % 7; // ensure it's in 0..6
          const char* day_str = "today";
          char wake_time[50];
          snprintf(wake_time, sizeof(wake_time), "Sleeping until: [%04d-%02d-%02d: %s] %02d:%02d:%02d",
              future.year, future.month, future.day_of_month,
                            day_str,
              future.hour, future.minute, future.second);
          if (dow_future != dow) {  // sleeping till another day, show different text
            const char* day_str = days[dow_future];
            snprintf(wake_time, sizeof(wake_time), "Sleeping until: [%s] %04d-%02d-%02d %02d:%02d:%02d",
                day_str,
                future.year, future.month, future.day_of_month,
                future.hour, future.minute, future.second);
          }          



          id(overlay_text) = wake_time;
          id(is_going_to_sleep) = true;
          ESP_LOGI("sleep", "Device will sleep until: %s", wake_time);
      - component.update: t5_display
#      - delay: 3s
      - script.execute: sleep_now

# to immediately put ESP into sleep
  - id: sleep_now
    then:
      - lambda: |-
          ESP.deepSleep(id(sleep_duration) * 1000000ULL);

deep_sleep:
  id: deep_sleep_wakeup_pin
  esp32_ext1_wakeup:
    pins: GPIO21
    mode: ALL_LOW
#  allow_other_uses: true

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

# Not needed, as the API connect takes way longer than the WIFI connect 
#
#  fast_connect: true
#  manual_ip:
#    static_ip: 172.16.0.200
#    gateway: 172.16.0.1
#    subnet: 255.255.255.0


external_components:
  # https://github.com/nickolay/esphome-lilygo-t547plus
  - source: github://nickolay/esphome-lilygo-t547plus
    components: ["t547"]
  # https://github.com/kaeltis/esphome-lilygo-t547plus
  - source: github://kaeltis/esphome-lilygo-t547plus
    components: ["lilygo_t5_47_battery"]

font:
  - file: 'fonts/GothamRnd-Book.ttf'
    id: font_medium_gotham
    size: 30

  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: font_mdi_medlarge
    size: 35
    glyphs:
      - "${mdi_wifi}"
      - "${mdi_wifi_strength_4}"
      - "${mdi_wifi_strength_3}"
      - "${mdi_wifi_strength_2}"
      - "${mdi_wifi_strength_1}"
      - "${mdi_wifi_strength_alert_outline}"
      - "${mdi_book_outline}"
      # battery
      - "${mdi_battery_100}"
      - "${mdi_battery_80}"
      - "${mdi_battery_60}"
      - "${mdi_battery_40}"
      - "${mdi_battery_20}"
      - "${mdi_battery_10}"
      - "${mdi_battery_charging_100}"
      - "${mdi_battery_charging_80}"
      - "${mdi_battery_charging_60}"
      - "${mdi_battery_charging_40}"
      - "${mdi_battery_charging_20}"
      - "${mdi_battery_charging_10}"
      - "${mdi_battery_unknown}"
      

display:
  - platform: t547
    id: t5_display
    update_interval: never
    lambda: |-
      auto draw_rect_frame = [&](display::Display &it,
                            int x1, int y1, int x2, int y2,
                            int thickness,
                            std::string label = "",
                            esphome::font::Font* font = nullptr) {
        int width = x2 - x1;
        int height = y2 - y1;

        // Draw borders
        it.filled_rectangle(x1, y1, width, thickness);                     // top
        it.filled_rectangle(x1, y2 - thickness, width, thickness);         // bottom
        it.filled_rectangle(x1, y1, thickness, height);                    // left
        it.filled_rectangle(x2 - thickness, y1, thickness, height);        // right

        // Optional: draw label text centered inside
        if (!label.empty() && font != nullptr) {
          int cx = x1 + width / 2;
          int cy = y1 + height / 2 + 5;
          it.printf(cx, cy, font, TextAlign::CENTER, "%s", label.c_str());
        }
      };

      auto draw_always = [&](display::Display &it) {
            int max_x = it.get_width();
            int max_y = it.get_height();

            int event_x = 50;
            int event_y = 50;
            int line_height = 30;  // Adjust based on your font size
            size_t max_line_length = 50;
            bool was_cut = false;

            int bottom_margin = 250;  // Keep this space clear
            int max_event_y = max_y - bottom_margin;

            for (const auto &event : id(esp_drawing_events)) {
                  size_t start = 0;
                  bool first_line = true;

                  while (start < event.length()) {
                        size_t remaining = event.length() - start;
                        size_t len = (remaining > max_line_length) ? max_line_length : remaining;
                        size_t end = start + len;

                        // Look backwards for last space before end
                        size_t space_pos = event.rfind(' ', end);
                        if (space_pos == std::string::npos || space_pos < start) {
                              space_pos = end;  // no space found, force cut
                        }

                        std::string line = event.substr(start, space_pos - start);

                        int indent = first_line ? 0 : 30;
                        it.printf(event_x + indent, event_y, id(font_medium_gotham), TextAlign::TOP_LEFT, "%s", line.c_str());

                        event_y += line_height;
                        start = (space_pos < event.length()) ? space_pos + 1 : event.length();
                        first_line = false;

                        if (event_y > max_event_y) {
                              was_cut = true;
                              break;
                        }
                  }

                  // Add more spacing between different events
                  event_y += 15;

                  if (event_y > max_event_y) {
                        was_cut = true;
                        break;
                  }
            }

            // If content was cut, show a warning at the bottom
            if (was_cut) {
                  it.printf(event_x + 50, event_y + line_height, id(font_medium_gotham), TextAlign::TOP_LEFT, "Text above !!! CUT !!!");
            }
      };

      auto draw_wifi_icon = [&](display::Display &it) {
        std::string wifi_symbol = "${mdi_access_point}";  // default if no state!

        if (id(wifisignal).has_state()) {
          float rssi = id(wifisignal).state;
          if (rssi >= -50) {
            wifi_symbol = "${mdi_wifi_strength_4}";
          } else if (rssi >= -60) {
            wifi_symbol = "${mdi_wifi_strength_3}";
          } else if (rssi >= -67) {
            wifi_symbol = "${mdi_wifi_strength_2}";
          } else if (rssi >= -70) {
            wifi_symbol = "${mdi_wifi_strength_1}";
          } else {
            wifi_symbol = "${mdi_wifi_strength_alert_outline}";
          }
        }
      // soll neben die Buttons gedruckt werden ĂĽber wifi
      it.printf(${button_br_x2} + 15, ${button_br_y1} + (${button_br_y2} - ${button_br_y1}) / 2 , id(font_mdi_medlarge), TextAlign::TOP_LEFT, wifi_symbol.c_str());
      };

      auto draw_battery_icon = [&](display::Display &it) {
        std::string batt_symbol = "${mdi_battery_unknown}";  // default if no state!

        if (id(battery_voltage_voltage).has_state()) {
          float batt = id(battery_voltage_voltage).state;
          bool charging = id(ha_charging_status).has_state() && id(ha_charging_status).state == "charging";
          if (charging) {
            if (batt >= 4.42) {
              batt_symbol = "${mdi_battery_charging_100}";
            } else if (batt >= 4.2) {
              batt_symbol = "${mdi_battery_charging_80}";
            } else if (batt >= 4) {
              batt_symbol = "${mdi_battery_charging_60}";
            } else if (batt >= 3.9) {
              batt_symbol = "${mdi_battery_charging_40}";
            } else if (batt >= 3.8) {
              batt_symbol = "${mdi_battery_charging_20}";
            } else {
              batt_symbol = "${mdi_battery_charging_10}";
            }
          } else {
              if (batt >= 4.35) {
                batt_symbol = "${mdi_battery_100}";
            } else if (batt >= 4.2) {
              batt_symbol = "${mdi_battery_80}";
            } else if (batt >= 4) {
              batt_symbol = "${mdi_battery_60}";
            } else if (batt >= 3.9) {
              batt_symbol = "${mdi_battery_40}";
            } else if (batt >= 3.8) {
              batt_symbol = "${mdi_battery_20}";
            } else {
              batt_symbol = "${mdi_battery_10}";
            }
          }
        }
  
      // soll neben die Buttons gedruckt werden ĂĽber wifi
      it.printf(${button_br_x2} + 15, ${button_br_y1} + (${button_br_y2} - ${button_br_y1}) / 2 - 40, id(font_mdi_medlarge), TextAlign::TOP_LEFT, batt_symbol.c_str());
      };

      auto draw_sleep_overlay = [&](display::Display &it) {
        it.printf(${button_br_x2}, ${button_br_y1} + (${button_br_y2} - ${button_br_y1}) / 2,
                  id(font_medium_gotham), TextAlign::CENTER_RIGHT,
                  id(overlay_text).c_str());
      };

      auto draw_buttons = [&](display::Display &it) {
        // below draw empty rectangle frame
        // draw_rect_frame(it, ${button_bl_x1}, ${button_bl_y1}, ${button_bl_x2}, ${button_bl_y2}, 8);
        // below draw rectanle with text centered
        draw_rect_frame(it, ${button_bl_x1}, ${button_bl_y1}, ${button_bl_x2}, ${button_bl_y2}, 12, "15 Min", id(font_medium_gotham));
        draw_rect_frame(it, ${button_br_x1}, ${button_br_y1}, ${button_br_x2}, ${button_br_y2}, 12, "30 Min", id(font_medium_gotham));
        draw_rect_frame(it, ${button_bs_x1}, ${button_bs_y1}, ${button_bs_x2}, ${button_bs_y2}, 12, "SLEEP", id(font_medium_gotham));

        //it.filled_rectangle(${button_bl_x1}, ${button_bl_y1},
        //                    ${button_bl_x2} - ${button_bl_x1}, ${button_bl_y2} - ${button_bl_y1});


      };




      // ##### below the drawing happens
      // What is printer before the "if" the stuff (after first else) is always on screen

      if (id(is_booting)) {
        it.printf(it.get_width() / 2, it.get_height() / 2, id(font_medium_gotham), TextAlign::CENTER, "Booting...");
        id(is_booting) = false;
        return;
      } else {
        // always on screen
        draw_always(it);
        draw_battery_icon(it);
        // Differen print if Sleep Mode or not (Basically touch Buttons off in Sleep)
        if (id(is_going_to_sleep) == true) {
          // Here we go to Sleep Mode
          draw_sleep_overlay(it);
        } else {
          // Printed in NON Sleep Mode
          draw_buttons(it);
          draw_wifi_icon(it);
        }
      }

sensor:
  - platform: lilygo_t5_47_battery
    id: battery_voltage
    update_interval: 600s # we sleep a lot, and call it during wakeup
    voltage:
      name: "Battery Voltage"
      id: battery_voltage_voltage
  - platform: wifi_signal
    name: "WiFi Signal Strength"
    id: "wifisignal"
    unit_of_measurement: "dBm"
    entity_category: "diagnostic"
# update not really needed due to deep_sleep
    update_interval: never

Hi,
where can I delete this device in HomeAssistant?

Juergen

Deleting the device in HomeAssistant was the trick:

  1. Go to Settings → Devices & Services
  2. Klick ESPHome
  3. Than in de devices List of ESPHome three dots above each other for this device → delete

If you are in the individual device, there I see no “delete” for this devide.