mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 05:24:53 +01:00
Uncouple safe_mode from OTA (#6759)
This commit is contained in:
parent
83d3584173
commit
76abf2200c
21 changed files with 282 additions and 200 deletions
|
@ -306,7 +306,7 @@ esphome/components/rp2040_pwm/* @jesserockz
|
|||
esphome/components/rpi_dpi_rgb/* @clydebarrow
|
||||
esphome/components/rtl87xx/* @kuba2k2
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
||||
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
|
||||
esphome/components/scd4x/* @martgras @sjtrny
|
||||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
from esphome.cpp_generator import RawExpression
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_NUM_ATTEMPTS,
|
||||
CONF_OTA,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_REBOOT_TIMEOUT,
|
||||
CONF_SAFE_MODE,
|
||||
CONF_VERSION,
|
||||
KEY_PAST_SAFE_MODE,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core import coroutine_with_priority
|
||||
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
@ -28,7 +25,6 @@ CONFIG_SCHEMA = (
|
|||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESPHomeOTAComponent),
|
||||
cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, int=True),
|
||||
cv.SplitDefault(
|
||||
CONF_PORT,
|
||||
|
@ -39,10 +35,15 @@ CONFIG_SCHEMA = (
|
|||
rtl87xx=8892,
|
||||
): cv.port,
|
||||
cv.Optional(CONF_PASSWORD): cv.string,
|
||||
cv.Optional(
|
||||
CONF_REBOOT_TIMEOUT, default="5min"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_NUM_ATTEMPTS, default="10"): cv.positive_not_null_int,
|
||||
cv.Optional(CONF_NUM_ATTEMPTS): cv.invalid(
|
||||
f"'{CONF_SAFE_MODE}' (and its related configuration variables) has moved from 'ota' to its own component. See https://esphome.io/components/safe_mode"
|
||||
),
|
||||
cv.Optional(CONF_REBOOT_TIMEOUT): cv.invalid(
|
||||
f"'{CONF_SAFE_MODE}' (and its related configuration variables) has moved from 'ota' to its own component. See https://esphome.io/components/safe_mode"
|
||||
),
|
||||
cv.Optional(CONF_SAFE_MODE): cv.invalid(
|
||||
f"'{CONF_SAFE_MODE}' (and its related configuration variables) has moved from 'ota' to its own component. See https://esphome.io/components/safe_mode"
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(BASE_OTA_SCHEMA)
|
||||
|
@ -50,10 +51,8 @@ CONFIG_SCHEMA = (
|
|||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(50.0)
|
||||
@coroutine_with_priority(52.0)
|
||||
async def to_code(config):
|
||||
CORE.data[CONF_OTA] = {}
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await ota_to_code(var, config)
|
||||
cg.add(var.set_port(config[CONF_PORT]))
|
||||
|
@ -63,10 +62,3 @@ async def to_code(config):
|
|||
cg.add_define("USE_OTA_VERSION", config[CONF_VERSION])
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if config[CONF_SAFE_MODE]:
|
||||
condition = var.should_enter_safe_mode(
|
||||
config[CONF_NUM_ATTEMPTS], config[CONF_REBOOT_TIMEOUT]
|
||||
)
|
||||
cg.add(RawExpression(f"if ({condition}) return"))
|
||||
CORE.data[CONF_OTA][KEY_PAST_SAFE_MODE] = True
|
||||
|
|
|
@ -78,23 +78,9 @@ void ESPHomeOTAComponent::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, " Password configured");
|
||||
}
|
||||
#endif
|
||||
if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1 &&
|
||||
this->safe_mode_rtc_value_ != ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC) {
|
||||
ESP_LOGW(TAG, "Last reset occurred too quickly; safe mode will be invoked in %" PRIu32 " restarts",
|
||||
this->safe_mode_num_attempts_ - this->safe_mode_rtc_value_);
|
||||
}
|
||||
}
|
||||
|
||||
void ESPHomeOTAComponent::loop() {
|
||||
this->handle_();
|
||||
|
||||
if (this->has_safe_mode_ && (millis() - this->safe_mode_start_time_) > this->safe_mode_enable_time_) {
|
||||
this->has_safe_mode_ = false;
|
||||
// successful boot, reset counter
|
||||
ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter");
|
||||
this->clean_rtc();
|
||||
}
|
||||
}
|
||||
void ESPHomeOTAComponent::loop() { this->handle_(); }
|
||||
|
||||
static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01;
|
||||
|
||||
|
@ -423,86 +409,4 @@ bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) {
|
|||
float ESPHomeOTAComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||
uint16_t ESPHomeOTAComponent::get_port() const { return this->port_; }
|
||||
void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; }
|
||||
|
||||
void ESPHomeOTAComponent::set_safe_mode_pending(const bool &pending) {
|
||||
if (!this->has_safe_mode_)
|
||||
return;
|
||||
|
||||
uint32_t current_rtc = this->read_rtc_();
|
||||
|
||||
if (pending && current_rtc != ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC) {
|
||||
ESP_LOGI(TAG, "Device will enter safe mode on next boot");
|
||||
this->write_rtc_(ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC);
|
||||
}
|
||||
|
||||
if (!pending && current_rtc == ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC) {
|
||||
ESP_LOGI(TAG, "Safe mode pending has been cleared");
|
||||
this->clean_rtc();
|
||||
}
|
||||
}
|
||||
bool ESPHomeOTAComponent::get_safe_mode_pending() {
|
||||
return this->has_safe_mode_ && this->read_rtc_() == ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC;
|
||||
}
|
||||
|
||||
bool ESPHomeOTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time) {
|
||||
this->has_safe_mode_ = true;
|
||||
this->safe_mode_start_time_ = millis();
|
||||
this->safe_mode_enable_time_ = enable_time;
|
||||
this->safe_mode_num_attempts_ = num_attempts;
|
||||
this->rtc_ = global_preferences->make_preference<uint32_t>(233825507UL, false);
|
||||
this->safe_mode_rtc_value_ = this->read_rtc_();
|
||||
|
||||
bool is_manual_safe_mode = this->safe_mode_rtc_value_ == ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC;
|
||||
|
||||
if (is_manual_safe_mode) {
|
||||
ESP_LOGI(TAG, "Safe mode has been entered manually");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "There have been %" PRIu32 " suspected unsuccessful boot attempts", this->safe_mode_rtc_value_);
|
||||
}
|
||||
|
||||
if (this->safe_mode_rtc_value_ >= num_attempts || is_manual_safe_mode) {
|
||||
this->clean_rtc();
|
||||
|
||||
if (!is_manual_safe_mode) {
|
||||
ESP_LOGE(TAG, "Boot loop detected. Proceeding to safe mode");
|
||||
}
|
||||
|
||||
this->status_set_error();
|
||||
this->set_timeout(enable_time, []() {
|
||||
ESP_LOGE(TAG, "No OTA attempt made, restarting");
|
||||
App.reboot();
|
||||
});
|
||||
|
||||
// Delay here to allow power to stabilise before Wi-Fi/Ethernet is initialised.
|
||||
delay(300); // NOLINT
|
||||
App.setup();
|
||||
|
||||
ESP_LOGI(TAG, "Waiting for OTA attempt");
|
||||
|
||||
return true;
|
||||
} else {
|
||||
// increment counter
|
||||
this->write_rtc_(this->safe_mode_rtc_value_ + 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ESPHomeOTAComponent::write_rtc_(uint32_t val) {
|
||||
this->rtc_.save(&val);
|
||||
global_preferences->sync();
|
||||
}
|
||||
|
||||
uint32_t ESPHomeOTAComponent::read_rtc_() {
|
||||
uint32_t val;
|
||||
if (!this->rtc_.load(&val))
|
||||
return 0;
|
||||
return val;
|
||||
}
|
||||
|
||||
void ESPHomeOTAComponent::clean_rtc() { this->write_rtc_(0); }
|
||||
|
||||
void ESPHomeOTAComponent::on_safe_shutdown() {
|
||||
if (this->has_safe_mode_ && this->read_rtc_() != ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC)
|
||||
this->clean_rtc();
|
||||
}
|
||||
} // namespace esphome
|
||||
|
|
|
@ -15,17 +15,9 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
|||
void set_auth_password(const std::string &password) { password_ = password; }
|
||||
#endif // USE_OTA_PASSWORD
|
||||
|
||||
/// Manually set the port OTA should listen on.
|
||||
/// Manually set the port OTA should listen on
|
||||
void set_port(uint16_t port);
|
||||
|
||||
bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time);
|
||||
|
||||
/// Set to true if the next startup will enter safe mode
|
||||
void set_safe_mode_pending(const bool &pending);
|
||||
bool get_safe_mode_pending();
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
@ -33,14 +25,7 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
|||
|
||||
uint16_t get_port() const;
|
||||
|
||||
void clean_rtc();
|
||||
|
||||
void on_safe_shutdown() override;
|
||||
|
||||
protected:
|
||||
void write_rtc_(uint32_t val);
|
||||
uint32_t read_rtc_();
|
||||
|
||||
void handle_();
|
||||
bool readall_(uint8_t *buf, size_t len);
|
||||
bool writeall_(const uint8_t *buf, size_t len);
|
||||
|
@ -53,16 +38,6 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
|||
|
||||
std::unique_ptr<socket::Socket> server_;
|
||||
std::unique_ptr<socket::Socket> client_;
|
||||
|
||||
bool has_safe_mode_{false}; ///< stores whether safe mode can be enabled
|
||||
uint32_t safe_mode_start_time_; ///< stores when safe mode was enabled
|
||||
uint32_t safe_mode_enable_time_{60000}; ///< The time safe mode should be on for
|
||||
uint32_t safe_mode_rtc_value_;
|
||||
uint8_t safe_mode_num_attempts_;
|
||||
ESPPreferenceObject rtc_;
|
||||
|
||||
static const uint32_t ENTER_SAFE_MODE_MAGIC =
|
||||
0x5afe5afe; ///< a magic number to indicate that safe mode should be entered on next boot
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
|
|
|
@ -6,7 +6,7 @@ from esphome.core import CORE, coroutine_with_priority
|
|||
from esphome.const import CONF_ESPHOME, CONF_OTA, CONF_PLATFORM, CONF_TRIGGER_ID
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
AUTO_LOAD = ["md5"]
|
||||
AUTO_LOAD = ["md5", "safe_mode"]
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
|
@ -76,7 +76,7 @@ BASE_OTA_SCHEMA = cv.Schema(
|
|||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(51.0)
|
||||
@coroutine_with_priority(54.0)
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_OTA")
|
||||
|
||||
|
|
|
@ -1,5 +1,56 @@
|
|||
from esphome.cpp_generator import RawExpression
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DISABLED,
|
||||
CONF_ID,
|
||||
CONF_NUM_ATTEMPTS,
|
||||
CONF_REBOOT_TIMEOUT,
|
||||
CONF_SAFE_MODE,
|
||||
KEY_PAST_SAFE_MODE,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
CODEOWNERS = ["@paulmonigatti", "@jsuanet"]
|
||||
|
||||
CODEOWNERS = ["@paulmonigatti", "@jsuanet", "@kbx81"]
|
||||
|
||||
safe_mode_ns = cg.esphome_ns.namespace("safe_mode")
|
||||
SafeModeComponent = safe_mode_ns.class_("SafeModeComponent", cg.Component)
|
||||
|
||||
|
||||
def _remove_id_if_disabled(value):
|
||||
value = value.copy()
|
||||
if value[CONF_DISABLED]:
|
||||
value.pop(CONF_ID)
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SafeModeComponent),
|
||||
cv.Optional(CONF_DISABLED, default=False): cv.boolean,
|
||||
cv.Optional(CONF_NUM_ATTEMPTS, default="10"): cv.positive_not_null_int,
|
||||
cv.Optional(
|
||||
CONF_REBOOT_TIMEOUT, default="5min"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
_remove_id_if_disabled,
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(50.0)
|
||||
async def to_code(config):
|
||||
if config[CONF_DISABLED]:
|
||||
return
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
condition = var.should_enter_safe_mode(
|
||||
config[CONF_NUM_ATTEMPTS], config[CONF_REBOOT_TIMEOUT]
|
||||
)
|
||||
cg.add(RawExpression(f"if ({condition}) return"))
|
||||
CORE.data[CONF_SAFE_MODE] = {}
|
||||
CORE.data[CONF_SAFE_MODE][KEY_PAST_SAFE_MODE] = True
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import button
|
||||
from esphome.components.esphome.ota import ESPHomeOTAComponent
|
||||
from esphome.const import (
|
||||
CONF_ESPHOME,
|
||||
CONF_SAFE_MODE,
|
||||
DEVICE_CLASS_RESTART,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_RESTART_ALERT,
|
||||
)
|
||||
from .. import safe_mode_ns
|
||||
from .. import safe_mode_ns, SafeModeComponent
|
||||
|
||||
DEPENDENCIES = ["ota.esphome"]
|
||||
DEPENDENCIES = ["safe_mode"]
|
||||
|
||||
SafeModeButton = safe_mode_ns.class_("SafeModeButton", button.Button, cg.Component)
|
||||
|
||||
|
@ -21,7 +20,7 @@ CONFIG_SCHEMA = (
|
|||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART_ALERT,
|
||||
)
|
||||
.extend({cv.GenerateID(CONF_ESPHOME): cv.use_id(ESPHomeOTAComponent)})
|
||||
.extend({cv.GenerateID(CONF_SAFE_MODE): cv.use_id(SafeModeComponent)})
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
@ -30,5 +29,5 @@ async def to_code(config):
|
|||
var = await button.new_button(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
ota = await cg.get_variable(config[CONF_ESPHOME])
|
||||
cg.add(var.set_ota(ota))
|
||||
safe_mode_component = await cg.get_variable(config[CONF_SAFE_MODE])
|
||||
cg.add(var.set_safe_mode(safe_mode_component))
|
||||
|
|
|
@ -8,11 +8,13 @@ namespace safe_mode {
|
|||
|
||||
static const char *const TAG = "safe_mode.button";
|
||||
|
||||
void SafeModeButton::set_ota(esphome::ESPHomeOTAComponent *ota) { this->ota_ = ota; }
|
||||
void SafeModeButton::set_safe_mode(SafeModeComponent *safe_mode_component) {
|
||||
this->safe_mode_component_ = safe_mode_component;
|
||||
}
|
||||
|
||||
void SafeModeButton::press_action() {
|
||||
ESP_LOGI(TAG, "Restarting device in safe mode...");
|
||||
this->ota_->set_safe_mode_pending(true);
|
||||
this->safe_mode_component_->set_safe_mode_pending(true);
|
||||
|
||||
// Let MQTT settle a bit
|
||||
delay(100); // NOLINT
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "esphome/components/esphome/ota/ota_esphome.h"
|
||||
#include "esphome/components/safe_mode/safe_mode.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
|
@ -10,10 +10,10 @@ namespace safe_mode {
|
|||
class SafeModeButton : public button::Button, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_ota(esphome::ESPHomeOTAComponent *ota);
|
||||
void set_safe_mode(SafeModeComponent *safe_mode_component);
|
||||
|
||||
protected:
|
||||
esphome::ESPHomeOTAComponent *ota_;
|
||||
SafeModeComponent *safe_mode_component_;
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
|
|
125
esphome/components/safe_mode/safe_mode.cpp
Normal file
125
esphome/components/safe_mode/safe_mode.cpp
Normal file
|
@ -0,0 +1,125 @@
|
|||
#include "safe_mode.h"
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
|
||||
namespace esphome {
|
||||
namespace safe_mode {
|
||||
|
||||
static const char *const TAG = "safe_mode";
|
||||
|
||||
void SafeModeComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Safe Mode:");
|
||||
ESP_LOGCONFIG(TAG, " Invoke after %u boot attempts", this->safe_mode_num_attempts_);
|
||||
ESP_LOGCONFIG(TAG, " Remain in safe mode for %" PRIu32 " seconds",
|
||||
this->safe_mode_enable_time_ / 1000); // because milliseconds
|
||||
|
||||
if (this->safe_mode_rtc_value_ > 1 && this->safe_mode_rtc_value_ != SafeModeComponent::ENTER_SAFE_MODE_MAGIC) {
|
||||
auto remaining_restarts = this->safe_mode_num_attempts_ - this->safe_mode_rtc_value_;
|
||||
if (remaining_restarts) {
|
||||
ESP_LOGW(TAG, "Last reset occurred too quickly; safe mode will be invoked in %" PRIu32 " restarts",
|
||||
remaining_restarts);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "SAFE MODE IS ACTIVE");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float SafeModeComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void SafeModeComponent::loop() {
|
||||
if (!this->boot_successful_ && (millis() - this->safe_mode_start_time_) > this->safe_mode_enable_time_) {
|
||||
// successful boot, reset counter
|
||||
ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter");
|
||||
this->clean_rtc();
|
||||
this->boot_successful_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SafeModeComponent::set_safe_mode_pending(const bool &pending) {
|
||||
uint32_t current_rtc = this->read_rtc_();
|
||||
|
||||
if (pending && current_rtc != SafeModeComponent::ENTER_SAFE_MODE_MAGIC) {
|
||||
ESP_LOGI(TAG, "Device will enter safe mode on next boot");
|
||||
this->write_rtc_(SafeModeComponent::ENTER_SAFE_MODE_MAGIC);
|
||||
}
|
||||
|
||||
if (!pending && current_rtc == SafeModeComponent::ENTER_SAFE_MODE_MAGIC) {
|
||||
ESP_LOGI(TAG, "Safe mode pending has been cleared");
|
||||
this->clean_rtc();
|
||||
}
|
||||
}
|
||||
|
||||
bool SafeModeComponent::get_safe_mode_pending() {
|
||||
return this->read_rtc_() == SafeModeComponent::ENTER_SAFE_MODE_MAGIC;
|
||||
}
|
||||
|
||||
bool SafeModeComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time) {
|
||||
this->safe_mode_start_time_ = millis();
|
||||
this->safe_mode_enable_time_ = enable_time;
|
||||
this->safe_mode_num_attempts_ = num_attempts;
|
||||
this->rtc_ = global_preferences->make_preference<uint32_t>(233825507UL, false);
|
||||
this->safe_mode_rtc_value_ = this->read_rtc_();
|
||||
|
||||
bool is_manual_safe_mode = this->safe_mode_rtc_value_ == SafeModeComponent::ENTER_SAFE_MODE_MAGIC;
|
||||
|
||||
if (is_manual_safe_mode) {
|
||||
ESP_LOGI(TAG, "Safe mode invoked manually");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "There have been %" PRIu32 " suspected unsuccessful boot attempts", this->safe_mode_rtc_value_);
|
||||
}
|
||||
|
||||
if (this->safe_mode_rtc_value_ >= num_attempts || is_manual_safe_mode) {
|
||||
this->clean_rtc();
|
||||
|
||||
if (!is_manual_safe_mode) {
|
||||
ESP_LOGE(TAG, "Boot loop detected. Proceeding to safe mode");
|
||||
}
|
||||
|
||||
this->status_set_error();
|
||||
this->set_timeout(enable_time, []() {
|
||||
ESP_LOGW(TAG, "Safe mode enable time has elapsed -- restarting");
|
||||
App.reboot();
|
||||
});
|
||||
|
||||
// Delay here to allow power to stabilize before Wi-Fi/Ethernet is initialised
|
||||
delay(300); // NOLINT
|
||||
App.setup();
|
||||
|
||||
ESP_LOGW(TAG, "SAFE MODE IS ACTIVE");
|
||||
|
||||
return true;
|
||||
} else {
|
||||
// increment counter
|
||||
this->write_rtc_(this->safe_mode_rtc_value_ + 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void SafeModeComponent::write_rtc_(uint32_t val) {
|
||||
this->rtc_.save(&val);
|
||||
global_preferences->sync();
|
||||
}
|
||||
|
||||
uint32_t SafeModeComponent::read_rtc_() {
|
||||
uint32_t val;
|
||||
if (!this->rtc_.load(&val))
|
||||
return 0;
|
||||
return val;
|
||||
}
|
||||
|
||||
void SafeModeComponent::clean_rtc() { this->write_rtc_(0); }
|
||||
|
||||
void SafeModeComponent::on_safe_shutdown() {
|
||||
if (this->read_rtc_() != SafeModeComponent::ENTER_SAFE_MODE_MAGIC)
|
||||
this->clean_rtc();
|
||||
}
|
||||
|
||||
} // namespace safe_mode
|
||||
} // namespace esphome
|
44
esphome/components/safe_mode/safe_mode.h
Normal file
44
esphome/components/safe_mode/safe_mode.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace safe_mode {
|
||||
|
||||
/// SafeModeComponent provides a safe way to recover from repeated boot failures
|
||||
class SafeModeComponent : public Component {
|
||||
public:
|
||||
bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time);
|
||||
|
||||
/// Set to true if the next startup will enter safe mode
|
||||
void set_safe_mode_pending(const bool &pending);
|
||||
bool get_safe_mode_pending();
|
||||
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void loop() override;
|
||||
|
||||
void clean_rtc();
|
||||
|
||||
void on_safe_shutdown() override;
|
||||
|
||||
protected:
|
||||
void write_rtc_(uint32_t val);
|
||||
uint32_t read_rtc_();
|
||||
|
||||
bool boot_successful_{false}; ///< set to true after boot is considered successful
|
||||
uint32_t safe_mode_start_time_; ///< stores when safe mode was enabled
|
||||
uint32_t safe_mode_enable_time_{60000}; ///< The time safe mode should remain active for
|
||||
uint32_t safe_mode_rtc_value_;
|
||||
uint8_t safe_mode_num_attempts_;
|
||||
ESPPreferenceObject rtc_;
|
||||
|
||||
static const uint32_t ENTER_SAFE_MODE_MAGIC =
|
||||
0x5afe5afe; ///< a magic number to indicate that safe mode should be entered on next boot
|
||||
};
|
||||
|
||||
} // namespace safe_mode
|
||||
} // namespace esphome
|
|
@ -1,15 +1,14 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import switch
|
||||
from esphome.components.esphome.ota import ESPHomeOTAComponent
|
||||
from esphome.const import (
|
||||
CONF_ESPHOME,
|
||||
CONF_SAFE_MODE,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_RESTART_ALERT,
|
||||
)
|
||||
from .. import safe_mode_ns
|
||||
from .. import safe_mode_ns, SafeModeComponent
|
||||
|
||||
DEPENDENCIES = ["ota.esphome"]
|
||||
DEPENDENCIES = ["safe_mode"]
|
||||
|
||||
SafeModeSwitch = safe_mode_ns.class_("SafeModeSwitch", switch.Switch, cg.Component)
|
||||
|
||||
|
@ -20,7 +19,7 @@ CONFIG_SCHEMA = (
|
|||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART_ALERT,
|
||||
)
|
||||
.extend({cv.GenerateID(CONF_ESPHOME): cv.use_id(ESPHomeOTAComponent)})
|
||||
.extend({cv.GenerateID(CONF_SAFE_MODE): cv.use_id(SafeModeComponent)})
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
@ -29,5 +28,5 @@ async def to_code(config):
|
|||
var = await switch.new_switch(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
ota = await cg.get_variable(config[CONF_ESPHOME])
|
||||
cg.add(var.set_ota(ota))
|
||||
safe_mode_component = await cg.get_variable(config[CONF_SAFE_MODE])
|
||||
cg.add(var.set_safe_mode(safe_mode_component))
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
namespace esphome {
|
||||
namespace safe_mode {
|
||||
|
||||
static const char *const TAG = "safe_mode_switch";
|
||||
static const char *const TAG = "safe_mode.switch";
|
||||
|
||||
void SafeModeSwitch::set_ota(esphome::ESPHomeOTAComponent *ota) { this->ota_ = ota; }
|
||||
void SafeModeSwitch::set_safe_mode(SafeModeComponent *safe_mode_component) {
|
||||
this->safe_mode_component_ = safe_mode_component;
|
||||
}
|
||||
|
||||
void SafeModeSwitch::write_state(bool state) {
|
||||
// Acknowledge
|
||||
|
@ -16,13 +18,14 @@ void SafeModeSwitch::write_state(bool state) {
|
|||
|
||||
if (state) {
|
||||
ESP_LOGI(TAG, "Restarting device in safe mode...");
|
||||
this->ota_->set_safe_mode_pending(true);
|
||||
this->safe_mode_component_->set_safe_mode_pending(true);
|
||||
|
||||
// Let MQTT settle a bit
|
||||
delay(100); // NOLINT
|
||||
App.safe_reboot();
|
||||
}
|
||||
}
|
||||
|
||||
void SafeModeSwitch::dump_config() { LOG_SWITCH("", "Safe Mode Switch", this); }
|
||||
|
||||
} // namespace safe_mode
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/esphome/ota/ota_esphome.h"
|
||||
#include "esphome/components/safe_mode/safe_mode.h"
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
|
@ -10,10 +10,10 @@ namespace safe_mode {
|
|||
class SafeModeSwitch : public switch_::Switch, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_ota(esphome::ESPHomeOTAComponent *ota);
|
||||
void set_safe_mode(SafeModeComponent *safe_mode_component);
|
||||
|
||||
protected:
|
||||
esphome::ESPHomeOTAComponent *ota_;
|
||||
SafeModeComponent *safe_mode_component_;
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
|
|
|
@ -3,12 +3,9 @@ import logging
|
|||
from esphome.const import (
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_ESPHOME,
|
||||
CONF_ICON,
|
||||
CONF_INTERNAL,
|
||||
CONF_NAME,
|
||||
CONF_OTA,
|
||||
CONF_PLATFORM,
|
||||
CONF_SAFE_MODE,
|
||||
CONF_SETUP_PRIORITY,
|
||||
CONF_TYPE_ID,
|
||||
|
@ -141,22 +138,12 @@ async def build_registry_list(registry, config):
|
|||
|
||||
|
||||
async def past_safe_mode():
|
||||
ota_conf = {}
|
||||
for ota_item in CORE.config.get(CONF_OTA, []):
|
||||
if ota_item[CONF_PLATFORM] == CONF_ESPHOME:
|
||||
ota_conf = ota_item
|
||||
break
|
||||
|
||||
if not ota_conf:
|
||||
return
|
||||
|
||||
safe_mode_enabled = ota_conf[CONF_SAFE_MODE]
|
||||
if not safe_mode_enabled:
|
||||
if CONF_SAFE_MODE not in CORE.config:
|
||||
return
|
||||
|
||||
def _safe_mode_generator():
|
||||
while True:
|
||||
if CORE.data.get(CONF_OTA, {}).get(KEY_PAST_SAFE_MODE, False):
|
||||
if CORE.data.get(CONF_SAFE_MODE, {}).get(KEY_PAST_SAFE_MODE, False):
|
||||
return
|
||||
yield
|
||||
|
||||
|
|
|
@ -4,11 +4,8 @@ wifi:
|
|||
|
||||
ota:
|
||||
- platform: esphome
|
||||
safe_mode: true
|
||||
password: "superlongpasswordthatnoonewillknow"
|
||||
port: 3286
|
||||
reboot_timeout: 2min
|
||||
num_attempts: 5
|
||||
on_begin:
|
||||
then:
|
||||
- logger.log: "OTA start"
|
||||
|
|
|
@ -2,9 +2,9 @@ wifi:
|
|||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
safe_mode: true
|
||||
safe_mode:
|
||||
num_attempts: 3
|
||||
reboot_timeout: 2min
|
||||
|
||||
button:
|
||||
- platform: safe_mode
|
||||
|
|
|
@ -264,13 +264,14 @@ uart:
|
|||
parity: EVEN
|
||||
baud_rate: 9600
|
||||
|
||||
safe_mode:
|
||||
num_attempts: 3
|
||||
reboot_timeout: 2min
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
safe_mode: true
|
||||
password: "superlongpasswordthatnoonewillknow"
|
||||
port: 3286
|
||||
reboot_timeout: 2min
|
||||
num_attempts: 5
|
||||
on_state_change:
|
||||
then:
|
||||
lambda: >-
|
||||
|
|
|
@ -79,11 +79,11 @@ uart:
|
|||
sequence:
|
||||
- lambda: UARTDebug::log_hex(direction, bytes, ':');
|
||||
|
||||
safe_mode:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
safe_mode: true
|
||||
port: 3286
|
||||
num_attempts: 15
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
|
|
@ -327,11 +327,13 @@ modbus:
|
|||
vbus:
|
||||
uart_id: uart_4
|
||||
|
||||
safe_mode:
|
||||
num_attempts: 5
|
||||
reboot_timeout: 10min
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
safe_mode: true
|
||||
port: 3286
|
||||
reboot_timeout: 15min
|
||||
|
||||
logger:
|
||||
hardware_uart: UART1
|
||||
|
|
|
@ -102,9 +102,10 @@ uart:
|
|||
baud_rate: 1200
|
||||
parity: EVEN
|
||||
|
||||
safe_mode:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
safe_mode: true
|
||||
port: 3286
|
||||
|
||||
logger:
|
||||
|
|
Loading…
Reference in a new issue