ESP32 freezing every night

Hi,
I have a ESP32 C6 with an 1.47 Inch LCD screen.

I use it to display the reading of two temperature sensors. It works great, except every night, exactly at 2:29 it freezes. Since it is always the same time, I assume it relates to Wifi router restarting (have to do that - different topic) or some other kind of nightly networking hiccup. Is there something I can do in my esphome config to avoid this? Here is my code:

# This sets the default values
substitutions:
  friendly_name: "UNSET_FRIENDLY_NAME"
  device_id: "UNSET_DEVICE_ID"
  description: "UNSET DESCRIPTION"
  area: "UNSET AREA"
  is_esp_32: "true"
  logger: "INFO"
  inside_temp_sensor_id: sensor.climate_temperature_2
  inside_temp_name: "OFFICE"
  outside_temp_sensor_id: sensor.climate_temperature
  outside_temp_name: "OUTSIDE"

i2c:
  - id: bus_a
    sda: GPIO1
    scl: GPIO2
    scan: False
    #frequency: 400kHz # didn't change anything

spi:
  clk_pin: GPIO7
  miso_pin: GPIO5
  mosi_pin: GPIO6

one_wire:
  - pin: GPIO10
    platform: gpio

esp32:
  board: esp32-c6-devkitc-1
  variant: esp32c6
  flash_size: 4MB
  framework:
    type: esp-idf
    version: 5.3.1
    platform_version: 6.9.0
    sdkconfig_options:
      CONFIG_ESPTOOLPY_FLASHSIZE_DETECT: y
      CONFIG_ESPTOOLPY_FLASHSIZE_4MB: y
      CONFIG_OPENTHREAD_ENABLED: n
      CONFIG_USE_MINIMAL_MDNS: y
      # Memory optimization
      COMPILER_OPTIMIZATION_SIZE: y
      COMPILER_OPTIMIZATION_LEVEL_RELEASE: y
      CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE: y
      CONFIG_HEAP_POISONING_DISABLED: y

esphome:
  friendly_name: $friendly_name
  name: $device_id
  name_add_mac_suffix: false
  comment: "${description}"
  area: $area
  includes:
    - ./common/yaml/functions.h
  project:
    name: "Waveshare.ESP32-C6-LCD-1-47in"
    version: "v"
  platformio_options:
    board_build.flash_mode: dio
    build_flags:
      - "-DI2C_NUM_1=I2C_NUM_0"
      - "-DBOARD_HAS_PSRAM"
      - "-Wl,-Map,output.map"
    board_build.arduino.memory_type: qio_opi
    platform_packages:
      - platformio/toolchain-xtensa-esp-elf @ 14.2.0+20241119

# binary_sensor:
#   # https://esphome.io/components/binary_sensor/status.html
#   # Reports if this device is Connected or not
#   - platform: status
#     name: "Status"
#     id: "${device_id}_status"
#     icon: mdi:check-network
#     entity_category: diagnostic

light:
  # RGB LED
  - platform: esp32_rmt_led_strip
    pin:
      number: GPIO8
      ignore_strapping_warning: true
    name: "RGB LED"
    id: rgb_led
    chipset: ws2812
    #rmt_channel: 0
    num_leds: 1
    rgb_order: RGB
    # gamma_correct: 2.8
    default_transition_length: 0s
    restore_mode: RESTORE_DEFAULT_OFF

sensor:
  - platform: uptime
    type: timestamp
    name: Uptime
    entity_category: diagnostic
  # WiFi Signal sensor
  - platform: wifi_signal
    name: WiFi Signal
    id: wifi_signal_db
    icon: mdi:wifi-strength-outline
    entity_category: diagnostic
    update_interval: 30s
    disabled_by_default: True
  - platform: copy
    source_id: wifi_signal_db
    name: "WiFi Signal Percent"
    id: wifi_signal_percent
    icon: mdi:wifi-strength-outline
    unit_of_measurement: "%"
    filters:
      - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
    entity_category: "diagnostic"
    device_class: ""
    disabled_by_default: True
  - platform: uptime
    type: seconds
    name: Uptime Sensor
    entity_category: diagnostic

  - platform: homeassistant
    id: outside_temperature
    entity_id: $outside_temp_sensor_id

  - platform: homeassistant
    id: inside_temperature
    entity_id: $inside_temp_sensor_id

