Crashing esphome device- hoping for help (logs included)

Hello all,

Can you all help me figure out how to get the device to stop crashing. It seems to crash when I use the service calls spec’d out in the code.

I think it has something to do with these comments:

And it seems to happen the most when I take the device from state 1 to state 4.(It crashes almost everytime in this instance.-- like the last 10 lines of the logs was a state change 1 to 4)

I can do all other direct state changes often without crash.

[16:30:37][W][component:237]: Component api took a long time for an operation (2014 ms).
[16:30:37][W][component:238]: Components should block for at most 30 ms.

I am pasting the logs first and then the code.

[16:30:12][D][main:060]: Set light state to Device OFF, counter updated to 1
[16:30:12][W][component:237]: Component api took a long time for an operation (4015 ms).
[16:30:12][W][component:238]: Components should block for at most 30 ms.
[16:30:12][D][sensor:093]: 'Button Press Counter': Sending state 1.00000  with 1 decimals of accuracy
[16:30:12][D][main:060]: Set light state to Device OFF, counter updated to 1
[16:30:23][D][sensor:093]: 'Button Press Counter': Sending state 3.00000  with 1 decimals of accuracy
[16:30:23][D][main:060]: Set light state to Device PULSING, counter updated to 3
[16:30:23][W][component:237]: Component api took a long time for an operation (4014 ms).
[16:30:23][W][component:238]: Components should block for at most 30 ms.
[16:30:23][D][sensor:093]: 'Button Press Counter': Sending state 3.00000  with 1 decimals of accuracy
[16:30:23][D][main:060]: Set light state to Device PULSING, counter updated to 3
[16:30:37][D][sensor:093]: 'Button Press Counter': Sending state 4.00000  with 1 decimals of accuracy
[16:30:37][D][main:060]: Set light state to Device REACTS TO SOUND, counter updated to 4
[16:30:37][W][component:237]: Component api took a long time for an operation (2014 ms).
[16:30:37][W][component:238]: Components should block for at most 30 ms.
[16:30:37][D][esp32.preferences:114]: Saving 1 preferences to flash...
[16:30:37][D][esp32.preferences:142]: Saving 1 preferences to flash: 1 cached, 0 written, 0 failed
[16:30:37][D][sensor:093]: 'Button Press Counter': Sending state 4.00000  with 1 decimals of accuracy
[16:30:37][D][main:060]: Set light state to Device REACTS TO SOUND, counter updated to 4
[16:30:48][D][sensor:093]: 'Button Press Counter': Sending state 4.00000  with 1 decimals of accuracy
WARNING supermariolight @ 192.168.1.128: Connection error occurred: [Errno 104] Connection reset by peer
INFO Processing unexpected disconnect from ESPHome API for supermariolight @ 192.168.1.128
WARNING Disconnected from API
INFO Successfully connected to supermariolight @ 192.168.1.128 in 0.005s
INFO Successful handshake with supermariolight @ 192.168.1.128 in 0.034s
[16:37:24][D][api:103]: Accepted 192.168.1.25
[16:37:24][D][api.connection:1446]: Home Assistant 2025.1.4 (192.168.1.25): Connected successfully
esphome:
  name: "supermariolight"
  friendly_name: supermariolight
  min_version: 2024.11.0
  name_add_mac_suffix: false

esp32:
  board: esp32dev
  framework:
    type: esp-idf

# Enable logging
logger:

# Enable Home Assistant API
api:
  services:
    - service: set_light_state
      variables:
        state: string
      then:
        - lambda: |-
            // Map the desired state string to a target number
            int target_state;
            if (state == "Device OFF") {
              target_state = 1;
            } else if (state == "Device ON") {
              target_state = 2;
            } else if (state == "Device PULSING") {
              target_state = 3;
            } else if (state == "Device REACTS TO SOUND") {
              target_state = 4;
            } else {
              ESP_LOGE("main", "Invalid state received: %s", state.c_str());
              return;
            }

            // Calculate how many presses are needed to reach the target state
            int current_state = id(button_counter);
            int presses_needed = target_state - current_state;
            if (presses_needed < 0) {
              presses_needed += 4; // Wrap around to handle state cycling
            }

            // Simulate the required number of presses
            for (int i = 0; i < presses_needed; i++) {
              id(simulated_button).turn_on();
              delay(1000);  // Simulate the physical button press duration
              id(simulated_button).turn_off();
              delay(1000);  // Wait before the next press
            }

            // Update the counter to match the target state
            id(button_counter) = target_state;

            // Publish the new counter value to Home Assistant
            id(button_counter_sensor).publish_state(id(button_counter));

            // Log the update
            ESP_LOGD("main", "Set light state to %s, counter updated to %d", state.c_str(), id(button_counter));

