Reboot esp32 with SMS "reboot"

I’m having trouble getting this seemingly simple function to work,

I want to send a SMS “reboot” to the sim800l, to simply reboot the device.

I have managed to get the device to reboot, but when it boots, the reboot command somehow comes back to the sms_message, and the device reboots, causing it to get stuck in a boot loop.

I have tried adding an on_boot function to clear the sms_message on boot, however this does not seem to help.

on_boot:
    priority: 600
    then:
      - lambda: |-
          // Clear the SMS message on boot
          id(sms_message).publish_state("");
      - lambda: |-
          // Log the reboot event
          ESP_LOGD("on_boot", "Device has rebooted. Waiting for GPS lock...");

What is causing the “reboot” command to keep coming back, the SMS is not being sent again and again?

Thanks

esphome:
  name: "car-gps"
  friendly_name: "car-gps"
  on_boot:
    priority: 600
    then:
      - lambda: |-
          // Clear the SMS message on boot
          id(sms_message).publish_state("");
      - lambda: |-
          // Log the reboot event
          ESP_LOGD("on_boot", "Device has rebooted. Waiting for GPS lock...");

globals:
  - id: sms_sent
    type: bool
    initial_value: "false"
  - id: alarm_armed
    type: bool
    initial_value: "false"
  - id: phone_number
    type: std::string
    restore_value: no
    initial_value: '"redacted"'  # Replace with your actual phone number

interval:
  - interval: 10s
    then:
      - lambda: |-
          if (!id(sms_sent) && id(gps_time).now().is_valid() && id(gps_satellites).state > 3) {
            auto now = id(gps_time).now();
            char buffer[100];
            snprintf(buffer, sizeof(buffer), "Device rebooted successfully at (GPS time): %04d-%02d-%02d %02d:%02d:%02d UTC",
                     now.year, now.month, now.day_of_month, now.hour, now.minute, now.second);
            id(gsm).send_sms(id(phone_number).c_str(), buffer);
            id(sms_sent) = true;
            ESP_LOGD("interval", "SMS sent successfully.");
          } else if (!id(gps_time).now().is_valid() || id(gps_satellites).state <= 3) {
            ESP_LOGD("interval", "Waiting for valid GPS time or sufficient satellites...");
          }

esp32:
  board: esp32dev
  framework:
    type: arduino

logger:

api:
  encryption:
    key: ""
  reboot_timeout: 0s

ota:
  platform: esphome
  password: ""

wifi:
  ap:
    ssid: ""
    password: ""
    id: access_point

web_server:
  port: 80
  log: true
  local: True
  version: 3
  include_internal: true


text_sensor:
  - platform: template
    id: sms_sender
    name: "Sms Sender"
  - platform: template
    id: sms_message
    name: "SMS Message"
    update_interval: never
  - platform: template
    id: alarm_state
    name: "Alarm State"
    lambda: |-
      if (id(alarm_armed)) {
        return {"Armed"};
      } else {
        return {"Disarmed"};
      }
    update_interval: 10s
  - platform: template
    name: "Phone Number"
    id: phone_number_sms


sensor:
  - platform: uptime
    name: Uptime
    id: uptime_sensor

  - platform: wifi_signal
    name: "WiFi Signal Strength"
    id: wifi_signal_sensor
    update_interval: 30s

uart:
  - id: sim
    baud_rate: 9600
    tx_pin: 27
    rx_pin: 26
  - id: gpsmodule
    baud_rate: 9600
    rx_pin: RX

