From 280d46002535de0ae2b88678ef6e804aa7959cb1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:40:20 -1000 Subject: [PATCH 1/5] [statsd] Use direct appends and stack buffer instead of str_sprintf (#13223) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/statsd/statsd.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/esphome/components/statsd/statsd.cpp b/esphome/components/statsd/statsd.cpp index 7729f36858..7d773bc56e 100644 --- a/esphome/components/statsd/statsd.cpp +++ b/esphome/components/statsd/statsd.cpp @@ -114,14 +114,22 @@ void StatsdComponent::update() { // This implies you can't explicitly set a gauge to a negative number without first setting it to zero. if (val < 0) { if (this->prefix_) { - out.append(str_sprintf("%s.", this->prefix_)); + out.append(this->prefix_); + out.append("."); } - out.append(str_sprintf("%s:0|g\n", s.name)); + out.append(s.name); + out.append(":0|g\n"); } if (this->prefix_) { - out.append(str_sprintf("%s.", this->prefix_)); + out.append(this->prefix_); + out.append("."); } - out.append(str_sprintf("%s:%f|g\n", s.name, val)); + out.append(s.name); + // Buffer for ":" + value + "|g\n". + // %f with -DBL_MAX can produce up to 321 chars, plus ":" and "|g\n" (4) + null = 326 + char val_buf[330]; + buf_append_printf(val_buf, sizeof(val_buf), 0, ":%f|g\n", val); + out.append(val_buf); if (out.length() > SEND_THRESHOLD) { this->send_(&out); From d0e50ed03078f10755bef8c032c20752c8c661f0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:40:51 -1000 Subject: [PATCH 2/5] [lock] Extract set_state_ helper to reduce code duplication (#13359) --- esphome/components/lock/lock.cpp | 12 +++++------- esphome/components/lock/lock.h | 3 +++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/esphome/components/lock/lock.cpp b/esphome/components/lock/lock.cpp index aca6ec10f3..9fa1ba3600 100644 --- a/esphome/components/lock/lock.cpp +++ b/esphome/components/lock/lock.cpp @@ -28,16 +28,14 @@ const LogString *lock_state_to_string(LockState state) { Lock::Lock() : state(LOCK_STATE_NONE) {} LockCall Lock::make_call() { return LockCall(this); } -void Lock::lock() { +void Lock::set_state_(LockState state) { auto call = this->make_call(); - call.set_state(LOCK_STATE_LOCKED); - this->control(call); -} -void Lock::unlock() { - auto call = this->make_call(); - call.set_state(LOCK_STATE_UNLOCKED); + call.set_state(state); this->control(call); } + +void Lock::lock() { this->set_state_(LOCK_STATE_LOCKED); } +void Lock::unlock() { this->set_state_(LOCK_STATE_UNLOCKED); } void Lock::open() { if (traits.get_supports_open()) { ESP_LOGD(TAG, "'%s' Opening.", this->get_name().c_str()); diff --git a/esphome/components/lock/lock.h b/esphome/components/lock/lock.h index f77b11b145..b518c8b846 100644 --- a/esphome/components/lock/lock.h +++ b/esphome/components/lock/lock.h @@ -156,6 +156,9 @@ class Lock : public EntityBase { protected: friend LockCall; + /// Helper for lock/unlock convenience methods + void set_state_(LockState state); + /** Perform the open latch action with hardware. This method is optional to implement * when creating a new lock. * From aeea340bc66109def4cb346ede88bd23b98f856a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:41:03 -1000 Subject: [PATCH 3/5] [cs5460a] Remove unnecessary empty loop override (#13357) --- esphome/components/cs5460a/cs5460a.h | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/cs5460a/cs5460a.h b/esphome/components/cs5460a/cs5460a.h index 11b13f5851..99c3017510 100644 --- a/esphome/components/cs5460a/cs5460a.h +++ b/esphome/components/cs5460a/cs5460a.h @@ -76,7 +76,6 @@ class CS5460AComponent : public Component, void restart() { restart_(); } void setup() override; - void loop() override {} void dump_config() override; protected: From 6cf320fd608c73bb24c580100894a5bf25c24d8c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:41:55 -1000 Subject: [PATCH 4/5] [mqtt] Eliminate per-entity loop overhead and heap churn (#13356) --- esphome/components/mqtt/mqtt_client.cpp | 6 ++++++ esphome/components/mqtt/mqtt_component.cpp | 12 ++++-------- esphome/components/mqtt/mqtt_component.h | 5 +++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 96eba37a57..de79b61358 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -403,6 +403,12 @@ void MQTTClientComponent::loop() { this->last_connected_ = now; this->resubscribe_subscriptions_(); + + // Process pending resends for all MQTT components centrally + // This is more efficient than each component polling in its own loop + for (MQTTComponent *component : this->children_) { + component->process_resend(); + } } break; } diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 3b9290259b..2ba466af3b 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -308,16 +308,12 @@ void MQTTComponent::call_setup() { } } -void MQTTComponent::call_loop() { - if (this->is_internal()) +void MQTTComponent::process_resend() { + // Called by MQTTClientComponent when connected to process pending resends + // Note: is_internal() check not needed - internal components are never registered + if (!this->resend_state_) return; - this->loop(); - - if (!this->resend_state_ || !this->is_connected_()) { - return; - } - this->resend_state_ = false; if (this->is_discovery_enabled()) { if (!this->send_discovery_()) { diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 676e3ad35d..dea91e3d5a 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -81,8 +81,6 @@ class MQTTComponent : public Component { /// Override setup_ so that we can call send_discovery() when needed. void call_setup() override; - void call_loop() override; - void call_dump_config() override; /// Send discovery info the Home Assistant, override this. @@ -133,6 +131,9 @@ class MQTTComponent : public Component { /// Internal method for the MQTT client base to schedule a resend of the state on reconnect. void schedule_resend_state(); + /// Process pending resend if needed (called by MQTTClientComponent) + void process_resend(); + /** Send a MQTT message. * * @param topic The topic. From c213de4861762cf3474e4fad8ec2ce3c37440c67 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:42:08 -1000 Subject: [PATCH 5/5] [mapping] Use stack buffers for numeric key error logging (#13299) --- esphome/components/mapping/mapping.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/esphome/components/mapping/mapping.h b/esphome/components/mapping/mapping.h index 99c1f38829..2b8f0d39b2 100644 --- a/esphome/components/mapping/mapping.h +++ b/esphome/components/mapping/mapping.h @@ -2,6 +2,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include #include #include @@ -43,8 +44,17 @@ template class Mapping { esph_log_e(TAG, "Key '%p' not found in mapping", key); } else if constexpr (std::is_same_v) { esph_log_e(TAG, "Key '%s' not found in mapping", key.c_str()); + } else if constexpr (std::is_integral_v) { + char buf[24]; // enough for 64-bit integer + if constexpr (std::is_unsigned_v) { + buf_append_printf(buf, sizeof(buf), 0, "%" PRIu64, static_cast(key)); + } else { + buf_append_printf(buf, sizeof(buf), 0, "%" PRId64, static_cast(key)); + } + esph_log_e(TAG, "Key '%s' not found in mapping", buf); } else { - esph_log_e(TAG, "Key '%s' not found in mapping", to_string(key).c_str()); + // All supported key types are handled above - this should never be reached + static_assert(sizeof(K) == 0, "Unsupported key type for Mapping error logging"); } return {}; }