graph:
  # auto-ranged graph
  - id: inside_graph
    duration: 48h
    width: 320
    height: 59
    border: false
    min_value: -10
    max_value: 40
    traces:
      - sensor: inside_temperature
        line_thickness: 3
        line_type: SOLID
        continuous: true
  - id: outside_graph
    duration: 48h
    width: 320
    height: 59
    border: false
    min_value: -10
    max_value: 40
    traces:
      - sensor: outside_temperature
        line_thickness: 3
        line_type: SOLID
        continuous: true

font:
  - file: "common/fonts/8-bit-nintendo.ttf"
    id: nes_1x
    size: 7 # Pixel Perfect
  - file: "common/fonts/8-bit-nintendo.ttf"
    id: nes_2x
    size: 14
  - file: "common/fonts/8-bit-nintendo.ttf"
    id: nes_25x
    size: 18
  - file: "common/fonts/8-bit-nintendo.ttf"
    id: nes_3x
    size: 21
  - file: "common/fonts/8-bit-nintendo.ttf"
    id: nes_4x
    size: 32
  # - file: "common/fonts/gotham-Light-webfont.ttf"
  #   id: gotham_lt
  #   size: 25
  - file: "common/fonts/gotham-Medium-webfont.ttf"
    id: gotham_16
    size: 16
  - file: "common/fonts/gotham-Medium-webfont.ttf"
    id: gotham_40
    size: 40
  - file: "common/fonts/gotham-Medium-webfont.ttf"
    id: gotham_md
    size: 64
  # - file: "common/fonts/gotham-Black-webfont.ttf"
  #   id: gotham_b
  #   size: 30

image:
  # Home Assistant Logos
  - file: mdi:home-assistant
    id: home_assistant_logo_20
    type: "BINARY"
    resize: 20x20x

    # WiFi
  - file: mdi:signal-cellular-3
    id: wifi_3_20
    type: "BINARY"
    resize: 20x20
  - file: mdi:signal-cellular-2
    id: wifi_2_20
    type: "BINARY"
    resize: 20x20
  - file: mdi:signal-cellular-1
    id: wifi_1_20
    type: "BINARY"
    resize: 20x20
  - file: mdi:signal-cellular-outline
    id: wifi_0_20
    type: "BINARY"
    resize: 20x20
  - file: mdi:signal-off
    id: wifi_off_20
    resize: 20x20
    type: "BINARY"

output:
  - platform: ledc
    pin: GPIO22
    id: lcd_backlight
    frequency: 1000 Hz

number:
  - platform: template
    name: "Display Brightness"
    id: display_brightness
    optimistic: true
    min_value: 0
    max_value: 100
    step: 1
    initial_value: 100
    restore_value: true
    mode: slider
    on_value:
      then:
        - output.set_level:
            id: lcd_backlight
            level: !lambda "return x / 100.0;"

color:
  - id: black
    red_int: 0
    green_int: 0
    blue_int: 0
    white_int: 0
  - id: gray
    red_int: 128
    green_int: 128
    blue_int: 128
    white_int: 0
  - id: gray_dark
    red_int: 50
    green_int: 50
    blue_int: 50
    white_int: 0
  - id: red
    red_int: 255
    green_int: 0
    blue_int: 0
    white_int: 0
  - id: orange
    red_int: 255
    green_int: 165
    blue_int: 0
    white_int: 0
  - id: yellow
    red_int: 255
    green_int: 255
    blue_int: 0
    white_int: 0
  - id: green
    red_int: 0
    green_int: 255
    blue_int: 0
    white_int: 0
  - id: blue
    red_int: 0
    green_int: 0
    blue_int: 255
    white_int: 0
  - id: hablue
    red_int: 3
    green_int: 169
    blue_int: 244
    white_int: 0
  - id: white
    red_int: 255
    green_int: 255
    blue_int: 255
    white_int: 0

