From c77bb3b269bf7fba517fb359535c59cccef49152 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 15:46:16 -0600 Subject: [PATCH] [event] Store event types in flash memory (#11767) --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_connection.cpp | 3 +- esphome/components/api/api_pb2.cpp | 10 +++--- esphome/components/api/api_pb2.h | 2 +- esphome/components/api/api_pb2_dump.cpp | 2 +- esphome/components/event/event.cpp | 30 +++++++++++++---- esphome/components/event/event.h | 35 +++++++++++++++++--- esphome/components/mqtt/mqtt_event.cpp | 4 +-- esphome/components/web_server/web_server.cpp | 5 +-- 9 files changed, 67 insertions(+), 26 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 7a50fa6b17..e115e4630d 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -2147,7 +2147,7 @@ message ListEntitiesEventResponse { EntityCategory entity_category = 7; string device_class = 8; - repeated string event_types = 9; + repeated string event_types = 9 [(container_pointer_no_template) = "FixedVector"]; uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"]; } message EventResponse { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 5ab8a6eb05..8c293b41a2 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1310,8 +1310,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c auto *event = static_cast(entity); ListEntitiesEventResponse msg; msg.set_device_class(event->get_device_class_ref()); - for (const auto &event_type : event->get_event_types()) - msg.event_types.push_back(event_type); + msg.event_types = &event->get_event_types(); return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index dfa1a1320f..0a073fb662 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2877,8 +2877,8 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_string(8, this->device_class_ref_); - for (auto &it : this->event_types) { - buffer.encode_string(9, it, true); + for (const char *it : *this->event_types) { + buffer.encode_string(9, it, strlen(it), true); } #ifdef USE_DEVICES buffer.encode_uint32(10, this->device_id); @@ -2894,9 +2894,9 @@ void ListEntitiesEventResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); size.add_length(1, this->device_class_ref_.size()); - if (!this->event_types.empty()) { - for (const auto &it : this->event_types) { - size.add_length_force(1, it.size()); + if (!this->event_types->empty()) { + for (const char *it : *this->event_types) { + size.add_length_force(1, strlen(it)); } } #ifdef USE_DEVICES diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 716f1a6e9b..358049026e 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -2788,7 +2788,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage { #endif StringRef device_class_ref_{}; void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } - std::vector event_types{}; + const FixedVector *event_types{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index d94ceaaa9c..d9662483bf 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -2053,7 +2053,7 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const { dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); dump_field(out, "device_class", this->device_class_ref_); - for (const auto &it : this->event_types) { + for (const auto &it : *this->event_types) { dump_field(out, "event_types", it, 4); } #ifdef USE_DEVICES diff --git a/esphome/components/event/event.cpp b/esphome/components/event/event.cpp index 20549ad0a5..a14afbd7f5 100644 --- a/esphome/components/event/event.cpp +++ b/esphome/components/event/event.cpp @@ -8,11 +8,11 @@ namespace event { static const char *const TAG = "event"; void Event::trigger(const std::string &event_type) { - // Linear search - faster than std::set for small datasets (1-5 items typical) - const std::string *found = nullptr; - for (const auto &type : this->types_) { - if (type == event_type) { - found = &type; + // Linear search with strcmp - faster than std::set for small datasets (1-5 items typical) + const char *found = nullptr; + for (const char *type : this->types_) { + if (strcmp(type, event_type.c_str()) == 0) { + found = type; break; } } @@ -20,11 +20,27 @@ void Event::trigger(const std::string &event_type) { ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str()); return; } - last_event_type = found; - ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), last_event_type->c_str()); + this->last_event_type_ = found; + ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), this->last_event_type_); this->event_callback_.call(event_type); } +void Event::set_event_types(const FixedVector &event_types) { + this->types_.init(event_types.size()); + for (const char *type : event_types) { + this->types_.push_back(type); + } + this->last_event_type_ = nullptr; // Reset when types change +} + +void Event::set_event_types(const std::vector &event_types) { + this->types_.init(event_types.size()); + for (const char *type : event_types) { + this->types_.push_back(type); + } + this->last_event_type_ = nullptr; // Reset when types change +} + void Event::add_on_event_callback(std::function &&callback) { this->event_callback_.add(std::move(callback)); } diff --git a/esphome/components/event/event.h b/esphome/components/event/event.h index 2f6267a200..e4b2e0b845 100644 --- a/esphome/components/event/event.h +++ b/esphome/components/event/event.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include "esphome/core/component.h" #include "esphome/core/entity_base.h" @@ -22,16 +24,39 @@ namespace event { class Event : public EntityBase, public EntityBase_DeviceClass { public: - const std::string *last_event_type; - void trigger(const std::string &event_type); - void set_event_types(const std::initializer_list &event_types) { this->types_ = event_types; } - const FixedVector &get_event_types() const { return this->types_; } + + /// Set the event types supported by this event (from initializer list). + void set_event_types(std::initializer_list event_types) { + this->types_ = event_types; + this->last_event_type_ = nullptr; // Reset when types change + } + /// Set the event types supported by this event (from FixedVector). + void set_event_types(const FixedVector &event_types); + /// Set the event types supported by this event (from vector). + void set_event_types(const std::vector &event_types); + + // Deleted overloads to catch incorrect std::string usage at compile time with clear error messages + void set_event_types(std::initializer_list event_types) = delete; + void set_event_types(const FixedVector &event_types) = delete; + void set_event_types(const std::vector &event_types) = delete; + + /// Return the event types supported by this event. + const FixedVector &get_event_types() const { return this->types_; } + + /// Return the last triggered event type (pointer to string in types_), or nullptr if no event triggered yet. + const char *get_last_event_type() const { return this->last_event_type_; } + void add_on_event_callback(std::function &&callback); protected: CallbackManager event_callback_; - FixedVector types_; + FixedVector types_; + + private: + /// Last triggered event type - must point to entry in types_ to ensure valid lifetime. + /// Set by trigger() after validation, reset to nullptr when types_ changes. + const char *last_event_type_{nullptr}; }; } // namespace event diff --git a/esphome/components/mqtt/mqtt_event.cpp b/esphome/components/mqtt/mqtt_event.cpp index e206335446..fd095ea041 100644 --- a/esphome/components/mqtt/mqtt_event.cpp +++ b/esphome/components/mqtt/mqtt_event.cpp @@ -38,8 +38,8 @@ void MQTTEventComponent::setup() { void MQTTEventComponent::dump_config() { ESP_LOGCONFIG(TAG, "MQTT Event '%s': ", this->event_->get_name().c_str()); ESP_LOGCONFIG(TAG, "Event Types: "); - for (const auto &event_type : this->event_->get_event_types()) { - ESP_LOGCONFIG(TAG, "- %s", event_type.c_str()); + for (const char *event_type : this->event_->get_event_types()) { + ESP_LOGCONFIG(TAG, "- %s", event_type); } LOG_MQTT_COMPONENT(true, true); } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index f1d1a75875..91ca076474 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1628,7 +1628,8 @@ void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMa } static std::string get_event_type(event::Event *event) { - return (event && event->last_event_type) ? *event->last_event_type : ""; + const char *last_type = event ? event->get_last_event_type() : nullptr; + return last_type ? last_type : ""; } std::string WebServer::event_state_json_generator(WebServer *web_server, void *source) { @@ -1649,7 +1650,7 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty } if (start_config == DETAIL_ALL) { JsonArray event_types = root["event_types"].to(); - for (auto const &event_type : obj->get_event_types()) { + for (const char *event_type : obj->get_event_types()) { event_types.add(event_type); } root["device_class"] = obj->get_device_class_ref();