From 0193464f92e90f765242b6ce0837f009ee611cdb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:34:49 -1000 Subject: [PATCH 01/12] [dsmr] Avoid std::string allocation for decryption key (#13375) --- esphome/components/dsmr/__init__.py | 22 +++------------------- esphome/components/dsmr/dsmr.cpp | 19 +++++++------------ esphome/components/dsmr/dsmr.h | 2 +- esphome/config_validation.py | 8 ++++---- 4 files changed, 15 insertions(+), 36 deletions(-) diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index 0ba68daf5d..386da3ce21 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -25,29 +25,13 @@ dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr") Dsmr = dsmr_ns.class_("Dsmr", cg.Component, uart.UARTDevice) -def _validate_key(value): - value = cv.string_strict(value) - parts = [value[i : i + 2] for i in range(0, len(value), 2)] - if len(parts) != 16: - raise cv.Invalid("Decryption key must consist of 16 hexadecimal numbers") - parts_int = [] - if any(len(part) != 2 for part in parts): - raise cv.Invalid("Decryption key must be format XX") - for part in parts: - try: - parts_int.append(int(part, 16)) - except ValueError: - # pylint: disable=raise-missing-from - raise cv.Invalid("Decryption key must be hex values from 00 to FF") - - return "".join(f"{part:02X}" for part in parts_int) - - CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(Dsmr), - cv.Optional(CONF_DECRYPTION_KEY): _validate_key, + cv.Optional(CONF_DECRYPTION_KEY): lambda value: cv.bind_key( + value, name="Decryption key" + ), cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, cv.Optional(CONF_WATER_MBUS_ID, default=2): cv.int_, diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 5c62aa93ab..c78d37bf5e 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -1,4 +1,5 @@ #include "dsmr.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include @@ -294,8 +295,8 @@ void Dsmr::dump_config() { DSMR_TEXT_SENSOR_LIST(DSMR_LOG_TEXT_SENSOR, ) } -void Dsmr::set_decryption_key(const std::string &decryption_key) { - if (decryption_key.empty()) { +void Dsmr::set_decryption_key(const char *decryption_key) { + if (decryption_key == nullptr || decryption_key[0] == '\0') { ESP_LOGI(TAG, "Disabling decryption"); this->decryption_key_.clear(); if (this->crypt_telegram_ != nullptr) { @@ -305,21 +306,15 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { return; } - if (decryption_key.length() != 32) { - ESP_LOGE(TAG, "Error, decryption key must be 32 character long"); + if (!parse_hex(decryption_key, this->decryption_key_, 16)) { + ESP_LOGE(TAG, "Error, decryption key must be 32 hex characters"); + this->decryption_key_.clear(); return; } - this->decryption_key_.clear(); ESP_LOGI(TAG, "Decryption key is set"); // Verbose level prints decryption key - ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str()); - - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(decryption_key.c_str()[i * 2]), 2); - this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16)); - } + ESP_LOGV(TAG, "Using decryption key: %s", decryption_key); if (this->crypt_telegram_ == nullptr) { this->crypt_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index 56ba75b5fa..b7e05a22b3 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -63,7 +63,7 @@ class Dsmr : public Component, public uart::UARTDevice { void dump_config() override; - void set_decryption_key(const std::string &decryption_key); + void set_decryption_key(const char *decryption_key); void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; } void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; } void set_request_interval(uint32_t interval) { this->request_interval_ = interval; } diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 8e2fadbea8..b7ab02013d 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1046,20 +1046,20 @@ def mac_address(value): return core.MACAddress(*parts_int) -def bind_key(value): +def bind_key(value, *, name="Bind key"): value = string_strict(value) parts = [value[i : i + 2] for i in range(0, len(value), 2)] if len(parts) != 16: - raise Invalid("Bind key must consist of 16 hexadecimal numbers") + raise Invalid(f"{name} must consist of 16 hexadecimal numbers") parts_int = [] if any(len(part) != 2 for part in parts): - raise Invalid("Bind key must be format XX") + raise Invalid(f"{name} must be format XX") for part in parts: try: parts_int.append(int(part, 16)) except ValueError: # pylint: disable=raise-missing-from - raise Invalid("Bind key must be hex values from 00 to FF") + raise Invalid(f"{name} must be hex values from 00 to FF") return "".join(f"{part:02X}" for part in parts_int) From 8ec31dd7693028d1e987d39b67708c04133ebc66 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:35:19 -1000 Subject: [PATCH 02/12] [voice_assistant] Deprecate Timer::to_string() in favor of heap-free to_str() (#13377) --- esphome/components/voice_assistant/voice_assistant.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index b1b3df7bbd..d61a8fbbc1 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -81,6 +81,8 @@ struct Timer { this->id.c_str(), this->name.c_str(), this->total_seconds, this->seconds_left, YESNO(this->is_active)); return buffer.data(); } + // Remove before 2026.8.0 + ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0") std::string to_string() const { char buffer[TO_STR_BUFFER_SIZE]; return this->to_str(buffer); From 8998ef0bc309ec1ed5f97dc8ab372a6c5007f332 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:35:32 -1000 Subject: [PATCH 03/12] [network] Deprecate IPAddress::str() in favor of heap-free str_to() (#13378) --- esphome/components/network/ip_address.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/network/ip_address.h b/esphome/components/network/ip_address.h index 3dfcf0cb64..d0ac8164af 100644 --- a/esphome/components/network/ip_address.h +++ b/esphome/components/network/ip_address.h @@ -60,6 +60,8 @@ struct IPAddress { } IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); } IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; } + // Remove before 2026.8.0 + ESPDEPRECATED("Use str_to() instead. Removed in 2026.8.0", "2026.2.0") std::string str() const { char buf[IP_ADDRESS_BUFFER_SIZE]; this->str_to(buf); @@ -147,6 +149,8 @@ struct IPAddress { bool is_ip4() const { return IP_IS_V4(&ip_addr_); } bool is_ip6() const { return IP_IS_V6(&ip_addr_); } bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); } + // Remove before 2026.8.0 + ESPDEPRECATED("Use str_to() instead. Removed in 2026.8.0", "2026.2.0") std::string str() const { char buf[IP_ADDRESS_BUFFER_SIZE]; this->str_to(buf); From 5d787e2512aecfb7ef434a00e3cca74d51c67f4d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:35:58 -1000 Subject: [PATCH 04/12] [sprinkler] Eliminate std::string heap allocations (#13379) --- esphome/components/sprinkler/sprinkler.cpp | 20 +++++++++----------- esphome/components/sprinkler/sprinkler.h | 8 ++++---- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index 2813b4450b..35310fa2f9 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -327,14 +327,13 @@ SprinklerValveOperator *SprinklerValveRunRequest::valve_operator() { return this SprinklerValveRunRequestOrigin SprinklerValveRunRequest::request_is_from() { return this->origin_; } -Sprinkler::Sprinkler() {} -Sprinkler::Sprinkler(const std::string &name) { - // The `name` is needed to set timers up, hence non-default constructor - // replaces `set_name()` method previously existed - this->name_ = name; +Sprinkler::Sprinkler() : Sprinkler("") {} +Sprinkler::Sprinkler(const char *name) : name_(name) { + // The `name` is stored for dump_config logging this->timer_.init(2); - this->timer_.push_back({this->name_ + "sm", false, 0, 0, std::bind(&Sprinkler::sm_timer_callback_, this)}); - this->timer_.push_back({this->name_ + "vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)}); + // Timer names only need to be unique within this component instance + this->timer_.push_back({"sm", false, 0, 0, std::bind(&Sprinkler::sm_timer_callback_, this)}); + this->timer_.push_back({"vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)}); } void Sprinkler::setup() { this->all_valves_off_(true); } @@ -1575,8 +1574,7 @@ const LogString *Sprinkler::state_as_str_(SprinklerState state) { void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) { if (this->timer_duration_(timer_index) > 0) { - // FixedVector ensures timer_ can't be resized, so .c_str() pointers remain valid - this->set_timeout(this->timer_[timer_index].name.c_str(), this->timer_duration_(timer_index), + this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index), this->timer_cbf_(timer_index)); this->timer_[timer_index].start_time = millis(); this->timer_[timer_index].active = true; @@ -1587,7 +1585,7 @@ void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) { bool Sprinkler::cancel_timer_(const SprinklerTimerIndex timer_index) { this->timer_[timer_index].active = false; - return this->cancel_timeout(this->timer_[timer_index].name.c_str()); + return this->cancel_timeout(this->timer_[timer_index].name); } bool Sprinkler::timer_active_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].active; } @@ -1618,7 +1616,7 @@ void Sprinkler::sm_timer_callback_() { } void Sprinkler::dump_config() { - ESP_LOGCONFIG(TAG, "Sprinkler Controller -- %s", this->name_.c_str()); + ESP_LOGCONFIG(TAG, "Sprinkler Controller -- %s", this->name_); if (this->manual_selection_delay_.has_value()) { ESP_LOGCONFIG(TAG, " Manual Selection Delay: %" PRIu32 " seconds", this->manual_selection_delay_.value_or(0)); } diff --git a/esphome/components/sprinkler/sprinkler.h b/esphome/components/sprinkler/sprinkler.h index 273c0e9208..04efa28031 100644 --- a/esphome/components/sprinkler/sprinkler.h +++ b/esphome/components/sprinkler/sprinkler.h @@ -11,7 +11,7 @@ namespace esphome::sprinkler { -const std::string MIN_STR = "min"; +inline constexpr const char *MIN_STR = "min"; enum SprinklerState : uint8_t { // NOTE: these states are used by both SprinklerValveOperator and Sprinkler (the controller)! @@ -49,7 +49,7 @@ struct SprinklerQueueItem { }; struct SprinklerTimer { - const std::string name; + const char *name; bool active; uint32_t time; uint32_t start_time; @@ -176,7 +176,7 @@ class SprinklerValveRunRequest { class Sprinkler : public Component { public: Sprinkler(); - Sprinkler(const std::string &name); + Sprinkler(const char *name); void setup() override; void loop() override; void dump_config() override; @@ -504,7 +504,7 @@ class Sprinkler : public Component { uint32_t start_delay_{0}; uint32_t stop_delay_{0}; - std::string name_; + const char *name_{""}; /// Sprinkler controller state SprinklerState state_{IDLE}; From b5fe271d6bc9cc069d31e2c8706594c2b819eb09 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:36:47 -1000 Subject: [PATCH 05/12] [sprinkler] Disable loops when idle to reduce CPU overhead (#13381) --- esphome/components/sprinkler/sprinkler.cpp | 36 +++++++++++++++------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index 35310fa2f9..369ee5e6ff 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -43,13 +43,11 @@ SprinklerControllerSwitch::SprinklerControllerSwitch() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {} void SprinklerControllerSwitch::loop() { - if (!this->f_.has_value()) - return; + // Loop is only enabled when f_ has a value (see setup()) auto s = (*this->f_)(); - if (!s.has_value()) - return; - - this->publish_state(*s); + if (s.has_value()) { + this->publish_state(*s); + } } void SprinklerControllerSwitch::write_state(bool state) { @@ -74,7 +72,13 @@ float SprinklerControllerSwitch::get_setup_priority() const { return setup_prior Trigger<> *SprinklerControllerSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; } Trigger<> *SprinklerControllerSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; } -void SprinklerControllerSwitch::setup() { this->state = this->get_initial_state_with_restore_mode().value_or(false); } +void SprinklerControllerSwitch::setup() { + this->state = this->get_initial_state_with_restore_mode().value_or(false); + // Disable loop if no state lambda is set - nothing to poll + if (!this->f_.has_value()) { + this->disable_loop(); + } +} void SprinklerControllerSwitch::dump_config() { LOG_SWITCH("", "Sprinkler Switch", this); } @@ -336,15 +340,23 @@ Sprinkler::Sprinkler(const char *name) : name_(name) { this->timer_.push_back({"vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)}); } -void Sprinkler::setup() { this->all_valves_off_(true); } +void Sprinkler::setup() { + this->all_valves_off_(true); + // Start with loop disabled - nothing to do when idle + this->disable_loop(); +} void Sprinkler::loop() { for (auto &vo : this->valve_op_) { vo.loop(); } - if (this->prev_req_.has_request() && this->prev_req_.has_valve_operator() && - this->prev_req_.valve_operator()->state() == IDLE) { - this->prev_req_.reset(); + if (this->prev_req_.has_request()) { + if (this->prev_req_.has_valve_operator() && this->prev_req_.valve_operator()->state() == IDLE) { + this->prev_req_.reset(); + } + } else if (this->state_ == IDLE) { + // Nothing more to do - disable loop until next activation + this->disable_loop(); } } @@ -1332,6 +1344,8 @@ void Sprinkler::start_valve_(SprinklerValveRunRequest *req) { if (!this->is_a_valid_valve(req->valve())) { return; // we can't do anything if the valve number isn't valid } + // Enable loop to monitor valve operator states + this->enable_loop(); for (auto &vo : this->valve_op_) { // find the first available SprinklerValveOperator, load it and start it up if (vo.state() == IDLE) { auto run_duration = req->run_duration() ? req->run_duration() : this->valve_run_duration_adjusted(req->valve()); From 62b6c9bf7cee3a67fab39f6dc9870d69add352dd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:37:03 -1000 Subject: [PATCH 06/12] [esp32_ble] Deprecate ESPBTUUID::to_string() in favor of heap-free to_str() (#13376) --- esphome/components/esp32_ble/ble_uuid.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/esp32_ble/ble_uuid.h b/esphome/components/esp32_ble/ble_uuid.h index ae593955a4..6c8ef7bfd9 100644 --- a/esphome/components/esp32_ble/ble_uuid.h +++ b/esphome/components/esp32_ble/ble_uuid.h @@ -46,6 +46,8 @@ class ESPBTUUID { esp_bt_uuid_t get_uuid() const; + // Remove before 2026.8.0 + ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0") std::string to_string() const; const char *to_str(std::span output) const; From 4e0e7796ded16e118bb95ff63fc956850d9b3e7f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:37:19 -1000 Subject: [PATCH 07/12] [mqtt] Remove unnecessary defer in ESP8266 on_message callback (#13373) --- esphome/components/mqtt/mqtt_client.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 7d4050284f..f8517c4f89 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -610,18 +610,10 @@ static bool topic_match(const char *message, const char *subscription) { } void MQTTClientComponent::on_message(const std::string &topic, const std::string &payload) { -#ifdef USE_ESP8266 - // on ESP8266, this is called in lwIP/AsyncTCP task; some components do not like running - // from a different task. - this->defer([this, topic, payload]() { -#endif - for (auto &subscription : this->subscriptions_) { - if (topic_match(topic.c_str(), subscription.topic.c_str())) - subscription.callback(topic, payload); - } -#ifdef USE_ESP8266 - }); -#endif + for (auto &subscription : this->subscriptions_) { + if (topic_match(topic.c_str(), subscription.topic.c_str())) + subscription.callback(topic, payload); + } } // Setters From 8ade9dfc107b752bb5ac3cf03936f75bb85f5af6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:37:33 -1000 Subject: [PATCH 08/12] [shtcx] Use LogString for type to_string to save RAM on ESP8266 (#13370) --- esphome/components/shtcx/shtcx.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/shtcx/shtcx.cpp b/esphome/components/shtcx/shtcx.cpp index 933dd9bde9..aca305b88d 100644 --- a/esphome/components/shtcx/shtcx.cpp +++ b/esphome/components/shtcx/shtcx.cpp @@ -13,14 +13,14 @@ static const uint16_t SHTCX_COMMAND_READ_ID_REGISTER = 0xEFC8; static const uint16_t SHTCX_COMMAND_SOFT_RESET = 0x805D; static const uint16_t SHTCX_COMMAND_POLLING_H = 0x7866; -inline const char *to_string(SHTCXType type) { +static const LogString *shtcx_type_to_string(SHTCXType type) { switch (type) { case SHTCX_TYPE_SHTC3: - return "SHTC3"; + return LOG_STR("SHTC3"); case SHTCX_TYPE_SHTC1: - return "SHTC1"; + return LOG_STR("SHTC1"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } @@ -52,7 +52,7 @@ void SHTCXComponent::dump_config() { ESP_LOGCONFIG(TAG, "SHTCx:\n" " Model: %s (%04x)", - to_string(this->type_), this->sensor_id_); + LOG_STR_ARG(shtcx_type_to_string(this->type_)), this->sensor_id_); LOG_I2C_DEVICE(this); if (this->is_failed()) { ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); From b48d4ab785f1deb84d59865b2a0abb9adc69a44f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:37:54 -1000 Subject: [PATCH 09/12] [mqtt] Reduce heap allocations in publish path (#13372) --- esphome/components/mqtt/mqtt_client.cpp | 51 ++++++++++++++++--------- esphome/components/mqtt/mqtt_client.h | 6 +++ 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index f8517c4f89..96eba37a57 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -5,6 +5,7 @@ #include #include "esphome/components/network/util.h" #include "esphome/core/application.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/version.h" @@ -66,10 +67,13 @@ void MQTTClientComponent::setup() { "esphome/discover", [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2); - std::string topic = "esphome/ping/"; - topic.append(App.get_name()); + // Format topic on stack - subscribe() copies it + // "esphome/ping/" (13) + name (ESPHOME_DEVICE_NAME_MAX_LEN) + null (1) + constexpr size_t ping_topic_buffer_size = 13 + ESPHOME_DEVICE_NAME_MAX_LEN + 1; + char ping_topic[ping_topic_buffer_size]; + buf_append_printf(ping_topic, sizeof(ping_topic), 0, "esphome/ping/%s", App.get_name().c_str()); this->subscribe( - topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2); + ping_topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2); } if (this->enable_on_boot_) { @@ -81,8 +85,11 @@ void MQTTClientComponent::send_device_info_() { if (!this->is_connected() or !this->is_discovery_ip_enabled()) { return; } - std::string topic = "esphome/discover/"; - topic.append(App.get_name()); + // Format topic on stack to avoid heap allocation + // "esphome/discover/" (17) + name (ESPHOME_DEVICE_NAME_MAX_LEN) + null (1) + constexpr size_t topic_buffer_size = 17 + ESPHOME_DEVICE_NAME_MAX_LEN + 1; + char topic[topic_buffer_size]; + buf_append_printf(topic, sizeof(topic), 0, "esphome/discover/%s", App.get_name().c_str()); // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson this->publish_json( @@ -500,39 +507,49 @@ bool MQTTClientComponent::publish(const std::string &topic, const std::string &p bool MQTTClientComponent::publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos, bool retain) { - return publish({.topic = topic, .payload = std::string(payload, payload_length), .qos = qos, .retain = retain}); + return this->publish(topic.c_str(), payload, payload_length, qos, retain); } bool MQTTClientComponent::publish(const MQTTMessage &message) { + return this->publish(message.topic.c_str(), message.payload.c_str(), message.payload.length(), message.qos, + message.retain); +} +bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos, + bool retain) { + return this->publish_json(topic.c_str(), f, qos, retain); +} + +bool MQTTClientComponent::publish(const char *topic, const char *payload, size_t payload_length, uint8_t qos, + bool retain) { if (!this->is_connected()) { - // critical components will re-transmit their messages return false; } - bool logging_topic = this->log_message_.topic == message.topic; - bool ret = this->mqtt_backend_.publish(message); + size_t topic_len = strlen(topic); + bool logging_topic = (topic_len == this->log_message_.topic.size()) && + (memcmp(this->log_message_.topic.c_str(), topic, topic_len) == 0); + bool ret = this->mqtt_backend_.publish(topic, payload, payload_length, qos, retain); delay(0); if (!ret && !logging_topic && this->is_connected()) { delay(0); - ret = this->mqtt_backend_.publish(message); + ret = this->mqtt_backend_.publish(topic, payload, payload_length, qos, retain); delay(0); } if (!logging_topic) { if (ret) { - ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d qos=%d)", message.topic.c_str(), message.payload.c_str(), - message.retain, message.qos); + ESP_LOGV(TAG, "Publish(topic='%s' retain=%d qos=%d)", topic, retain, qos); + ESP_LOGVV(TAG, "Publish payload (len=%u): '%.*s'", payload_length, static_cast(payload_length), payload); } else { - ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). Will retry", message.topic.c_str(), - message.payload.length()); + ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). Will retry", topic, payload_length); this->status_momentary_warning("publish", 1000); } } return ret != 0; } -bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos, - bool retain) { + +bool MQTTClientComponent::publish_json(const char *topic, const json::json_build_t &f, uint8_t qos, bool retain) { std::string message = json::build_json(f); - return this->publish(topic, message, qos, retain); + return this->publish(topic, message.c_str(), message.length(), qos, retain); } void MQTTClientComponent::enable() { diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 9e9db03b19..38bc0b4da3 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -229,6 +229,9 @@ class MQTTClientComponent : public Component bool publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos = 0, bool retain = false); + /// Publish directly without creating MQTTMessage (avoids heap allocation for topic) + bool publish(const char *topic, const char *payload, size_t payload_length, uint8_t qos = 0, bool retain = false); + /** Construct and send a JSON MQTT message. * * @param topic The topic. @@ -237,6 +240,9 @@ class MQTTClientComponent : public Component */ bool publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos = 0, bool retain = false); + /// Publish JSON directly without heap allocation for topic + bool publish_json(const char *topic, const json::json_build_t &f, uint8_t qos = 0, bool retain = false); + /// Setup the MQTT client, registering a bunch of callbacks and attempting to connect. void setup() override; void dump_config() override; From e88093ca605411cdd5f500c90d888d9ab66428e2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:38:08 -1000 Subject: [PATCH 10/12] [am43][lightwaverf][rf_bridge][spi_led_strip] Replace sprintf with safe alternatives (#13302) --- esphome/components/am43/am43_base.cpp | 18 ++++++------------ esphome/components/lightwaverf/lightwaverf.cpp | 8 ++++++-- esphome/components/rf_bridge/rf_bridge.cpp | 11 ++++++----- .../components/spi_led_strip/spi_led_strip.cpp | 14 +++++++------- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/esphome/components/am43/am43_base.cpp b/esphome/components/am43/am43_base.cpp index af474dcb79..d70e638382 100644 --- a/esphome/components/am43/am43_base.cpp +++ b/esphome/components/am43/am43_base.cpp @@ -1,21 +1,12 @@ #include "am43_base.h" +#include "esphome/core/helpers.h" #include -#include namespace esphome { namespace am43 { const uint8_t START_PACKET[5] = {0x00, 0xff, 0x00, 0x00, 0x9a}; -std::string pkt_to_hex(const uint8_t *data, uint16_t len) { - char buf[64]; - memset(buf, 0, 64); - for (int i = 0; i < len; i++) - sprintf(&buf[i * 2], "%02x", data[i]); - std::string ret = buf; - return ret; -} - Am43Packet *Am43Encoder::get_battery_level_request() { uint8_t data = 0x1; return this->encode_(0xA2, &data, 1); @@ -73,7 +64,9 @@ Am43Packet *Am43Encoder::encode_(uint8_t command, uint8_t *data, uint8_t length) memcpy(&this->packet_.data[7], data, length); this->packet_.length = length + 7; this->checksum_(); - ESP_LOGV("am43", "ENC(%d): 0x%s", packet_.length, pkt_to_hex(packet_.data, packet_.length).c_str()); + char hex_buf[format_hex_size(sizeof(this->packet_.data))]; + ESP_LOGV("am43", "ENC(%d): 0x%s", this->packet_.length, + format_hex_to(hex_buf, this->packet_.data, this->packet_.length)); return &this->packet_; } @@ -88,7 +81,8 @@ void Am43Decoder::decode(const uint8_t *data, uint16_t length) { this->has_set_state_response_ = false; this->has_position_ = false; this->has_pin_response_ = false; - ESP_LOGV("am43", "DEC(%d): 0x%s", length, pkt_to_hex(data, length).c_str()); + char hex_buf[format_hex_size(24)]; // Max expected packet size + ESP_LOGV("am43", "DEC(%d): 0x%s", length, format_hex_to(hex_buf, data, length)); if (length < 2 || data[0] != 0x9a) return; diff --git a/esphome/components/lightwaverf/lightwaverf.cpp b/esphome/components/lightwaverf/lightwaverf.cpp index 31ac1fc576..2b44195c97 100644 --- a/esphome/components/lightwaverf/lightwaverf.cpp +++ b/esphome/components/lightwaverf/lightwaverf.cpp @@ -1,3 +1,4 @@ +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP8266 @@ -44,13 +45,16 @@ void LightWaveRF::send_rx(const std::vector &msg, uint8_t repeats, bool } void LightWaveRF::print_msg_(uint8_t *msg, uint8_t len) { - char buffer[65]; +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG + char buffer[65]; // max 10 entries * 6 chars + null ESP_LOGD(TAG, " Received code (len:%i): ", len); + size_t pos = 0; for (int i = 0; i < len; i++) { - sprintf(&buffer[i * 6], "0x%02x, ", msg[i]); + pos = buf_append_printf(buffer, sizeof(buffer), pos, "0x%02x, ", msg[i]); } ESP_LOGD(TAG, "[%s]", buffer); +#endif } void LightWaveRF::dump_config() { diff --git a/esphome/components/rf_bridge/rf_bridge.cpp b/esphome/components/rf_bridge/rf_bridge.cpp index 52ce037dbe..8105767485 100644 --- a/esphome/components/rf_bridge/rf_bridge.cpp +++ b/esphome/components/rf_bridge/rf_bridge.cpp @@ -1,6 +1,7 @@ #include "rf_bridge.h" -#include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" #include #include @@ -72,9 +73,9 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) { data.length = raw[2]; data.protocol = raw[3]; - char next_byte[3]; + char next_byte[3]; // 2 hex chars + null for (uint8_t i = 0; i < data.length - 1; i++) { - sprintf(next_byte, "%02X", raw[4 + i]); + buf_append_printf(next_byte, sizeof(next_byte), 0, "%02X", raw[4 + i]); data.code += next_byte; } @@ -90,10 +91,10 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) { uint8_t buckets = raw[2] << 1; std::string str; - char next_byte[3]; + char next_byte[3]; // 2 hex chars + null for (uint32_t i = 0; i <= at; i++) { - sprintf(next_byte, "%02X", raw[i]); + buf_append_printf(next_byte, sizeof(next_byte), 0, "%02X", raw[i]); str += next_byte; if ((i > 3) && buckets) { buckets--; diff --git a/esphome/components/spi_led_strip/spi_led_strip.cpp b/esphome/components/spi_led_strip/spi_led_strip.cpp index afb51afe3a..ff8d2e6ee0 100644 --- a/esphome/components/spi_led_strip/spi_led_strip.cpp +++ b/esphome/components/spi_led_strip/spi_led_strip.cpp @@ -1,4 +1,5 @@ #include "spi_led_strip.h" +#include "esphome/core/helpers.h" namespace esphome { namespace spi_led_strip { @@ -47,15 +48,14 @@ void SpiLedStrip::dump_config() { void SpiLedStrip::write_state(light::LightState *state) { if (this->is_failed()) return; - if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) { - char strbuf[49]; - size_t len = std::min(this->buffer_size_, (size_t) (sizeof(strbuf) - 1) / 3); - memset(strbuf, 0, sizeof(strbuf)); - for (size_t i = 0; i != len; i++) { - sprintf(strbuf + i * 3, "%02X ", this->buf_[i]); - } +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + { + char strbuf[49]; // format_hex_pretty_size(16) = 48, fits 16 bytes + size_t len = std::min(this->buffer_size_, (size_t) 16); + format_hex_pretty_to(strbuf, sizeof(strbuf), this->buf_, len, ' '); esph_log_v(TAG, "write_state: buf = %s", strbuf); } +#endif this->enable(); this->write_array(this->buf_, this->buffer_size_); this->disable(); From 5d7b38b26164940883c2d45715d20b85cb7de874 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:38:22 -1000 Subject: [PATCH 11/12] [ezo_pmp] Replace sprintf with bounds-checked snprintf (#13304) --- esphome/components/ezo_pmp/ezo_pmp.cpp | 47 ++++++++++++++------------ 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/esphome/components/ezo_pmp/ezo_pmp.cpp b/esphome/components/ezo_pmp/ezo_pmp.cpp index 61b601328a..9d2f4fc687 100644 --- a/esphome/components/ezo_pmp/ezo_pmp.cpp +++ b/esphome/components/ezo_pmp/ezo_pmp.cpp @@ -318,90 +318,93 @@ void EzoPMP::send_next_command_() { switch (this->next_command_) { // Read Commands case EZO_PMP_COMMAND_READ_DOSING: // Page 54 - command_buffer_length = sprintf((char *) command_buffer, "D,?"); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "D,?"); break; case EZO_PMP_COMMAND_READ_SINGLE_REPORT: // Single Report (page 53) - command_buffer_length = sprintf((char *) command_buffer, "R"); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "R"); break; case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE: - command_buffer_length = sprintf((char *) command_buffer, "DC,?"); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "DC,?"); break; case EZO_PMP_COMMAND_READ_PAUSE_STATUS: - command_buffer_length = sprintf((char *) command_buffer, "P,?"); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "P,?"); break; case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED: - command_buffer_length = sprintf((char *) command_buffer, "TV,?"); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "TV,?"); break; case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED: - command_buffer_length = sprintf((char *) command_buffer, "ATV,?"); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "ATV,?"); break; case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS: - command_buffer_length = sprintf((char *) command_buffer, "Cal,?"); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Cal,?"); break; case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE: - command_buffer_length = sprintf((char *) command_buffer, "PV,?"); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "PV,?"); break; // Non-Read Commands case EZO_PMP_COMMAND_FIND: // Find (page 52) - command_buffer_length = sprintf((char *) command_buffer, "Find"); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Find"); wait_time_for_command = 60000; // This command will block all updates for a minute break; case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54) - command_buffer_length = sprintf((char *) command_buffer, "D,*"); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "D,*"); break; case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED: // Clear Total Volume Dosed (page 64) - command_buffer_length = sprintf((char *) command_buffer, "Clear"); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Clear"); break; case EZO_PMP_COMMAND_CLEAR_CALIBRATION: // Clear Calibration (page 65) - command_buffer_length = sprintf((char *) command_buffer, "Cal,clear"); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Cal,clear"); break; case EZO_PMP_COMMAND_PAUSE_DOSING: // Pause (page 61) - command_buffer_length = sprintf((char *) command_buffer, "P"); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "P"); break; case EZO_PMP_COMMAND_STOP_DOSING: // Stop (page 62) - command_buffer_length = sprintf((char *) command_buffer, "X"); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "X"); break; // Non-Read commands with parameters case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55) - command_buffer_length = sprintf((char *) command_buffer, "D,%0.1f", this->next_command_volume_); + command_buffer_length = + snprintf((char *) command_buffer, sizeof(command_buffer), "D,%0.1f", this->next_command_volume_); break; case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56) - command_buffer_length = - sprintf((char *) command_buffer, "D,%0.1f,%i", this->next_command_volume_, this->next_command_duration_); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "D,%0.1f,%i", + this->next_command_volume_, this->next_command_duration_); break; case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57) - command_buffer_length = - sprintf((char *) command_buffer, "DC,%0.1f,%i", this->next_command_volume_, this->next_command_duration_); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "DC,%0.1f,%i", + this->next_command_volume_, this->next_command_duration_); break; case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME: // Set Calibration Volume (page 65) - command_buffer_length = sprintf((char *) command_buffer, "Cal,%0.2f", this->next_command_volume_); + command_buffer_length = + snprintf((char *) command_buffer, sizeof(command_buffer), "Cal,%0.2f", this->next_command_volume_); break; case EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS: // Change I2C Address (page 73) - command_buffer_length = sprintf((char *) command_buffer, "I2C,%i", this->next_command_duration_); + command_buffer_length = + snprintf((char *) command_buffer, sizeof(command_buffer), "I2C,%i", this->next_command_duration_); break; case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS: // Run an arbitrary command - command_buffer_length = sprintf((char *) command_buffer, this->arbitrary_command_, this->next_command_duration_); + command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "%s", this->arbitrary_command_); ESP_LOGI(TAG, "Sending arbitrary command: %s", (char *) command_buffer); break; From ea70faf642e12f6d6cab14b70cee6861bda1cdc0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:38:56 -1000 Subject: [PATCH 12/12] [debug] Use shared buf_append_printf helper from core (#13260) --- esphome/components/debug/debug_component.cpp | 2 +- esphome/components/debug/debug_component.h | 41 +------------------- esphome/components/debug/debug_esp32.cpp | 30 +++++++------- esphome/components/debug/debug_esp8266.cpp | 22 +++++------ esphome/components/debug/debug_libretiny.cpp | 12 +++--- esphome/components/debug/debug_rp2040.cpp | 2 +- esphome/components/debug/debug_zephyr.cpp | 20 +++++----- 7 files changed, 45 insertions(+), 84 deletions(-) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index ae38fb2ccd..15f68c3a3b 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -30,7 +30,7 @@ void DebugComponent::dump_config() { char device_info_buffer[DEVICE_INFO_BUFFER_SIZE]; ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); - size_t pos = buf_append(device_info_buffer, DEVICE_INFO_BUFFER_SIZE, 0, "%s", ESPHOME_VERSION); + size_t pos = buf_append_printf(device_info_buffer, DEVICE_INFO_BUFFER_SIZE, 0, "%s", ESPHOME_VERSION); this->free_heap_ = get_free_heap_(); ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_); diff --git a/esphome/components/debug/debug_component.h b/esphome/components/debug/debug_component.h index 6cf52d890c..e4f4bb36eb 100644 --- a/esphome/components/debug/debug_component.h +++ b/esphome/components/debug/debug_component.h @@ -5,12 +5,6 @@ #include "esphome/core/helpers.h" #include "esphome/core/macros.h" #include -#include -#include -#include -#ifdef USE_ESP8266 -#include -#endif #ifdef USE_SENSOR #include "esphome/components/sensor/sensor.h" @@ -25,40 +19,7 @@ namespace debug { static constexpr size_t DEVICE_INFO_BUFFER_SIZE = 256; static constexpr size_t RESET_REASON_BUFFER_SIZE = 128; -#ifdef USE_ESP8266 -// ESP8266: Use vsnprintf_P to keep format strings in flash (PROGMEM) -// Format strings must be wrapped with PSTR() macro -inline size_t buf_append_p(char *buf, size_t size, size_t pos, PGM_P fmt, ...) { - if (pos >= size) { - return size; - } - va_list args; - va_start(args, fmt); - int written = vsnprintf_P(buf + pos, size - pos, fmt, args); - va_end(args); - if (written < 0) { - return pos; // encoding error - } - return std::min(pos + static_cast(written), size); -} -#define buf_append(buf, size, pos, fmt, ...) buf_append_p(buf, size, pos, PSTR(fmt), ##__VA_ARGS__) -#else -/// Safely append formatted string to buffer, returning new position (capped at size) -__attribute__((format(printf, 4, 5))) inline size_t buf_append(char *buf, size_t size, size_t pos, const char *fmt, - ...) { - if (pos >= size) { - return size; - } - va_list args; - va_start(args, fmt); - int written = vsnprintf(buf + pos, size - pos, fmt, args); - va_end(args); - if (written < 0) { - return pos; // encoding error - } - return std::min(pos + static_cast(written), size); -} -#endif +// buf_append_printf is now provided by esphome/core/helpers.h class DebugComponent : public PollingComponent { public: diff --git a/esphome/components/debug/debug_esp32.cpp b/esphome/components/debug/debug_esp32.cpp index 8c41011f7d..aad4c7426c 100644 --- a/esphome/components/debug/debug_esp32.cpp +++ b/esphome/components/debug/debug_esp32.cpp @@ -173,8 +173,8 @@ size_t DebugComponent::get_device_info_(std::span uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode); - pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed, - flash_mode); + pos = buf_append_printf(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed, + flash_mode); #endif esp_chip_info_t info; @@ -182,52 +182,52 @@ size_t DebugComponent::get_device_info_(std::span const char *model = ESPHOME_VARIANT; // Build features string - pos = buf_append(buf, size, pos, "|Chip: %s Features:", model); + pos = buf_append_printf(buf, size, pos, "|Chip: %s Features:", model); bool first_feature = true; for (const auto &feature : CHIP_FEATURES) { if (info.features & feature.bit) { - pos = buf_append(buf, size, pos, "%s%s", first_feature ? "" : ", ", feature.name); + pos = buf_append_printf(buf, size, pos, "%s%s", first_feature ? "" : ", ", feature.name); first_feature = false; info.features &= ~feature.bit; } } if (info.features != 0) { - pos = buf_append(buf, size, pos, "%sOther:0x%" PRIx32, first_feature ? "" : ", ", info.features); + pos = buf_append_printf(buf, size, pos, "%sOther:0x%" PRIx32, first_feature ? "" : ", ", info.features); } ESP_LOGD(TAG, "Chip: Model=%s, Cores=%u, Revision=%u", model, info.cores, info.revision); - pos = buf_append(buf, size, pos, " Cores:%u Revision:%u", info.cores, info.revision); + pos = buf_append_printf(buf, size, pos, " Cores:%u Revision:%u", info.cores, info.revision); uint32_t cpu_freq_mhz = arch_get_cpu_freq_hz() / 1000000; ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz); - pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz); + pos = buf_append_printf(buf, size, pos, "|CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz); // Framework detection #ifdef USE_ARDUINO ESP_LOGD(TAG, "Framework: Arduino"); - pos = buf_append(buf, size, pos, "|Framework: Arduino"); + pos = buf_append_printf(buf, size, pos, "|Framework: Arduino"); #elif defined(USE_ESP32) ESP_LOGD(TAG, "Framework: ESP-IDF"); - pos = buf_append(buf, size, pos, "|Framework: ESP-IDF"); + pos = buf_append_printf(buf, size, pos, "|Framework: ESP-IDF"); #else ESP_LOGW(TAG, "Framework: UNKNOWN"); - pos = buf_append(buf, size, pos, "|Framework: UNKNOWN"); + pos = buf_append_printf(buf, size, pos, "|Framework: UNKNOWN"); #endif ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version()); - pos = buf_append(buf, size, pos, "|ESP-IDF: %s", esp_get_idf_version()); + pos = buf_append_printf(buf, size, pos, "|ESP-IDF: %s", esp_get_idf_version()); uint8_t mac[6]; get_mac_address_raw(mac); ESP_LOGD(TAG, "EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - pos = buf_append(buf, size, pos, "|EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], - mac[5]); + pos = buf_append_printf(buf, size, pos, "|EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], + mac[4], mac[5]); char reason_buffer[RESET_REASON_BUFFER_SIZE]; const char *reset_reason = get_reset_reason_(std::span(reason_buffer)); - pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason); + pos = buf_append_printf(buf, size, pos, "|Reset: %s", reset_reason); const char *wakeup_cause = get_wakeup_cause_(std::span(reason_buffer)); - pos = buf_append(buf, size, pos, "|Wakeup: %s", wakeup_cause); + pos = buf_append_printf(buf, size, pos, "|Wakeup: %s", wakeup_cause); return pos; } diff --git a/esphome/components/debug/debug_esp8266.cpp b/esphome/components/debug/debug_esp8266.cpp index 274f77e20d..19f15d7d98 100644 --- a/esphome/components/debug/debug_esp8266.cpp +++ b/esphome/components/debug/debug_esp8266.cpp @@ -53,8 +53,8 @@ size_t DebugComponent::get_device_info_(std::span uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode); - pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed, - flash_mode); + pos = buf_append_printf(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed, + flash_mode); #if !defined(CLANG_TIDY) char reason_buffer[RESET_REASON_BUFFER_SIZE]; @@ -77,15 +77,15 @@ size_t DebugComponent::get_device_info_(std::span chip_id, ESP.getSdkVersion(), ESP.getCoreVersion().c_str(), boot_version, boot_mode, cpu_freq, flash_chip_id, reset_reason, ESP.getResetInfo().c_str()); - pos = buf_append(buf, size, pos, "|Chip: 0x%08" PRIX32, chip_id); - pos = buf_append(buf, size, pos, "|SDK: %s", ESP.getSdkVersion()); - pos = buf_append(buf, size, pos, "|Core: %s", ESP.getCoreVersion().c_str()); - pos = buf_append(buf, size, pos, "|Boot: %u", boot_version); - pos = buf_append(buf, size, pos, "|Mode: %u", boot_mode); - pos = buf_append(buf, size, pos, "|CPU: %u", cpu_freq); - pos = buf_append(buf, size, pos, "|Flash: 0x%08" PRIX32, flash_chip_id); - pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason); - pos = buf_append(buf, size, pos, "|%s", ESP.getResetInfo().c_str()); + pos = buf_append_printf(buf, size, pos, "|Chip: 0x%08" PRIX32, chip_id); + pos = buf_append_printf(buf, size, pos, "|SDK: %s", ESP.getSdkVersion()); + pos = buf_append_printf(buf, size, pos, "|Core: %s", ESP.getCoreVersion().c_str()); + pos = buf_append_printf(buf, size, pos, "|Boot: %u", boot_version); + pos = buf_append_printf(buf, size, pos, "|Mode: %u", boot_mode); + pos = buf_append_printf(buf, size, pos, "|CPU: %u", cpu_freq); + pos = buf_append_printf(buf, size, pos, "|Flash: 0x%08" PRIX32, flash_chip_id); + pos = buf_append_printf(buf, size, pos, "|Reset: %s", reset_reason); + pos = buf_append_printf(buf, size, pos, "|%s", ESP.getResetInfo().c_str()); #endif return pos; diff --git a/esphome/components/debug/debug_libretiny.cpp b/esphome/components/debug/debug_libretiny.cpp index aae27c8ca2..14bbdb945a 100644 --- a/esphome/components/debug/debug_libretiny.cpp +++ b/esphome/components/debug/debug_libretiny.cpp @@ -36,12 +36,12 @@ size_t DebugComponent::get_device_info_(std::span lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), mac_id, lt_get_board_code(), flash_kib, ram_kib, reset_reason); - pos = buf_append(buf, size, pos, "|Version: %s", LT_BANNER_STR + 10); - pos = buf_append(buf, size, pos, "|Reset Reason: %s", reset_reason); - pos = buf_append(buf, size, pos, "|Chip Name: %s", lt_cpu_get_model_name()); - pos = buf_append(buf, size, pos, "|Chip ID: 0x%06" PRIX32, mac_id); - pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 " KiB", flash_kib); - pos = buf_append(buf, size, pos, "|RAM: %" PRIu32 " KiB", ram_kib); + pos = buf_append_printf(buf, size, pos, "|Version: %s", LT_BANNER_STR + 10); + pos = buf_append_printf(buf, size, pos, "|Reset Reason: %s", reset_reason); + pos = buf_append_printf(buf, size, pos, "|Chip Name: %s", lt_cpu_get_model_name()); + pos = buf_append_printf(buf, size, pos, "|Chip ID: 0x%06" PRIX32, mac_id); + pos = buf_append_printf(buf, size, pos, "|Flash: %" PRIu32 " KiB", flash_kib); + pos = buf_append_printf(buf, size, pos, "|RAM: %" PRIu32 " KiB", ram_kib); return pos; } diff --git a/esphome/components/debug/debug_rp2040.cpp b/esphome/components/debug/debug_rp2040.cpp index a426a73bc2..c9d41942db 100644 --- a/esphome/components/debug/debug_rp2040.cpp +++ b/esphome/components/debug/debug_rp2040.cpp @@ -19,7 +19,7 @@ size_t DebugComponent::get_device_info_(std::span uint32_t cpu_freq = rp2040.f_cpu(); ESP_LOGD(TAG, "CPU Frequency: %" PRIu32, cpu_freq); - pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32, cpu_freq); + pos = buf_append_printf(buf, size, pos, "|CPU Frequency: %" PRIu32, cpu_freq); return pos; } diff --git a/esphome/components/debug/debug_zephyr.cpp b/esphome/components/debug/debug_zephyr.cpp index 3f9af03b2b..6a88522b0d 100644 --- a/esphome/components/debug/debug_zephyr.cpp +++ b/esphome/components/debug/debug_zephyr.cpp @@ -20,9 +20,9 @@ static size_t append_reset_reason(char *buf, size_t size, size_t pos, bool set, return pos; } if (pos > 0) { - pos = buf_append(buf, size, pos, ", "); + pos = buf_append_printf(buf, size, pos, ", "); } - return buf_append(buf, size, pos, "%s", reason); + return buf_append_printf(buf, size, pos, "%s", reason); } static inline uint32_t read_mem_u32(uintptr_t addr) { @@ -140,7 +140,7 @@ size_t DebugComponent::get_device_info_(std::span const char *supply_status = (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) ? "Normal voltage." : "High voltage."; ESP_LOGD(TAG, "Main supply status: %s", supply_status); - pos = buf_append(buf, size, pos, "|Main supply status: %s", supply_status); + pos = buf_append_printf(buf, size, pos, "|Main supply status: %s", supply_status); // Regulator stage 0 if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_HIGH) { @@ -172,16 +172,16 @@ size_t DebugComponent::get_device_info_(std::span reg0_voltage = "???V"; } ESP_LOGD(TAG, "Regulator stage 0: %s, %s", reg0_type, reg0_voltage); - pos = buf_append(buf, size, pos, "|Regulator stage 0: %s, %s", reg0_type, reg0_voltage); + pos = buf_append_printf(buf, size, pos, "|Regulator stage 0: %s, %s", reg0_type, reg0_voltage); } else { ESP_LOGD(TAG, "Regulator stage 0: disabled"); - pos = buf_append(buf, size, pos, "|Regulator stage 0: disabled"); + pos = buf_append_printf(buf, size, pos, "|Regulator stage 0: disabled"); } // Regulator stage 1 const char *reg1_type = nrf_power_dcdcen_get(NRF_POWER) ? "DC/DC" : "LDO"; ESP_LOGD(TAG, "Regulator stage 1: %s", reg1_type); - pos = buf_append(buf, size, pos, "|Regulator stage 1: %s", reg1_type); + pos = buf_append_printf(buf, size, pos, "|Regulator stage 1: %s", reg1_type); // USB power state const char *usb_state; @@ -195,7 +195,7 @@ size_t DebugComponent::get_device_info_(std::span usb_state = "disconnected"; } ESP_LOGD(TAG, "USB power state: %s", usb_state); - pos = buf_append(buf, size, pos, "|USB power state: %s", usb_state); + pos = buf_append_printf(buf, size, pos, "|USB power state: %s", usb_state); // Power-fail comparator bool enabled; @@ -300,14 +300,14 @@ size_t DebugComponent::get_device_info_(std::span break; } ESP_LOGD(TAG, "Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage); - pos = buf_append(buf, size, pos, "|Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage); + pos = buf_append_printf(buf, size, pos, "|Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage); } else { ESP_LOGD(TAG, "Power-fail comparator: %s", pof_voltage); - pos = buf_append(buf, size, pos, "|Power-fail comparator: %s", pof_voltage); + pos = buf_append_printf(buf, size, pos, "|Power-fail comparator: %s", pof_voltage); } } else { ESP_LOGD(TAG, "Power-fail comparator: disabled"); - pos = buf_append(buf, size, pos, "|Power-fail comparator: disabled"); + pos = buf_append_printf(buf, size, pos, "|Power-fail comparator: disabled"); } auto package = [](uint32_t value) {