globals:
  - id: screen_rotation
    type: int
    restore_value: yes
    initial_value: "0"
  - id: inside_temp_name
    type: std::string
    restore_value: yes
    initial_value: '"${inside_temp_name}"'
  - id: outside_temp_name
    type: std::string
    restore_value: yes
    initial_value: '"${outside_temp_name}"'

# button:
#   - platform: template
#     name: "switch page"
#     on_press:
#       - display.page.show_next: tft_ha
#       - component.update: tft_ha

switch:
  - platform: template
    name: "flip screen"
    lambda: |-
      // return true; if the switch should be reported as ON.
      // return false; if the switch should be reported as OFF.
      return id(screen_rotation) > 50;
    turn_on_action:
      - globals.set:
          id: screen_rotation
          value: "180"
    turn_off_action:
      - globals.set:
          id: screen_rotation
          value: "0"

# https://esphome.io/components/display/
# TFT Display (ST7789)
display:
  - platform: st7789v
    cs_pin: GPIO14
    dc_pin:
      number: GPIO15
      ignore_strapping_warning: true
    reset_pin: GPIO21
    model: Waveshare 1.47in 172X320
    id: tft_ha
    rotation: 0
    pages:
      - id: page1
        lambda: |-

          // id(screen_rotation) is either 0 or 180
          // we want it to be 90 or 270
          // but then we also want to flip it, so that 270 => 90 and 90 => 270
          auto current_rot = id(screen_rotation) + 90;

          if (current_rot >= 100) {current_rot = 90;}
          else {current_rot = 270;}

          it.set_rotation(id(static_cast<DisplayRotation>(current_rot)));

          auto cur_time = id(homeassistant_time).now();
          if (cur_time.is_valid()) {
            it.printf(it.get_width()/2, -12, id(nes_2x), TextAlign::TOP_CENTER, "%02d:%02d", cur_time.hour, cur_time.minute);
          } else {}

          // Home Assistant API Connection Status
          if (!id(api_id).is_connected()) {
            it.image(it.get_width()-2, 0, id(home_assistant_logo_20), ImageAlign::TOP_RIGHT, gray);
          } else {
            it.image(it.get_width()-2, 0, id(home_assistant_logo_20), ImageAlign::TOP_RIGHT, hablue);
          }

          // Determine the appropriate WiFi icon
          if (!id(wifi_signal_percent).has_state()) {
            it.image(5, 0, id(wifi_off_20), ImageAlign::TOP_LEFT, gray);
          } else {
            // WiFi Signal Strength Status
            int wifi_strength = id(wifi_signal_percent).state;
            if (wifi_strength >= 75) {
              it.image(5, 0, id(wifi_3_20), ImageAlign::TOP_LEFT, green);
            } else if (wifi_strength >= 50) {
              it.image(5, 0, id(wifi_2_20), ImageAlign::TOP_LEFT, orange);
            } else if (wifi_strength >= 25) {
              it.image(5, 0, id(wifi_1_20), ImageAlign::TOP_LEFT, yellow);
            } else {
              it.image(5, 0, id(wifi_0_20), ImageAlign::TOP_LEFT, red);
            }
          }

          it.line(0, 20, it.get_width(), 20, white);

          auto width = it.get_width();
          auto height = it.get_height();

          auto header_height = 24;
          auto space_under_header = height - header_height;

          auto rect_width = width;
          auto rect_height = 30;
          auto rect_start_x = 0;
          auto rect_end_x = width;
          auto rect_vertical_center = header_height + (space_under_header / 2);
          auto rect_start_y = rect_vertical_center - (rect_height / 2);
          auto rect_end_y = rect_start_y + rect_height;

          it.printf(it.get_width() / 2, header_height, id(gotham_md), gray_dark, TextAlign::TOP_CENTER, id(inside_temp_name).c_str());
          it.printf(it.get_width() / 2, it.get_height() + 20, id(gotham_md), gray_dark, TextAlign::BOTTOM_CENTER, id(outside_temp_name).c_str());

          #define DRAW_TEMP_COL(pixel_x) it.filled_rectangle(pixel_x, rect_start_y, 1, rect_height, TemperatureToColor(PixelToTemperature(pixel_x, -10,40,0,320)));
          #define DRAW_LINE_VERTI(temp, clr) it.filled_rectangle(TemperatureToPixel(temp, -10,40,0,320), rect_start_y, 1, rect_height, clr);

          // draw gradient
          for (int i = rect_start_x; i <= rect_end_x; i++) {
            DRAW_TEMP_COL(i);
          }

          // draw numbers on gradient
          for (int i = 0; i <= 30; i+=10) {
            it.printf(TemperatureToPixel(i,-10,40,0,320), rect_vertical_center+2, id(gotham_16), gray, TextAlign::CENTER, "%d", i);
          }

          // draw top graph
          it.graph(0, header_height, id(inside_graph), yellow);
          // draw bottom graph
          it.graph(0, rect_end_y, id(outside_graph), yellow);

          auto inside_temp = 0.0;
          if (id(inside_temperature).has_state()){ inside_temp = id(inside_temperature).state; } else {}
          auto inside_clr = TemperatureToColor(inside_temp);
          auto inside_xpos = TemperatureToPixel(inside_temp,-10,40,0,320);

          auto outside_temp = 0.0;
          if (id(outside_temperature).has_state()){ outside_temp = id(outside_temperature).state; } else {}
          auto outside_clr = TemperatureToColor(outside_temp);
          auto outside_xpos = TemperatureToPixel(outside_temp,-10,40,0,320);

          auto triangle_width = 25;
          auto triangle_height = 25;
          auto box_width = 108;
          auto box_height = 40;

          struct Point {
              int x;
              int y;
          };

          Point line_points[8];

          line_points[0] = {inside_xpos, rect_vertical_center - 10}; // triangle tip
          line_points[1] = {inside_xpos - (triangle_width/2), rect_vertical_center - triangle_height}; // triangle left corner
          line_points[2] = {inside_xpos - (box_width/2), rect_vertical_center - triangle_height}; // box bottom left
          line_points[3] = {inside_xpos - (box_width/2), rect_vertical_center - triangle_height - box_height}; // box top left
          line_points[4] = {inside_xpos + (box_width/2), rect_vertical_center - triangle_height - box_height}; // box top right
          line_points[5] = {inside_xpos + (box_width/2), rect_vertical_center - triangle_height}; // box bottom right
          line_points[6] = {inside_xpos + (triangle_width/2), rect_vertical_center - triangle_height}; // triangle right corner
          line_points[7] = {inside_xpos, rect_vertical_center - 10}; // triangle tip

          it.filled_triangle(line_points[0].x,line_points[0].y, line_points[1].x,line_points[1].y, line_points[6].x,line_points[6].y, black);
          it.filled_rectangle(line_points[3].x, line_points[3].y, box_width, box_height, black);
          for(int i = 1; i < std::ssize(line_points); i++){ it.line(line_points[i-1].x, line_points[i-1].y, line_points[i].x, line_points[i].y, white);}

          line_points[0] = {outside_xpos, rect_vertical_center + 10}; // triangle tip
          line_points[1] = {outside_xpos - (triangle_width/2), rect_vertical_center + triangle_height}; // triangle left corner
          line_points[2] = {outside_xpos - (box_width/2), rect_vertical_center + triangle_height}; // box bottom left
          line_points[3] = {outside_xpos - (box_width/2), rect_vertical_center + triangle_height + box_height}; // box top left
          line_points[4] = {outside_xpos + (box_width/2), rect_vertical_center + triangle_height + box_height}; // box top right
          line_points[5] = {outside_xpos + (box_width/2), rect_vertical_center + triangle_height}; // box bottom right
          line_points[6] = {outside_xpos + (triangle_width/2), rect_vertical_center + triangle_height}; // triangle right corner
          line_points[7] = {outside_xpos, rect_vertical_center + 10}; // triangle tip          

          it.filled_triangle(line_points[0].x,line_points[0].y, line_points[1].x,line_points[1].y, line_points[6].x,line_points[6].y, black);
          it.filled_rectangle(line_points[3].x, line_points[3].y, box_width, box_height, black);
          for(int i = 1; i < std::ssize(line_points); i++){ it.line(line_points[i-1].x, line_points[i-1].y, line_points[i].x, line_points[i].y, white);}

          it.printf(inside_xpos, rect_start_y+1, id(gotham_40), inside_clr, TextAlign::BOTTOM_CENTER, "%.1f°", inside_temp);
          it.printf(outside_xpos, rect_end_y+12, id(gotham_40), outside_clr, TextAlign::TOP_CENTER, "%.1f°", outside_temp);