sim800l:
  id: gsm
  uart_id: sim
  on_sms_received:
    # Publish the sender and message state to text sensors
    - lambda: |-
        id(sms_sender).publish_state(sender);
        id(sms_message).publish_state(message);

    # Check if the SMS sender is authorized
    - lambda: |-
        std::string sender_str = id(sms_sender).state.c_str();
        if (sender_str != id(phone_number)) {
          id(gsm).send_sms(sender_str.c_str(), "Unauthorized");
          return;
        }

    # Handle the "phone_number" command to return the current phone number
    - lambda: |-
        auto sms_sender_raw = id(sms_sender).state;
        auto sms_message_raw = id(sms_message).state;
        std::string sms_sender(sms_sender_raw.begin(), sms_sender_raw.end());
        std::string sms_message(sms_message_raw.begin(), sms_message_raw.end());

        if (sms_sender == id(phone_number) && sms_message == "phone_number") {
          std::string response = "The phone number is: " + id(phone_number);
          id(gsm).send_sms(sms_sender.c_str(), response.c_str());
        }

      # Handle the "new_number" command to update the phone number
    - lambda: |-
        auto sms_sender_raw = id(sms_sender).state;
        auto sms_message_raw = id(sms_message).state;
        std::string sms_sender(sms_sender_raw.begin(), sms_sender_raw.end());
        std::string sms_message(sms_message_raw.begin(), sms_message_raw.end());

        if (sms_sender == id(phone_number) && sms_message.substr(0, 11) == "new_number ") {
          std::string new_phone_number = sms_message.substr(11);

          id(phone_number) = new_phone_number;

          std::string response = "Phone number updated to: " + new_phone_number;
          id(gsm).send_sms(sms_sender.c_str(), response.c_str());
        }

    # Handle "relay_on" and "relay_off" commands for the fuel shutoff relay
    - lambda: |-
        if ((id(sms_sender).state == id(phone_number)) && 
            ((id(sms_message).state == "relay_on") || (id(sms_message).state == "Relay_on"))) {
          id(shutoff).turn_on();  // Turn the relay on
          delay(100);
          if (id(shutoff).state) {
            id(gsm).send_sms(id(phone_number).c_str(), "Fuel Shutoff Relay has been turned on.");
          } else {
            id(gsm).send_sms(id(phone_number).c_str(), "Fuel Shutoff Relay failed to turn on.");
          }
        }
        if ((id(sms_sender).state == id(phone_number)) && 
            ((id(sms_message).state == "relay_off") || (id(sms_message).state == "Relay_off"))) {
          id(shutoff).turn_off();  // Turn the relay off
          delay(100);
          if (!id(shutoff).state) {
            id(gsm).send_sms(id(phone_number).c_str(), "Fuel Shutoff Relay has been turned off.");
          } else {
            id(gsm).send_sms(id(phone_number).c_str(), "Fuel Shutoff Relay failed to turn off.");
          }
        }

    # Handle the "where" command to send GPS and ignition data
    - lambda: |-
        auto to_lower = [](std::string str) -> std::string {
          std::transform(str.begin(), str.end(), str.begin(), ::tolower);
          return str;
        };
        auto sms_sender_raw = id(sms_sender).state;
        auto sms_message_raw = id(sms_message).state;
        std::string sms_sender(sms_sender_raw.begin(), sms_sender_raw.end());
        std::string sms_message_lower = to_lower(std::string(sms_message_raw.begin(), sms_message_raw.end()));

        if (sms_sender == id(phone_number) && sms_message_lower == "where") {
          std::string response;

          if (id(latitude).has_state() && id(longitude).has_state()) {
            float lat = id(latitude).state;
            float lon = id(longitude).state;
            response += "Device location:\n";
            response += "Lat: " + std::to_string(lat) + "\n";
            response += "Lon: " + std::to_string(lon) + "\n";
            response += "Google Maps: https://www.google.com/maps?q=" + std::to_string(lat) + "," + std::to_string(lon) + "\n";
          } else {
            response += "GPS coordinates are not available yet.\n";
          }

          if (id(ignition).state) {
            response += "Ignition: ON\n";
          } else {
            response += "Ignition: OFF\n";
          }

          if (id(speed).has_state()) {
            float gps_speed = id(speed).state;
            response += "Speed: " + std::to_string(gps_speed) + " km/h\n";
          } else {
            response += "Speed: N/A\n";
          }

          id(gsm).send_sms(id(phone_number).c_str(), response.c_str());
        }

    # Handle the "arm" and "disarm" commands to arm/disarm the alarm
    - lambda: |-
        auto sms_sender_raw = id(sms_sender).state;
        auto sms_message_raw = id(sms_message).state;

        std::string sms_sender(sms_sender_raw.begin(), sms_sender_raw.end());
        std::string sms_message(sms_message_raw.begin(), sms_message_raw.end());

        auto to_upper = [](std::string str) -> std::string {
          std::transform(str.begin(), str.end(), str.begin(), ::toupper);
          return str;
        };
        sms_message = to_upper(sms_message);

        if (sms_sender == id(phone_number)) {
          if (sms_message == "ARM") {
            id(alarm_armed) = true;
            id(shutoff).turn_on();
            id(gsm).send_sms(id(phone_number).c_str(), "Alarm armed. Notifications enabled, and fuel shutoff is ON.");
            ESP_LOGD("sms", "Alarm armed by SMS.");
          } else if (sms_message == "DISARM") {
            id(alarm_armed) = false;
            id(shutoff).turn_off();
            id(gsm).send_sms(id(phone_number).c_str(), "Alarm disarmed. Notifications disabled, and fuel shutoff is OFF.");
            ESP_LOGD("sms", "Alarm disarmed by SMS.");
          }
        }


    - lambda: |-
        if (id(sms_sender).state == id(phone_number) && id(sms_message).state == "reboot") {
          id(gsm).send_sms(id(phone_number).c_str(), "Rebooting device...");

          // Clear the SMS message immediately to avoid loop
          id(sms_message).publish_state("");

          // Trigger the reboot
          id(reboot_trigger).turn_on();
        }


    # Handle the "STATUS" command to return sensor data
    - lambda: |-
        id(sms_sender).publish_state(sender);
        id(sms_message).publish_state(message);

        auto sms_sender_raw = id(sms_sender).state;
        auto sms_message_raw = id(sms_message).state;

        std::string sms_sender(sms_sender_raw.begin(), sms_sender_raw.end());
        std::string sms_message(sms_message_raw.begin(), sms_message_raw.end());

        auto to_upper = [](std::string str) -> std::string {
            std::transform(str.begin(), str.end(), str.begin(), ::toupper);
            return str;
        };
        sms_message = to_upper(sms_message);

        if (sms_sender == id(phone_number) && sms_message == "STATUS") { 
            std::string response;

            if (id(gps_time).now().is_valid()) {
                response += "GPS Fix: True\n";
            } else {
                response += "GPS Fix: False\n";
            }

            if (id(gps_satellites).has_state()) {
                response += "Satellites: " + std::to_string((int)id(gps_satellites).state) + "\n";
            } else {
                response += "Satellites: N/A\n";
            }

            if (id(uptime_sensor).has_state()) {
                int total_seconds = (int)id(uptime_sensor).state;
                int days = total_seconds / 86400;
                int hours = (total_seconds % 86400) / 3600;
                int minutes = (total_seconds % 3600) / 60;
                response += "Uptime: " + std::to_string(days) + "d " + std::to_string(hours) + "h " + std::to_string(minutes) + "m\n";
            } else {
                response += "Uptime: N/A\n";
            }

            if (id(ignition).state) {
                response += "Ignition: ON\n";
            } else {
                response += "Ignition: OFF\n";
            }

            if (id(shutoff).state) {
                response += "Relay: ON\n";
            } else {
                response += "Relay: OFF\n";
            }

            if (id(alarm_armed)) {
                response += "Alarm State: Armed\n";
            } else {
                response += "Alarm State: Disarmed\n";
            }

            id(gsm).send_sms(id(phone_number).c_str(), response.c_str());
        }

    # Fallback for unrecognized commands
    - lambda: |-
        auto sms_sender_raw = id(sms_sender).state;
        auto sms_message_raw = id(sms_message).state;
        std::string sms_sender(sms_sender_raw.begin(), sms_sender_raw.end());
        std::string sms_message(sms_message_raw.begin(), sms_message_raw.end());
        
        std::vector<std::string> valid_commands = {"ARM", "DISARM", "STATUS", "REBOOT", "WHERE", "RELAY_ON", "RELAY_OFF", "PHONE_NUMBER"};

        auto to_upper = [](std::string str) -> std::string {
            std::transform(str.begin(), str.end(), str.begin(), ::toupper);
            return str;
        };
        sms_message = to_upper(sms_message);

        if (sms_sender == id(phone_number)) {
          bool is_valid_command = false;
          for (auto& command : valid_commands) {
            if (sms_message == command) {
              is_valid_command = true;
              break;
            }
          }

          if (!is_valid_command) {
            if (sms_message == "PHONE_NUMBER") {
              std::string response = "The current phone number is: " + id(phone_number);
              id(gsm).send_sms(sms_sender.c_str(), response.c_str());  // Send SMS back to sender
            } else {
              id(gsm).send_sms(id(phone_number).c_str(), "Unrecognized Command");
            }
          }
        }




