LilyGo T457 working ESPHome still some issues to solve

Hi,
I have a Lilygo T547 V2.4 with S3 ESP, got it do compile with ESPHome and connected to HomeAssistant but still have som issue I want to fix. The case is, that this ePaper should mostly “sleep”, but if people want to get new informations, they should press a button on the display, the display updates thru HomeAssistant API (connect works), display newest things, and than people are able to press a touch button on the touch display. My problem now is that the “wakeup” takes quite some time, where the user does not see “anything” on the display. So my idea was to show “ASAP” a “Booting…” on the ePaper display, probable with a hint, how long the boot usually takes. As the blue LED goes on immediately, I was hoping to be “as fast” with writing on the ePaper, but I could not manage to do it. It takes about 10 seconds till the user sees something which are somehow “ages”.
(For wakign up from book I am using the RESET switch), as I had no luck with GPIO15 or GPIO16, which I ratherwould like to use. I could see both GPIO beeing triggered if the ESP is running, but I could not get it to make the ESP wake up.
My configuration is mainly form this setup.

I am using

on the board is printed V 2.4

Here is my ESPHome Yaml:
with this variable set:
rmgr_device_name: “${rmgr_device_name}”
and called via an include

So if anyone has an idea for any of those question, it would help me:
1.) Printing “Booting…” imemdiately on the Screen if the ESP gets power/awakes from deep_sleep
2.) Check on the board directly if the battery is charging (At the moment I assume charging if the volatage is increasing, which is computed on the HA)
3.) Awaking from deep_sleep thru an GPIO and not the used RESET

Many thanks
Juergen


# 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://materialdesignicons.com/
# 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_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_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: 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
    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'

# Enable logging
logger:

# 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);
  on_client_connected:
    then:
      - logger.log: "Connected to HomeAssistant. -> booted"
      - homeassistant.event:
          event: esphome.${rmgr_device_name}.booted
      - lambda: 'id(is_booting) = false;' # to not print "Booting..." a second time
      - lambda: 'id(overlay_text) = "";' # to prevent the screen updated twice after wakeup from deep_sleep; having an extra boolean might be the better idea
      - component.update: t5_display

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