# Cycle through pages on a timer
# interval:
#   - interval: 5s
#     then:
#       - display.page.show_next: tft_ha
#       - component.update: tft_ha

When checking logs, I’m getting a warning:

[09:01:26][W][graph:118]: Graphing reducing y-scale to prevent too many gridlines
[09:01:26][W][graph:118]: Graphing reducing y-scale to prevent too many gridlines
[09:01:26][W][component:237]: Component display took a long time for an operation (290 ms).
[09:01:26][W][component:238]: Components should block for at most 30 ms.

I’m thankful for any input on this.

That should be the first priority.
But, to paste yet another band-aid on the problem…
Where is the api component defined?
I doubt your ESP is “freezing” but that you simply lost the API connection during your ill-advised router reboot and the ESP is not reconnecting because the ESP is not restarting. Try changing the time of the router reboot to verify that the router reboot is the problem.

If so, you could test the api connection every few seconds then if not connected, reconnect the api.

Your log is showing a time of 09.01 not 02:29. I don’t see a connection.

You could try manually logging a sensor, like the WiFi strength to see where or if it stops.

1 Like

The Wifi issues are also on my todo-list :wink:

Where is the api component defined?

Sorry about the api component, it is in a shared file and is included here:

# This sets the default values
substitutions:
  is_esp_32: false
  logger: "INFO"
  device_id: "UNSET_DEVICE_ID"
  friendly_name: "UNSET_FRIENDLY_NAME"