# Allow Over-The-Air updates
ota:
  platform: esphome

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Supermariolight Fallback Hotspot"
    password: "pPrZpEH1vZK0"

captive_portal:

# Global variable to track the state
globals:
  - id: button_counter
    type: int
    restore_value: yes
    initial_value: '1'

# Define the GPIO output to simulate button presses
output:
  - platform: gpio
    pin: GPIO5  # Use GPIO5 to simulate the button press
    id: simulated_button
    inverted: True  # Inverts the signal, making it low by default

# Template switch to simulate button presses
switch:
  - platform: template
    name: "Simulated Button"
    turn_on_action:
      - lambda: |-
          // Increment the counter
          id(button_counter) += 1;

          // Reset the counter after state 4
          if (id(button_counter) > 4) {
            id(button_counter) = 1;
          }

          // Log the current state
          ESP_LOGD("main", "Simulated button pressed, current state: %d", id(button_counter));

          // Simulate the button press
          id(simulated_button).turn_on();
          delay(1000);  // Simulate the physical button press duration
          id(simulated_button).turn_off();

          // Update the sensor value
          id(button_counter_sensor).publish_state(id(button_counter));

  # Virtual reset button
  - platform: template
    name: "Reset Button"
    turn_on_action:
      - lambda: |-
          // Reset the counter to 1
          id(button_counter) = 1;
          ESP_LOGD("main", "Counter reset to 1");

          // Publish the updated state
          id(button_counter_sensor).publish_state(id(button_counter));

# Expose the button counter as a sensor to Home Assistant
sensor:
  - platform: template
    name: "Button Press Counter"
    id: button_counter_sensor
    lambda: |-
      return id(button_counter);
    on_value:
      then:
        - homeassistant.service:
            service: input_select.select_option
            data:
              entity_id: input_select.super_mario_light_state
              option: !lambda |-
                if (x == 1) {
                  return "Device OFF";
                } else if (x == 2) {
                  return "Device ON";
                } else if (x == 3) {
                  return "Device PULSING";
                } else if (x == 4) {
                  return "Device REACTS TO SOUND";
                } else {
                  return "Device OFF";
                }

Possibly all those second long delays is causing an api timeout?

Try taking your lambda out of the service call and simply execute a script with that code in it. In the script ensure you set mode to single: to avoid collisions in your code execution.

1 Like

Watchdog timeout more likely, IIRC it’s about 4 seconds.

@zoogara @clydebarrow I can already tell you all know way more about this than I ever will.

Can one of you show me the idea of putting some of this in a script to hopefully help bypass this situation?

Sorry, I will be trying it myself in the next few days but I definitely won’t turn down any help. I am by no means a programmer, but overly persistent, no doubt :slight_smile: in getting this device to work the way I would like it to.

EDIT:

I think I may have this figured out. I will post the end solution here if I get it straightened out or need additional help. Thank you all again!

Something like this - assuming ESPHome doesn’t snipe you with some weird parameter type error:

api:
  services:
    - service: set_light_state
      variables:
        state: string
      then:
        - lambda: id(buttonpress)->execute(state);

