diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 1c142ca7bd..fc8c65a2f4 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -1,4 +1,5 @@ from collections.abc import Callable, MutableMapping +from dataclasses import dataclass from enum import Enum import logging import re @@ -16,7 +17,7 @@ from esphome.const import ( CONF_NAME, CONF_NAME_ADD_MAC_SUFFIX, ) -from esphome.core import CORE, TimePeriod +from esphome.core import CORE, CoroPriority, TimePeriod, coroutine_with_priority import esphome.final_validate as fv DEPENDENCIES = ["esp32"] @@ -111,6 +112,58 @@ class BTLoggers(Enum): _required_loggers: set[BTLoggers] = set() +# Dataclass for handler registration counts +@dataclass +class HandlerCounts: + gap_event: int = 0 + gap_scan_event: int = 0 + gattc_event: int = 0 + gatts_event: int = 0 + ble_status_event: int = 0 + + +# Track handler registration counts for StaticVector sizing +_handler_counts = HandlerCounts() + + +def register_gap_event_handler(parent_var: cg.MockObj, handler_var: cg.MockObj) -> None: + """Register a GAP event handler and track the count.""" + _handler_counts.gap_event += 1 + cg.add(parent_var.register_gap_event_handler(handler_var)) + + +def register_gap_scan_event_handler( + parent_var: cg.MockObj, handler_var: cg.MockObj +) -> None: + """Register a GAP scan event handler and track the count.""" + _handler_counts.gap_scan_event += 1 + cg.add(parent_var.register_gap_scan_event_handler(handler_var)) + + +def register_gattc_event_handler( + parent_var: cg.MockObj, handler_var: cg.MockObj +) -> None: + """Register a GATTc event handler and track the count.""" + _handler_counts.gattc_event += 1 + cg.add(parent_var.register_gattc_event_handler(handler_var)) + + +def register_gatts_event_handler( + parent_var: cg.MockObj, handler_var: cg.MockObj +) -> None: + """Register a GATTs event handler and track the count.""" + _handler_counts.gatts_event += 1 + cg.add(parent_var.register_gatts_event_handler(handler_var)) + + +def register_ble_status_event_handler( + parent_var: cg.MockObj, handler_var: cg.MockObj +) -> None: + """Register a BLE status event handler and track the count.""" + _handler_counts.ble_status_event += 1 + cg.add(parent_var.register_ble_status_event_handler(handler_var)) + + def register_bt_logger(*loggers: BTLoggers) -> None: """Register Bluetooth logger categories that a component needs. @@ -374,6 +427,36 @@ def final_validation(config): FINAL_VALIDATE_SCHEMA = final_validation +# This needs to be run as a job with CoroPriority.FINAL priority so that all components have +# a chance to register their handlers before the counts are added to defines. +@coroutine_with_priority(CoroPriority.FINAL) +async def _add_ble_handler_defines(): + # Add defines for StaticVector sizing based on handler registration counts + # Only define if count > 0 to avoid allocating unnecessary memory + if _handler_counts.gap_event > 0: + cg.add_define( + "ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT", _handler_counts.gap_event + ) + if _handler_counts.gap_scan_event > 0: + cg.add_define( + "ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT", + _handler_counts.gap_scan_event, + ) + if _handler_counts.gattc_event > 0: + cg.add_define( + "ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT", _handler_counts.gattc_event + ) + if _handler_counts.gatts_event > 0: + cg.add_define( + "ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT", _handler_counts.gatts_event + ) + if _handler_counts.ble_status_event > 0: + cg.add_define( + "ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT", + _handler_counts.ble_status_event, + ) + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) @@ -428,6 +511,9 @@ async def to_code(config): cg.add_define("USE_ESP32_BLE_ADVERTISING") cg.add_define("USE_ESP32_BLE_UUID") + # Schedule the handler defines to be added after all components register + CORE.add_job(_add_ble_handler_defines) + @automation.register_condition("ble.enabled", BLEEnabledCondition, cv.Schema({})) async def ble_enabled_to_code(config, condition_id, template_arg, args): diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 1f6bc6d0a0..9f8ca1d149 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -181,31 +181,27 @@ bool ESP32BLE::ble_setup_() { return false; } - if (!this->gap_event_handlers_.empty()) { - err = esp_ble_gap_register_callback(ESP32BLE::gap_event_handler); - if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err); - return false; - } - } - -#ifdef USE_ESP32_BLE_SERVER - if (!this->gatts_event_handlers_.empty()) { - err = esp_ble_gatts_register_callback(ESP32BLE::gatts_event_handler); - if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ble_gatts_register_callback failed: %d", err); - return false; - } +#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT + err = esp_ble_gap_register_callback(ESP32BLE::gap_event_handler); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err); + return false; } #endif -#ifdef USE_ESP32_BLE_CLIENT - if (!this->gattc_event_handlers_.empty()) { - err = esp_ble_gattc_register_callback(ESP32BLE::gattc_event_handler); - if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ble_gattc_register_callback failed: %d", err); - return false; - } +#if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT) + err = esp_ble_gatts_register_callback(ESP32BLE::gatts_event_handler); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gatts_register_callback failed: %d", err); + return false; + } +#endif + +#if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT) + err = esp_ble_gattc_register_callback(ESP32BLE::gattc_event_handler); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gattc_register_callback failed: %d", err); + return false; } #endif @@ -302,9 +298,11 @@ void ESP32BLE::loop() { case BLE_COMPONENT_STATE_DISABLE: { ESP_LOGD(TAG, "Disabling"); +#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT for (auto *ble_event_handler : this->ble_status_event_handlers_) { ble_event_handler->ble_before_disabled_event_handler(); } +#endif if (!ble_dismantle_()) { ESP_LOGE(TAG, "Could not be dismantled"); @@ -334,7 +332,7 @@ void ESP32BLE::loop() { BLEEvent *ble_event = this->ble_events_.pop(); while (ble_event != nullptr) { switch (ble_event->type_) { -#ifdef USE_ESP32_BLE_SERVER +#if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT) case BLEEvent::GATTS: { esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event; esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if; @@ -346,7 +344,7 @@ void ESP32BLE::loop() { break; } #endif -#ifdef USE_ESP32_BLE_CLIENT +#if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT) case BLEEvent::GATTC: { esp_gattc_cb_event_t event = ble_event->event_.gattc.gattc_event; esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if; @@ -362,10 +360,12 @@ void ESP32BLE::loop() { esp_gap_ble_cb_event_t gap_event = ble_event->event_.gap.gap_event; switch (gap_event) { case ESP_GAP_BLE_SCAN_RESULT_EVT: +#ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT // Use the new scan event handler - no memcpy! for (auto *scan_handler : this->gap_scan_event_handlers_) { scan_handler->gap_scan_event_handler(ble_event->scan_result()); } +#endif break; // Scan complete events @@ -377,10 +377,12 @@ void ESP32BLE::loop() { // This is verified at compile-time by static_assert checks in ble_event.h // The struct already contains our copy of the status (copied in BLEEvent constructor) ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); +#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT for (auto *gap_handler : this->gap_event_handlers_) { gap_handler->gap_event_handler( gap_event, reinterpret_cast(&ble_event->event_.gap.scan_complete)); } +#endif break; // Advertising complete events @@ -391,19 +393,23 @@ void ESP32BLE::loop() { case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: // All advertising complete events have the same structure with just status ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); +#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT for (auto *gap_handler : this->gap_event_handlers_) { gap_handler->gap_event_handler( gap_event, reinterpret_cast(&ble_event->event_.gap.adv_complete)); } +#endif break; // RSSI complete event case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); +#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT for (auto *gap_handler : this->gap_event_handlers_) { gap_handler->gap_event_handler( gap_event, reinterpret_cast(&ble_event->event_.gap.read_rssi_complete)); } +#endif break; // Security events @@ -413,10 +419,12 @@ void ESP32BLE::loop() { case ESP_GAP_BLE_PASSKEY_REQ_EVT: case ESP_GAP_BLE_NC_REQ_EVT: ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); +#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT for (auto *gap_handler : this->gap_event_handlers_) { gap_handler->gap_event_handler( gap_event, reinterpret_cast(&ble_event->event_.gap.security)); } +#endif break; default: diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 1aa3bc86ef..dc973f0e82 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -125,19 +125,25 @@ class ESP32BLE : public Component { void advertising_register_raw_advertisement_callback(std::function &&callback); #endif +#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); } +#endif +#ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT void register_gap_scan_event_handler(GAPScanEventHandler *handler) { this->gap_scan_event_handlers_.push_back(handler); } -#ifdef USE_ESP32_BLE_CLIENT +#endif +#if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT) void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); } #endif -#ifdef USE_ESP32_BLE_SERVER +#if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT) void register_gatts_event_handler(GATTsEventHandler *handler) { this->gatts_event_handlers_.push_back(handler); } #endif +#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT void register_ble_status_event_handler(BLEStatusEventHandler *handler) { this->ble_status_event_handlers_.push_back(handler); } +#endif void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; } protected: @@ -159,16 +165,22 @@ class ESP32BLE : public Component { private: template friend void enqueue_ble_event(Args... args); - // Vectors (12 bytes each on 32-bit, naturally aligned to 4 bytes) - std::vector gap_event_handlers_; - std::vector gap_scan_event_handlers_; -#ifdef USE_ESP32_BLE_CLIENT - std::vector gattc_event_handlers_; + // Handler vectors - use StaticVector when counts are known at compile time +#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT + StaticVector gap_event_handlers_; #endif -#ifdef USE_ESP32_BLE_SERVER - std::vector gatts_event_handlers_; +#ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT + StaticVector gap_scan_event_handlers_; +#endif +#if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT) + StaticVector gattc_event_handlers_; +#endif +#if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT) + StaticVector gatts_event_handlers_; +#endif +#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT + StaticVector ble_status_event_handlers_; #endif - std::vector ble_status_event_handlers_; // Large objects (size depends on template parameters, but typically aligned to 4 bytes) esphome::LockFreeQueue ble_events_; diff --git a/esphome/components/esp32_ble_beacon/__init__.py b/esphome/components/esp32_ble_beacon/__init__.py index 794f5637a4..ba5ae4331c 100644 --- a/esphome/components/esp32_ble_beacon/__init__.py +++ b/esphome/components/esp32_ble_beacon/__init__.py @@ -74,7 +74,7 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID], uuid_arr) parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID]) - cg.add(parent.register_gap_event_handler(var)) + esp32_ble.register_gap_event_handler(parent, var) await cg.register_component(var, config) cg.add(var.set_major(config[CONF_MAJOR])) diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index 10fa09fcc3..55310f3275 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -546,8 +546,8 @@ async def to_code(config): await cg.register_component(var, config) parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID]) - cg.add(parent.register_gatts_event_handler(var)) - cg.add(parent.register_ble_status_event_handler(var)) + esp32_ble.register_gatts_event_handler(parent, var) + esp32_ble.register_ble_status_event_handler(parent, var) cg.add(var.set_parent(parent)) cg.add(parent.advertising_set_appearance(config[CONF_APPEARANCE])) if CONF_MANUFACTURER_DATA in config: diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 8c7f3e3930..5910be67af 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -246,10 +246,10 @@ async def to_code(config): await cg.register_component(var, config) parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID]) - cg.add(parent.register_gap_event_handler(var)) - cg.add(parent.register_gap_scan_event_handler(var)) - cg.add(parent.register_gattc_event_handler(var)) - cg.add(parent.register_ble_status_event_handler(var)) + esp32_ble.register_gap_event_handler(parent, var) + esp32_ble.register_gap_scan_event_handler(parent, var) + esp32_ble.register_gattc_event_handler(parent, var) + esp32_ble.register_ble_status_event_handler(parent, var) cg.add(var.set_parent(parent)) params = config[CONF_SCAN_PARAMETERS] diff --git a/esphome/core/defines.h b/esphome/core/defines.h index ed30efd0b5..f770edaa00 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -178,6 +178,11 @@ #define USE_ESP32_BLE_SERVER_ON_DISCONNECT #define ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT 1 #define ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT 1 +#define ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT 2 +#define ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT 1 +#define ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT 1 +#define ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT 1 +#define ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT 2 #define USE_ESP32_CAMERA_JPEG_ENCODER #define USE_I2C #define USE_IMPROV