logger:
  level: $logger

# Enable Home Assistant API
api:
  id: api_id
  encryption:
    key: !secret api_key
  reboot_timeout: 300s
  on_client_connected:
    if:
      condition:
        api.connected:
      then:
        - logger.log:
            format: "API Connection: %s [%s]"
            args: ["client_info.c_str()", "client_address.c_str()"]
  on_client_disconnected:
    - logger.log:
        format: "API Disonnection: %s [%s]"
        args: ["client_info.c_str()", "client_address.c_str()"]

ota:
  - platform: esphome
    password: !secret ota_password
    id: ota_esphome

safe_mode:
  on_safe_mode:
    - logger.log: "Safe Mode Activated"

wifi:
  id: wifi_id
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: off
  reboot_timeout: 5min
  power_save_mode: HIGH

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${friendly_name} Fallback"
    password: "wgxmDkZuHZ2t"
  on_connect:
    then:
      - logger.log: "WiFi connected"
  on_disconnect:
    then:
      - logger.log: "WiFi disconnected"
  # ESP-IDF ONLY https://esphome.io/components/wifi.html
  # Enable 802.11v BSS Transition Management support.
  enable_btm: $is_esp_32
  # Enable 802.11k Radio Resource Management support.
  enable_rrm: $is_esp_32

