mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 02:02:51 +00:00
[event] Keep event types in a shared flash table (#20217)
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user