1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-08 16:51:52 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
J. Nick Koston
2a6e20dd32 [core] Deprecate set_retry, cancel_retry, and RetryResult
set_retry does a std::make_shared<RetryArgs>() heap allocation on every
invocation. No core component needs this pattern - all callers have been
migrated to set_timeout or set_interval in prior PRs. The feature wastes
flash and RAM on every firmware for a pattern that set_interval covers
better, and the hidden heap allocation is a footgun for component authors.

Deprecated in 2026.2.0, removal in 2026.8.0.

Depends on:
- #13841 [lps22] Replace set_retry with set_interval
- #13842 [ms8607] Replace set_retry with set_timeout chain
- #13843 [speaker] Replace set_retry with set_interval
- #13844 [esp32_hosted] Replace set_retry with set_interval
2026-02-07 17:25:59 -06:00
9 changed files with 83 additions and 105 deletions

View File

@@ -4,7 +4,7 @@
namespace esphome::epaper_spi {
class EPaperSpectraE6 final : public EPaperBase {
class EPaperSpectraE6 : public EPaperBase {
public:
EPaperSpectraE6(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length)

View File

@@ -6,7 +6,7 @@ namespace esphome::epaper_spi {
/**
* An epaper display that needs LUTs to be sent to it.
*/
class EpaperWaveshare final : public EPaperMono {
class EpaperWaveshare : public EPaperMono {
public:
EpaperWaveshare(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length, const uint8_t *lut, size_t lut_length, const uint8_t *partial_lut,

View File

@@ -219,50 +219,39 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
return;
}
static constexpr size_t ADDR_SIZE = 1;
static constexpr size_t FC_SIZE = 1;
static constexpr size_t START_ADDR_SIZE = 2;
static constexpr size_t NUM_ENTITIES_SIZE = 2;
static constexpr size_t BYTE_COUNT_SIZE = 1;
static constexpr size_t MAX_PAYLOAD_SIZE = std::numeric_limits<uint8_t>::max();
static constexpr size_t CRC_SIZE = 2;
static constexpr size_t MAX_FRAME_SIZE =
ADDR_SIZE + FC_SIZE + START_ADDR_SIZE + NUM_ENTITIES_SIZE + BYTE_COUNT_SIZE + MAX_PAYLOAD_SIZE + CRC_SIZE;
uint8_t data[MAX_FRAME_SIZE];
size_t pos = 0;
data[pos++] = address;
data[pos++] = function_code;
std::vector<uint8_t> data;
data.push_back(address);
data.push_back(function_code);
if (this->role == ModbusRole::CLIENT) {
data[pos++] = start_address >> 8;
data[pos++] = start_address >> 0;
data.push_back(start_address >> 8);
data.push_back(start_address >> 0);
if (function_code != ModbusFunctionCode::WRITE_SINGLE_COIL &&
function_code != ModbusFunctionCode::WRITE_SINGLE_REGISTER) {
data[pos++] = number_of_entities >> 8;
data[pos++] = number_of_entities >> 0;
data.push_back(number_of_entities >> 8);
data.push_back(number_of_entities >> 0);
}
}
if (payload != nullptr) {
if (this->role == ModbusRole::SERVER || function_code == ModbusFunctionCode::WRITE_MULTIPLE_COILS ||
function_code == ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS) { // Write multiple
data[pos++] = payload_len; // Byte count is required for write
data.push_back(payload_len); // Byte count is required for write
} else {
payload_len = 2; // Write single register or coil
}
for (int i = 0; i < payload_len; i++) {
data[pos++] = payload[i];
data.push_back(payload[i]);
}
}
auto crc = crc16(data, pos);
data[pos++] = crc >> 0;
data[pos++] = crc >> 8;
auto crc = crc16(data.data(), data.size());
data.push_back(crc >> 0);
data.push_back(crc >> 8);
if (this->flow_control_pin_ != nullptr)
this->flow_control_pin_->digital_write(true);
this->write_array(data, pos);
this->write_array(data);
this->flush();
if (this->flow_control_pin_ != nullptr)
@@ -272,7 +261,7 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MODBUS_MAX_LOG_BYTES)];
#endif
ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty_to(hex_buf, data, pos));
ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty_to(hex_buf, data.data(), data.size()));
}
// Helper function for lambdas

View File

@@ -152,7 +152,10 @@ void Component::set_retry(const std::string &name, uint32_t initial_wait_time, u
void Component::set_retry(const char *name, uint32_t initial_wait_time, uint8_t max_attempts,
std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor) { // NOLINT
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
App.scheduler.set_retry(this, name, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor);
#pragma GCC diagnostic pop
}
bool Component::cancel_retry(const std::string &name) { // NOLINT
@@ -163,7 +166,10 @@ bool Component::cancel_retry(const std::string &name) { // NOLINT
}
bool Component::cancel_retry(const char *name) { // NOLINT
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
return App.scheduler.cancel_retry(this, name);
#pragma GCC diagnostic pop
}
void Component::set_timeout(const std::string &name, uint32_t timeout, std::function<void()> &&f) { // NOLINT
@@ -203,10 +209,18 @@ bool Component::cancel_interval(uint32_t id) { return App.scheduler.cancel_inter
void Component::set_retry(uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts,
std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor) { // NOLINT
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
App.scheduler.set_retry(this, id, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor);
#pragma GCC diagnostic pop
}
bool Component::cancel_retry(uint32_t id) { return App.scheduler.cancel_retry(this, id); }
bool Component::cancel_retry(uint32_t id) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
return App.scheduler.cancel_retry(this, id);
#pragma GCC diagnostic pop
}
void Component::call_loop() { this->loop(); }
void Component::call_setup() { this->setup(); }
@@ -371,7 +385,10 @@ void Component::set_interval(uint32_t interval, std::function<void()> &&f) { //
}
void Component::set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function<RetryResult(uint8_t)> &&f,
float backoff_increase_factor) { // NOLINT
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
App.scheduler.set_retry(this, "", initial_wait_time, max_attempts, std::move(f), backoff_increase_factor);
#pragma GCC diagnostic pop
}
bool Component::is_failed() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; }
bool Component::is_ready() const {

View File

@@ -68,6 +68,7 @@ extern const uint8_t STATUS_LED_OK;
extern const uint8_t STATUS_LED_WARNING;
extern const uint8_t STATUS_LED_ERROR;
// Remove before 2026.8.0
enum class RetryResult { DONE, RETRY };
extern const uint16_t WARN_IF_BLOCKING_OVER_MS;
@@ -347,68 +348,40 @@ class Component {
bool cancel_interval(const char *name); // NOLINT
bool cancel_interval(uint32_t id); // NOLINT
/** Set an retry function with a unique name. Empty name means no cancelling possible.
*
* This will call the retry function f on the next scheduler loop. f should return RetryResult::DONE if
* it is successful and no repeat is required. Otherwise, returning RetryResult::RETRY will call f
* again in the future.
*
* The first retry of f happens after `initial_wait_time` milliseconds. The delay between retries is
* increased by multiplying by `backoff_increase_factor` each time. If no backoff_increase_factor is
* supplied (default = 1.0), the wait time will stay constant.
*
* The retry function f needs to accept a single argument: the number of attempts remaining. On the
* final retry of f, this value will be 0.
*
* This retry function can also be cancelled by name via cancel_retry().
*
* IMPORTANT: Do not rely on this having correct timing. This is only called from
* loop() and therefore can be significantly delayed.
*
* REMARK: It is an error to supply a negative or zero `backoff_increase_factor`, and 1.0 will be used instead.
*
* REMARK: The interval between retries is stored into a `uint32_t`, so this doesn't behave correctly
* if `initial_wait_time * (backoff_increase_factor ** (max_attempts - 2))` overflows.
*
* @param name The identifier for this retry function.
* @param initial_wait_time The time in ms before f is called again
* @param max_attempts The maximum number of executions
* @param f The function (or lambda) that should be called
* @param backoff_increase_factor time between retries is multiplied by this factor on every retry after the first
* @see cancel_retry()
*/
// Remove before 2026.7.0
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
/// @deprecated set_retry is deprecated. Use set_timeout or set_interval instead. Removed in 2026.8.0.
// Remove before 2026.8.0
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
"2026.2.0")
void set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT
std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor = 1.0f); // NOLINT
// Remove before 2026.8.0
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
"2026.2.0")
void set_retry(const char *name, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT
std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor = 1.0f); // NOLINT
/** Set a retry function with a numeric ID (zero heap allocation).
*
* @param id The numeric identifier for this retry function
* @param initial_wait_time The wait time after the first execution
* @param max_attempts The max number of attempts
* @param f The function to call
* @param backoff_increase_factor The factor to increase the retry interval by
*/
// Remove before 2026.8.0
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
"2026.2.0")
void set_retry(uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT
std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor = 1.0f); // NOLINT
// Remove before 2026.8.0
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
"2026.2.0")
void set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function<RetryResult(uint8_t)> &&f, // NOLINT
float backoff_increase_factor = 1.0f); // NOLINT
/** Cancel a retry function.
*
* @param name The identifier for this retry function.
* @return Whether a retry function was deleted.
*/
// Remove before 2026.7.0
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
// Remove before 2026.8.0
ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
bool cancel_retry(const std::string &name); // NOLINT
bool cancel_retry(const char *name); // NOLINT
bool cancel_retry(uint32_t id); // NOLINT
// Remove before 2026.8.0
ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
bool cancel_retry(const char *name); // NOLINT
// Remove before 2026.8.0
ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
bool cancel_retry(uint32_t id); // NOLINT
/** Set a timeout function with a unique name.
*

View File

@@ -252,6 +252,11 @@ bool HOT Scheduler::cancel_interval(Component *component, uint32_t id) {
return this->cancel_item_(component, NameType::NUMERIC_ID, nullptr, id, SchedulerItem::INTERVAL);
}
// Suppress deprecation warnings for RetryResult usage in the still-present (but deprecated) retry implementation.
// Remove before 2026.8.0 along with all retry code.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
struct RetryArgs {
// Ordered to minimize padding on 32-bit systems
std::function<RetryResult(uint8_t)> func;
@@ -364,6 +369,8 @@ bool HOT Scheduler::cancel_retry(Component *component, uint32_t id) {
return this->cancel_retry_(component, NameType::NUMERIC_ID, nullptr, id);
}
#pragma GCC diagnostic pop // End suppression of deprecated RetryResult warnings
optional<uint32_t> HOT Scheduler::next_schedule_in(uint32_t now) {
// IMPORTANT: This method should only be called from the main thread (loop task).
// It performs cleanup and accesses items_[0] without holding a lock, which is only

View File

@@ -72,18 +72,30 @@ class Scheduler {
bool cancel_interval(Component *component, const char *name);
bool cancel_interval(Component *component, uint32_t id);
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
// Remove before 2026.8.0
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
"2026.2.0")
void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
// Remove before 2026.8.0
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
"2026.2.0")
void set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts,
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
/// Set a retry with a numeric ID (zero heap allocation)
// Remove before 2026.8.0
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
"2026.2.0")
void set_retry(Component *component, uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts,
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
// Remove before 2026.8.0
ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
bool cancel_retry(Component *component, const std::string &name);
// Remove before 2026.8.0
ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
bool cancel_retry(Component *component, const char *name);
// Remove before 2026.8.0
ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
bool cancel_retry(Component *component, uint32_t id);
// Calculate when the next scheduled item should run
@@ -231,11 +243,14 @@ class Scheduler {
uint32_t hash_or_id, uint32_t delay, std::function<void()> func, bool is_retry = false,
bool skip_cancel = false);
// Common implementation for retry
// Common implementation for retry - Remove before 2026.8.0
// name_type determines storage type: STATIC_STRING uses static_name, others use hash_or_id
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
void set_retry_common_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id,
uint32_t initial_wait_time, uint8_t max_attempts, std::function<RetryResult(uint8_t)> func,
float backoff_increase_factor);
#pragma GCC diagnostic pop
// Common implementation for cancel_retry
bool cancel_retry_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id);

View File

@@ -317,7 +317,6 @@ class EsphomeCommandWebSocket(CheckOriginMixin, tornado.websocket.WebSocketHandl
# Check if the proc was not forcibly closed
_LOGGER.info("Process exited with return code %s", returncode)
self.write_message({"event": "exit", "code": returncode})
self.close()
def on_close(self) -> None:
# Check if proc exists (if 'start' has been run)

View File

@@ -29,7 +29,7 @@ from esphome.dashboard.entries import (
bool_to_entry_state,
)
from esphome.dashboard.models import build_importable_device_dict
from esphome.dashboard.web_server import DashboardSubscriber, EsphomeCommandWebSocket
from esphome.dashboard.web_server import DashboardSubscriber
from esphome.zeroconf import DiscoveredImport
from .common import get_fixture_path
@@ -1654,25 +1654,3 @@ async def test_websocket_check_origin_multiple_trusted_domains(
assert data["event"] == "initial_state"
finally:
ws.close()
def test_proc_on_exit_calls_close() -> None:
"""Test _proc_on_exit sends exit event and closes the WebSocket."""
handler = Mock(spec=EsphomeCommandWebSocket)
handler._is_closed = False
EsphomeCommandWebSocket._proc_on_exit(handler, 0)
handler.write_message.assert_called_once_with({"event": "exit", "code": 0})
handler.close.assert_called_once()
def test_proc_on_exit_skips_when_already_closed() -> None:
"""Test _proc_on_exit does nothing when WebSocket is already closed."""
handler = Mock(spec=EsphomeCommandWebSocket)
handler._is_closed = True
EsphomeCommandWebSocket._proc_on_exit(handler, 0)
handler.write_message.assert_not_called()
handler.close.assert_not_called()