From 52645842027852b8298d26d6384884dcdd59a4bd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 6 Dec 2025 23:29:10 -0600 Subject: [PATCH 1/8] reduce --- esphome/components/text_sensor/__init__.py | 12 ++++++------ esphome/components/text_sensor/filter.cpp | 18 ++++++++++-------- esphome/components/text_sensor/filter.h | 13 ++++++------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index 2be14b36fd..0d22400a8e 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -86,12 +86,12 @@ async def to_lower_filter_to_code(config, filter_id): @FILTER_REGISTRY.register("append", AppendFilter, cv.string) async def append_filter_to_code(config, filter_id): - return cg.new_Pvariable(filter_id, cg.StringRefLiteral(config)) + return cg.new_Pvariable(filter_id, config) @FILTER_REGISTRY.register("prepend", PrependFilter, cv.string) async def prepend_filter_to_code(config, filter_id): - return cg.new_Pvariable(filter_id, cg.StringRefLiteral(config)) + return cg.new_Pvariable(filter_id, config) def validate_mapping(value): @@ -114,8 +114,8 @@ async def substitute_filter_to_code(config, filter_id): substitutions = [ cg.StructInitializer( cg.MockObj("Substitution", "esphome::text_sensor::"), - ("from", cg.StringRefLiteral(conf[CONF_FROM])), - ("to", cg.StringRefLiteral(conf[CONF_TO])), + ("from", conf[CONF_FROM]), + ("to", conf[CONF_TO]), ) for conf in config ] @@ -127,8 +127,8 @@ async def map_filter_to_code(config, filter_id): mappings = [ cg.StructInitializer( cg.MockObj("Substitution", "esphome::text_sensor::"), - ("from", cg.StringRefLiteral(conf[CONF_FROM])), - ("to", cg.StringRefLiteral(conf[CONF_TO])), + ("from", conf[CONF_FROM]), + ("to", conf[CONF_TO]), ) for conf in config ] diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp index d9afaf80f1..4cace372ae 100644 --- a/esphome/components/text_sensor/filter.cpp +++ b/esphome/components/text_sensor/filter.cpp @@ -57,13 +57,13 @@ optional ToLowerFilter::new_value(std::string value) { // Append optional AppendFilter::new_value(std::string value) { - value += this->suffix_; + value.append(this->suffix_); return value; } // Prepend optional PrependFilter::new_value(std::string value) { - value.insert(0, this->prefix_.c_str(), this->prefix_.size()); + value.insert(0, this->prefix_); return value; } @@ -73,13 +73,15 @@ SubstituteFilter::SubstituteFilter(const std::initializer_list &su optional SubstituteFilter::new_value(std::string value) { for (const auto &sub : this->substitutions_) { + // Compute lengths once per substitution (strlen is fast, called infrequently) + const size_t from_len = strlen(sub.from); + const size_t to_len = strlen(sub.to); std::size_t pos = 0; - // Use c_str()/size() to avoid temporary std::string allocation from implicit conversion - while ((pos = value.find(sub.from.c_str(), pos, sub.from.size())) != std::string::npos) { - value.replace(pos, sub.from.size(), sub.to.c_str(), sub.to.size()); + while ((pos = value.find(sub.from, pos, from_len)) != std::string::npos) { + value.replace(pos, from_len, sub.to, to_len); // Advance past the replacement to avoid infinite loop when // the replacement contains the search pattern (e.g., f -> foo) - pos += sub.to.size(); + pos += to_len; } } return value; @@ -90,8 +92,8 @@ MapFilter::MapFilter(const std::initializer_list &mappings) : mapp optional MapFilter::new_value(std::string value) { for (const auto &mapping : this->mappings_) { - if (mapping.from == value) { - value.assign(mapping.to.c_str(), mapping.to.size()); + if (value == mapping.from) { + value.assign(mapping.to); return value; } } diff --git a/esphome/components/text_sensor/filter.h b/esphome/components/text_sensor/filter.h index 472dd87f15..0f66b753b4 100644 --- a/esphome/components/text_sensor/filter.h +++ b/esphome/components/text_sensor/filter.h @@ -2,7 +2,6 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" -#include "esphome/core/string_ref.h" namespace esphome { namespace text_sensor { @@ -93,26 +92,26 @@ class ToLowerFilter : public Filter { /// A simple filter that adds a string to the end of another string class AppendFilter : public Filter { public: - explicit AppendFilter(StringRef suffix) : suffix_(suffix) {} + explicit AppendFilter(const char *suffix) : suffix_(suffix) {} optional new_value(std::string value) override; protected: - StringRef suffix_; + const char *suffix_; }; /// A simple filter that adds a string to the start of another string class PrependFilter : public Filter { public: - explicit PrependFilter(StringRef prefix) : prefix_(prefix) {} + explicit PrependFilter(const char *prefix) : prefix_(prefix) {} optional new_value(std::string value) override; protected: - StringRef prefix_; + const char *prefix_; }; struct Substitution { - StringRef from; - StringRef to; + const char *from; + const char *to; }; /// A simple filter that replaces a substring with another substring From 36036014cc5ae0c61c8253ae0a3f8996c7d35f8b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 6 Dec 2025 23:30:15 -0600 Subject: [PATCH 2/8] reduce --- esphome/codegen.py | 1 - esphome/cpp_generator.py | 17 ----------------- tests/unit_tests/test_cpp_generator.py | 6 ------ 3 files changed, 24 deletions(-) diff --git a/esphome/codegen.py b/esphome/codegen.py index 4ea41ad191..6d55c6023d 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -19,7 +19,6 @@ from esphome.cpp_generator import ( # noqa: F401 RawExpression, RawStatement, Statement, - StringRefLiteral, StructInitializer, TemplateArguments, add, diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 89993d997c..1a47b346b7 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -243,23 +243,6 @@ class LogStringLiteral(Literal): return f"LOG_STR({cpp_string_escape(self.string)})" -class StringRefLiteral(Literal): - """A StringRef literal using from_lit() for compile-time string references. - - Uses StringRef::from_lit() which stores pointer + length (8 bytes) instead of - std::string (24-32 bytes + heap allocation). The string data stays in flash. - """ - - __slots__ = ("string",) - - def __init__(self, string: str) -> None: - super().__init__() - self.string = string - - def __str__(self) -> str: - return f"StringRef::from_lit({cpp_string_escape(self.string)})" - - class IntLiteral(Literal): __slots__ = ("i",) diff --git a/tests/unit_tests/test_cpp_generator.py b/tests/unit_tests/test_cpp_generator.py index 428e7626a1..2c9f760c8e 100644 --- a/tests/unit_tests/test_cpp_generator.py +++ b/tests/unit_tests/test_cpp_generator.py @@ -248,12 +248,6 @@ class TestLiterals: (cg.FloatLiteral(4.2), "4.2f"), (cg.FloatLiteral(1.23456789), "1.23456789f"), (cg.FloatLiteral(math.nan), "NAN"), - (cg.StringRefLiteral("foo"), 'StringRef::from_lit("foo")'), - (cg.StringRefLiteral(""), 'StringRef::from_lit("")'), - ( - cg.StringRefLiteral('with "quotes"'), - 'StringRef::from_lit("with \\042quotes\\042")', - ), ), ) def test_str__simple(self, target: cg.Literal, expected: str): From 0610b3a60af4f4ea51cb4e38acc12ece4552a3a1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 6 Dec 2025 23:49:09 -0600 Subject: [PATCH 3/8] [text] Store pattern as const char* to reduce memory usage --- esphome/components/template/text/template_text.cpp | 2 +- esphome/components/text/text_traits.h | 9 ++++----- esphome/components/web_server/web_server.cpp | 2 +- esphome/components/web_server/web_server_v1.cpp | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/esphome/components/template/text/template_text.cpp b/esphome/components/template/text/template_text.cpp index a917c72a14..7baed6cb20 100644 --- a/esphome/components/template/text/template_text.cpp +++ b/esphome/components/template/text/template_text.cpp @@ -16,7 +16,7 @@ void TemplateText::setup() { uint32_t key = this->get_preference_hash(); key += this->traits.get_min_length() << 2; key += this->traits.get_max_length() << 4; - key += fnv1_hash(this->traits.get_pattern()) << 6; + key += fnv1_hash(this->traits.get_pattern_ref().c_str()) << 6; this->pref_->setup(key, value); } if (!value.empty()) diff --git a/esphome/components/text/text_traits.h b/esphome/components/text/text_traits.h index ceaba2dead..f182a7721e 100644 --- a/esphome/components/text/text_traits.h +++ b/esphome/components/text/text_traits.h @@ -1,8 +1,7 @@ #pragma once -#include +#include -#include "esphome/core/helpers.h" #include "esphome/core/string_ref.h" namespace esphome { @@ -22,8 +21,8 @@ class TextTraits { int get_max_length() const { return this->max_length_; } // Set/get the pattern. - void set_pattern(std::string pattern) { this->pattern_ = std::move(pattern); } - std::string get_pattern() const { return this->pattern_; } + void set_pattern(const char *pattern) { this->pattern_ = pattern; } + std::string get_pattern() const { return std::string(this->pattern_); } StringRef get_pattern_ref() const { return StringRef(this->pattern_); } // Set/get the frontend mode. @@ -33,7 +32,7 @@ class TextTraits { protected: int min_length_; int max_length_; - std::string pattern_; + const char *pattern_{""}; TextMode mode_{TEXT_MODE_TEXT}; }; diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 1f3605a082..ff37f5ea03 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1211,7 +1211,7 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json set_json_icon_state_value(root, obj, "text", state, value, start_config); root[ESPHOME_F("min_length")] = obj->traits.get_min_length(); root[ESPHOME_F("max_length")] = obj->traits.get_max_length(); - root[ESPHOME_F("pattern")] = obj->traits.get_pattern(); + root[ESPHOME_F("pattern")] = obj->traits.get_pattern_ref(); if (start_config == DETAIL_ALL) { root[ESPHOME_F("mode")] = (int) obj->traits.get_mode(); this->add_sorting_info_(root, obj); diff --git a/esphome/components/web_server/web_server_v1.cpp b/esphome/components/web_server/web_server_v1.cpp index 870a338620..d46476a870 100644 --- a/esphome/components/web_server/web_server_v1.cpp +++ b/esphome/components/web_server/web_server_v1.cpp @@ -142,7 +142,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream.print(R"(" maxlength=")"); stream.print(text->traits.get_max_length()); stream.print(R"(" pattern=")"); - stream.print(text->traits.get_pattern().c_str()); + stream.print(text->traits.get_pattern_ref().c_str()); stream.print(R"(" value=")"); stream.print(text->state.c_str()); stream.print(R"("/>)"); From 789faca7c4b19ee52c8f93b0d06cfdc1b637a03a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 6 Dec 2025 23:50:48 -0600 Subject: [PATCH 4/8] [text] Store pattern as const char* to reduce memory usage --- tests/integration/fixtures/api_message_size_batching.yaml | 1 + tests/integration/test_api_message_size_batching.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/tests/integration/fixtures/api_message_size_batching.yaml b/tests/integration/fixtures/api_message_size_batching.yaml index c730dc1aa3..0fed311e63 100644 --- a/tests/integration/fixtures/api_message_size_batching.yaml +++ b/tests/integration/fixtures/api_message_size_batching.yaml @@ -143,6 +143,7 @@ text: mode: text min_length: 0 max_length: 255 + pattern: "[A-Za-z0-9 ]+" initial_value: "Initial value" update_interval: 5.0s diff --git a/tests/integration/test_api_message_size_batching.py b/tests/integration/test_api_message_size_batching.py index f7859eb902..5b123318c4 100644 --- a/tests/integration/test_api_message_size_batching.py +++ b/tests/integration/test_api_message_size_batching.py @@ -141,6 +141,9 @@ async def test_api_message_size_batching( assert text_input.max_length == 255, ( f"Expected max_length 255, got {text_input.max_length}" ) + assert text_input.pattern == "[A-Za-z0-9 ]+", ( + f"Expected pattern '[A-Za-z0-9 ]+', got '{text_input.pattern}'" + ) # Verify total entity count - messages of various sizes were batched successfully # We have: 3 selects + 3 text sensors + 1 text input + 1 number = 8 total From e63673f5efb4e2ed9d44fb42f7c3f3fda5f878ce Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 6 Dec 2025 23:55:15 -0600 Subject: [PATCH 5/8] [text] Store pattern as const char* to reduce memory usage --- esphome/components/template/text/template_text.cpp | 2 +- esphome/components/text/text_traits.h | 1 + esphome/components/web_server/web_server_v1.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/components/template/text/template_text.cpp b/esphome/components/template/text/template_text.cpp index 7baed6cb20..7244ad7c29 100644 --- a/esphome/components/template/text/template_text.cpp +++ b/esphome/components/template/text/template_text.cpp @@ -16,7 +16,7 @@ void TemplateText::setup() { uint32_t key = this->get_preference_hash(); key += this->traits.get_min_length() << 2; key += this->traits.get_max_length() << 4; - key += fnv1_hash(this->traits.get_pattern_ref().c_str()) << 6; + key += fnv1_hash(this->traits.get_pattern_c_str()) << 6; this->pref_->setup(key, value); } if (!value.empty()) diff --git a/esphome/components/text/text_traits.h b/esphome/components/text/text_traits.h index f182a7721e..473daafb8e 100644 --- a/esphome/components/text/text_traits.h +++ b/esphome/components/text/text_traits.h @@ -23,6 +23,7 @@ class TextTraits { // Set/get the pattern. void set_pattern(const char *pattern) { this->pattern_ = pattern; } std::string get_pattern() const { return std::string(this->pattern_); } + const char *get_pattern_c_str() const { return this->pattern_; } StringRef get_pattern_ref() const { return StringRef(this->pattern_); } // Set/get the frontend mode. diff --git a/esphome/components/web_server/web_server_v1.cpp b/esphome/components/web_server/web_server_v1.cpp index d46476a870..486c38a2ab 100644 --- a/esphome/components/web_server/web_server_v1.cpp +++ b/esphome/components/web_server/web_server_v1.cpp @@ -142,7 +142,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream.print(R"(" maxlength=")"); stream.print(text->traits.get_max_length()); stream.print(R"(" pattern=")"); - stream.print(text->traits.get_pattern_ref().c_str()); + stream.print(text->traits.get_pattern_c_str()); stream.print(R"(" value=")"); stream.print(text->state.c_str()); stream.print(R"("/>)"); From 188148546e2d37863226866d18d28951d7415b0b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 6 Dec 2025 23:55:38 -0600 Subject: [PATCH 6/8] [text] Store pattern as const char* to reduce memory usage --- esphome/components/web_server/web_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index ff37f5ea03..ca3aa21a95 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1211,7 +1211,7 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json set_json_icon_state_value(root, obj, "text", state, value, start_config); root[ESPHOME_F("min_length")] = obj->traits.get_min_length(); root[ESPHOME_F("max_length")] = obj->traits.get_max_length(); - root[ESPHOME_F("pattern")] = obj->traits.get_pattern_ref(); + root[ESPHOME_F("pattern")] = obj->traits.get_pattern_c_str(); if (start_config == DETAIL_ALL) { root[ESPHOME_F("mode")] = (int) obj->traits.get_mode(); this->add_sorting_info_(root, obj); From d881e6055eac33c95cdd25aba867b834a6c32b5a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 7 Dec 2025 10:31:33 -0600 Subject: [PATCH 7/8] tweak --- esphome/components/api/api_connection.cpp | 2 +- esphome/components/text/text_traits.h | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index f0428546de..0a0435e25b 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -866,7 +866,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co msg.mode = static_cast(text->traits.get_mode()); msg.min_length = text->traits.get_min_length(); msg.max_length = text->traits.get_max_length(); - msg.set_pattern(text->traits.get_pattern_ref()); + msg.set_pattern(text->traits.get_pattern_c_str()); return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } diff --git a/esphome/components/text/text_traits.h b/esphome/components/text/text_traits.h index 473daafb8e..d869097700 100644 --- a/esphome/components/text/text_traits.h +++ b/esphome/components/text/text_traits.h @@ -2,8 +2,6 @@ #include -#include "esphome/core/string_ref.h" - namespace esphome { namespace text { @@ -24,7 +22,6 @@ class TextTraits { void set_pattern(const char *pattern) { this->pattern_ = pattern; } std::string get_pattern() const { return std::string(this->pattern_); } const char *get_pattern_c_str() const { return this->pattern_; } - StringRef get_pattern_ref() const { return StringRef(this->pattern_); } // Set/get the frontend mode. void set_mode(TextMode mode) { this->mode_ = mode; } From 475ce1f3fa80626f31aa7c37e03e3dc05273efec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 7 Dec 2025 12:43:02 -0600 Subject: [PATCH 8/8] tweaks --- esphome/components/api/api_connection.cpp | 2 +- esphome/components/text/text_traits.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 0a0435e25b..f0428546de 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -866,7 +866,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co msg.mode = static_cast(text->traits.get_mode()); msg.min_length = text->traits.get_min_length(); msg.max_length = text->traits.get_max_length(); - msg.set_pattern(text->traits.get_pattern_c_str()); + msg.set_pattern(text->traits.get_pattern_ref()); return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } diff --git a/esphome/components/text/text_traits.h b/esphome/components/text/text_traits.h index d869097700..473daafb8e 100644 --- a/esphome/components/text/text_traits.h +++ b/esphome/components/text/text_traits.h @@ -2,6 +2,8 @@ #include +#include "esphome/core/string_ref.h" + namespace esphome { namespace text { @@ -22,6 +24,7 @@ class TextTraits { void set_pattern(const char *pattern) { this->pattern_ = pattern; } std::string get_pattern() const { return std::string(this->pattern_); } const char *get_pattern_c_str() const { return this->pattern_; } + StringRef get_pattern_ref() const { return StringRef(this->pattern_); } // Set/get the frontend mode. void set_mode(TextMode mode) { this->mode_ = mode; }