diff --git a/esphome/components/bk72xx_ble/__init__.py b/esphome/components/bk72xx_ble/__init__.py index 29e2a6b13d..b5b4691eea 100644 --- a/esphome/components/bk72xx_ble/__init__.py +++ b/esphome/components/bk72xx_ble/__init__.py @@ -24,6 +24,7 @@ from esphome.components import libretiny from esphome.components.libretiny.const import FAMILY_BK7231N, FAMILY_BK7238 import esphome.config_validation as cv from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID +from esphome.core import CORE, CoroPriority, coroutine_with_priority from esphome.types import ConfigType DEPENDENCIES = ["bk72xx"] @@ -45,6 +46,23 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) +KEY_SCAN_LISTENER_COUNT = "bk72xx_ble_scan_listener_count" + + +def request_scan_listener_slot() -> None: + """Called from a consumer's codegen once per registered scan listener; sizes + the controller's StaticVector listener storage (heap-free, mirrors the + tracker's ble_device_base listener storage).""" + CORE.data[KEY_SCAN_LISTENER_COUNT] = CORE.data.get(KEY_SCAN_LISTENER_COUNT, 0) + 1 + + +@coroutine_with_priority(CoroPriority.FINAL) +async def _add_listener_count() -> None: + # FINAL: every consumer's to_code has requested its slot by now. + if count := CORE.data.get(KEY_SCAN_LISTENER_COUNT, 0): + cg.add_define("BK72XX_BLE_SCAN_LISTENER_COUNT", count) + + async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) @@ -92,3 +110,5 @@ async def to_code(config: ConfigType) -> None: ) cg.add_define("USE_BK72XX_BLE") + + CORE.add_job(_add_listener_count) diff --git a/esphome/components/bk72xx_ble/bk72xx_ble.cpp b/esphome/components/bk72xx_ble/bk72xx_ble.cpp index a5ecaf4abb..69c7df0b96 100644 --- a/esphome/components/bk72xx_ble/bk72xx_ble.cpp +++ b/esphome/components/bk72xx_ble/bk72xx_ble.cpp @@ -179,8 +179,10 @@ void BK72xxBLE::loop() { if (report == nullptr) return; do { +#ifdef BK72XX_BLE_SCAN_LISTENER_COUNT for (auto *listener : this->scan_listeners_) listener->on_scan_report(*report); +#endif this->report_pool_.release(report); } while ((report = this->report_queue_.pop()) != nullptr); diff --git a/esphome/components/bk72xx_ble/bk72xx_ble.h b/esphome/components/bk72xx_ble/bk72xx_ble.h index 2654f4e68e..4e615af159 100644 --- a/esphome/components/bk72xx_ble/bk72xx_ble.h +++ b/esphome/components/bk72xx_ble/bk72xx_ble.h @@ -6,10 +6,10 @@ #include "esphome/core/component.h" #include "esphome/core/event_pool.h" +#include "esphome/core/helpers.h" #include "esphome/core/lock_free_queue.h" #include -#include namespace esphome::bk72xx_ble { @@ -62,8 +62,12 @@ class BK72xxBLE final : public Component { /// Controller BLE address, least-significant octet first (BLE convention). void get_mac_lsb_first(uint8_t out[6]) const; +#ifdef BK72XX_BLE_SCAN_LISTENER_COUNT /// Register a consumer for scan reports (delivered on the main task via loop()). + /// Storage is codegen-sized: the consumer's codegen requests a slot via + /// request_scan_listener_slot(), which emits BK72XX_BLE_SCAN_LISTENER_COUNT. void register_scan_listener(BLEScanListener *listener) { this->scan_listeners_.push_back(listener); } +#endif /// Start the controller scan. Interval/window are in BLE units (0.625 ms). /// Enables the stack first if needed. Returns false on controller failure. @@ -78,7 +82,11 @@ class BK72xxBLE final : public Component { protected: void resolve_mac_(); - std::vector scan_listeners_; +#ifdef BK72XX_BLE_SCAN_LISTENER_COUNT + // Codegen-sized: no heap allocation, no std::vector template instantiation — + // the same StaticVector pattern as the tracker's ble_device_base listeners. + StaticVector scan_listeners_; +#endif // Report ring: the BDK notice callback (BLE task) allocates a report from the // pool, fills it and pushes the pointer; loop() pops, dispatches and releases. // Lock-free SPSC, zero allocation at steady state — the esp32_ble pattern. diff --git a/esphome/components/bk72xx_ble_tracker/__init__.py b/esphome/components/bk72xx_ble_tracker/__init__.py index e53e8e13d7..05dd4a7a3c 100644 --- a/esphome/components/bk72xx_ble_tracker/__init__.py +++ b/esphome/components/bk72xx_ble_tracker/__init__.py @@ -69,6 +69,9 @@ async def to_code(config: ConfigType) -> None: parent = await cg.get_variable(config[CONF_BK72XX_BLE_ID]) cg.add(var.set_parent(parent)) + # The tracker registers itself as a controller scan listener in setup(); + # request the codegen-sized StaticVector slot for it. + bk72xx_ble.request_scan_listener_slot() # Get notified when an OTA update starts, to pause scanning (esp32_ble_tracker parity) ota.request_ota_state_listeners() diff --git a/esphome/core/defines.h b/esphome/core/defines.h index ed6a117622..d946b106d1 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -454,6 +454,7 @@ #ifdef USE_LIBRETINY #define USE_BK72XX_BLE +#define BK72XX_BLE_SCAN_LISTENER_COUNT 1 #define USE_LN882H_BLE #define LN882H_BLE_SCAN_LISTENER_COUNT 1 #define USE_CAPTIVE_PORTAL