captive_portal:

web_server:
  port: 80
  #local: True # No internet/intranet required on the clients (all assets are inlined, compressed and served from flash):

network:
  enable_ipv6: False

# Sync time with Home Assistant.
time:
  - platform: homeassistant
    id: homeassistant_time

text_sensor:
  # ESPHome Version
  - platform: version
    name: "ESPHome Version"
    id: "${device_id}_esphome_version"
    icon: si:esphomes
    hide_timestamp: "true"
    entity_category: diagnostic
  # WiFi Info: IP, MAC, SSID, BSSID, Scan
  - platform: wifi_info
    ip_address:
      name: "IP Address"
      icon: mdi:ip
      entity_category: diagnostic
      update_interval: never
      disabled_by_default: True
    mac_address:
      name: "WiFi MAC Address"
      icon: mdi:fingerprint
      entity_category: diagnostic
      disabled_by_default: True
    ssid:
      name: "Connected SSID"
      icon: mdi:wifi-settings
      entity_category: diagnostic
      update_interval: never
      disabled_by_default: True
    bssid:
      name: "Connected BSSID"
      icon: mdi:wifi
      entity_category: diagnostic
      update_interval: never
      disabled_by_default: True
    scan_results:
      name: "Latest Scan Results"
      icon: mdi:wifi-sync
      entity_category: diagnostic
      disabled_by_default: True

I then use both yaml files and combine them like so:

<<: !include
  file: ./common/yaml/DEVICE_TYPE-esp32-c6-1.47lcd.yaml
  vars:
    device_id: esp32c6lcd14601
    friendly_name: "Temp Display"
    description: ""
    area: "Office"
    is_esp_32: "true"
    logger: "INFO"
    inside_temp_sensor_id: sensor.climate_temperature_2
    inside_temp_name: "OFFICE"
    outside_temp_sensor_id: sensor.climate_temperature
    outside_temp_name: "OUTSIDE"

<<: !include
  file: ./common/yaml/common.yaml
  vars:
    device_id: esp32c6lcd14601
    friendly_name: "Temp Display"
    description: ""
    area: "Office"
    is_esp_32: "true"
    logger: "INFO"

Your log is showing a time of 09.01 not 02:29. I don’t see a connection.

Same, I wasn’t sure if the logs are relevant in this case. The reason i know 2:29 is the clock on the display sticking to that time.

If so, you could test the api connection every few seconds then if not connected, reconnect the api.

How would i do that? I think this would fix it for now. Propably using on_client_disconnected?

Does this help?

I’m going through the list right now and will debug more. Tomorrow I’ll see the memory through the night.

But there’s a reason why I don’t think it’s a memory issue.
The “freeze” always happen at the same time, no matter at what time of day i start the device. I would expect a memory issue should to happen after the same amount of data/uptime.

Some more logs (taken from logs → Home Assistant core)

