From 9498c554bfe8ec103ff0f549b10298492bee7329 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 18 Mar 2026 08:52:44 -1000 Subject: [PATCH] [ota] Pack deferred state args into uint32 to avoid heap allocation The notify_state_deferred_ lambda captured [this, state, progress, error] (16 bytes on 32-bit), exceeding std::function SBO and forcing a heap allocation on every OTA progress update. Pack the three values into a single uint32_t: - state (8 bits) + error (8 bits) + progress as fixed-point (16 bits) The lambda now captures [this, packed] (8 bytes), fitting in SBO. Progress resolution of 0.01% is more than adequate for OTA reporting. --- esphome/components/ota/ota_backend.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/ota/ota_backend.h b/esphome/components/ota/ota_backend.h index bc603a6e9e..cc6ef365be 100644 --- a/esphome/components/ota/ota_backend.h +++ b/esphome/components/ota/ota_backend.h @@ -74,7 +74,15 @@ class OTAComponent : public Component { * (like web_server OTA) to ensure listeners execute in the main loop. */ void notify_state_deferred_(OTAState state, float progress, uint8_t error) { - this->defer([this, state, progress, error]() { this->notify_state_(state, progress, error); }); + // Pack state, error, and progress into a single uint32_t so the lambda + // captures only [this, packed] (8 bytes) — fits in std::function SBO. + // Layout: [state:8][error:8][progress_fixed:16] where progress is 0–10000 (0.01% resolution) + uint32_t packed = (static_cast(state) << 24) | (static_cast(error) << 16) | + static_cast(progress * 100.0f); + this->defer([this, packed]() { + this->notify_state_(static_cast(packed >> 24), static_cast(packed & 0xFFFF) / 100.0f, + static_cast(packed >> 16)); + }); } std::vector state_listeners_;