From 6727fe9040a1ce760ebcb86c54e1ae8686b06df9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 2 Feb 2026 05:18:17 +0100 Subject: [PATCH 01/10] [remote_transmitter] Avoid heap allocation for triggers (#13708) --- .../components/remote_transmitter/remote_transmitter.cpp | 4 ++-- .../components/remote_transmitter/remote_transmitter.h | 8 ++++---- .../remote_transmitter/remote_transmitter_esp32.cpp | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/esphome/components/remote_transmitter/remote_transmitter.cpp b/esphome/components/remote_transmitter/remote_transmitter.cpp index f20789fb9f..d35541e2e1 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter.cpp @@ -83,7 +83,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen uint32_t on_time, off_time; this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time); this->target_time_ = 0; - this->transmit_trigger_->trigger(); + this->transmit_trigger_.trigger(); for (uint32_t i = 0; i < send_times; i++) { InterruptLock lock; for (int32_t item : this->temp_.get_data()) { @@ -102,7 +102,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen if (i + 1 < send_times) this->target_time_ += send_wait; } - this->complete_trigger_->trigger(); + this->complete_trigger_.trigger(); } } // namespace remote_transmitter diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index dd6a849e4c..65bd2ac8b2 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -57,8 +57,8 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, void set_non_blocking(bool non_blocking) { this->non_blocking_ = non_blocking; } #endif - Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; }; - Trigger<> *get_complete_trigger() const { return this->complete_trigger_; }; + Trigger<> *get_transmit_trigger() { return &this->transmit_trigger_; } + Trigger<> *get_complete_trigger() { return &this->complete_trigger_; } protected: void send_internal(uint32_t send_times, uint32_t send_wait) override; @@ -96,8 +96,8 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, #endif uint8_t carrier_duty_percent_; - Trigger<> *transmit_trigger_{new Trigger<>()}; - Trigger<> *complete_trigger_{new Trigger<>()}; + Trigger<> transmit_trigger_; + Trigger<> complete_trigger_; }; } // namespace remote_transmitter diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 59c85c99a8..89d97895b2 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -203,7 +203,7 @@ void RemoteTransmitterComponent::wait_for_rmt_() { this->status_set_warning(); } - this->complete_trigger_->trigger(); + this->complete_trigger_.trigger(); } #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) @@ -264,7 +264,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen return; } - this->transmit_trigger_->trigger(); + this->transmit_trigger_.trigger(); rmt_transmit_config_t config; memset(&config, 0, sizeof(config)); @@ -333,7 +333,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen ESP_LOGE(TAG, "Empty data"); return; } - this->transmit_trigger_->trigger(); + this->transmit_trigger_.trigger(); for (uint32_t i = 0; i < send_times; i++) { rmt_transmit_config_t config; memset(&config, 0, sizeof(config)); @@ -354,7 +354,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen if (i + 1 < send_times) delayMicroseconds(send_wait); } - this->complete_trigger_->trigger(); + this->complete_trigger_.trigger(); } #endif From bc9fc6622553f0e58bcf5f8301fa2dd43da72f7a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 2 Feb 2026 05:30:46 +0100 Subject: [PATCH 02/10] [template.datetime] Avoid heap allocation for triggers (#13710) --- esphome/components/template/datetime/template_date.cpp | 2 +- esphome/components/template/datetime/template_date.h | 4 ++-- esphome/components/template/datetime/template_datetime.cpp | 2 +- esphome/components/template/datetime/template_datetime.h | 4 ++-- esphome/components/template/datetime/template_time.cpp | 2 +- esphome/components/template/datetime/template_time.h | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/esphome/components/template/datetime/template_date.cpp b/esphome/components/template/datetime/template_date.cpp index be1d875a7e..8a5f11b876 100644 --- a/esphome/components/template/datetime/template_date.cpp +++ b/esphome/components/template/datetime/template_date.cpp @@ -62,7 +62,7 @@ void TemplateDate::control(const datetime::DateCall &call) { if (has_day) value.day_of_month = *call.get_day(); - this->set_trigger_->trigger(value); + this->set_trigger_.trigger(value); if (this->optimistic_) { if (has_year) diff --git a/esphome/components/template/datetime/template_date.h b/esphome/components/template/datetime/template_date.h index 0379a9bc67..acf823a34d 100644 --- a/esphome/components/template/datetime/template_date.h +++ b/esphome/components/template/datetime/template_date.h @@ -22,7 +22,7 @@ class TemplateDate final : public datetime::DateEntity, public PollingComponent void dump_config() override; float get_setup_priority() const override { return setup_priority::HARDWARE; } - Trigger *get_set_trigger() const { return this->set_trigger_; } + Trigger *get_set_trigger() { return &this->set_trigger_; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } @@ -34,7 +34,7 @@ class TemplateDate final : public datetime::DateEntity, public PollingComponent bool optimistic_{false}; ESPTime initial_value_{}; bool restore_value_{false}; - Trigger *set_trigger_ = new Trigger(); + Trigger set_trigger_; TemplateLambda f_; ESPPreferenceObject pref_; diff --git a/esphome/components/template/datetime/template_datetime.cpp b/esphome/components/template/datetime/template_datetime.cpp index e134f2b654..269a1d06ca 100644 --- a/esphome/components/template/datetime/template_datetime.cpp +++ b/esphome/components/template/datetime/template_datetime.cpp @@ -80,7 +80,7 @@ void TemplateDateTime::control(const datetime::DateTimeCall &call) { if (has_second) value.second = *call.get_second(); - this->set_trigger_->trigger(value); + this->set_trigger_.trigger(value); if (this->optimistic_) { if (has_year) diff --git a/esphome/components/template/datetime/template_datetime.h b/esphome/components/template/datetime/template_datetime.h index b7eb490933..575065a3dd 100644 --- a/esphome/components/template/datetime/template_datetime.h +++ b/esphome/components/template/datetime/template_datetime.h @@ -22,7 +22,7 @@ class TemplateDateTime final : public datetime::DateTimeEntity, public PollingCo void dump_config() override; float get_setup_priority() const override { return setup_priority::HARDWARE; } - Trigger *get_set_trigger() const { return this->set_trigger_; } + Trigger *get_set_trigger() { return &this->set_trigger_; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } @@ -34,7 +34,7 @@ class TemplateDateTime final : public datetime::DateTimeEntity, public PollingCo bool optimistic_{false}; ESPTime initial_value_{}; bool restore_value_{false}; - Trigger *set_trigger_ = new Trigger(); + Trigger set_trigger_; TemplateLambda f_; ESPPreferenceObject pref_; diff --git a/esphome/components/template/datetime/template_time.cpp b/esphome/components/template/datetime/template_time.cpp index 586e126e3b..9c81687116 100644 --- a/esphome/components/template/datetime/template_time.cpp +++ b/esphome/components/template/datetime/template_time.cpp @@ -62,7 +62,7 @@ void TemplateTime::control(const datetime::TimeCall &call) { if (has_second) value.second = *call.get_second(); - this->set_trigger_->trigger(value); + this->set_trigger_.trigger(value); if (this->optimistic_) { if (has_hour) diff --git a/esphome/components/template/datetime/template_time.h b/esphome/components/template/datetime/template_time.h index cb83b1b3e5..924b53cc71 100644 --- a/esphome/components/template/datetime/template_time.h +++ b/esphome/components/template/datetime/template_time.h @@ -22,7 +22,7 @@ class TemplateTime final : public datetime::TimeEntity, public PollingComponent void dump_config() override; float get_setup_priority() const override { return setup_priority::HARDWARE; } - Trigger *get_set_trigger() const { return this->set_trigger_; } + Trigger *get_set_trigger() { return &this->set_trigger_; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } @@ -34,7 +34,7 @@ class TemplateTime final : public datetime::TimeEntity, public PollingComponent bool optimistic_{false}; ESPTime initial_value_{}; bool restore_value_{false}; - Trigger *set_trigger_ = new Trigger(); + Trigger set_trigger_; TemplateLambda f_; ESPPreferenceObject pref_; From b5b9a895619e10dfe7784874133c7a8ebd444af6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 2 Feb 2026 07:34:34 +0100 Subject: [PATCH 03/10] [light] Avoid heap allocation for AutomationLightEffect trigger (#13713) --- esphome/components/light/base_light_effects.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/light/base_light_effects.h b/esphome/components/light/base_light_effects.h index 2eeae574e7..cdb9f1f666 100644 --- a/esphome/components/light/base_light_effects.h +++ b/esphome/components/light/base_light_effects.h @@ -138,20 +138,20 @@ class LambdaLightEffect : public LightEffect { class AutomationLightEffect : public LightEffect { public: AutomationLightEffect(const char *name) : LightEffect(name) {} - void stop() override { this->trig_->stop_action(); } + void stop() override { this->trig_.stop_action(); } void apply() override { - if (!this->trig_->is_action_running()) { - this->trig_->trigger(); + if (!this->trig_.is_action_running()) { + this->trig_.trigger(); } } - Trigger<> *get_trig() const { return trig_; } + Trigger<> *get_trig() { return &this->trig_; } /// Get the current effect index for use in automations. /// Useful for automations that need to know which effect is running. uint32_t get_current_index() const { return this->get_index(); } protected: - Trigger<> *trig_{new Trigger<>}; + Trigger<> trig_; }; struct StrobeLightEffectColor { From 61e33217cd1995ae9fca8caba309268dd5b36a09 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 2 Feb 2026 07:34:50 +0100 Subject: [PATCH 04/10] [cc1101] Avoid heap allocation for trigger (#13715) --- esphome/components/cc1101/cc1101.cpp | 2 +- esphome/components/cc1101/cc1101.h | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 46cd89e0e8..b6973da78d 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -156,7 +156,7 @@ void CC1101Component::call_listeners_(const std::vector &packet, float for (auto &listener : this->listeners_) { listener->on_packet(packet, freq_offset, rssi, lqi); } - this->packet_trigger_->trigger(packet, freq_offset, rssi, lqi); + this->packet_trigger_.trigger(packet, freq_offset, rssi, lqi); } void CC1101Component::loop() { diff --git a/esphome/components/cc1101/cc1101.h b/esphome/components/cc1101/cc1101.h index 6e3f01af90..e55071e7e3 100644 --- a/esphome/components/cc1101/cc1101.h +++ b/esphome/components/cc1101/cc1101.h @@ -79,7 +79,7 @@ class CC1101Component : public Component, // Packet mode operations CC1101Error transmit_packet(const std::vector &packet); void register_listener(CC1101Listener *listener) { this->listeners_.push_back(listener); } - Trigger, float, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; } + Trigger, float, float, uint8_t> *get_packet_trigger() { return &this->packet_trigger_; } protected: uint16_t chip_id_{0}; @@ -96,8 +96,7 @@ class CC1101Component : public Component, // Packet handling void call_listeners_(const std::vector &packet, float freq_offset, float rssi, uint8_t lqi); - Trigger, float, float, uint8_t> *packet_trigger_{ - new Trigger, float, float, uint8_t>()}; + Trigger, float, float, uint8_t> packet_trigger_; std::vector packet_; std::vector listeners_; From 420de987bcd19edd991592632da82784c371ddc5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 2 Feb 2026 07:35:03 +0100 Subject: [PATCH 05/10] [micro_wake_word] Avoid heap allocation for trigger (#13714) --- esphome/components/micro_wake_word/micro_wake_word.cpp | 2 +- esphome/components/micro_wake_word/micro_wake_word.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/micro_wake_word/micro_wake_word.cpp b/esphome/components/micro_wake_word/micro_wake_word.cpp index d7e80efc84..b93bf1b556 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.cpp +++ b/esphome/components/micro_wake_word/micro_wake_word.cpp @@ -325,7 +325,7 @@ void MicroWakeWord::loop() { ESP_LOGD(TAG, "Detected '%s' with sliding average probability is %.2f and max probability is %.2f", detection_event.wake_word->c_str(), (detection_event.average_probability / uint8_to_float_divisor), (detection_event.max_probability / uint8_to_float_divisor)); - this->wake_word_detected_trigger_->trigger(*detection_event.wake_word); + this->wake_word_detected_trigger_.trigger(*detection_event.wake_word); if (this->stop_after_detection_) { this->stop(); } diff --git a/esphome/components/micro_wake_word/micro_wake_word.h b/esphome/components/micro_wake_word/micro_wake_word.h index b427e4dfcb..44d5d89372 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.h +++ b/esphome/components/micro_wake_word/micro_wake_word.h @@ -60,7 +60,7 @@ class MicroWakeWord : public Component void set_stop_after_detection(bool stop_after_detection) { this->stop_after_detection_ = stop_after_detection; } - Trigger *get_wake_word_detected_trigger() const { return this->wake_word_detected_trigger_; } + Trigger *get_wake_word_detected_trigger() { return &this->wake_word_detected_trigger_; } void add_wake_word_model(WakeWordModel *model); @@ -78,7 +78,7 @@ class MicroWakeWord : public Component protected: microphone::MicrophoneSource *microphone_source_{nullptr}; - Trigger *wake_word_detected_trigger_ = new Trigger(); + Trigger wake_word_detected_trigger_; State state_{State::STOPPED}; std::weak_ptr ring_buffer_; From c0e5ae4298162b1c971121255de9bf5358be93d0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 2 Feb 2026 07:35:21 +0100 Subject: [PATCH 06/10] [template.text] Avoid heap allocation for trigger (#13711) --- esphome/components/template/text/template_text.cpp | 2 +- esphome/components/template/text/template_text.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/template/text/template_text.cpp b/esphome/components/template/text/template_text.cpp index 70b8dce312..af134e6ed4 100644 --- a/esphome/components/template/text/template_text.cpp +++ b/esphome/components/template/text/template_text.cpp @@ -47,7 +47,7 @@ void TemplateText::update() { } void TemplateText::control(const std::string &value) { - this->set_trigger_->trigger(value); + this->set_trigger_.trigger(value); if (this->optimistic_) this->publish_state(value); diff --git a/esphome/components/template/text/template_text.h b/esphome/components/template/text/template_text.h index e5e5e4f4a8..88c6afdf2c 100644 --- a/esphome/components/template/text/template_text.h +++ b/esphome/components/template/text/template_text.h @@ -68,7 +68,7 @@ class TemplateText final : public text::Text, public PollingComponent { void dump_config() override; float get_setup_priority() const override { return setup_priority::HARDWARE; } - Trigger *get_set_trigger() const { return this->set_trigger_; } + Trigger *get_set_trigger() { return &this->set_trigger_; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_initial_value(const char *initial_value) { this->initial_value_ = initial_value; } /// Prevent accidental use of std::string which would dangle @@ -79,7 +79,7 @@ class TemplateText final : public text::Text, public PollingComponent { void control(const std::string &value) override; bool optimistic_ = false; const char *initial_value_{nullptr}; - Trigger *set_trigger_ = new Trigger(); + Trigger set_trigger_; TemplateLambda f_{}; TemplateTextSaverBase *pref_ = nullptr; From 61140059524d837dc9ac40bc0dfe6acc7a027216 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 2 Feb 2026 07:36:08 +0100 Subject: [PATCH 07/10] [template.water_heater] Avoid heap allocation for trigger (#13712) --- .../template/water_heater/template_water_heater.cpp | 4 ++-- .../components/template/water_heater/template_water_heater.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/template/water_heater/template_water_heater.cpp b/esphome/components/template/water_heater/template_water_heater.cpp index e89c96ca48..f888edb1df 100644 --- a/esphome/components/template/water_heater/template_water_heater.cpp +++ b/esphome/components/template/water_heater/template_water_heater.cpp @@ -5,7 +5,7 @@ namespace esphome::template_ { static const char *const TAG = "template.water_heater"; -TemplateWaterHeater::TemplateWaterHeater() : set_trigger_(new Trigger<>()) {} +TemplateWaterHeater::TemplateWaterHeater() = default; void TemplateWaterHeater::setup() { if (this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE || @@ -78,7 +78,7 @@ void TemplateWaterHeater::control(const water_heater::WaterHeaterCall &call) { } } - this->set_trigger_->trigger(); + this->set_trigger_.trigger(); if (this->optimistic_) { this->publish_state(); diff --git a/esphome/components/template/water_heater/template_water_heater.h b/esphome/components/template/water_heater/template_water_heater.h index c2a2dcbb23..f1cf00a115 100644 --- a/esphome/components/template/water_heater/template_water_heater.h +++ b/esphome/components/template/water_heater/template_water_heater.h @@ -28,7 +28,7 @@ class TemplateWaterHeater : public Component, public water_heater::WaterHeater { this->supported_modes_ = modes; } - Trigger<> *get_set_trigger() const { return this->set_trigger_; } + Trigger<> *get_set_trigger() { return &this->set_trigger_; } void setup() override; void loop() override; @@ -42,7 +42,7 @@ class TemplateWaterHeater : public Component, public water_heater::WaterHeater { water_heater::WaterHeaterTraits traits() override; // Ordered to minimize padding on 32-bit: 4-byte members first, then smaller - Trigger<> *set_trigger_; + Trigger<> set_trigger_; TemplateLambda current_temperature_f_; TemplateLambda mode_f_; TemplateWaterHeaterRestoreMode restore_mode_{WATER_HEATER_NO_RESTORE}; From 62f34bea83c1423a8b894cebf04c3cc40bfc78f2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 2 Feb 2026 07:36:27 +0100 Subject: [PATCH 08/10] [template.output] Avoid heap allocation for triggers (#13709) --- esphome/components/template/output/template_output.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/template/output/template_output.h b/esphome/components/template/output/template_output.h index e536660b02..6fe8e53855 100644 --- a/esphome/components/template/output/template_output.h +++ b/esphome/components/template/output/template_output.h @@ -8,22 +8,22 @@ namespace esphome::template_ { class TemplateBinaryOutput final : public output::BinaryOutput { public: - Trigger *get_trigger() const { return trigger_; } + Trigger *get_trigger() { return &this->trigger_; } protected: - void write_state(bool state) override { this->trigger_->trigger(state); } + void write_state(bool state) override { this->trigger_.trigger(state); } - Trigger *trigger_ = new Trigger(); + Trigger trigger_; }; class TemplateFloatOutput final : public output::FloatOutput { public: - Trigger *get_trigger() const { return trigger_; } + Trigger *get_trigger() { return &this->trigger_; } protected: - void write_state(float state) override { this->trigger_->trigger(state); } + void write_state(float state) override { this->trigger_.trigger(state); } - Trigger *trigger_ = new Trigger(); + Trigger trigger_; }; } // namespace esphome::template_ From bc6d88fabe7505f43f0941f3883135523a96e9dc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 2 Feb 2026 07:57:00 +0100 Subject: [PATCH 09/10] [logger] Use vsnprintf_P directly for ESP8266 flash format strings Instead of copying the format string from flash to RAM before formatting, use vsnprintf_P to read the format string directly from flash memory. This eliminates: - The byte-by-byte copy loop from PROGMEM - The complex dual-purpose buffer management - Potential buffer overflow if format string is very long The new format_body_to_buffer_P_() function is a simple variant that uses vsnprintf_P instead of vsnprintf. --- esphome/components/logger/logger.cpp | 50 ++++++++-------------------- esphome/components/logger/logger.h | 23 +++++++++++++ 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 3a726d4046..e7eba8fd45 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -128,22 +128,7 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch // Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266. // // This function handles format strings stored in flash memory (PROGMEM) to save RAM. -// The buffer is used in a special way to avoid allocating extra memory: -// -// Memory layout during execution: -// Step 1: Copy format string from flash to buffer -// tx_buffer_: [format_string][null][.....................] -// tx_buffer_at_: ------------------^ -// msg_start: saved here -----------^ -// -// Step 2: format_log_to_buffer_with_terminator_ reads format string from beginning -// and writes formatted output starting at msg_start position -// tx_buffer_: [format_string][null][formatted_message][null] -// tx_buffer_at_: -------------------------------------^ -// -// Step 3: Output the formatted message (starting at msg_start) -// write_msg_ and callbacks receive: this->tx_buffer_ + msg_start -// which points to: [formatted_message][null] +// Uses vsnprintf_P to read the format string directly from flash without copying to RAM. // void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format, va_list args) { // NOLINT @@ -153,35 +138,28 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas RecursionGuard guard(global_recursion_guard_); this->tx_buffer_at_ = 0; - // Copy format string from progmem - auto *format_pgm_p = reinterpret_cast(format); - char ch = '.'; - while (this->tx_buffer_at_ < this->tx_buffer_size_ && ch != '\0') { - this->tx_buffer_[this->tx_buffer_at_++] = ch = (char) progmem_read_byte(format_pgm_p++); - } + // Write header, format body directly from flash, and write footer + this->write_header_to_buffer_(level, tag, line, nullptr, this->tx_buffer_, &this->tx_buffer_at_, + this->tx_buffer_size_); + this->format_body_to_buffer_P_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_, + reinterpret_cast(format), args); + this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); - // Buffer full from copying format - RAII guard handles cleanup on return + // Ensure null termination if (this->tx_buffer_at_ >= this->tx_buffer_size_) { - return; + this->tx_buffer_[this->tx_buffer_size_ - 1] = '\0'; + } else { + this->tx_buffer_[this->tx_buffer_at_] = '\0'; } - // Save the offset before calling format_log_to_buffer_with_terminator_ - // since it will increment tx_buffer_at_ to the end of the formatted string - uint16_t msg_start = this->tx_buffer_at_; - this->format_log_to_buffer_with_terminator_(level, tag, line, this->tx_buffer_, args, this->tx_buffer_, - &this->tx_buffer_at_, this->tx_buffer_size_); - - uint16_t msg_length = - this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position - // Listeners get message first (before console write) #ifdef USE_LOG_LISTENERS for (auto *listener : this->log_listeners_) - listener->on_log(level, tag, this->tx_buffer_ + msg_start, msg_length); + listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_); #endif - // Write to console starting at the msg_start - this->write_tx_buffer_to_console_(msg_start, &msg_length); + // Write to console + this->write_tx_buffer_to_console_(); } #endif // USE_STORE_LOG_STR_IN_FLASH diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index fe9cab4993..7e41030b19 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -622,6 +622,29 @@ class Logger : public Component { } } +#ifdef USE_STORE_LOG_STR_IN_FLASH + // ESP8266 variant that reads format string directly from flash using vsnprintf_P + inline void HOT format_body_to_buffer_P_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, PGM_P format, + va_list args) { + if (*buffer_at >= buffer_size) + return; + const uint16_t remaining = buffer_size - *buffer_at; + + const int ret = vsnprintf_P(buffer + *buffer_at, remaining, format, args); + + if (ret < 0) { + return; + } + + uint16_t formatted_len = (ret >= remaining) ? (remaining - 1) : ret; + *buffer_at += formatted_len; + + while (*buffer_at > 0 && buffer[*buffer_at - 1] == '\n') { + (*buffer_at)--; + } + } +#endif + inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1; this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size); From 1501db38b188e1a91b4fa824634b3312ebcd8dd0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 2 Feb 2026 13:28:00 +0100 Subject: [PATCH 10/10] tweak --- esphome/components/logger/logger.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index e7eba8fd45..25243ff3f6 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -146,11 +146,8 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); // Ensure null termination - if (this->tx_buffer_at_ >= this->tx_buffer_size_) { - this->tx_buffer_[this->tx_buffer_size_ - 1] = '\0'; - } else { - this->tx_buffer_[this->tx_buffer_at_] = '\0'; - } + uint16_t null_pos = this->tx_buffer_at_ >= this->tx_buffer_size_ ? this->tx_buffer_size_ - 1 : this->tx_buffer_at_; + this->tx_buffer_[null_pos] = '\0'; // Listeners get message first (before console write) #ifdef USE_LOG_LISTENERS