switch:
  - platform: template
    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 + 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.day_of_week % 7;  // ensure it's in 0..6
          const char* day_str = days[dow];

          char wake_time[50];
          snprintf(wake_time, sizeof(wake_time), "Sleeping until: [%02d:%s] %04d-%02d-%02d %02d:%02d:%02d",
              dow,day_str,
              future.year, future.month, future.day_of_month,
              future.hour, future.minute, future.second);

          id(overlay_text) = wake_time;
          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);


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:
  - source: github://nickolay/esphome-lilygo-t547plus
    components: ["t547"]
  - 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_80}"
      - "${mdi_battery_60}"
      - "${mdi_battery_40}"
      - "${mdi_battery_20}"
      - "${mdi_battery_10}"
      - "${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_for_test = [&](display::Display &it) {
        int x = it.get_width() / 2;
        int y = it.get_height() / 2;
        it.printf(x, y, id(font_medium_gotham), TextAlign::TOP_CENTER, "Waiting ...");
        it.filled_circle(20, 32, 15);
        it.circle(20, 42, 15);
        it.filled_gauge(75, 75, 30, 20, 80);
        it.filled_regular_polygon(170, 45, 20, EDGES_HEXAGON);
        it.regular_polygon(340, 90, 40, EDGES_OCTAGON, VARIATION_FLAT_TOP);

        it.printf(520,300, id(font_mdi_medlarge), "${mdi_book_outline}");
      };

      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.43) {
              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.43) {
                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
      // befor the "if" the stuff 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...");
        return;
      } else {
        draw_for_test(it);
        draw_battery_icon(it);
        if (id(overlay_text) != "") {
          // Here we go to Sleep Mode
          draw_sleep_overlay(it);
        } else {
          // Printe 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 Juergen.
Can you post the latest stand of yaml for your device ?
Im working with a H752-A.
cheers,

Hi @sibelle-labs,

Code follows. I gave up on my touch problem, I hope it works as a start.


# goal of the project: Display a calendar of a room and give people at the room the chance 
# to book a slot, if they see that the room is free. I do not care if the 15 minutes would 
# overlap the next booking
# known problems:
# 1.) Touch is not working reliably. According to 
# https://github.com/Xinyuan-LilyGO/LilyGo-EPD47/blob/067ece6bddfd8d0fe6c2c18043930236cee4ac23/examples/touch/touch.ino
# lines 105ff there are 2 addresses where the touch screen could be found. Those addresses are
# randomly used. And as you could win a 50:50 bet a couple of times, you could also lose it
# a couple of time. It is beyond my knowledge to get this running reliably in ESPHome
# I would bet that this can be fixed with some lambda code, but I have no clue where and how.
# 
# 2.) What I found out by chance and "watching" is that
# ESP.deepSleep(id(sleep_duration) * 1000000ULL); makes the ESP sleep for about max 71 minutes.
# If you use more sleep time (over 4260 seconds somewhere) it seems there is an integer overflow
# and it "randomly" sleep for the overfolw time. If you use
# esp_sleep_enable_timer_wakeup(id(sleep_duration) * 1000000ULL);
# esp_deep_sleep_start();
# in combination instead (which basically should to the same, I could use more than 8 
# hours for sleeping; I did not check where the limit is, as "testing" is a little 
# awkward and I am more than happy with the 8 hours.
# 
# This YAML cannot really work, if you do not have a counterpart in HomeAssistant, as it
# gets all the data from HA. But this makes the code here also "slim" and I hope it is 
# a good start for others. I am not a "pro", just someone that is passionated and does not
# give up.
# if anyone enhances the touch code, so that it always works, and notifies me here, I owe
# him/her more than one beer. 


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

substitutions:
# Booting message
  line_height: "30" # // Adjust based on font size
# Bottom line y where the touches go
  bottom_line_y1: "450"
  bottom_line_y2: "525"
# Button Bottom Right
  button_br_x1: "680"
  button_br_y1: "${bottom_line_y1}"
  button_br_x2: "890"
  button_br_y2: "${bottom_line_y2}"
# Button Bottom Left
  button_bl_x1: "420" # 680 - length - padding = 680 - (button_br_x2 + button_br_x1) - 50 
  button_bl_y1: "${bottom_line_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: "${bottom_line_y2}" # same height as br
# Button Bottom sleep
  button_bs_x1: "60" # 
  button_bs_y1: "${bottom_line_y1}" # same height as br but used for clarity below
  button_bs_x2: "270" # 
  button_bs_y2: "${bottom_line_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
  mdi_cast_connected: "\U000F0119" # mdi-cast-connected
  mdi_power_standby: "\U000F0906" # mdi-power-standby

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
    - priority: 500
      then:
        - lambda: |-
            // esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
            // const char* reason = "Unknown";
            // switch (cause) {
            //   case ESP_SLEEP_WAKEUP_TIMER: reason = "Timer"; break;
            //   case ESP_SLEEP_WAKEUP_TOUCHPAD: reason = "Touchpad"; break;
            //   case ESP_SLEEP_WAKEUP_ULP: reason = "ULP"; break;
            //   case ESP_SLEEP_WAKEUP_GPIO: reason = "GPIO"; break;
            //   case ESP_SLEEP_WAKEUP_EXT0: reason = "EXT0"; break;
            //   case ESP_SLEEP_WAKEUP_EXT1: reason = "EXT1"; break;
            //   case ESP_SLEEP_WAKEUP_UART: reason = "UART"; break;
            // default: reason = "Unknown"; break;
            // }

            auto now = id(ha_time).now();
            char timestr[16];
            snprintf(timestr, sizeof(timestr), "%02d:%02d:%02d", now.hour, now.minute, now.second);

            // id(wakeup_reason) = std::string("W: ") + reason + " @ " + timestr;
            id(wakeup_reason) = std::string("last Wakeup: ") + " @ " + timestr;





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: true


touchscreen:
    platform: gt911
#    platform: lilygo_t5_47
    id: lilygo_touchscreen
#    interrupt_pin: GPIO47
    interrupt_pin: GPIO47
    address: 0x5D   # Either this or the one below is correct
#    address: 0x14    # One should do it like here: https://github.com/Xinyuan-LilyGO/LilyGo-EPD47/blob/067ece6bddfd8d0fe6c2c18043930236cee4ac23/examples/touch/touch.ino
                      # lines from 108 which try both addresses, and just choose the right one.
    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_LOGD("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
                        touch.x,
                        touch.y,
                        touch.x_raw,
                        touch.y_raw
                        );
                  }
              }


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

