From d8b541e92da82edea1bc8ffb559ce64d2a41abaa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 12:16:20 -1000 Subject: [PATCH] [text_sensor][text] Add const char* overloads to publish_state to eliminate heap churn --- esphome/components/text/text.cpp | 18 +++++--- esphome/components/text/text.h | 2 + .../components/text_sensor/text_sensor.cpp | 46 +++++++++++++------ esphome/components/text_sensor/text_sensor.h | 5 ++ 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/esphome/components/text/text.cpp b/esphome/components/text/text.cpp index d06c350832..3824c5004d 100644 --- a/esphome/components/text/text.cpp +++ b/esphome/components/text/text.cpp @@ -2,22 +2,26 @@ #include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" +#include namespace esphome { namespace text { static const char *const TAG = "text"; -void Text::publish_state(const std::string &state) { - this->set_has_state(true); - this->state = state; - if (this->traits.get_mode() == TEXT_MODE_PASSWORD) { - ESP_LOGD(TAG, "'%s': Sending state " LOG_SECRET("'%s'"), this->get_name().c_str(), state.c_str()); +void Text::publish_state(const std::string &state) { this->publish_state(state.data(), state.size()); } +void Text::publish_state(const char *state) { this->publish_state(state, strlen(state)); } + +void Text::publish_state(const char *state, size_t len) { + this->set_has_state(true); + this->state.assign(state, len); + if (this->traits.get_mode() == TEXT_MODE_PASSWORD) { + ESP_LOGD(TAG, "'%s': Sending state " LOG_SECRET("'%s'"), this->get_name().c_str(), this->state.c_str()); } else { - ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), state.c_str()); + ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), this->state.c_str()); } - this->state_callback_.call(state); + this->state_callback_.call(this->state); #if defined(USE_TEXT) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_text_update(this); #endif diff --git a/esphome/components/text/text.h b/esphome/components/text/text.h index b8881c59e6..e4ad64334b 100644 --- a/esphome/components/text/text.h +++ b/esphome/components/text/text.h @@ -27,6 +27,8 @@ class Text : public EntityBase { TextTraits traits; void publish_state(const std::string &state); + void publish_state(const char *state); + void publish_state(const char *state, size_t len); /// Instantiate a TextCall object to modify this text component's state. TextCall make_call() { return TextCall(this); } diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 8dfb9dad05..d53bdfeee5 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" +#include namespace esphome { namespace text_sensor { @@ -24,20 +25,26 @@ void log_text_sensor(const char *tag, const char *prefix, const char *type, Text } } -void TextSensor::publish_state(const std::string &state) { -// Suppress deprecation warning - we need to populate raw_state for backwards compatibility +void TextSensor::publish_state(const std::string &state) { this->publish_state(state.data(), state.size()); } + +void TextSensor::publish_state(const char *state) { this->publish_state(state, strlen(state)); } + +void TextSensor::publish_state(const char *state, size_t len) { + if (this->filter_list_ == nullptr) { + // No filters: raw_state == state, store once and use for both callbacks + this->state.assign(state, len); + this->raw_callback_.call(this->state); + ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), this->state.c_str()); + this->notify_frontend_(); + } else { + // Has filters: need separate raw storage #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - this->raw_state = state; + this->raw_state.assign(state, len); #pragma GCC diagnostic pop - this->raw_callback_.call(state); - - ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), state.c_str()); - - if (this->filter_list_ == nullptr) { - this->internal_send_state_to_frontend(state); - } else { - this->filter_list_->input(state); + this->raw_callback_.call(this->raw_state); + ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), this->raw_state.c_str()); + this->filter_list_->input(this->raw_state); } } @@ -80,6 +87,9 @@ void TextSensor::add_on_raw_state_callback(std::functionstate; } const std::string &TextSensor::get_raw_state() const { + if (this->filter_list_ == nullptr) { + return this->state; // No filters, raw == filtered + } // Suppress deprecation warning - get_raw_state() is the replacement API #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" @@ -87,10 +97,18 @@ const std::string &TextSensor::get_raw_state() const { #pragma GCC diagnostic pop } void TextSensor::internal_send_state_to_frontend(const std::string &state) { - this->state = state; + this->internal_send_state_to_frontend(state.data(), state.size()); +} + +void TextSensor::internal_send_state_to_frontend(const char *state, size_t len) { + this->state.assign(state, len); + this->notify_frontend_(); +} + +void TextSensor::notify_frontend_() { this->set_has_state(true); - ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str()); - this->callback_.call(state); + ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), this->state.c_str()); + this->callback_.call(this->state); #if defined(USE_TEXT_SENSOR) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_text_sensor_update(this); #endif diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 2cd8a65e87..1352a8c1e4 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -42,6 +42,8 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { const std::string &get_raw_state() const; void publish_state(const std::string &state); + void publish_state(const char *state); + void publish_state(const char *state, size_t len); /// Add a filter to the filter chain. Will be appended to the back. void add_filter(Filter *filter); @@ -63,8 +65,11 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { // (In most use cases you won't need these) void internal_send_state_to_frontend(const std::string &state); + void internal_send_state_to_frontend(const char *state, size_t len); protected: + /// Notify frontend that state has changed (assumes this->state is already set) + void notify_frontend_(); LazyCallbackManager raw_callback_; ///< Storage for raw state callbacks. LazyCallbackManager callback_; ///< Storage for filtered state callbacks.