ESPHome 2026.3.0
Loading...
Searching...
No Matches
safe_mode.cpp
Go to the documentation of this file.
1#include "safe_mode.h"
2
4#include "esphome/core/hal.h"
5#include "esphome/core/log.h"
6#include "esphome/core/util.h"
7
8#include <cerrno>
9#include <cinttypes>
10#include <cstdio>
11
12#ifdef USE_OTA_ROLLBACK
13#ifdef USE_ZEPHYR
14#include <zephyr/dfu/mcuboot.h>
15#elif defined(USE_ESP32)
16#include <esp_ota_ops.h>
17#include <esp_system.h>
18#endif
19#endif
20
21namespace esphome::safe_mode {
22
23static const char *const TAG = "safe_mode";
24
26 ESP_LOGCONFIG(TAG,
27 "Safe Mode:\n"
28 " Successful after: %" PRIu32 "s\n"
29 " Invoke after: %u attempts\n"
30 " Duration: %" PRIu32 "s",
31 this->safe_mode_boot_is_good_after_ / 1000, // because milliseconds
33 this->safe_mode_enable_time_ / 1000); // because milliseconds
34#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
35 const char *state_str;
36 if (this->ota_state_ == ESP_OTA_IMG_NEW) {
37 state_str = "not supported";
38 } else if (this->ota_state_ == ESP_OTA_IMG_PENDING_VERIFY) {
39 state_str = "supported";
40 } else {
41 state_str = "support unknown";
42 }
43 ESP_LOGCONFIG(TAG, " Bootloader rollback: %s", state_str);
44#endif
45
47 auto remaining_restarts = this->safe_mode_num_attempts_ - this->safe_mode_rtc_value_;
48 if (remaining_restarts) {
49 ESP_LOGW(TAG, "Last reset too quick; invoke in %" PRIu32 " restarts", remaining_restarts);
50 } else {
51 ESP_LOGW(TAG, "SAFE MODE IS ACTIVE");
52 }
53 }
54
55#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
56 const esp_partition_t *last_invalid = esp_ota_get_last_invalid_partition();
57 if (last_invalid != nullptr) {
58 ESP_LOGW(TAG, "OTA rollback detected! Rolled back from partition '%s'", last_invalid->label);
59 ESP_LOGW(TAG, "The device reset before the boot was marked successful");
60 if (esp_reset_reason() == ESP_RST_BROWNOUT) {
61 ESP_LOGW(TAG, "Last reset was due to brownout - check your power supply!");
62 ESP_LOGW(TAG, "See https://esphome.io/guides/faq.html#brownout-detector-was-triggered");
63 }
64 }
65#endif
66}
67
69
71 this->clean_rtc();
72 this->boot_successful_ = true;
73#if defined(USE_OTA_ROLLBACK)
74// Mark OTA partition as valid to prevent rollback
75#if defined(USE_ZEPHYR)
76 if (!boot_is_img_confirmed()) {
77 boot_write_img_confirmed();
78 }
79#elif defined(USE_ESP32)
80 // Mark OTA partition as valid to prevent rollback
81 esp_ota_mark_app_valid_cancel_rollback();
82#endif
83#endif
84 // Disable loop since we no longer need to check
85 this->disable_loop();
86}
87
90 // successful boot, reset counter
91 ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter");
92 this->mark_successful();
93 }
94}
95
97 uint32_t current_rtc = this->read_rtc_();
98
99 if (pending && current_rtc != SafeModeComponent::ENTER_SAFE_MODE_MAGIC) {
100 ESP_LOGI(TAG, "Device will enter on next boot");
102 }
103
104 if (!pending && current_rtc == SafeModeComponent::ENTER_SAFE_MODE_MAGIC) {
105 ESP_LOGI(TAG, "Safe mode pending has been cleared");
106 this->clean_rtc();
107 }
108}
109
113
114bool SafeModeComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time,
115 uint32_t boot_is_good_after) {
117 this->safe_mode_enable_time_ = enable_time;
118 this->safe_mode_boot_is_good_after_ = boot_is_good_after;
119 this->safe_mode_num_attempts_ = num_attempts;
121
122#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
123 // Check partition state to detect if bootloader supports rollback
124 const esp_partition_t *running = esp_ota_get_running_partition();
125 esp_ota_get_state_partition(running, &this->ota_state_);
126#endif
127
128 uint32_t rtc_val = this->read_rtc_();
129 this->safe_mode_rtc_value_ = rtc_val;
130
131 bool is_manual = rtc_val == SafeModeComponent::ENTER_SAFE_MODE_MAGIC;
132
133 if (is_manual) {
134 ESP_LOGI(TAG, "Manual mode");
135 } else {
136 ESP_LOGCONFIG(TAG, "Unsuccessful boot attempts: %" PRIu32, rtc_val);
137 }
138
139 if (rtc_val < num_attempts && !is_manual) {
140 // increment counter
141 this->write_rtc_(rtc_val + 1);
142 return false;
143 }
144
145 this->clean_rtc();
146
147 if (!is_manual) {
148 ESP_LOGE(TAG, "Boot loop detected");
149 }
150
151 this->status_set_error();
152 this->set_timeout(enable_time, []() {
153 ESP_LOGW(TAG, "Timeout, restarting");
154 App.reboot();
155 });
156
157 // Delay here to allow power to stabilize before Wi-Fi/Ethernet is initialised
158 delay(300); // NOLINT
159 App.setup();
160
161 ESP_LOGW(TAG, "SAFE MODE IS ACTIVE");
162
163#ifdef USE_SAFE_MODE_CALLBACK
164 this->safe_mode_callback_.call();
165#endif
166
167 return true;
168}
169
174
177 if (!this->rtc_.load(&val))
178 return 0;
179 return val;
180}
181
183 // Save without sync - preferences will be written at shutdown or by IntervalSyncer.
184 // This avoids blocking the loop for 50+ ms on flash write. If the device crashes
185 // before sync, the boot wasn't really successful anyway and the counter should
186 // remain incremented.
187 uint32_t val = 0;
188 this->rtc_.save(&val);
189}
190
195
196} // namespace esphome::safe_mode
void setup()
Reserve space for components to avoid memory fragmentation.
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_timeout(const std voi set_timeout)(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition component.h:451
void disable_loop()
Disable this component's loop.
bool save(const T *src)
Definition preferences.h:21
virtual bool sync()=0
Commit pending writes to flash.
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time, uint32_t boot_is_good_after)
uint32_t safe_mode_enable_time_
The time safe mode should remain active for.
Definition safe_mode.h:48
bool boot_successful_
set to true after boot is considered successful
Definition safe_mode.h:52
uint32_t safe_mode_start_time_
stores when safe mode was enabled
Definition safe_mode.h:50
uint32_t safe_mode_boot_is_good_after_
The amount of time after which the boot is considered successful.
Definition safe_mode.h:47
float get_setup_priority() const override
Definition safe_mode.cpp:68
esp_ota_img_states_t ota_state_
Definition safe_mode.h:55
void set_safe_mode_pending(const bool &pending)
Set to true if the next startup will enter safe mode.
Definition safe_mode.cpp:96
static const uint32_t ENTER_SAFE_MODE_MAGIC
a magic number to indicate that safe mode should be entered on next boot
Definition safe_mode.h:63
CallbackManager< void()> safe_mode_callback_
Definition safe_mode.h:60
mopeka_std_values val[3]
constexpr uint32_t RTC_KEY
RTC key for storing boot loop counter - used by safe_mode and preferences backends.
Definition safe_mode.h:15
constexpr float AFTER_WIFI
For components that should be initialized after WiFi is connected.
Definition component.h:41
ESPPreferences * global_preferences
void HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t