[event] Keep event types in a shared flash table (#20217)

This commit is contained in:
J. Nick Koston
2026-10-11 14:58:49 -10:00
committed by GitHub
parent e16b3a6245
commit 9295342c16
9 changed files with 122 additions and 14 deletions
+1 -1
View File
@@ -2570,7 +2570,7 @@ message ListEntitiesEventResponse {
EntityCategory entity_category = 7;
string device_class = 8 [(max_data_length) = 47];
repeated string event_types = 9 [(container_pointer_no_template) = "FixedVector<const char *>"];
repeated string event_types = 9 [(container_pointer_no_template) = "ConstVector<const char *, true>"];
uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"];
}
message EventResponse {
+1 -1
View File
@@ -3460,7 +3460,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage {
const LogString *message_name() const override { return LOG_STR("list_entities_event_response"); }
#endif
StringRef device_class{nullptr, 0}; // null until set, encode only
const FixedVector<const char *> *event_types{};
const ConstVector<const char *, true> *event_types{};
static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM);
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG);
+3 -1
View File
@@ -98,7 +98,9 @@ async def setup_event_core_(
) -> None:
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
cg.add(var.set_event_types(event_types))
if event_types:
table = cg.shared_progmem_array("event_types", cg.const_char_ptr, event_types)
cg.add(var.set_event_types_static(table, len(event_types)))
setup_device_class(config)
+2 -5
View File
@@ -28,11 +28,8 @@ void Event::trigger(const char *event_type) {
#endif
}
void Event::set_event_types(const FixedVector<const char *> &event_types) {
this->types_.init(event_types.size());
for (const char *type : event_types) {
this->types_.push_back(type);
}
void Event::set_event_types_copy_(const char *const *event_types, size_t count) {
this->types_.assign_copy(event_types, count);
this->last_event_type_ = nullptr; // Reset when types change
}
+20 -6
View File
@@ -11,6 +11,9 @@
namespace esphome::event {
/// A codegen table in flash, or a heap copy of a runtime list (see ConstVector).
using EventTypes = ConstVector<const char *, true>;
#define LOG_EVENT(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
@@ -24,20 +27,29 @@ class Event : public EntityBase {
void trigger(const char *event_type);
void trigger(const std::string &event_type) { this->trigger(event_type.c_str()); }
/// Set the event types supported by this event; called by generated code with string literals.
/// Codegen only: points at a table that outlives the event (see ConstVector::assign_static).
void set_event_types_static(const char *const *event_types, size_t count) {
this->types_.assign_static(event_types, count);
this->last_event_type_ = nullptr;
}
/// Runtime lists are copied; the strings must still outlive the event.
void set_event_types(std::initializer_list<const char *> event_types) {
this->types_ = event_types;
this->last_event_type_ = nullptr; // Reset when types change
this->set_event_types_copy_(event_types.begin(), event_types.size());
}
/// Copy the event types of another event, for components that wrap one.
void set_event_types(const FixedVector<const char *> &event_types);
void set_event_types(const EventTypes &event_types) {
this->set_event_types_copy_(event_types.data(), event_types.size());
}
void set_event_types(const FixedVector<const char *> &event_types) {
this->set_event_types_copy_(event_types.begin(), event_types.size());
}
// Deleted overloads to catch incorrect std::string usage at compile time with clear error messages
void set_event_types(std::initializer_list<std::string> event_types) = delete;
void set_event_types(const FixedVector<std::string> &event_types) = delete;
/// Return the event types supported by this event.
const FixedVector<const char *> &get_event_types() const { return this->types_; }
const EventTypes &get_event_types() const { return this->types_; }
/// Return the last triggered event type, or empty StringRef if no event triggered yet.
StringRef get_last_event_type() const { return StringRef::from_maybe_nullptr(this->last_event_type_); }
@@ -68,8 +80,10 @@ class Event : public EntityBase {
}
protected:
void set_event_types_copy_(const char *const *event_types, size_t count);
LazyCallbackManager<void(StringRef event_type)> event_callback_;
FixedVector<const char *> types_;
EventTypes types_;
private:
/// Last triggered event type - must point to entry in types_ to ensure valid lifetime.
@@ -0,0 +1,19 @@
esphome:
name: test
esp8266:
board: d1_mini
event:
- platform: template
id: event_a
name: Event A
event_types: [pressed, held]
- platform: template
id: event_b
name: Event B
event_types: [pressed, held]
- platform: template
id: event_c
name: Event C
event_types: [ring]
@@ -0,0 +1,25 @@
"""Event types are shared PROGMEM tables of type pointers."""
from collections.abc import Callable
from pathlib import Path
import re
def test_event_types_use_shared_tables(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
main_cpp = generate_main(component_config_path("event_types.yaml"))
calls = {
var: table
for var, table, _ in re.findall(
r"(\w+)->set_event_types_static\((\w+), (\d+)\);", main_cpp
)
}
assert set(calls) == {"event_a", "event_b", "event_c"}
assert calls["event_a"] == calls["event_b"] != calls["event_c"]
assert (
f'static constexpr const char * {calls["event_a"]}[] PROGMEM = {{"pressed", "held"}};'
in main_cpp
)
@@ -0,0 +1,51 @@
#include <gtest/gtest.h>
#include "esphome/components/event/event.h"
namespace esphome::event::testing {
static const char *const TYPES[] = {"pressed", "held"};
TEST(EventTypes, StaticTableIsViewed) {
Event event;
event.set_event_types_static(TYPES, 2);
EXPECT_EQ(event.get_event_types().data(), TYPES);
EXPECT_EQ(event.get_event_types().size(), 2u);
}
TEST(EventTypes, CopiesAnotherEventAndResetsLastType) {
Event source;
source.set_event_types({"x", "y"});
Event copy;
copy.set_event_types_static(TYPES, 2);
copy.trigger("held");
ASSERT_TRUE(copy.has_event());
copy.set_event_types(source.get_event_types());
EXPECT_FALSE(copy.has_event());
EXPECT_NE(copy.get_event_types().data(), TYPES);
EXPECT_NE(copy.get_event_types().data(), source.get_event_types().data());
EXPECT_EQ(copy.get_event_types().size(), 2u);
EXPECT_STREQ(copy.get_event_types()[1], "y");
}
TEST(EventTypes, CopiesItsOwnList) {
Event event;
event.set_event_types({"a", "b"});
event.set_event_types(event.get_event_types()); // copies before freeing the old list
EXPECT_EQ(event.get_event_types().size(), 2u);
EXPECT_STREQ(event.get_event_types()[1], "b");
}
TEST(EventTypes, CopiesFromFixedVector) {
FixedVector<const char *> types;
types.init(2);
types.push_back("on");
types.push_back("off");
Event event;
event.set_event_types(types);
EXPECT_NE(event.get_event_types().data(), types.begin());
EXPECT_EQ(event.get_event_types().size(), 2u);
EXPECT_STREQ(event.get_event_types()[0], "on");
}
} // namespace esphome::event::testing