binary_sensor:
  - platform: template
    id: ${rmgr_device_name}
    icon: mdi:gesture-tap-button
    name: "Touchscreen OK"
    device_class: connectivity
    lambda: |-
      if (id(lilygo_touchscreen).is_failed()) {
        return false;
      } else {
        return true;
      }
  - platform: status
    name: "API Status connected"
    id: ${rmgr_device_name}_api_status_connected
    icon: mdi:cast-connected
  - 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}
# the doubleclicked is really bad here, but it makes it compatible to a decision before
# it is just a try
  - platform: touchscreen
    name: "bottom right touch doubleclicked"
#    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}
    on_press:
      - component.update: t5_display
  - 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
  - platform: template
    name: "${rmgr_device_name} Wakeup Reason"
    id: wakeup_reason_sensor
    lambda: |-
      return id(wakeup_reason);


globals:
  - id: sleep_forever
    type: bool
    restore_value: no
    initial_value: 'false'
  - 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: wakeup_reason
    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);
            if (id(sleep_duration) == 0) {
              id(sleep_forever) = true;
              ESP_LOGI("Sleep", "sleep_forever 1");
            }



# MUSS Angepasst werden an das LilyGo!
    - service: "draw_schedule"
      variables:
        param_rmgr_booking_slot_length_minutes: 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(subheadline_text) = param_subheadline_text;'