script:
  - id: buttonpress
    parameters:
      new_state: string
    mode: single
    then:
      then:
        - lambda: |-
            // Map the desired state string to a target number
            int target_state;
            if (new_state == "Device OFF") {
              target_state = 1;
            } else if (new_state == "Device ON") {
              target_state = 2;
            } else if (new_state == "Device PULSING") {
              target_state = 3;
            } else if (new_state == "Device REACTS TO SOUND") {
              target_state = 4;
            } else {
              ESP_LOGE("main", "Invalid state received: %s", new_state.c_str());
              return;
            }

            // Calculate how many presses are needed to reach the target state
            int current_state = id(button_counter);
            int presses_needed = target_state - current_state;
            if (presses_needed < 0) {
              presses_needed += 4; // Wrap around to handle state cycling
            }

            // Simulate the required number of presses
            for (int i = 0; i < presses_needed; i++) {
              id(simulated_button).turn_on();
              delay(1000);  // Simulate the physical button press duration
              id(simulated_button).turn_off();
              delay(1000);  // Wait before the next press
            }

            // Update the counter to match the target state
            id(button_counter) = target_state;

            // Publish the new counter value to Home Assistant
            id(button_counter_sensor).publish_state(id(button_counter));

            // Log the update
            ESP_LOGD("main", "Set light state to %s, counter updated to %d", new_state.c_str(), id(button_counter));

https://esphome.io/components/script

Thank you for helping with this. I will test it shortly. ChatGPT is crapping the bed on helping me fix it. And it probably doesn’t help that I have zero programming experience trying to take on a task like this :slight_smile:

Ah, no. Just wrapping consecutive delays in a script won’t help. Use the script delay action. This doesn’t block the loop thread.

Something like this is what you want - this is not tested, so check the docs to get the details right, but the principle will work.

script:
  - id: pulse_output
    parameters:
      times: int
    then:
      - repeat:
          count: !lambda 'return times;'
          then:
            - switch.turn_on: pulse_output
            - delay: 1s
            - switch.turn_off: pulse_output
            - delay: 1s
      - sensor.template.publish:
          id: counter_sensor
          state: !lambda 'return times;'

api:
  services:
    - service: set_light_state
      variables:
        state: string
      then:
        - if:
            condition:
              lambda: 'return state == "ON";'
            then:
              - script.execute:
                  id: pulse_output
                  times: 1
        - if:
            condition:
              lambda: 'return state == "OFF";'
            then:
              - script.execute:
                  id: pulse_output
                  times: 2

I would have though moving the delays out of the main code may have worked, in other words I thought scripts were asynchronous. Thus you needed to specify the mode. Happy to be corrected though.

Scripts are not asynchronous as such - each action is executed in sequence on the main loop thread, and certainly any lambda code is executed in its entirety before returning.

The delay action operates differently to the C++ delay() function, it is equivalent to set_timeout() in that it schedules a callback in the future, whereas delay() in a lambda busy-loops and blocks the thread.

@clydebarrow @zoogara Thank you all again for helping me with this. I will be glad to get out of this project :slight_smile: It was a bit out of my pay grade based on my skillset. I will report back after I’ve done some testing/ troubleshooting with the last provided code. Thank you all again! Very appreciative of both of you.

So I’ve had a chance to look at everything a bit more. Are we saying that @zoogara code could also be used if delay action is used instead of the current way it is written in the post? If this is the case, could one of you show me how to make that change? Sorry again, I’m swimming in the deep end for me.

If not, with regards to @clydebarrow post. I’m not sure I understand what is happening with it or possibly how to adapt it to my situation of 4 states that can all be reach by shorting a single button.

I have no doubt it’s my lack of understanding, def not a lack of good communication.

Thank you all again for the help and education.
@clydebarrow I definitely understand your explanation of the delay types/ reasoning

@clydebarrow @zoogara

Thank you all again for your help and ideas. I did end up using information from both of you all and am so thankful for it. I probably had no business doing this project but that’s where were at. My final code is below. There was definitely some AI assistance involved in this also as I am sure you all will be able to pick out things that are asinine compared to how a human would do it.

For anyone that comes across this post in the future, below was my final code. The project was using an esp32 gpio pin to short a button of a device that had 4 states. The device (a paladone super mario light) has 1 button. Each press of the button takes the light to a different state. There was also a input select created to help toggle the “set light state” service. The states are 1,2,3,4 respectively off, on, pulsing, reacts to sound.

