From 9efe9f1c198b2d6039b4d5319afab53cad44d6be Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 29 Sep 2025 17:49:03 -0500 Subject: [PATCH 1/6] wip --- .../components/esp32_ble_server/__init__.py | 87 ++++++++++++++++ .../esp32_ble_server/ble_characteristic.cpp | 9 +- .../esp32_ble_server/ble_characteristic.h | 18 +++- .../esp32_ble_server/ble_descriptor.cpp | 4 +- .../esp32_ble_server/ble_descriptor.h | 14 ++- .../esp32_ble_server/ble_server.cpp | 4 +- .../components/esp32_ble_server/ble_server.h | 17 +++- .../ble_server_automations.cpp | 23 +++-- .../esp32_ble_server/ble_server_automations.h | 15 +-- .../esp32_ble_server/ble_service.cpp | 4 + .../components/esp32_ble_server/ble_service.h | 3 + .../esp32_improv/esp32_improv_component.cpp | 14 ++- .../components/event_emitter/event_emitter.h | 99 ++++++------------- 13 files changed, 197 insertions(+), 114 deletions(-) diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index 9eab9647b3..7bd3a0e585 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -1,4 +1,5 @@ import encodings +from typing import TypeAlias from esphome import automation import esphome.codegen as cg @@ -31,6 +32,92 @@ CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"] DEPENDENCIES = ["esp32"] DOMAIN = "esp32_ble_server" +# Type aliases +_ListenerAllocation: TypeAlias = tuple[str, int | str, int] # (component, uuid, count) +_ServerListenerAllocation: TypeAlias = tuple[str, int] # (component, count) + +# Event listener allocation tracking - used by components to reserve slots +_LISTENER_ALLOCATIONS: dict[ + str, list[_ListenerAllocation | _ServerListenerAllocation] +] = { + "characteristic_write": [], + "characteristic_read": [], + "descriptor_write": [], + "server_connect": [], + "server_disconnect": [], +} + + +def allocate_characteristic_event_listener( + uuid: int | str, event_type: str, component: str, count: int = 1 +) -> None: + """ + Allocate event listener slots for a characteristic. + + Args: + uuid: The characteristic UUID (int or string) + event_type: "WRITE" or "READ" + component: Name of the component requesting allocation + count: Number of listeners needed (default 1) + """ + if event_type not in ("WRITE", "READ"): + raise ValueError(f"Unknown event_type: {event_type}") + + key = f"characteristic_{event_type.lower()}" + _LISTENER_ALLOCATIONS[key].append((component, uuid, count)) + + +def allocate_descriptor_event_listener( + uuid: int | str, event_type: str, component: str, count: int = 1 +) -> None: + """Allocate event listener slots for a descriptor.""" + if event_type != "WRITE": + raise ValueError(f"Unknown event_type: {event_type}") + + _LISTENER_ALLOCATIONS["descriptor_write"].append((component, uuid, count)) + + +def allocate_server_event_listener( + event_type: str, component: str, count: int = 1 +) -> None: + """Allocate event listener slots for server events.""" + if event_type not in ("CONNECT", "DISCONNECT"): + raise ValueError(f"Unknown event_type: {event_type}") + + key = f"server_{event_type.lower()}" + _LISTENER_ALLOCATIONS[key].append((component, count)) + + +def _sum_allocations_for_uuid(allocation_key: str, uuid: int | str) -> int: + """Helper to sum allocations for a specific UUID.""" + return sum( + count + for comp, alloc_uuid, count in _LISTENER_ALLOCATIONS[allocation_key] + if alloc_uuid == uuid + ) + + +def _get_allocations_for_uuid(uuid: int | str, event_type: str) -> int: + """Get total allocated listeners for a specific UUID and event type.""" + if event_type not in ("WRITE", "READ"): + return 0 + key = f"characteristic_{event_type.lower()}" + return _sum_allocations_for_uuid(key, uuid) + + +def _get_descriptor_allocations_for_uuid(uuid: int | str) -> int: + """Get total allocated listeners for a descriptor UUID.""" + return _sum_allocations_for_uuid("descriptor_write", uuid) + + +def sanitize_uuid_for_identifier(uuid: int | str) -> str: + """Convert UUID to valid C++ identifier.""" + if isinstance(uuid, int): + return f"0x{uuid:04X}" + # For string UUIDs, replace dashes and colons with underscores + return str(uuid).replace("-", "_").replace(":", "_").lower() + + CONF_ADVERTISE = "advertise" CONF_APPEARANCE = "appearance" CONF_BROADCAST = "broadcast" diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index fabcc75321..efa9c80b30 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -208,8 +208,7 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt if (!param->read.need_rsp) break; // For some reason you can request a read but not want a response - this->EventEmitter::emit_(BLECharacteristicEvt::EmptyEvt::ON_READ, - param->read.conn_id); + this->emit_on_read_(param->read.conn_id); uint16_t max_offset = 22; @@ -277,8 +276,7 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt } if (!param->write.is_prep) { - this->EventEmitter, uint16_t>::emit_( - BLECharacteristicEvt::VectorEvt::ON_WRITE, this->value_, param->write.conn_id); + this->emit_on_write_(this->value_, param->write.conn_id); } break; @@ -289,8 +287,7 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt break; this->write_event_ = false; if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { - this->EventEmitter, uint16_t>::emit_( - BLECharacteristicEvt::VectorEvt::ON_WRITE, this->value_, param->exec_write.conn_id); + this->emit_on_write_(this->value_, param->exec_write.conn_id); } esp_err_t err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, nullptr); diff --git a/esphome/components/esp32_ble_server/ble_characteristic.h b/esphome/components/esp32_ble_server/ble_characteristic.h index 97b3af2a21..6b322bc7ff 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.h +++ b/esphome/components/esp32_ble_server/ble_characteristic.h @@ -6,6 +6,7 @@ #include "esphome/components/bytebuffer/bytebuffer.h" #include +#include #ifdef USE_ESP32 @@ -36,8 +37,9 @@ enum EmptyEvt { }; } // namespace BLECharacteristicEvt -class BLECharacteristic : public EventEmitter, uint16_t>, - public EventEmitter { +// Base class for BLE characteristics +// Specialized classes with EventEmitter support are generated per-UUID in the build process +class BLECharacteristic { public: BLECharacteristic(ESPBTUUID uuid, uint32_t properties); ~BLECharacteristic(); @@ -76,7 +78,19 @@ class BLECharacteristic : public EventEmitter, uint16_t)> &&listener) { + return INVALID_LISTENER_ID; + } + virtual EventEmitterListenerID on_read(std::function &&listener) { return INVALID_LISTENER_ID; } + virtual void off_write(EventEmitterListenerID id) {} + virtual void off_read(EventEmitterListenerID id) {} + protected: + // Virtual methods for emitting events - overridden by generated specialized classes + virtual void emit_on_write_(std::span value, uint16_t conn_id) {} + virtual void emit_on_read_(uint16_t conn_id) {} + bool write_event_{false}; BLEService *service_{}; ESPBTUUID uuid_; diff --git a/esphome/components/esp32_ble_server/ble_descriptor.cpp b/esphome/components/esp32_ble_server/ble_descriptor.cpp index afbe579513..dbfa8bc632 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.cpp +++ b/esphome/components/esp32_ble_server/ble_descriptor.cpp @@ -74,9 +74,7 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ break; this->value_.attr_len = param->write.len; memcpy(this->value_.attr_value, param->write.value, param->write.len); - this->emit_(BLEDescriptorEvt::VectorEvt::ON_WRITE, - std::vector(param->write.value, param->write.value + param->write.len), - param->write.conn_id); + this->emit_on_write_(std::span(param->write.value, param->write.len), param->write.conn_id); break; } default: diff --git a/esphome/components/esp32_ble_server/ble_descriptor.h b/esphome/components/esp32_ble_server/ble_descriptor.h index 8d3c22c5a1..1b787f7a35 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.h +++ b/esphome/components/esp32_ble_server/ble_descriptor.h @@ -8,6 +8,7 @@ #include #include +#include namespace esphome { namespace esp32_ble_server { @@ -24,7 +25,9 @@ enum VectorEvt { }; } // namespace BLEDescriptorEvt -class BLEDescriptor : public EventEmitter, uint16_t> { +// Base class for BLE descriptors +// Specialized classes with EventEmitter support are generated per-UUID in the build process +class BLEDescriptor { public: BLEDescriptor(ESPBTUUID uuid, uint16_t max_len = 100, bool read = true, bool write = true); virtual ~BLEDescriptor(); @@ -39,7 +42,16 @@ class BLEDescriptor : public EventEmitterstate_ == CREATED; } bool is_failed() { return this->state_ == FAILED; } + // Event listener registration - overridden by generated specialized classes if needed + virtual EventEmitterListenerID on_write(std::function, uint16_t)> &&listener) { + return INVALID_LISTENER_ID; + } + virtual void off_write(EventEmitterListenerID id) {} + protected: + // Virtual method for emitting events - overridden by generated specialized classes + virtual void emit_on_write_(std::span value, uint16_t conn_id) {} + BLECharacteristic *characteristic_{nullptr}; ESPBTUUID uuid_; uint16_t handle_{0xFFFF}; diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index 89299bb417..d73c343b4d 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -153,14 +153,14 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga case ESP_GATTS_CONNECT_EVT: { ESP_LOGD(TAG, "BLE Client connected"); this->add_client_(param->connect.conn_id); - this->emit_(BLEServerEvt::EmptyEvt::ON_CONNECT, param->connect.conn_id); + this->emit_on_connect_(param->connect.conn_id); break; } case ESP_GATTS_DISCONNECT_EVT: { ESP_LOGD(TAG, "BLE Client disconnected"); this->remove_client_(param->disconnect.conn_id); this->parent_->advertising_start(); - this->emit_(BLEServerEvt::EmptyEvt::ON_DISCONNECT, param->disconnect.conn_id); + this->emit_on_disconnect_(param->disconnect.conn_id); break; } case ESP_GATTS_REG_EVT: { diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index b5973ed099..e3181a945d 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -31,11 +31,9 @@ enum EmptyEvt { }; } // namespace BLEServerEvt -class BLEServer : public Component, - public GATTsEventHandler, - public BLEStatusEventHandler, - public Parented, - public EventEmitter { +// Base class for BLE server +// Note: Only one BLEServer instance exists per build, so we can use fixed defines +class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEventHandler, public Parented { public: void setup() override; void loop() override; @@ -65,7 +63,16 @@ class BLEServer : public Component, void ble_before_disabled_event_handler() override; + // Event listener registration - overridden by generated specialized classes if needed + virtual EventEmitterListenerID on_connect(std::function &&listener) { return INVALID_LISTENER_ID; } + virtual EventEmitterListenerID on_disconnect(std::function &&listener) { return INVALID_LISTENER_ID; } + virtual void off_connect(EventEmitterListenerID id) {} + virtual void off_disconnect(EventEmitterListenerID id) {} + protected: + // Virtual methods for emitting events + virtual void emit_on_connect_(uint16_t conn_id) {} + virtual void emit_on_disconnect_(uint16_t conn_id) {} struct ServiceEntry { ESPBTUUID uuid; uint8_t inst_id; diff --git a/esphome/components/esp32_ble_server/ble_server_automations.cpp b/esphome/components/esp32_ble_server/ble_server_automations.cpp index 67e00a9bfe..afa958a4a0 100644 --- a/esphome/components/esp32_ble_server/ble_server_automations.cpp +++ b/esphome/components/esp32_ble_server/ble_server_automations.cpp @@ -14,9 +14,10 @@ Trigger, uint16_t> *BLETriggers::create_characteristic_on_w BLECharacteristic *characteristic) { Trigger, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory) new Trigger, uint16_t>(); - characteristic->EventEmitter, uint16_t>::on( - BLECharacteristicEvt::VectorEvt::ON_WRITE, - [on_write_trigger](const std::vector &data, uint16_t id) { on_write_trigger->trigger(data, id); }); + characteristic->on_write([on_write_trigger](std::span data, uint16_t id) { + // Convert span to vector for trigger + on_write_trigger->trigger(std::vector(data.begin(), data.end()), id); + }); return on_write_trigger; } #endif @@ -25,9 +26,10 @@ Trigger, uint16_t> *BLETriggers::create_characteristic_on_w Trigger, uint16_t> *BLETriggers::create_descriptor_on_write_trigger(BLEDescriptor *descriptor) { Trigger, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory) new Trigger, uint16_t>(); - descriptor->on( - BLEDescriptorEvt::VectorEvt::ON_WRITE, - [on_write_trigger](const std::vector &data, uint16_t id) { on_write_trigger->trigger(data, id); }); + descriptor->on_write([on_write_trigger](std::span data, uint16_t id) { + // Convert span to vector for trigger + on_write_trigger->trigger(std::vector(data.begin(), data.end()), id); + }); return on_write_trigger; } #endif @@ -35,8 +37,7 @@ Trigger, uint16_t> *BLETriggers::create_descriptor_on_write #ifdef USE_ESP32_BLE_SERVER_ON_CONNECT Trigger *BLETriggers::create_server_on_connect_trigger(BLEServer *server) { Trigger *on_connect_trigger = new Trigger(); // NOLINT(cppcoreguidelines-owning-memory) - server->on(BLEServerEvt::EmptyEvt::ON_CONNECT, - [on_connect_trigger](uint16_t conn_id) { on_connect_trigger->trigger(conn_id); }); + server->on_connect([on_connect_trigger](uint16_t conn_id) { on_connect_trigger->trigger(conn_id); }); return on_connect_trigger; } #endif @@ -44,8 +45,7 @@ Trigger *BLETriggers::create_server_on_connect_trigger(BLEServer *serv #ifdef USE_ESP32_BLE_SERVER_ON_DISCONNECT Trigger *BLETriggers::create_server_on_disconnect_trigger(BLEServer *server) { Trigger *on_disconnect_trigger = new Trigger(); // NOLINT(cppcoreguidelines-owning-memory) - server->on(BLEServerEvt::EmptyEvt::ON_DISCONNECT, - [on_disconnect_trigger](uint16_t conn_id) { on_disconnect_trigger->trigger(conn_id); }); + server->on_disconnect([on_disconnect_trigger](uint16_t conn_id) { on_disconnect_trigger->trigger(conn_id); }); return on_disconnect_trigger; } #endif @@ -58,8 +58,7 @@ void BLECharacteristicSetValueActionManager::set_listener(BLECharacteristic *cha auto *existing = this->find_listener_(characteristic); if (existing != nullptr) { // Remove the previous listener - characteristic->EventEmitter::off(BLECharacteristicEvt::EmptyEvt::ON_READ, - existing->listener_id); + characteristic->off_read(existing->listener_id); // Remove the pre-notify listener this->off(BLECharacteristicSetValueActionEvt::PRE_NOTIFY, existing->pre_notify_listener_id); // Remove from vector diff --git a/esphome/components/esp32_ble_server/ble_server_automations.h b/esphome/components/esp32_ble_server/ble_server_automations.h index 8fcb5842c3..08a3322367 100644 --- a/esphome/components/esp32_ble_server/ble_server_automations.h +++ b/esphome/components/esp32_ble_server/ble_server_automations.h @@ -46,8 +46,12 @@ enum BLECharacteristicSetValueActionEvt { }; // Class to make sure only one BLECharacteristicSetValueAction is active at a time for each characteristic +#ifndef BLE_SET_VALUE_ACTION_MAX_LISTENERS +#define BLE_SET_VALUE_ACTION_MAX_LISTENERS 1 +#endif + class BLECharacteristicSetValueActionManager - : public EventEmitter { + : public EventEmitter { public: // Singleton pattern static BLECharacteristicSetValueActionManager *get_instance() { @@ -92,11 +96,10 @@ template class BLECharacteristicSetValueAction : public Actionparent_->set_value(this->buffer_.value(x...)); // Set the listener for read events - this->listener_id_ = this->parent_->EventEmitter::on( - BLECharacteristicEvt::EmptyEvt::ON_READ, [this, x...](uint16_t id) { - // Set the value of the characteristic every time it is read - this->parent_->set_value(this->buffer_.value(x...)); - }); + this->listener_id_ = this->parent_->on_read([this, x...](uint16_t id) { + // Set the value of the characteristic every time it is read + this->parent_->set_value(this->buffer_.value(x...)); + }); // Set the listener in the global manager so only one BLECharacteristicSetValueAction is set for each characteristic BLECharacteristicSetValueActionManager::get_instance()->set_listener( this->parent_, this->listener_id_, [this, x...]() { this->parent_->set_value(this->buffer_.value(x...)); }); diff --git a/esphome/components/esp32_ble_server/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp index 96fedf2346..3c5b40ba9e 100644 --- a/esphome/components/esp32_ble_server/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -41,6 +41,10 @@ BLECharacteristic *BLEService::create_characteristic(ESPBTUUID uuid, esp_gatt_ch return characteristic; } +void BLEService::add_characteristic(BLECharacteristic *characteristic) { + this->characteristics_.push_back(characteristic); +} + void BLEService::do_create(BLEServer *server) { this->server_ = server; diff --git a/esphome/components/esp32_ble_server/ble_service.h b/esphome/components/esp32_ble_server/ble_service.h index dcfad5f501..311c937c28 100644 --- a/esphome/components/esp32_ble_server/ble_service.h +++ b/esphome/components/esp32_ble_server/ble_service.h @@ -31,6 +31,9 @@ class BLEService { BLECharacteristic *create_characteristic(uint16_t uuid, esp_gatt_char_prop_t properties); BLECharacteristic *create_characteristic(ESPBTUUID uuid, esp_gatt_char_prop_t properties); + // Add pre-constructed characteristic (used by generated code) + void add_characteristic(BLECharacteristic *characteristic); + ESPBTUUID get_uuid() { return this->uuid_; } uint8_t get_inst_id() { return this->inst_id_; } BLECharacteristic *get_last_created_characteristic() { return this->last_created_characteristic_; } diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index ca08ff0cca..f773083890 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -38,8 +38,7 @@ void ESP32ImprovComponent::setup() { }); } #endif - global_ble_server->on(BLEServerEvt::EmptyEvt::ON_DISCONNECT, - [this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); }); + global_ble_server->on_disconnect([this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); }); // Start with loop disabled - will be enabled by start() when needed this->disable_loop(); @@ -57,12 +56,11 @@ void ESP32ImprovComponent::setup_characteristics() { this->error_->add_descriptor(error_descriptor); this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE); - this->rpc_->EventEmitter, uint16_t>::on( - BLECharacteristicEvt::VectorEvt::ON_WRITE, [this](const std::vector &data, uint16_t id) { - if (!data.empty()) { - this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end()); - } - }); + this->rpc_->on_write([this](std::span data, uint16_t id) { + if (!data.empty()) { + this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end()); + } + }); BLEDescriptor *rpc_descriptor = new BLE2902(); this->rpc_->add_descriptor(rpc_descriptor); diff --git a/esphome/components/event_emitter/event_emitter.h b/esphome/components/event_emitter/event_emitter.h index 74afde03c0..e44cac7cc7 100644 --- a/esphome/components/event_emitter/event_emitter.h +++ b/esphome/components/event_emitter/event_emitter.h @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include @@ -13,34 +13,31 @@ static constexpr EventEmitterListenerID INVALID_LISTENER_ID = 0; // EventEmitter class that can emit events with a specific name (it is highly recommended to use an enum class for this) // and a list of arguments. Supports multiple listeners for each event. -template class EventEmitter { +// MaxListeners is the compile-time maximum number of listeners per event type. +template class EventEmitter { public: EventEmitterListenerID on(EvtType event, std::function listener) { - EventEmitterListenerID listener_id = this->get_next_id_(); - - // Find or create event entry - EventEntry *entry = this->find_or_create_event_(event); - entry->listeners.push_back({listener_id, listener}); - - return listener_id; + // Find a free slot in the listeners array + for (auto &entry : this->listeners_) { + if (entry.id == INVALID_LISTENER_ID) { + // Found empty slot + EventEmitterListenerID listener_id = this->get_next_id_(); + entry.id = listener_id; + entry.event = event; + entry.callback = std::move(listener); + return listener_id; + } + } + // No free slots - array is full + return INVALID_LISTENER_ID; } void off(EvtType event, EventEmitterListenerID id) { - EventEntry *entry = this->find_event_(event); - if (entry == nullptr) - return; - - // Remove listener with given id - for (auto it = entry->listeners.begin(); it != entry->listeners.end(); ++it) { - if (it->id == id) { - // Swap with last and pop for efficient removal - *it = entry->listeners.back(); - entry->listeners.pop_back(); - - // Remove event entry if no more listeners - if (entry->listeners.empty()) { - this->remove_event_(event); - } + // Find and remove listener with given id + for (auto &entry : this->listeners_) { + if (entry.id == id && entry.event == event) { + entry.id = INVALID_LISTENER_ID; + entry.callback = nullptr; return; } } @@ -48,25 +45,19 @@ template class EventEmitter { protected: void emit_(EvtType event, Args... args) { - EventEntry *entry = this->find_event_(event); - if (entry == nullptr) - return; - // Call all listeners for this event - for (const auto &listener : entry->listeners) { - listener.callback(args...); + for (const auto &entry : this->listeners_) { + if (entry.id != INVALID_LISTENER_ID && entry.event == event) { + entry.callback(args...); + } } } private: - struct Listener { - EventEmitterListenerID id; - std::function callback; - }; - - struct EventEntry { + struct ListenerEntry { EvtType event; - std::vector listeners; + EventEmitterListenerID id{INVALID_LISTENER_ID}; + std::function callback{nullptr}; }; EventEmitterListenerID get_next_id_() { @@ -79,38 +70,8 @@ template class EventEmitter { return this->current_id_; } - EventEntry *find_event_(EvtType event) { - for (auto &entry : this->events_) { - if (entry.event == event) { - return &entry; - } - } - return nullptr; - } - - EventEntry *find_or_create_event_(EvtType event) { - EventEntry *entry = this->find_event_(event); - if (entry != nullptr) - return entry; - - // Create new event entry - this->events_.push_back({event, {}}); - return &this->events_.back(); - } - - void remove_event_(EvtType event) { - for (auto it = this->events_.begin(); it != this->events_.end(); ++it) { - if (it->event == event) { - // Swap with last and pop - *it = this->events_.back(); - this->events_.pop_back(); - return; - } - } - } - - std::vector events_; - EventEmitterListenerID current_id_ = 0; + std::array listeners_{}; + EventEmitterListenerID current_id_{0}; }; } // namespace event_emitter From e7750250e068175389f9f0df880281c1e16a5ee4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 29 Sep 2025 17:55:26 -0500 Subject: [PATCH 2/6] wip --- .../components/esp32_ble_server/__init__.py | 194 ++++++++++++++++++ .../esp32_ble_server/ble_characteristic.cpp | 2 +- .../esp32_ble_server/ble_characteristic.h | 2 +- .../esp32_ble_server/ble_descriptor.h | 2 +- esphome/components/esp32_improv/__init__.py | 11 +- 5 files changed, 207 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index 7bd3a0e585..f8f913b8e9 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -591,6 +591,12 @@ async def to_code_characteristic(service_var, char_conf): parse_properties(char_conf), ), ) + + # If this characteristic has notify or indicate, it will get a CCCD descriptor (0x2902) + # and ble_characteristic.cpp will register a listener for it + if char_conf.get(CONF_NOTIFY, False) or char_conf.get(CONF_INDICATE, False): + allocate_descriptor_event_listener(0x2902, "WRITE", "esp32_ble_server", 1) + if CONF_ON_WRITE in char_conf: on_write_conf = char_conf[CONF_ON_WRITE] cg.add_define("USE_ESP32_BLE_SERVER_CHARACTERISTIC_ON_WRITE") @@ -670,6 +676,194 @@ async def to_code(config): [(cg.uint16, "id")], config[CONF_ON_DISCONNECT], ) + + # Generate defines for BLECharacteristicSetValueActionManager + set_value_action_count = sum( + count for comp, count in _LISTENER_ALLOCATIONS["server_connect"] + ) + if set_value_action_count > 0: + cg.add_define("BLE_SET_VALUE_ACTION_MAX_LISTENERS", str(set_value_action_count)) + + # Generate defines and specialized classes for server events + server_connect_count = sum( + count for comp, count in _LISTENER_ALLOCATIONS["server_connect"] + ) + server_disconnect_count = sum( + count for comp, count in _LISTENER_ALLOCATIONS["server_disconnect"] + ) + + if server_connect_count > 0 or server_disconnect_count > 0: + # Generate the specialized BLEServer class with EventEmitter support + cg.add_define( + "BLE_SERVER_CONNECT_MAX_LISTENERS", str(max(server_connect_count, 1)) + ) + cg.add_define( + "BLE_SERVER_DISCONNECT_MAX_LISTENERS", + str(max(server_disconnect_count, 1)), + ) + # TODO: Generate specialized BLEServer class with EventEmitter mixins + + # Generate defines and specialized classes for characteristics + # Group allocations by UUID + char_write_by_uuid: dict[int | str, int] = {} + for comp, uuid, count in _LISTENER_ALLOCATIONS["characteristic_write"]: + char_write_by_uuid[uuid] = char_write_by_uuid.get(uuid, 0) + count + + char_read_by_uuid: dict[int | str, int] = {} + for comp, uuid, count in _LISTENER_ALLOCATIONS["characteristic_read"]: + char_read_by_uuid[uuid] = char_read_by_uuid.get(uuid, 0) + count + + # Generate defines for each UUID + for uuid, count in char_write_by_uuid.items(): + uuid_id = sanitize_uuid_for_identifier(uuid) + cg.add_define(f"BLE_CHAR_{uuid_id}_WRITE_MAX_LISTENERS", str(count)) + + for uuid, count in char_read_by_uuid.items(): + uuid_id = sanitize_uuid_for_identifier(uuid) + cg.add_define(f"BLE_CHAR_{uuid_id}_READ_MAX_LISTENERS", str(count)) + + # Generate defines and specialized classes for descriptors + descriptor_write_by_uuid: dict[int | str, int] = {} + for comp, uuid, count in _LISTENER_ALLOCATIONS["descriptor_write"]: + descriptor_write_by_uuid[uuid] = descriptor_write_by_uuid.get(uuid, 0) + count + + for uuid, count in descriptor_write_by_uuid.items(): + uuid_id = sanitize_uuid_for_identifier(uuid) + cg.add_define(f"BLE_DESC_{uuid_id}_WRITE_MAX_LISTENERS", str(count)) + + # Generate specialized characteristic classes with EventEmitter support + for uuid in set(list(char_write_by_uuid.keys()) + list(char_read_by_uuid.keys())): + uuid_id = sanitize_uuid_for_identifier(uuid) + write_count = char_write_by_uuid.get(uuid, 0) + read_count = char_read_by_uuid.get(uuid, 0) + + # Generate specialized class that inherits from BLECharacteristic and adds EventEmitter support + class_name = f"BLECharacteristic_{uuid_id}" + + # Build the class header with appropriate EventEmitter base classes + base_classes = ["BLECharacteristic"] + template_params = [] + + if write_count > 0: + base_classes.append( + f"EventEmitter, uint16_t>" + ) + template_params.append("write") + if read_count > 0: + base_classes.append( + f"EventEmitter" + ) + template_params.append("read") + + cg.add_global( + cg.RawExpression(f""" +class {class_name} : public {", public ".join(base_classes)} {{ + public: + {class_name}(ESPBTUUID uuid, uint32_t properties, uint16_t max_len = 100) + : BLECharacteristic(uuid, properties, max_len) {{}} + + // Override virtual methods to provide EventEmitter functionality + {"EventEmitterListenerID on_write(std::function, uint16_t)> &&listener) override {" if write_count > 0 else ""} + {" return this->EventEmitter, uint16_t>::on(BLECharacteristicEvt::SpanEvt::ON_WRITE, std::move(listener));" if write_count > 0 else ""} + {"}" if write_count > 0 else ""} + + {"void off_write(EventEmitterListenerID id) override {" if write_count > 0 else ""} + {" this->EventEmitter, uint16_t>::off(BLECharacteristicEvt::SpanEvt::ON_WRITE, id);" if write_count > 0 else ""} + {"}" if write_count > 0 else ""} + + {"EventEmitterListenerID on_read(std::function &&listener) override {" if read_count > 0 else ""} + {" return this->EventEmitter::on(BLECharacteristicEvt::EmptyEvt::ON_READ, std::move(listener));" if read_count > 0 else ""} + {"}" if read_count > 0 else ""} + + {"void off_read(EventEmitterListenerID id) override {" if read_count > 0 else ""} + {" this->EventEmitter::off(BLECharacteristicEvt::EmptyEvt::ON_READ, id);" if read_count > 0 else ""} + {"}" if read_count > 0 else ""} + + protected: + {"void emit_on_write_(std::span value, uint16_t conn_id) override {" if write_count > 0 else ""} + {" this->EventEmitter, uint16_t>::emit_(BLECharacteristicEvt::SpanEvt::ON_WRITE, value, conn_id);" if write_count > 0 else ""} + {"}" if write_count > 0 else ""} + + {"void emit_on_read_(uint16_t conn_id) override {" if read_count > 0 else ""} + {" this->EventEmitter::emit_(BLECharacteristicEvt::EmptyEvt::ON_READ, conn_id);" if read_count > 0 else ""} + {"}" if read_count > 0 else ""} +}}; +""") + ) + + # Generate specialized descriptor classes with EventEmitter support + for uuid, count in descriptor_write_by_uuid.items(): + uuid_id = sanitize_uuid_for_identifier(uuid) + class_name = f"BLEDescriptor_{uuid_id}" + + cg.add_global( + cg.RawExpression(f""" +class {class_name} : public BLEDescriptor, + public EventEmitter, uint16_t> {{ + public: + {class_name}(ESPBTUUID uuid, uint16_t max_len = 100, bool read = true, bool write = true) + : BLEDescriptor(uuid, max_len, read, write) {{}} + + EventEmitterListenerID on_write(std::function, uint16_t)> &&listener) override {{ + return this->EventEmitter, uint16_t>::on(BLEDescriptorEvt::SpanEvt::ON_WRITE, std::move(listener)); + }} + + void off_write(EventEmitterListenerID id) override {{ + this->EventEmitter, uint16_t>::off(BLEDescriptorEvt::SpanEvt::ON_WRITE, id); + }} + + protected: + void emit_on_write_(std::span value, uint16_t conn_id) override {{ + this->EventEmitter, uint16_t>::emit_(BLEDescriptorEvt::SpanEvt::ON_WRITE, value, conn_id); + }} +}}; +""") + ) + + # Generate specialized BLEServer class if needed + if server_connect_count > 0 or server_disconnect_count > 0: + base_classes = ["BLEServer"] + if server_connect_count > 0: + base_classes.append( + "EventEmitter" + ) + if server_disconnect_count > 0: + base_classes.append( + "EventEmitter" + ) + + cg.add_global( + cg.RawExpression(f""" +class BLEServerWithEvents : public {", public ".join(base_classes)} {{ + public: + {"EventEmitterListenerID on_connect(std::function &&listener) override {" if server_connect_count > 0 else ""} + {" return this->EventEmitter::on(BLEServerEvt::EmptyEvt::ON_CONNECT, std::move(listener));" if server_connect_count > 0 else ""} + {"}" if server_connect_count > 0 else ""} + + {"void off_connect(EventEmitterListenerID id) override {" if server_connect_count > 0 else ""} + {" this->EventEmitter::off(BLEServerEvt::EmptyEvt::ON_CONNECT, id);" if server_connect_count > 0 else ""} + {"}" if server_connect_count > 0 else ""} + + {"EventEmitterListenerID on_disconnect(std::function &&listener) override {" if server_disconnect_count > 0 else ""} + {" return this->EventEmitter::on(BLEServerEvt::EmptyEvt::ON_DISCONNECT, std::move(listener));" if server_disconnect_count > 0 else ""} + {"}" if server_disconnect_count > 0 else ""} + + {"void off_disconnect(EventEmitterListenerID id) override {" if server_disconnect_count > 0 else ""} + {" this->EventEmitter::off(BLEServerEvt::EmptyEvt::ON_DISCONNECT, id);" if server_disconnect_count > 0 else ""} + {"}" if server_disconnect_count > 0 else ""} + + protected: + {"void emit_on_connect_(uint16_t conn_id) override {" if server_connect_count > 0 else ""} + {" this->EventEmitter::emit_(BLEServerEvt::EmptyEvt::ON_CONNECT, conn_id);" if server_connect_count > 0 else ""} + {"}" if server_connect_count > 0 else ""} + + {"void emit_on_disconnect_(uint16_t conn_id) override {" if server_disconnect_count > 0 else ""} + {" this->EventEmitter::emit_(BLEServerEvt::EmptyEvt::ON_DISCONNECT, conn_id);" if server_disconnect_count > 0 else ""} + {"}" if server_disconnect_count > 0 else ""} +}}; +""") + ) + cg.add_define("USE_ESP32_BLE_SERVER") cg.add_define("USE_ESP32_BLE_ADVERTISING") add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index efa9c80b30..d3fde4ec25 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -73,7 +73,7 @@ void BLECharacteristic::notify() { void BLECharacteristic::add_descriptor(BLEDescriptor *descriptor) { // If the descriptor is the CCCD descriptor, listen to its write event to know if the client wants to be notified if (descriptor->get_uuid() == ESPBTUUID::from_uint16(ESP_GATT_UUID_CHAR_CLIENT_CONFIG)) { - descriptor->on(BLEDescriptorEvt::VectorEvt::ON_WRITE, [this](const std::vector &value, uint16_t conn_id) { + descriptor->on_write([this](std::span value, uint16_t conn_id) { if (value.size() != 2) return; uint16_t cccd = encode_uint16(value[1], value[0]); diff --git a/esphome/components/esp32_ble_server/ble_characteristic.h b/esphome/components/esp32_ble_server/ble_characteristic.h index 6b322bc7ff..a64610d73f 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.h +++ b/esphome/components/esp32_ble_server/ble_characteristic.h @@ -28,7 +28,7 @@ using namespace event_emitter; class BLEService; namespace BLECharacteristicEvt { -enum VectorEvt { +enum SpanEvt { ON_WRITE, }; diff --git a/esphome/components/esp32_ble_server/ble_descriptor.h b/esphome/components/esp32_ble_server/ble_descriptor.h index 1b787f7a35..af1cbfd18d 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.h +++ b/esphome/components/esp32_ble_server/ble_descriptor.h @@ -20,7 +20,7 @@ using namespace event_emitter; class BLECharacteristic; namespace BLEDescriptorEvt { -enum VectorEvt { +enum SpanEvt { ON_WRITE, }; } // namespace BLEDescriptorEvt diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index fa33bd947a..43377326bd 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -1,6 +1,6 @@ from esphome import automation import esphome.codegen as cg -from esphome.components import binary_sensor, esp32_ble, output +from esphome.components import binary_sensor, esp32_ble, esp32_ble_server, output from esphome.components.esp32_ble import BTLoggers import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID @@ -98,6 +98,15 @@ async def to_code(config): # Register the loggers this component needs esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP) + # Allocate event listeners for esp32_improv + # Need 1 listener for server disconnect event + esp32_ble_server.allocate_server_event_listener("DISCONNECT", "esp32_improv", 1) + # The RPC characteristic UUID comes from the Improv library (0x00467768-6228-2272-4663-277478268000 + 0x01) + # We need 1 listener for the RPC write event + esp32_ble_server.allocate_characteristic_event_listener( + "00467768-6228-2272-4663-277478268001", "WRITE", "esp32_improv", 1 + ) + var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) From d802d70311000acc69b9ff2b38057c20b34487d7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 29 Sep 2025 18:07:46 -0500 Subject: [PATCH 3/6] wip --- .../components/esp32_ble_server/__init__.py | 280 +----------------- .../esp32_ble_server/ble_characteristic.cpp | 12 +- .../esp32_ble_server/ble_characteristic.h | 32 +- .../esp32_ble_server/ble_descriptor.cpp | 4 +- .../esp32_ble_server/ble_descriptor.h | 19 +- .../esp32_ble_server/ble_server.cpp | 8 +- .../components/esp32_ble_server/ble_server.h | 22 +- .../ble_server_automations.cpp | 16 +- .../esp32_ble_server/ble_server_automations.h | 39 +-- esphome/components/esp32_improv/__init__.py | 11 +- esphome/components/event_emitter/__init__.py | 5 - .../components/event_emitter/event_emitter.h | 78 ----- 12 files changed, 50 insertions(+), 476 deletions(-) delete mode 100644 esphome/components/event_emitter/__init__.py delete mode 100644 esphome/components/event_emitter/event_emitter.h diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index f8f913b8e9..bcb4044f94 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -1,5 +1,4 @@ import encodings -from typing import TypeAlias from esphome import automation import esphome.codegen as cg @@ -27,96 +26,11 @@ from esphome.const import ( from esphome.core import CORE from esphome.schema_extractors import SCHEMA_EXTRACT -AUTO_LOAD = ["esp32_ble", "bytebuffer", "event_emitter"] +AUTO_LOAD = ["esp32_ble", "bytebuffer"] CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"] DEPENDENCIES = ["esp32"] DOMAIN = "esp32_ble_server" -# Type aliases -_ListenerAllocation: TypeAlias = tuple[str, int | str, int] # (component, uuid, count) -_ServerListenerAllocation: TypeAlias = tuple[str, int] # (component, count) - -# Event listener allocation tracking - used by components to reserve slots -_LISTENER_ALLOCATIONS: dict[ - str, list[_ListenerAllocation | _ServerListenerAllocation] -] = { - "characteristic_write": [], - "characteristic_read": [], - "descriptor_write": [], - "server_connect": [], - "server_disconnect": [], -} - - -def allocate_characteristic_event_listener( - uuid: int | str, event_type: str, component: str, count: int = 1 -) -> None: - """ - Allocate event listener slots for a characteristic. - - Args: - uuid: The characteristic UUID (int or string) - event_type: "WRITE" or "READ" - component: Name of the component requesting allocation - count: Number of listeners needed (default 1) - """ - if event_type not in ("WRITE", "READ"): - raise ValueError(f"Unknown event_type: {event_type}") - - key = f"characteristic_{event_type.lower()}" - _LISTENER_ALLOCATIONS[key].append((component, uuid, count)) - - -def allocate_descriptor_event_listener( - uuid: int | str, event_type: str, component: str, count: int = 1 -) -> None: - """Allocate event listener slots for a descriptor.""" - if event_type != "WRITE": - raise ValueError(f"Unknown event_type: {event_type}") - - _LISTENER_ALLOCATIONS["descriptor_write"].append((component, uuid, count)) - - -def allocate_server_event_listener( - event_type: str, component: str, count: int = 1 -) -> None: - """Allocate event listener slots for server events.""" - if event_type not in ("CONNECT", "DISCONNECT"): - raise ValueError(f"Unknown event_type: {event_type}") - - key = f"server_{event_type.lower()}" - _LISTENER_ALLOCATIONS[key].append((component, count)) - - -def _sum_allocations_for_uuid(allocation_key: str, uuid: int | str) -> int: - """Helper to sum allocations for a specific UUID.""" - return sum( - count - for comp, alloc_uuid, count in _LISTENER_ALLOCATIONS[allocation_key] - if alloc_uuid == uuid - ) - - -def _get_allocations_for_uuid(uuid: int | str, event_type: str) -> int: - """Get total allocated listeners for a specific UUID and event type.""" - if event_type not in ("WRITE", "READ"): - return 0 - key = f"characteristic_{event_type.lower()}" - return _sum_allocations_for_uuid(key, uuid) - - -def _get_descriptor_allocations_for_uuid(uuid: int | str) -> int: - """Get total allocated listeners for a descriptor UUID.""" - return _sum_allocations_for_uuid("descriptor_write", uuid) - - -def sanitize_uuid_for_identifier(uuid: int | str) -> str: - """Convert UUID to valid C++ identifier.""" - if isinstance(uuid, int): - return f"0x{uuid:04X}" - # For string UUIDs, replace dashes and colons with underscores - return str(uuid).replace("-", "_").replace(":", "_").lower() - CONF_ADVERTISE = "advertise" CONF_APPEARANCE = "appearance" @@ -592,11 +506,6 @@ async def to_code_characteristic(service_var, char_conf): ), ) - # If this characteristic has notify or indicate, it will get a CCCD descriptor (0x2902) - # and ble_characteristic.cpp will register a listener for it - if char_conf.get(CONF_NOTIFY, False) or char_conf.get(CONF_INDICATE, False): - allocate_descriptor_event_listener(0x2902, "WRITE", "esp32_ble_server", 1) - if CONF_ON_WRITE in char_conf: on_write_conf = char_conf[CONF_ON_WRITE] cg.add_define("USE_ESP32_BLE_SERVER_CHARACTERISTIC_ON_WRITE") @@ -677,193 +586,6 @@ async def to_code(config): config[CONF_ON_DISCONNECT], ) - # Generate defines for BLECharacteristicSetValueActionManager - set_value_action_count = sum( - count for comp, count in _LISTENER_ALLOCATIONS["server_connect"] - ) - if set_value_action_count > 0: - cg.add_define("BLE_SET_VALUE_ACTION_MAX_LISTENERS", str(set_value_action_count)) - - # Generate defines and specialized classes for server events - server_connect_count = sum( - count for comp, count in _LISTENER_ALLOCATIONS["server_connect"] - ) - server_disconnect_count = sum( - count for comp, count in _LISTENER_ALLOCATIONS["server_disconnect"] - ) - - if server_connect_count > 0 or server_disconnect_count > 0: - # Generate the specialized BLEServer class with EventEmitter support - cg.add_define( - "BLE_SERVER_CONNECT_MAX_LISTENERS", str(max(server_connect_count, 1)) - ) - cg.add_define( - "BLE_SERVER_DISCONNECT_MAX_LISTENERS", - str(max(server_disconnect_count, 1)), - ) - # TODO: Generate specialized BLEServer class with EventEmitter mixins - - # Generate defines and specialized classes for characteristics - # Group allocations by UUID - char_write_by_uuid: dict[int | str, int] = {} - for comp, uuid, count in _LISTENER_ALLOCATIONS["characteristic_write"]: - char_write_by_uuid[uuid] = char_write_by_uuid.get(uuid, 0) + count - - char_read_by_uuid: dict[int | str, int] = {} - for comp, uuid, count in _LISTENER_ALLOCATIONS["characteristic_read"]: - char_read_by_uuid[uuid] = char_read_by_uuid.get(uuid, 0) + count - - # Generate defines for each UUID - for uuid, count in char_write_by_uuid.items(): - uuid_id = sanitize_uuid_for_identifier(uuid) - cg.add_define(f"BLE_CHAR_{uuid_id}_WRITE_MAX_LISTENERS", str(count)) - - for uuid, count in char_read_by_uuid.items(): - uuid_id = sanitize_uuid_for_identifier(uuid) - cg.add_define(f"BLE_CHAR_{uuid_id}_READ_MAX_LISTENERS", str(count)) - - # Generate defines and specialized classes for descriptors - descriptor_write_by_uuid: dict[int | str, int] = {} - for comp, uuid, count in _LISTENER_ALLOCATIONS["descriptor_write"]: - descriptor_write_by_uuid[uuid] = descriptor_write_by_uuid.get(uuid, 0) + count - - for uuid, count in descriptor_write_by_uuid.items(): - uuid_id = sanitize_uuid_for_identifier(uuid) - cg.add_define(f"BLE_DESC_{uuid_id}_WRITE_MAX_LISTENERS", str(count)) - - # Generate specialized characteristic classes with EventEmitter support - for uuid in set(list(char_write_by_uuid.keys()) + list(char_read_by_uuid.keys())): - uuid_id = sanitize_uuid_for_identifier(uuid) - write_count = char_write_by_uuid.get(uuid, 0) - read_count = char_read_by_uuid.get(uuid, 0) - - # Generate specialized class that inherits from BLECharacteristic and adds EventEmitter support - class_name = f"BLECharacteristic_{uuid_id}" - - # Build the class header with appropriate EventEmitter base classes - base_classes = ["BLECharacteristic"] - template_params = [] - - if write_count > 0: - base_classes.append( - f"EventEmitter, uint16_t>" - ) - template_params.append("write") - if read_count > 0: - base_classes.append( - f"EventEmitter" - ) - template_params.append("read") - - cg.add_global( - cg.RawExpression(f""" -class {class_name} : public {", public ".join(base_classes)} {{ - public: - {class_name}(ESPBTUUID uuid, uint32_t properties, uint16_t max_len = 100) - : BLECharacteristic(uuid, properties, max_len) {{}} - - // Override virtual methods to provide EventEmitter functionality - {"EventEmitterListenerID on_write(std::function, uint16_t)> &&listener) override {" if write_count > 0 else ""} - {" return this->EventEmitter, uint16_t>::on(BLECharacteristicEvt::SpanEvt::ON_WRITE, std::move(listener));" if write_count > 0 else ""} - {"}" if write_count > 0 else ""} - - {"void off_write(EventEmitterListenerID id) override {" if write_count > 0 else ""} - {" this->EventEmitter, uint16_t>::off(BLECharacteristicEvt::SpanEvt::ON_WRITE, id);" if write_count > 0 else ""} - {"}" if write_count > 0 else ""} - - {"EventEmitterListenerID on_read(std::function &&listener) override {" if read_count > 0 else ""} - {" return this->EventEmitter::on(BLECharacteristicEvt::EmptyEvt::ON_READ, std::move(listener));" if read_count > 0 else ""} - {"}" if read_count > 0 else ""} - - {"void off_read(EventEmitterListenerID id) override {" if read_count > 0 else ""} - {" this->EventEmitter::off(BLECharacteristicEvt::EmptyEvt::ON_READ, id);" if read_count > 0 else ""} - {"}" if read_count > 0 else ""} - - protected: - {"void emit_on_write_(std::span value, uint16_t conn_id) override {" if write_count > 0 else ""} - {" this->EventEmitter, uint16_t>::emit_(BLECharacteristicEvt::SpanEvt::ON_WRITE, value, conn_id);" if write_count > 0 else ""} - {"}" if write_count > 0 else ""} - - {"void emit_on_read_(uint16_t conn_id) override {" if read_count > 0 else ""} - {" this->EventEmitter::emit_(BLECharacteristicEvt::EmptyEvt::ON_READ, conn_id);" if read_count > 0 else ""} - {"}" if read_count > 0 else ""} -}}; -""") - ) - - # Generate specialized descriptor classes with EventEmitter support - for uuid, count in descriptor_write_by_uuid.items(): - uuid_id = sanitize_uuid_for_identifier(uuid) - class_name = f"BLEDescriptor_{uuid_id}" - - cg.add_global( - cg.RawExpression(f""" -class {class_name} : public BLEDescriptor, - public EventEmitter, uint16_t> {{ - public: - {class_name}(ESPBTUUID uuid, uint16_t max_len = 100, bool read = true, bool write = true) - : BLEDescriptor(uuid, max_len, read, write) {{}} - - EventEmitterListenerID on_write(std::function, uint16_t)> &&listener) override {{ - return this->EventEmitter, uint16_t>::on(BLEDescriptorEvt::SpanEvt::ON_WRITE, std::move(listener)); - }} - - void off_write(EventEmitterListenerID id) override {{ - this->EventEmitter, uint16_t>::off(BLEDescriptorEvt::SpanEvt::ON_WRITE, id); - }} - - protected: - void emit_on_write_(std::span value, uint16_t conn_id) override {{ - this->EventEmitter, uint16_t>::emit_(BLEDescriptorEvt::SpanEvt::ON_WRITE, value, conn_id); - }} -}}; -""") - ) - - # Generate specialized BLEServer class if needed - if server_connect_count > 0 or server_disconnect_count > 0: - base_classes = ["BLEServer"] - if server_connect_count > 0: - base_classes.append( - "EventEmitter" - ) - if server_disconnect_count > 0: - base_classes.append( - "EventEmitter" - ) - - cg.add_global( - cg.RawExpression(f""" -class BLEServerWithEvents : public {", public ".join(base_classes)} {{ - public: - {"EventEmitterListenerID on_connect(std::function &&listener) override {" if server_connect_count > 0 else ""} - {" return this->EventEmitter::on(BLEServerEvt::EmptyEvt::ON_CONNECT, std::move(listener));" if server_connect_count > 0 else ""} - {"}" if server_connect_count > 0 else ""} - - {"void off_connect(EventEmitterListenerID id) override {" if server_connect_count > 0 else ""} - {" this->EventEmitter::off(BLEServerEvt::EmptyEvt::ON_CONNECT, id);" if server_connect_count > 0 else ""} - {"}" if server_connect_count > 0 else ""} - - {"EventEmitterListenerID on_disconnect(std::function &&listener) override {" if server_disconnect_count > 0 else ""} - {" return this->EventEmitter::on(BLEServerEvt::EmptyEvt::ON_DISCONNECT, std::move(listener));" if server_disconnect_count > 0 else ""} - {"}" if server_disconnect_count > 0 else ""} - - {"void off_disconnect(EventEmitterListenerID id) override {" if server_disconnect_count > 0 else ""} - {" this->EventEmitter::off(BLEServerEvt::EmptyEvt::ON_DISCONNECT, id);" if server_disconnect_count > 0 else ""} - {"}" if server_disconnect_count > 0 else ""} - - protected: - {"void emit_on_connect_(uint16_t conn_id) override {" if server_connect_count > 0 else ""} - {" this->EventEmitter::emit_(BLEServerEvt::EmptyEvt::ON_CONNECT, conn_id);" if server_connect_count > 0 else ""} - {"}" if server_connect_count > 0 else ""} - - {"void emit_on_disconnect_(uint16_t conn_id) override {" if server_disconnect_count > 0 else ""} - {" this->EventEmitter::emit_(BLEServerEvt::EmptyEvt::ON_DISCONNECT, conn_id);" if server_disconnect_count > 0 else ""} - {"}" if server_disconnect_count > 0 else ""} -}}; -""") - ) - cg.add_define("USE_ESP32_BLE_SERVER") cg.add_define("USE_ESP32_BLE_ADVERTISING") add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index d3fde4ec25..12530b26ec 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -208,7 +208,9 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt if (!param->read.need_rsp) break; // For some reason you can request a read but not want a response - this->emit_on_read_(param->read.conn_id); + if (this->on_read_callback_) { + this->on_read_callback_(param->read.conn_id); + } uint16_t max_offset = 22; @@ -276,7 +278,9 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt } if (!param->write.is_prep) { - this->emit_on_write_(this->value_, param->write.conn_id); + if (this->on_write_callback_) { + this->on_write_callback_(this->value_, param->write.conn_id); + } } break; @@ -287,7 +291,9 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt break; this->write_event_ = false; if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { - this->emit_on_write_(this->value_, param->exec_write.conn_id); + if (this->on_write_callback_) { + this->on_write_callback_(this->value_, param->exec_write.conn_id); + } } esp_err_t err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, nullptr); diff --git a/esphome/components/esp32_ble_server/ble_characteristic.h b/esphome/components/esp32_ble_server/ble_characteristic.h index a64610d73f..3404cca55b 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.h +++ b/esphome/components/esp32_ble_server/ble_characteristic.h @@ -2,11 +2,11 @@ #include "ble_descriptor.h" #include "esphome/components/esp32_ble/ble_uuid.h" -#include "esphome/components/event_emitter/event_emitter.h" #include "esphome/components/bytebuffer/bytebuffer.h" #include #include +#include #ifdef USE_ESP32 @@ -23,22 +23,9 @@ namespace esp32_ble_server { using namespace esp32_ble; using namespace bytebuffer; -using namespace event_emitter; class BLEService; -namespace BLECharacteristicEvt { -enum SpanEvt { - ON_WRITE, -}; - -enum EmptyEvt { - ON_READ, -}; -} // namespace BLECharacteristicEvt - -// Base class for BLE characteristics -// Specialized classes with EventEmitter support are generated per-UUID in the build process class BLECharacteristic { public: BLECharacteristic(ESPBTUUID uuid, uint32_t properties); @@ -78,19 +65,13 @@ class BLECharacteristic { bool is_created(); bool is_failed(); - // Event listener registration - overridden by generated specialized classes - virtual EventEmitterListenerID on_write(std::function, uint16_t)> &&listener) { - return INVALID_LISTENER_ID; + // Direct callback registration + void on_write(std::function, uint16_t)> &&callback) { + this->on_write_callback_ = std::move(callback); } - virtual EventEmitterListenerID on_read(std::function &&listener) { return INVALID_LISTENER_ID; } - virtual void off_write(EventEmitterListenerID id) {} - virtual void off_read(EventEmitterListenerID id) {} + void on_read(std::function &&callback) { this->on_read_callback_ = std::move(callback); } protected: - // Virtual methods for emitting events - overridden by generated specialized classes - virtual void emit_on_write_(std::span value, uint16_t conn_id) {} - virtual void emit_on_read_(uint16_t conn_id) {} - bool write_event_{false}; BLEService *service_{}; ESPBTUUID uuid_; @@ -112,6 +93,9 @@ class BLECharacteristic { void remove_client_from_notify_list_(uint16_t conn_id); ClientNotificationEntry *find_client_in_notify_list_(uint16_t conn_id); + std::function, uint16_t)> on_write_callback_{nullptr}; + std::function on_read_callback_{nullptr}; + esp_gatt_perm_t permissions_ = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; enum State : uint8_t { diff --git a/esphome/components/esp32_ble_server/ble_descriptor.cpp b/esphome/components/esp32_ble_server/ble_descriptor.cpp index dbfa8bc632..1182a83b82 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.cpp +++ b/esphome/components/esp32_ble_server/ble_descriptor.cpp @@ -74,7 +74,9 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ break; this->value_.attr_len = param->write.len; memcpy(this->value_.attr_value, param->write.value, param->write.len); - this->emit_on_write_(std::span(param->write.value, param->write.len), param->write.conn_id); + if (this->on_write_callback_) { + this->on_write_callback_(std::span(param->write.value, param->write.len), param->write.conn_id); + } break; } default: diff --git a/esphome/components/esp32_ble_server/ble_descriptor.h b/esphome/components/esp32_ble_server/ble_descriptor.h index af1cbfd18d..00138d306d 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.h +++ b/esphome/components/esp32_ble_server/ble_descriptor.h @@ -19,14 +19,7 @@ using namespace event_emitter; class BLECharacteristic; -namespace BLEDescriptorEvt { -enum SpanEvt { - ON_WRITE, -}; -} // namespace BLEDescriptorEvt - // Base class for BLE descriptors -// Specialized classes with EventEmitter support are generated per-UUID in the build process class BLEDescriptor { public: BLEDescriptor(ESPBTUUID uuid, uint16_t max_len = 100, bool read = true, bool write = true); @@ -42,22 +35,20 @@ class BLEDescriptor { bool is_created() { return this->state_ == CREATED; } bool is_failed() { return this->state_ == FAILED; } - // Event listener registration - overridden by generated specialized classes if needed - virtual EventEmitterListenerID on_write(std::function, uint16_t)> &&listener) { - return INVALID_LISTENER_ID; + // Direct callback registration + void on_write(std::function, uint16_t)> &&callback) { + this->on_write_callback_ = std::move(callback); } - virtual void off_write(EventEmitterListenerID id) {} protected: - // Virtual method for emitting events - overridden by generated specialized classes - virtual void emit_on_write_(std::span value, uint16_t conn_id) {} - BLECharacteristic *characteristic_{nullptr}; ESPBTUUID uuid_; uint16_t handle_{0xFFFF}; esp_attr_value_t value_{}; + std::function, uint16_t)> on_write_callback_{nullptr}; + esp_gatt_perm_t permissions_{}; enum State : uint8_t { diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index d73c343b4d..0f993cad02 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -153,14 +153,18 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga case ESP_GATTS_CONNECT_EVT: { ESP_LOGD(TAG, "BLE Client connected"); this->add_client_(param->connect.conn_id); - this->emit_on_connect_(param->connect.conn_id); + if (this->on_connect_callback_) { + this->on_connect_callback_(param->connect.conn_id); + } break; } case ESP_GATTS_DISCONNECT_EVT: { ESP_LOGD(TAG, "BLE Client disconnected"); this->remove_client_(param->disconnect.conn_id); this->parent_->advertising_start(); - this->emit_on_disconnect_(param->disconnect.conn_id); + if (this->on_disconnect_callback_) { + this->on_disconnect_callback_(param->disconnect.conn_id); + } break; } case ESP_GATTS_REG_EVT: { diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index e3181a945d..8bb9429856 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -24,15 +24,7 @@ namespace esp32_ble_server { using namespace esp32_ble; using namespace bytebuffer; -namespace BLEServerEvt { -enum EmptyEvt { - ON_CONNECT, - ON_DISCONNECT, -}; -} // namespace BLEServerEvt - // Base class for BLE server -// Note: Only one BLEServer instance exists per build, so we can use fixed defines class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEventHandler, public Parented { public: void setup() override; @@ -63,16 +55,11 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv void ble_before_disabled_event_handler() override; - // Event listener registration - overridden by generated specialized classes if needed - virtual EventEmitterListenerID on_connect(std::function &&listener) { return INVALID_LISTENER_ID; } - virtual EventEmitterListenerID on_disconnect(std::function &&listener) { return INVALID_LISTENER_ID; } - virtual void off_connect(EventEmitterListenerID id) {} - virtual void off_disconnect(EventEmitterListenerID id) {} + // Direct callback registration + void on_connect(std::function &&callback) { this->on_connect_callback_ = std::move(callback); } + void on_disconnect(std::function &&callback) { this->on_disconnect_callback_ = std::move(callback); } protected: - // Virtual methods for emitting events - virtual void emit_on_connect_(uint16_t conn_id) {} - virtual void emit_on_disconnect_(uint16_t conn_id) {} struct ServiceEntry { ESPBTUUID uuid; uint8_t inst_id; @@ -84,6 +71,9 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv void add_client_(uint16_t conn_id) { this->clients_.insert(conn_id); } void remove_client_(uint16_t conn_id) { this->clients_.erase(conn_id); } + std::function on_connect_callback_{nullptr}; + std::function on_disconnect_callback_{nullptr}; + std::vector manufacturer_data_{}; esp_gatt_if_t gatts_if_{0}; bool registered_{false}; diff --git a/esphome/components/esp32_ble_server/ble_server_automations.cpp b/esphome/components/esp32_ble_server/ble_server_automations.cpp index afa958a4a0..0761de994a 100644 --- a/esphome/components/esp32_ble_server/ble_server_automations.cpp +++ b/esphome/components/esp32_ble_server/ble_server_automations.cpp @@ -52,29 +52,15 @@ Trigger *BLETriggers::create_server_on_disconnect_trigger(BLEServer *s #ifdef USE_ESP32_BLE_SERVER_SET_VALUE_ACTION void BLECharacteristicSetValueActionManager::set_listener(BLECharacteristic *characteristic, - EventEmitterListenerID listener_id, const std::function &pre_notify_listener) { // Find and remove existing listener for this characteristic auto *existing = this->find_listener_(characteristic); if (existing != nullptr) { - // Remove the previous listener - characteristic->off_read(existing->listener_id); - // Remove the pre-notify listener - this->off(BLECharacteristicSetValueActionEvt::PRE_NOTIFY, existing->pre_notify_listener_id); // Remove from vector this->remove_listener_(characteristic); } - // Create a new listener for the pre-notify event - EventEmitterListenerID pre_notify_listener_id = - this->on(BLECharacteristicSetValueActionEvt::PRE_NOTIFY, - [pre_notify_listener, characteristic](const BLECharacteristic *evt_characteristic) { - // Only call the pre-notify listener if the characteristic is the one we are interested in - if (characteristic == evt_characteristic) { - pre_notify_listener(); - } - }); // Save the entry to the vector - this->listeners_.push_back({characteristic, listener_id, pre_notify_listener_id}); + this->listeners_.push_back({characteristic, pre_notify_listener}); } BLECharacteristicSetValueActionManager::ListenerEntry *BLECharacteristicSetValueActionManager::find_listener_( diff --git a/esphome/components/esp32_ble_server/ble_server_automations.h b/esphome/components/esp32_ble_server/ble_server_automations.h index 08a3322367..543b1153fc 100644 --- a/esphome/components/esp32_ble_server/ble_server_automations.h +++ b/esphome/components/esp32_ble_server/ble_server_automations.h @@ -4,7 +4,6 @@ #include "ble_characteristic.h" #include "ble_descriptor.h" -#include "esphome/components/event_emitter/event_emitter.h" #include "esphome/core/automation.h" #include @@ -18,10 +17,6 @@ namespace esp32_ble_server { namespace esp32_ble_server_automations { using namespace esp32_ble; -using namespace event_emitter; - -// Invalid listener ID constant - 0 is used as sentinel value in EventEmitter -static constexpr EventEmitterListenerID INVALID_LISTENER_ID = 0; class BLETriggers { public: @@ -41,42 +36,29 @@ class BLETriggers { }; #ifdef USE_ESP32_BLE_SERVER_SET_VALUE_ACTION -enum BLECharacteristicSetValueActionEvt { - PRE_NOTIFY, -}; - // Class to make sure only one BLECharacteristicSetValueAction is active at a time for each characteristic -#ifndef BLE_SET_VALUE_ACTION_MAX_LISTENERS -#define BLE_SET_VALUE_ACTION_MAX_LISTENERS 1 -#endif - -class BLECharacteristicSetValueActionManager - : public EventEmitter { +class BLECharacteristicSetValueActionManager { public: // Singleton pattern static BLECharacteristicSetValueActionManager *get_instance() { static BLECharacteristicSetValueActionManager instance; return &instance; } - void set_listener(BLECharacteristic *characteristic, EventEmitterListenerID listener_id, - const std::function &pre_notify_listener); - EventEmitterListenerID get_listener(BLECharacteristic *characteristic) { + void set_listener(BLECharacteristic *characteristic, const std::function &pre_notify_listener); + bool has_listener(BLECharacteristic *characteristic) { return this->find_listener_(characteristic) != nullptr; } + void emit_pre_notify(BLECharacteristic *characteristic) { for (const auto &entry : this->listeners_) { if (entry.characteristic == characteristic) { - return entry.listener_id; + entry.pre_notify_listener(); + break; } } - return INVALID_LISTENER_ID; - } - void emit_pre_notify(BLECharacteristic *characteristic) { - this->emit_(BLECharacteristicSetValueActionEvt::PRE_NOTIFY, characteristic); } private: struct ListenerEntry { BLECharacteristic *characteristic; - EventEmitterListenerID listener_id; - EventEmitterListenerID pre_notify_listener_id; + std::function pre_notify_listener; }; std::vector listeners_; @@ -91,23 +73,22 @@ template class BLECharacteristicSetValueAction : public Actionset_buffer(buffer.get_data()); } void play(Ts... x) override { // If the listener is already set, do nothing - if (BLECharacteristicSetValueActionManager::get_instance()->get_listener(this->parent_) == this->listener_id_) + if (BLECharacteristicSetValueActionManager::get_instance()->has_listener(this->parent_)) return; // Set initial value this->parent_->set_value(this->buffer_.value(x...)); // Set the listener for read events - this->listener_id_ = this->parent_->on_read([this, x...](uint16_t id) { + this->parent_->on_read([this, x...](uint16_t id) { // Set the value of the characteristic every time it is read this->parent_->set_value(this->buffer_.value(x...)); }); // Set the listener in the global manager so only one BLECharacteristicSetValueAction is set for each characteristic BLECharacteristicSetValueActionManager::get_instance()->set_listener( - this->parent_, this->listener_id_, [this, x...]() { this->parent_->set_value(this->buffer_.value(x...)); }); + this->parent_, [this, x...]() { this->parent_->set_value(this->buffer_.value(x...)); }); } protected: BLECharacteristic *parent_; - EventEmitterListenerID listener_id_; }; #endif // USE_ESP32_BLE_SERVER_SET_VALUE_ACTION diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 43377326bd..fa33bd947a 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -1,6 +1,6 @@ from esphome import automation import esphome.codegen as cg -from esphome.components import binary_sensor, esp32_ble, esp32_ble_server, output +from esphome.components import binary_sensor, esp32_ble, output from esphome.components.esp32_ble import BTLoggers import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID @@ -98,15 +98,6 @@ async def to_code(config): # Register the loggers this component needs esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP) - # Allocate event listeners for esp32_improv - # Need 1 listener for server disconnect event - esp32_ble_server.allocate_server_event_listener("DISCONNECT", "esp32_improv", 1) - # The RPC characteristic UUID comes from the Improv library (0x00467768-6228-2272-4663-277478268000 + 0x01) - # We need 1 listener for the RPC write event - esp32_ble_server.allocate_characteristic_event_listener( - "00467768-6228-2272-4663-277478268001", "WRITE", "esp32_improv", 1 - ) - var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/event_emitter/__init__.py b/esphome/components/event_emitter/__init__.py deleted file mode 100644 index fcbbf26f02..0000000000 --- a/esphome/components/event_emitter/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -CODEOWNERS = ["@Rapsssito"] - -# Allows event_emitter to be configured in yaml, to allow use of the C++ api. - -CONFIG_SCHEMA = {} diff --git a/esphome/components/event_emitter/event_emitter.h b/esphome/components/event_emitter/event_emitter.h deleted file mode 100644 index e44cac7cc7..0000000000 --- a/esphome/components/event_emitter/event_emitter.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once -#include -#include -#include - -#include "esphome/core/log.h" - -namespace esphome { -namespace event_emitter { - -using EventEmitterListenerID = uint32_t; -static constexpr EventEmitterListenerID INVALID_LISTENER_ID = 0; - -// EventEmitter class that can emit events with a specific name (it is highly recommended to use an enum class for this) -// and a list of arguments. Supports multiple listeners for each event. -// MaxListeners is the compile-time maximum number of listeners per event type. -template class EventEmitter { - public: - EventEmitterListenerID on(EvtType event, std::function listener) { - // Find a free slot in the listeners array - for (auto &entry : this->listeners_) { - if (entry.id == INVALID_LISTENER_ID) { - // Found empty slot - EventEmitterListenerID listener_id = this->get_next_id_(); - entry.id = listener_id; - entry.event = event; - entry.callback = std::move(listener); - return listener_id; - } - } - // No free slots - array is full - return INVALID_LISTENER_ID; - } - - void off(EvtType event, EventEmitterListenerID id) { - // Find and remove listener with given id - for (auto &entry : this->listeners_) { - if (entry.id == id && entry.event == event) { - entry.id = INVALID_LISTENER_ID; - entry.callback = nullptr; - return; - } - } - } - - protected: - void emit_(EvtType event, Args... args) { - // Call all listeners for this event - for (const auto &entry : this->listeners_) { - if (entry.id != INVALID_LISTENER_ID && entry.event == event) { - entry.callback(args...); - } - } - } - - private: - struct ListenerEntry { - EvtType event; - EventEmitterListenerID id{INVALID_LISTENER_ID}; - std::function callback{nullptr}; - }; - - EventEmitterListenerID get_next_id_() { - // Simple incrementing ID, wrapping around at max - EventEmitterListenerID next_id = (this->current_id_ + 1); - if (next_id == INVALID_LISTENER_ID) { - next_id = 1; - } - this->current_id_ = next_id; - return this->current_id_; - } - - std::array listeners_{}; - EventEmitterListenerID current_id_{0}; -}; - -} // namespace event_emitter -} // namespace esphome From 9ff838bf35d75319adc36cf0fe8352d6bdfde9e6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 29 Sep 2025 18:13:46 -0500 Subject: [PATCH 4/6] wip --- .../esp32_ble_server/ble_characteristic.h | 14 +++++++++----- .../components/esp32_ble_server/ble_descriptor.h | 1 - 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/esphome/components/esp32_ble_server/ble_characteristic.h b/esphome/components/esp32_ble_server/ble_characteristic.h index 3404cca55b..4a29683f41 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.h +++ b/esphome/components/esp32_ble_server/ble_characteristic.h @@ -7,6 +7,7 @@ #include #include #include +#include #ifdef USE_ESP32 @@ -65,11 +66,14 @@ class BLECharacteristic { bool is_created(); bool is_failed(); - // Direct callback registration + // Direct callback registration - only allocates when callback is set void on_write(std::function, uint16_t)> &&callback) { - this->on_write_callback_ = std::move(callback); + this->on_write_callback_ = + std::make_unique, uint16_t)>>(std::move(callback)); + } + void on_read(std::function &&callback) { + this->on_read_callback_ = std::make_unique>(std::move(callback)); } - void on_read(std::function &&callback) { this->on_read_callback_ = std::move(callback); } protected: bool write_event_{false}; @@ -93,8 +97,8 @@ class BLECharacteristic { void remove_client_from_notify_list_(uint16_t conn_id); ClientNotificationEntry *find_client_in_notify_list_(uint16_t conn_id); - std::function, uint16_t)> on_write_callback_{nullptr}; - std::function on_read_callback_{nullptr}; + std::unique_ptr, uint16_t)>> on_write_callback_; + std::unique_ptr> on_read_callback_; esp_gatt_perm_t permissions_ = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; diff --git a/esphome/components/esp32_ble_server/ble_descriptor.h b/esphome/components/esp32_ble_server/ble_descriptor.h index 00138d306d..a43d9a84a0 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.h +++ b/esphome/components/esp32_ble_server/ble_descriptor.h @@ -1,7 +1,6 @@ #pragma once #include "esphome/components/esp32_ble/ble_uuid.h" -#include "esphome/components/event_emitter/event_emitter.h" #include "esphome/components/bytebuffer/bytebuffer.h" #ifdef USE_ESP32 From 43d8e213f631f82a78791fcfb2ef4abc48f1e4ab Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 29 Sep 2025 18:15:25 -0500 Subject: [PATCH 5/6] wip --- CODEOWNERS | 1 - .../esp32_ble_server/ble_characteristic.cpp | 6 +++--- .../esp32_ble_server/ble_descriptor.cpp | 3 ++- .../components/esp32_ble_server/ble_descriptor.h | 10 ++++++---- .../components/esp32_ble_server/ble_server.cpp | 4 ++-- esphome/components/esp32_ble_server/ble_server.h | 15 ++++++++++----- 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 3747acd2b5..0b9935faf7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -160,7 +160,6 @@ esphome/components/esp_ldo/* @clydebarrow esphome/components/espnow/* @jesserockz esphome/components/ethernet_info/* @gtjadsonsantos esphome/components/event/* @nohat -esphome/components/event_emitter/* @Rapsssito esphome/components/exposure_notifications/* @OttoWinter esphome/components/ezo/* @ssieb esphome/components/ezo_pmp/* @carlos-sarmiento diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index 12530b26ec..d485d9fe2d 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -209,7 +209,7 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt break; // For some reason you can request a read but not want a response if (this->on_read_callback_) { - this->on_read_callback_(param->read.conn_id); + (*this->on_read_callback_)(param->read.conn_id); } uint16_t max_offset = 22; @@ -279,7 +279,7 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt if (!param->write.is_prep) { if (this->on_write_callback_) { - this->on_write_callback_(this->value_, param->write.conn_id); + (*this->on_write_callback_)(this->value_, param->write.conn_id); } } @@ -292,7 +292,7 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt this->write_event_ = false; if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { if (this->on_write_callback_) { - this->on_write_callback_(this->value_, param->exec_write.conn_id); + (*this->on_write_callback_)(this->value_, param->exec_write.conn_id); } } esp_err_t err = diff --git a/esphome/components/esp32_ble_server/ble_descriptor.cpp b/esphome/components/esp32_ble_server/ble_descriptor.cpp index 1182a83b82..16941cca0f 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.cpp +++ b/esphome/components/esp32_ble_server/ble_descriptor.cpp @@ -75,7 +75,8 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ this->value_.attr_len = param->write.len; memcpy(this->value_.attr_value, param->write.value, param->write.len); if (this->on_write_callback_) { - this->on_write_callback_(std::span(param->write.value, param->write.len), param->write.conn_id); + (*this->on_write_callback_)(std::span(param->write.value, param->write.len), + param->write.conn_id); } break; } diff --git a/esphome/components/esp32_ble_server/ble_descriptor.h b/esphome/components/esp32_ble_server/ble_descriptor.h index a43d9a84a0..425462a316 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.h +++ b/esphome/components/esp32_ble_server/ble_descriptor.h @@ -8,13 +8,14 @@ #include #include #include +#include +#include namespace esphome { namespace esp32_ble_server { using namespace esp32_ble; using namespace bytebuffer; -using namespace event_emitter; class BLECharacteristic; @@ -34,9 +35,10 @@ class BLEDescriptor { bool is_created() { return this->state_ == CREATED; } bool is_failed() { return this->state_ == FAILED; } - // Direct callback registration + // Direct callback registration - only allocates when callback is set void on_write(std::function, uint16_t)> &&callback) { - this->on_write_callback_ = std::move(callback); + this->on_write_callback_ = + std::make_unique, uint16_t)>>(std::move(callback)); } protected: @@ -46,7 +48,7 @@ class BLEDescriptor { esp_attr_value_t value_{}; - std::function, uint16_t)> on_write_callback_{nullptr}; + std::unique_ptr, uint16_t)>> on_write_callback_; esp_gatt_perm_t permissions_{}; diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index 0f993cad02..8f7931d40e 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -154,7 +154,7 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga ESP_LOGD(TAG, "BLE Client connected"); this->add_client_(param->connect.conn_id); if (this->on_connect_callback_) { - this->on_connect_callback_(param->connect.conn_id); + (*this->on_connect_callback_)(param->connect.conn_id); } break; } @@ -163,7 +163,7 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga this->remove_client_(param->disconnect.conn_id); this->parent_->advertising_start(); if (this->on_disconnect_callback_) { - this->on_disconnect_callback_(param->disconnect.conn_id); + (*this->on_disconnect_callback_)(param->disconnect.conn_id); } break; } diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index 8bb9429856..7b3ee9bae0 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -13,6 +13,7 @@ #include #include #include +#include #ifdef USE_ESP32 @@ -55,9 +56,13 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv void ble_before_disabled_event_handler() override; - // Direct callback registration - void on_connect(std::function &&callback) { this->on_connect_callback_ = std::move(callback); } - void on_disconnect(std::function &&callback) { this->on_disconnect_callback_ = std::move(callback); } + // Direct callback registration - only allocates when callback is set + void on_connect(std::function &&callback) { + this->on_connect_callback_ = std::make_unique>(std::move(callback)); + } + void on_disconnect(std::function &&callback) { + this->on_disconnect_callback_ = std::make_unique>(std::move(callback)); + } protected: struct ServiceEntry { @@ -71,8 +76,8 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv void add_client_(uint16_t conn_id) { this->clients_.insert(conn_id); } void remove_client_(uint16_t conn_id) { this->clients_.erase(conn_id); } - std::function on_connect_callback_{nullptr}; - std::function on_disconnect_callback_{nullptr}; + std::unique_ptr> on_connect_callback_; + std::unique_ptr> on_disconnect_callback_; std::vector manufacturer_data_{}; esp_gatt_if_t gatts_if_{0}; From 87b54daee04a5c0ff7021413567e7aaa33877fd1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 29 Sep 2025 18:17:25 -0500 Subject: [PATCH 6/6] wip --- esphome/components/esp32_ble_server/ble_service.cpp | 4 ---- esphome/components/esp32_ble_server/ble_service.h | 3 --- 2 files changed, 7 deletions(-) diff --git a/esphome/components/esp32_ble_server/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp index 3c5b40ba9e..96fedf2346 100644 --- a/esphome/components/esp32_ble_server/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -41,10 +41,6 @@ BLECharacteristic *BLEService::create_characteristic(ESPBTUUID uuid, esp_gatt_ch return characteristic; } -void BLEService::add_characteristic(BLECharacteristic *characteristic) { - this->characteristics_.push_back(characteristic); -} - void BLEService::do_create(BLEServer *server) { this->server_ = server; diff --git a/esphome/components/esp32_ble_server/ble_service.h b/esphome/components/esp32_ble_server/ble_service.h index 311c937c28..dcfad5f501 100644 --- a/esphome/components/esp32_ble_server/ble_service.h +++ b/esphome/components/esp32_ble_server/ble_service.h @@ -31,9 +31,6 @@ class BLEService { BLECharacteristic *create_characteristic(uint16_t uuid, esp_gatt_char_prop_t properties); BLECharacteristic *create_characteristic(ESPBTUUID uuid, esp_gatt_char_prop_t properties); - // Add pre-constructed characteristic (used by generated code) - void add_characteristic(BLECharacteristic *characteristic); - ESPBTUUID get_uuid() { return this->uuid_; } uint8_t get_inst_id() { return this->inst_id_; } BLECharacteristic *get_last_created_characteristic() { return this->last_created_characteristic_; }