2025-06-26 02:31:51.749 WARNING (MainThread) [aioesphomeapi.connection] esp32c6lcd14601 @ 192.168.178.78: Connection error occurred: Ping response not received after 90.0 seconds
2025-06-26 02:32:22.086 WARNING (MainThread) [aioesphomeapi.reconnect_logic] Can't connect to ESPHome API for esp32c6lcd14601 @ 192.168.178.78: Handshake timed out after 30.0s (TimeoutAPIError)
2025-06-26 02:32:22.086 WARNING (MainThread) [aioesphomeapi.connection] esp32c6lcd14601 @ 192.168.178.78: Connection error occurred: esp32c6lcd14601 @ 192.168.178.78: Connection lost
2025-06-26 02:32:54.447 WARNING (MainThread) [aioesphomeapi.connection] esp32c6lcd14601 @ 192.168.178.78: Connection error occurred: esp32c6lcd14601 @ 192.168.178.78: Connection lost
2025-06-26 02:33:27.623 WARNING (MainThread) [aioesphomeapi.connection] esp32c6lcd14601 @ 192.168.178.78: Connection error occurred: esp32c6lcd14601 @ 192.168.178.78: Connection lost
2025-06-26 02:34:03.668 WARNING (MainThread) [aioesphomeapi.connection] esp32c6lcd14601 @ 192.168.178.78: Connection error occurred: esp32c6lcd14601 @ 192.168.178.78: Connection lost
2025-06-26 21:00:16.497 WARNING (MainThread) [aioesphomeapi.connection] esp32c6lcd14601 @ 192.168.178.78: Connection error occurred: [Errno 104] Connection reset by peer
2025-06-27 02:32:07.869 WARNING (MainThread) [aioesphomeapi.connection] esp32c6lcd14601 @ 192.168.178.78: Connection error occurred: Ping response not received after 90.0 seconds
2025-06-27 02:32:38.146 WARNING (MainThread) [aioesphomeapi.reconnect_logic] Can't connect to ESPHome API for esp32c6lcd14601 @ 192.168.178.78: Handshake timed out after 30.0s (TimeoutAPIError)
2025-06-27 02:32:38.146 WARNING (MainThread) [aioesphomeapi.connection] esp32c6lcd14601 @ 192.168.178.78: Connection error occurred: esp32c6lcd14601 @ 192.168.178.78: Connection lost
2025-06-27 02:33:10.503 WARNING (MainThread) [aioesphomeapi.connection] esp32c6lcd14601 @ 192.168.178.78: Connection error occurred: esp32c6lcd14601 @ 192.168.178.78: Connection lost
2025-06-27 02:33:43.676 WARNING (MainThread) [aioesphomeapi.connection] esp32c6lcd14601 @ 192.168.178.78: Connection error occurred: esp32c6lcd14601 @ 192.168.178.78: Connection lost
2025-06-27 02:34:19.721 WARNING (MainThread) [aioesphomeapi.connection] esp32c6lcd14601 @ 192.168.178.78: Connection error occurred: esp32c6lcd14601 @ 192.168.178.78: Connection lost

I will report back tomorrow.

Why don’t you restart the router manually to see if Esp freezes…

Could it be that your router is taking top long for the reboot, so the ESP device will do a start of its fallback AP and captive portal?

Oh, that could be the case! I have to wait until tomorrow to get the failure state again…

You can change the timeouts for wifi, and API which will reboot your ESPHome device if timeout will occure. If your router takes long time to connect esphome client again after reboot/ whatever, you can increase the timeout like to 30min from default 15min.

So I managed to reproduce my problem.
If i restart my home assistant VM (running in proxmox) I get the same issue.
The network did not cut out, only restarting hass.

Trying to get logs via esphome (wireless):

INFO ESPHome 2025.5.0
INFO Reading configuration /config/esphome/esp32-c6-display-01.yaml...
WARNING The selected ESP-IDF framework version is not the recommended one. If there are connectivity or build issues please remove the manual version.
WARNING The selected ESP-IDF framework version is not the recommended one. If there are connectivity or build issues please remove the manual version.
INFO Detected timezone 'Europe/Berlin'
INFO Starting log output from 192.168.178.78 using esphome API
WARNING Can't connect to ESPHome API for esp32c6lcd14601 @ 192.168.178.78: Timeout while connecting to [AddrInfo(family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, proto=6, sockaddr=IPv4Sockaddr(address='192.168.178.78', port=6053))] (TimeoutAPIError)
INFO Trying to connect to esp32c6lcd14601 @ 192.168.178.78 in the background

As far as I can tell, the fallback hotspot is not started. Checking for the device in my router also shows the device being offline.

Going offline and freezing are completely different things.

Going offline and freezing are completely different things.