binary_sensor:
  - platform: gpio
    id: ignition
    pin:
      number: 21
      mode: INPUT_PULLUP
      inverted: True
    name: "Ignition"
    filters:
    - delayed_on: 1000ms
    - delayed_off: 1000ms
    on_press:
      then:
        - lambda: |-
            if (id(alarm_armed)) {
              id(gsm).send_sms(id(phone_number).c_str(), "ALERT: Ignition turned ON while alarm is armed!");
              ESP_LOGD("ignition", "Ignition notification sent: Alarm is armed.");
            } else {
              ESP_LOGD("ignition", "Ignition ON detected, but alarm is not armed. No notification sent.");
            }
switch:
  - platform: gpio
    name: "SIM800_PWKEY"
    pin: 4
    restore_mode: ALWAYS_OFF
  - platform: gpio
    name: "SIM800_RST"
    pin: 5
    restore_mode: ALWAYS_ON
  - platform: gpio
    name: "SIM800_POWER"
    pin: 23
    restore_mode: ALWAYS_ON
  - platform: gpio
    id: shutoff
    pin: 14
    name: "Fuel Shutoff"
    inverted: True

button:
  - platform: restart
    id: reboot_trigger
    on_press:
      - lambda: |-
         ESP.restart();

# Declare GPS module
gps:
  uart_id: gpsmodule
  id: gps_sensor
  update_interval: 60s
  latitude:
    name: "GPS Latitude"
    id: latitude
  longitude:
    name: "GPS Longitude"
    id: longitude
  speed:
    name: "GPS Speed"
    id: speed
  satellites:
    name: "GPS Satellites"
    id: gps_satellites
    
time:
  - platform: gps
    id: gps_time

Clear the message before you make reboot, not after.

Tried like this, still doesn’t work.

esphome:
  name: "car-gps"
  friendly_name: "car-gps"
  on_boot:
    priority: 0  # Set the earliest priority for clearing SMS message
    then:
      - lambda: |-
          // Clear the SMS message before anything else to prevent a reboot loop
          id(sms_message).publish_state("");  
          ESP_LOGD("on_boot", "SMS message cleared on boot.");
      - lambda: |-
          // Log the reboot event after clearing SMS message
          ESP_LOGD("on_boot", "Device has rebooted. Waiting for GPS lock...");

So do you get those prints on your log?
Try different priorities between 600-800.

Or add some delay, bigger than flash save interval, on your code just before
id(reboot_trigger).turn_on();