#        - lambda: |-
#            id(subheadline_text) = param_subheadline_text;
#            ESP_LOGD("INFO", "subheadline_text: [%s]", id(subheadline_text).c_str());
        - lambda: 'id(esp_drawing_events) = param_esp_drawing_events;'
        - lambda: 'id(rmgr_booking_slot_length_minutes) = param_rmgr_booking_slot_length_minutes;'
        - logger.log: ".-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. "
        - component.update: wakeup_reason_sensor
        - component.update: t5_display


  on_client_connected:
    then:
      - logger.log: "${rmgr_device_name}_api_connected"
      - lambda: 'id(is_booting) = false;'
      - lambda: 'id(is_going_to_sleep) = false;'


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 + 11; // seems to have 11 sec offset 
          auto nowtime = esphome::ESPTime::from_epoch_local(timestamp);
          timestamp += id(sleep_duration);
          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_LOGD("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: |-
        if (id(sleep_duration) > 0) {
        // below seems to work with longer sleeping time periods
        // before I was using           ESP.deepSleep(id(sleep_duration) * 1000000ULL);
        // which only made sleep possible for about 71 minutes
        // The below makes sleep possible for more minutes
          esp_sleep_enable_timer_wakeup(id(sleep_duration) * 1000000ULL);
          esp_deep_sleep_start();
        } else {
          esp_deep_sleep_start();  // Sleep until external wake/reset
        }

#deep_sleep:
#  id: deep_sleep_configure
#  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_big_gotham
    size: 55
    glyphs: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ !?*<>\"%()=+-_:.,üäößÜÄÖ|[]@éÉ"

  - file: 'fonts/GothamRnd-Book.ttf'
    id: font_medium_gotham
    size: 30
    glyphs: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ !?*<>\"%()=+-_:.,üäößÜÄÖ|[]@éÉ"

  - file: 'fonts/GothamRnd-Book.ttf'
    id: font_small_gotham
    size: 15
    glyphs: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ !?*<>\"%()=+-_:.,üäößÜÄÖ|[]@éÉ"

  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: font_mdi_large
    size: 55
    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}"
      # misc
      - "${mdi_cast_connected}"
      - "$mdi_power_standby"


  - 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}"
      # misc
      - "${mdi_cast_connected}"
      - "$mdi_power_standby"
      

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());
        }
      };

      // Define trim using auto outside the draw_events lambda
      auto trim = [](const std::string &s) -> std::string {
            auto start = s.find_first_not_of(" \t\r\n");
            auto end = s.find_last_not_of(" \t\r\n");
            return (start == std::string::npos) ? "" : s.substr(start, end - start + 1);
      };
      // Main event drawing function
      auto draw_events = [&](display::Display &it) {
            int max_x = it.get_width();
            int max_y = it.get_height();

            int event_x = 50;
            int event_y = 40;
            int small_line_seperator = 15;
            int line_height = ${line_height};  
            size_t max_line_length = 50;
            bool was_cut = false;
            bool cut_after_free = false;
            bool cut_after_event = false;

            int bottom_margin = 125 + ${bottom_line_y2} - ${bottom_line_y1};  // Keep this space clear above touch buttons
            int max_event_y = max_y - bottom_margin;
        //    ESP_LOGD("${rmgr_device_name}", "Device will sleep until: %s", bottom_margin);
            std::string last_event_end_time;

            for (const auto &event : id(esp_drawing_events)) {
                  // Extract start and end times from the event string
                  size_t dash_pos = event.find('-');
                  size_t pipe_pos = event.find('|');
                  if (dash_pos != std::string::npos && pipe_pos != std::string::npos) {
                        std::string start_time = trim(event.substr(0, dash_pos));
                        std::string end_time = trim(event.substr(dash_pos + 1, pipe_pos - dash_pos - 1));

                        // If there's a gap, display it
                        if (!last_event_end_time.empty() && last_event_end_time != start_time) {
                              std::string time_range = last_event_end_time + " - " + start_time;
                              int free_label_x = event_x + 300;  // fixed x position for "FREE"
                              
                              it.printf(event_x, event_y, id(font_medium_gotham), TextAlign::TOP_LEFT, "%s", time_range.c_str());
                              it.printf(free_label_x, event_y, id(font_medium_gotham), TextAlign::TOP_LEFT, "FREE");

                              event_y += line_height + small_line_seperator;
                        }

                        last_event_end_time = end_time;  // Save for next iteration
                  }
                  if (event_y > max_event_y) {
                  //  was_cut = true;
                  //  cut_after_free = true;
                      break;
                  }
                  // --- Improved word wrapping logic ---
                  size_t start = 0;
                  bool first_line = true;

                  while (start < event.length()) {
                        size_t remaining = event.length() - start;

                        // Try to keep full line if it fits
                        std::string remaining_str = event.substr(start);
                        if (remaining_str.length() <= max_line_length) {
                              int indent = first_line ? 0 : 30;
                              it.printf(event_x + indent, event_y, id(font_medium_gotham), TextAlign::TOP_LEFT, "%s", remaining_str.c_str());
                              event_y += line_height;
                              break;
                        }

                        // Else find last space within line limit
                        size_t try_end = start + max_line_length;
                        size_t space_pos = event.rfind(' ', try_end);
                        if (space_pos == std::string::npos || space_pos < start) {
                              space_pos = try_end;  // force break if no space
                        }

                        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 += small_line_seperator;

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

            // If content was cut, show a warning at the bottom
            if (was_cut && !cut_after_free && !cut_after_event) {
                  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.30) {
                batt_symbol = "${mdi_battery_100}";
            } else if (batt >= 4.19) {
              batt_symbol = "${mdi_battery_80}";
            } else if (batt >= 4.12) {
              batt_symbol = "${mdi_battery_60}";
            } else if (batt >= 4.0) {
              batt_symbol = "${mdi_battery_40}";
            } else if (batt >= 3.9) {
              batt_symbol = "${mdi_battery_20}";
            } else {
              id(sleep_forever) = true;
              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) {
        if (id(sleep_forever)) {
          ESP_LOGI("Sleep", "sleep_forever 3");
          int ypixel = it.get_height() / 2;
          // won't work as ${rmgr_device_name} does not have same length always
          // it.printf(30, ypixel, id(font_big_gotham), TextAlign::CENTER_LEFT, "${rmgr_device_name} is    FF!!");
          // it.printf(506, ypixel - 10, id(font_mdi_large), TextAlign::CENTER_LEFT, "${mdi_power_standby}");

          // So we use this:
          it.printf(30, ypixel, id(font_big_gotham), TextAlign::CENTER_LEFT, "${rmgr_device_name} is OFF!!");

          ypixel += 55;
          it.printf(30, ypixel, id(font_medium_gotham), TextAlign::CENTER_LEFT, "If you see this, press button ");
          it.printf(450, ypixel - 5, id(font_mdi_medlarge), TextAlign::CENTER_LEFT, "${mdi_power_standby}");
        } else {
          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_broken_touch_overlay = [&](display::Display &it) {
        it.printf(${button_br_x2}, ${button_br_y1} + (${button_br_y2} - ${button_br_y1}) / 2 - ${line_height},
                  id(font_medium_gotham), TextAlign::CENTER_RIGHT, "Touchscreen FAILED!");
        it.printf(${button_br_x2}, ${button_br_y1} + (${button_br_y2} - ${button_br_y1}) / 2,
                  id(font_medium_gotham), TextAlign::CENTER_RIGHT, "Use button on top for 15 min! Calendar OK");
      };

      auto draw_wakeup_reason = [&](display::Display &it) {
        it.printf(${button_br_x2}, 3,
                  id(font_small_gotham), TextAlign::RIGHT,
                  id(wakeup_reason).c_str());
      };

      // auto draw_wakeup_reason = [&](display::Display &it) {
      //   it.printf(${button_br_x2}, 0, id(font_medium_gotham), TextAlign::RIGHT, " TEST TOP RIGHT");
      // };

      auto draw_buttons = [&](display::Display &it) {
          std::string slot = id(rmgr_booking_slot_length_minutes);
          std::string slot_text = slot + " Min";

          // Convert slot to integer and calculate double
          int slot_value = std::atoi(slot.c_str());
          int double_slot_value = slot_value * 2;

          char double_slot_text[20];
          snprintf(double_slot_text, sizeof(double_slot_text), "%d Min", double_slot_value);

          draw_rect_frame(it, ${button_bl_x1}, ${button_bl_y1}, ${button_bl_x2}, ${button_bl_y2}, 12, slot_text.c_str(), id(font_medium_gotham));
          draw_rect_frame(it, ${button_br_x1}, ${button_br_y1}, ${button_br_x2}, ${button_br_y2}, 12, double_slot_text, 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));
      };

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

      if (id(is_booting)) {
        draw_wakeup_reason(it);
        it.printf(it.get_width() / 2 - 200, it.get_height() / 2 - ${line_height}, id(font_medium_gotham), TextAlign::LEFT, "${rmgr_device_name} Booting");
        it.printf(it.get_width() / 2 - 200, it.get_height() / 2, id(font_medium_gotham), TextAlign::LEFT, "and Connecting to HomeAssistant");
        it.printf(it.get_width() / 2 - 200, it.get_height() / 2 + ${line_height}, id(font_medium_gotham), TextAlign::LEFT, " ...  [can take up to 33s]");
        id(is_booting) = false;
        return;
      } else {
        // always on screen
        draw_events(it);
        draw_battery_icon(it);
        draw_wakeup_reason(it);
        // Differen print if Sleep Mode or not (Basically touch Buttons off in Sleep)
        if (id(is_going_to_sleep) == true) {
          // Here we print that we are sleeping (instead of touch buttons)
          draw_sleep_overlay(it);
        } else {
          if (id(lilygo_touchscreen).is_failed()){ // non working touch
            draw_broken_touch_overlay(it);
            draw_wifi_icon(it);
          } else {
          // Printed in NON Sleep Mode with working touch
            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 !
I’ve this board since a few months and it works fine.

For the 1st point : you are using e-paper display, not TFT or OLED - so, each printing on the screen is done with a refresh (total or partial) : you can’t display on this screen like TFT or OLED

For the 2nd point : i detect charging with the delta of the actual voltage and the last measured - so i use a global to store the old voltage value and saved to survive a reset

globals:
# pour conserver le voltage précédent de la batterie entre chaque exécution
  - id: voltage_batterie
    type: float
    restore_value: yes
    initial_value: '0.0'

I use only one decimal on the voltage because the measure is not very accurate.

# init for the very first time
      if (id(voltage_batterie) == 0.0)
        { id(voltage_batterie) = (int(id(battery_voltage).voltage->state*10))/10.0; }

# remember the new voltage
      id(voltage_batterie) = int(id(battery_voltage).voltage->state*10)/10.0;

For the 3rd point : i use the native deep_sleep method with the left button.

deep_sleep:
  sleep_duration: ${sleep_time_jour}
  id: deep_sleep_1
  esp32_ext1_wakeup:
    pins: GPIO21
    mode: ALL_LOW

After the wake up, i wait for a limited time to be sure that all values from my HA sensors have been received by the ESP.