I am aware of that. But freezing would cause it to also go offline.

Because of this log warning

WARNING The selected ESP-IDF framework version is not the recommended one. If there are connectivity or build issues please remove the manual version.

I removed the pinned versions from my yaml for testing:

  framework:
    type: esp-idf
    #version: 5.3.1
    #platform_version: 6.9.0

and made another build.

When restarting the hass VM, everything on the screen just stops changing.
The displayed clock/time also does not update anymore (This is what makes me believe it’s frozen).

Accessing the device directly via IP still gives me the webserver, but with empty name/state table and empty logs. I’m not sure if this is chached or to be expected.

Trying to get logs via esphome:

INFO ESPHome 2025.5.0
INFO Reading configuration /config/esphome/esp32-c6-display-01.yaml...
INFO Detected timezone 'Europe/Berlin'
INFO Starting log output from 192.168.178.78 using esphome API
WARNING Can't connect to ESPHome API for esp32c6lcd14601 @ 192.168.178.78: Timeout while connecting to [AddrInfo(family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, proto=6, sockaddr=IPv4Sockaddr(address='192.168.178.78', port=6053))] (TimeoutAPIError)
INFO Trying to connect to esp32c6lcd14601 @ 192.168.178.78 in the background

followed by nothing.

So, access via IP works, but there’s no state reported.
HA can’t access the device anymore.

Rebooting the device connects to HA as expected.

As your logger is now set to info, have you tried to put log level to debug and see if there is any additional log entries about anything useful?

Quite likely.
You could use interval component to blink the onboard LED.
Also try serial logging (USB).
Then try to remove some components / options to see if any changes make difference.

im fairly new to this is it getting static ip

Also my good friend o3 mini said " Because the lock‑up happens at the exact minute your router reboots (02 : 29) the ESP32 is almost certainly getting confused when the Wi‑Fi and API links disappear.
A quick drop‑outs shouldn’t freeze the chip, but two things in your current YAML make it fragile:

  1. No “wifi:” block at all – so ESPHome uses its hidden defaults (power‑save on, DHCP renewals, no safety reboot).
  2. Your display lambda never yields – while it’s busy redrawing the screen the networking stack can’t fully process a long outage and the watchdog never bites."

So this morning I had the same issue again with device being stuck at 2:29.

I will try that.

I will try the LED blinking, good idea.
USB/Serial debugging also.

I was having a suspicion that my drawing is too heavy and blocking other functionality. Could this be the case? How would I yield? I don’t need a high refresh rate anyway.

So I ran another test. Setting the onboard LED to a pulse effect, and then stopping my hass VM.

The pulse effects stops right in its tracks, meaning it stops at a brightness level and keeps it. From this I conclude that the device is in fact freezing when home assistant connection is lost.

In this case, WiFi is not lost, only the connection to home assistant.

So now my best bet would be to somehow wait/yield/delay in my drawing lambda to give more breathing room for other tasks. But it’s hard to find information since I’m not sure what to search for. Refreshing the display say once in 5 seconds would be enough.

1 Like

You could try with
component.suspend:
right before 2:29 and later call component.resume

I think freezing when router reboots on shedule is only a side effect because the freeze is being caused by loosing connection to home assistant.

Since I can reproduce the problem by rebooting home assistant, this makes me think that there has to be an issue with (re-)connecting to hass. And I think it’s because my drawing lambda is/takes long, this throws of other tasks.

I will do a test now with almost empty drawing lambda.

UPDATE:

After changing nothing in my yaml, except the drawing lambda to only draw the clock and nothing else, the problem is gone.
It seems like a long/slow drawing lambda is blocking the other components.

1 Like

Every lambda is totally blocking everything else. If you have long for-loop there, esphome is not working like it’s designed.
If those are necessary, you should divide it in several parts using script component.
Like 1. part of lambda for loop >> yaml delay >> 2.part of lambda for loop >> yaml delay >> …

1 Like