I am not a programmer by trade and have 0 knowledge of programming. The best I could do is pick out illogical things that the AI did, and use the information that the community members gave me here to help me along. @clydebarrow @zoogara got me out a big hole with regards to ensuring I stopped trying to do this without using a script as the majority of my original issues were for that reason and the AI knew no better. Once I asked the AI to explore the ideas that Clyde and Daryl gave me, I eventually got it through annoyance. This took me probably 8-10 hours due to no knowledge of anything. I imagine this 30 min- 1 hour for someone that new the direction to go right from the start.

Thank you all again for your help!!

esphome:
  name: "supermariolight"
  friendly_name: supermariolight
  min_version: 2024.11.0
  name_add_mac_suffix: false

esp32:
  board: esp32dev
  framework:
    type: esp-idf

logger:

api:
  services:
    - service: set_light_state
      variables:
        state: string
      then:
        - lambda: |-
            int target_state;
            if (state == "Device OFF") {
              target_state = 1;
            } else if (state == "Device ON") {
              target_state = 2;
            } else if (state == "Device PULSING") {
              target_state = 3;
            } else if (state == "Device REACTS TO SOUND") {
              target_state = 4;
            } else {
              ESP_LOGE("main", "Invalid state received: %s", state.c_str());
              return;
            }

            int current_state = id(button_counter);

            // Check if the current state matches the target state and return early
            if (current_state == target_state) {
              ESP_LOGD("main", "Already in target state (%d), no action needed.", target_state);
              return;
            }

            int presses_needed = (target_state - current_state + 4) % 4;
            if (presses_needed == 0) presses_needed = 4;

            ESP_LOGD("main", "Executing simulate_presses with count: %d", presses_needed);
            id(simulate_presses).execute(presses_needed);

ota:
  platform: esphome  # ✅ Fix applied

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

  ap:
    ssid: "Supermariolight Fallback Hotspot"
    password: "pPrZpEH1vZK0"

captive_portal:

globals:
  - id: button_counter
    type: int
    restore_value: yes
    initial_value: '1'
  - id: simulate_presses_running
    type: bool
    initial_value: 'false'

output:
  - platform: gpio
    pin: GPIO5
    id: simulated_button
    inverted: True

switch:
  - platform: template
    name: "Simulated Button"
    turn_on_action:
      - lambda: |-
          id(button_counter) = (id(button_counter) % 4) + 1;
          ESP_LOGD("main", "Simulated button pressed, new state: %d", id(button_counter));

          id(simulated_button).turn_on();
          delay(500);
          id(simulated_button).turn_off();

          id(button_counter_sensor).publish_state(id(button_counter));

  - platform: template
    name: "Reset Button"
    turn_on_action:
      - lambda: |-
          id(button_counter) = 1;
          ESP_LOGD("main", "Counter reset to 1");
          id(button_counter_sensor).publish_state(id(button_counter));

sensor:
  - platform: template
    name: "Button Press Counter"
    id: button_counter_sensor
    lambda: |-
      return id(button_counter);
    update_interval: never
    on_value:
      then:
        - if:
            condition:
              lambda: 'return !id(simulate_presses_running);'  # Ensure no active press simulation
            then:
              - homeassistant.service:
                  service: input_select.select_option
                  data:
                    entity_id: input_select.super_mario_light_state
                    option: !lambda |-
                      if (x == 1) return "Device OFF";
                      if (x == 2) return "Device ON";
                      if (x == 3) return "Device PULSING";
                      if (x == 4) return "Device REACTS TO SOUND";
                      return "Device OFF";

script:
  - id: simulate_presses
    mode: restart
    parameters:
      count: int
    then:
      - if: 
          condition: 
            lambda: return !id(simulate_presses_running);
          then: 
            - globals.set: 
                id: simulate_presses_running
                value: 'true'
            - logger.log: "simulate_presses started, running flag set."
            - repeat:
                count: !lambda 'return count;'
                then:
                  - output.turn_on: simulated_button
                  - delay: 500ms
                  - output.turn_off: simulated_button
                  - delay: 500ms
                  - lambda: |-
                      id(button_counter) = (id(button_counter) % 4) + 1;
                      id(button_counter_sensor).publish_state(id(button_counter));
                      count -= 1;
      - globals.set:
          id: simulate_presses_running
          value: 'false'
























1 Like