ESPHome 2026.1.0-dev
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#include <esp_ota_ops.h>
14#endif
15
16namespace esphome {
17namespace safe_mode {
18
19static const char *const TAG = "safe_mode";
20
22 ESP_LOGCONFIG(TAG,
23 "Safe Mode:\n"
24 " Successful after: %" PRIu32 "s\n"
25 " Invoke after: %u attempts\n"
26 " Duration: %" PRIu32 "s",
27 this->safe_mode_boot_is_good_after_ / 1000, // because milliseconds
29 this->safe_mode_enable_time_ / 1000); // because milliseconds
30
32 auto remaining_restarts = this->safe_mode_num_attempts_ - this->safe_mode_rtc_value_;
33 if (remaining_restarts) {
34 ESP_LOGW(TAG, "Last reset too quick; invoke in %" PRIu32 " restarts", remaining_restarts);
35 } else {
36 ESP_LOGW(TAG, "SAFE MODE IS ACTIVE");
37 }
38 }
39
40#ifdef USE_OTA_ROLLBACK
41 const esp_partition_t *last_invalid = esp_ota_get_last_invalid_partition();
42 if (last_invalid != nullptr) {
43 ESP_LOGW(TAG, "OTA rollback detected! Rolled back from partition '%s'", last_invalid->label);
44 ESP_LOGW(TAG, "The device reset before the boot was marked successful");
45 }
46#endif
47}
48
50
53 // successful boot, reset counter
54 ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter");
55 this->clean_rtc();
56 this->boot_successful_ = true;
57#ifdef USE_OTA_ROLLBACK
58 // Mark OTA partition as valid to prevent rollback
59 esp_ota_mark_app_valid_cancel_rollback();
60#endif
61 // Disable loop since we no longer need to check
62 this->disable_loop();
63 }
64}
65
67 uint32_t current_rtc = this->read_rtc_();
68
69 if (pending && current_rtc != SafeModeComponent::ENTER_SAFE_MODE_MAGIC) {
70 ESP_LOGI(TAG, "Device will enter on next boot");
72 }
73
74 if (!pending && current_rtc == SafeModeComponent::ENTER_SAFE_MODE_MAGIC) {
75 ESP_LOGI(TAG, "Safe mode pending has been cleared");
76 this->clean_rtc();
77 }
78}
79
83
84bool SafeModeComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time,
85 uint32_t boot_is_good_after) {
87 this->safe_mode_enable_time_ = enable_time;
88 this->safe_mode_boot_is_good_after_ = boot_is_good_after;
89 this->safe_mode_num_attempts_ = num_attempts;
90 this->rtc_ = global_preferences->make_preference<uint32_t>(233825507UL, false);
91
92 uint32_t rtc_val = this->read_rtc_();
93 this->safe_mode_rtc_value_ = rtc_val;
94
95 bool is_manual = rtc_val == SafeModeComponent::ENTER_SAFE_MODE_MAGIC;
96
97 if (is_manual) {
98 ESP_LOGI(TAG, "Manual mode");
99 } else {
100 ESP_LOGCONFIG(TAG, "Unsuccessful boot attempts: %" PRIu32, rtc_val);
101 }
102
103 if (rtc_val < num_attempts && !is_manual) {
104 // increment counter
105 this->write_rtc_(rtc_val + 1);
106 return false;
107 }
108
109 this->clean_rtc();
110
111 if (!is_manual) {
112 ESP_LOGE(TAG, "Boot loop detected");
113 }
114
115 this->status_set_error();
116 this->set_timeout(enable_time, []() {
117 ESP_LOGW(TAG, "Timeout, restarting");
118 App.reboot();
119 });
120
121 // Delay here to allow power to stabilize before Wi-Fi/Ethernet is initialised
122 delay(300); // NOLINT
123 App.setup();
124
125 ESP_LOGW(TAG, "SAFE MODE IS ACTIVE");
126
127 this->safe_mode_callback_.call();
128
129 return true;
130}
131
133 this->rtc_.save(&val);
135}
136
138 uint32_t val;
139 if (!this->rtc_.load(&val))
140 return 0;
141 return val;
142}
143
145 // Save without sync - preferences will be written at shutdown or by IntervalSyncer.
146 // This avoids blocking the loop for 50+ ms on flash write. If the device crashes
147 // before sync, the boot wasn't really successful anyway and the counter should
148 // remain incremented.
149 uint32_t val = 0;
150 this->rtc_.save(&val);
151}
152
157
158} // namespace safe_mode
159} // namespace esphome
void setup()
Set up all the registered components. Call this at the end of your setup() function.
void disable_loop()
Disable this component's loop.
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
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)
Definition safe_mode.cpp:84
uint32_t safe_mode_enable_time_
The time safe mode should remain active for.
Definition safe_mode.h:38
bool boot_successful_
set to true after boot is considered successful
Definition safe_mode.h:42
uint32_t safe_mode_start_time_
stores when safe mode was enabled
Definition safe_mode.h:40
uint32_t safe_mode_boot_is_good_after_
The amount of time after which the boot is considered successful.
Definition safe_mode.h:37
float get_setup_priority() const override
Definition safe_mode.cpp:49
void set_safe_mode_pending(const bool &pending)
Set to true if the next startup will enter safe mode.
Definition safe_mode.cpp:66
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:48
CallbackManager< void()> safe_mode_callback_
Definition safe_mode.h:46
mopeka_std_values val[4]
const float AFTER_WIFI
For components that should be initialized after WiFi is connected.
Definition component.cpp:88
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
ESPPreferences * global_preferences
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:26
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:25
Application App
Global storage of Application pointer - only one Application can exist.