[rp2040_ble] Codegen sized StaticVector for scan listeners (#18058)

This commit is contained in:
J. Nick Koston
2026-08-04 21:04:22 +00:00
committed by GitHub
parent d47acf5d0b
commit 1f5df20cb2
8 changed files with 59 additions and 6 deletions
@@ -31,6 +31,11 @@ def _validate_board(config: ConfigType) -> ConfigType:
FINAL_VALIDATE_SCHEMA = _validate_board
# Once per registered scan listener; sizes the controller's StaticVector
# listener storage.
request_scan_listener_slot = cg.slot_counter("RP2040_BLE_SCAN_LISTENER_COUNT")
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
@@ -117,8 +117,10 @@ void RP2040BLE::loop() {
if (report == nullptr)
return;
do {
#ifdef RP2040_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);
+10 -2
View File
@@ -6,12 +6,12 @@
#include "esphome/core/component.h"
#include "esphome/core/event_pool.h"
#include "esphome/core/helpers.h"
#include "esphome/core/lock_free_queue.h"
#include <btstack.h>
#include <cstdint>
#include <vector>
namespace esphome::rp2040_ble {
@@ -79,8 +79,12 @@ class RP2040BLE final : public Component {
/// power-up).
void get_mac_msb_first(uint8_t out[6]) const;
#ifdef RP2040_BLE_SCAN_LISTENER_COUNT
/// Register a consumer for scan reports (delivered on the main loop via loop()).
/// Storage is codegen-sized: the consumer's codegen requests a slot via
/// request_scan_listener_slot(), which emits RP2040_BLE_SCAN_LISTENER_COUNT.
void register_scan_listener(BLEScanListener *listener) { this->scan_listeners_.push_back(listener); }
#endif
/// Start a controller scan; active sends scan requests and receives scan
/// responses as separate reports. Interval/window are in BLE units
@@ -102,7 +106,11 @@ class RP2040BLE final : public Component {
void enqueue_scan_report_(const uint8_t *mac_lsb_first, int8_t rssi, uint8_t addr_type, uint8_t adv_event_type,
const uint8_t *data, uint16_t data_len);
std::vector<BLEScanListener *> scan_listeners_;
#ifdef RP2040_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<BLEScanListener *, RP2040_BLE_SCAN_LISTENER_COUNT> scan_listeners_;
#endif
// Report ring: the BTstack packet handler (async-context IRQ) allocates a
// report from the pool, fills it and pushes the pointer; loop() pops,
// dispatches and releases. Lock-free SPSC — the esp32_ble/bk72xx_ble pattern.
@@ -59,6 +59,9 @@ async def to_code(config: ConfigType) -> None:
parent = await cg.get_variable(config[CONF_RP2040_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.
rp2040_ble.request_scan_listener_slot()
# Get notified when an OTA update starts, to pause scanning (esp32_ble_tracker parity)
ota.request_ota_state_listeners()
+1
View File
@@ -436,6 +436,7 @@
#define USE_LOGGER_USB_CDC
#define USE_SOCKET_IMPL_LWIP_TCP
#define USE_RP2040_BLE
#define RP2040_BLE_SCAN_LISTENER_COUNT 1
#define ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT 1
#define USE_RP2040_VARIANT_RP2040
#define USE_SPI
@@ -0,0 +1,7 @@
esphome:
name: slotcount-rp2-controller
rp2:
board: rpipicow
rp2040_ble:
@@ -0,0 +1,7 @@
esphome:
name: slotcount-rp2-tracker
rp2:
board: rpipicow
rp2_ble_tracker:
@@ -11,12 +11,23 @@ from __future__ import annotations
from collections.abc import Callable
from pathlib import Path
import pytest
from esphome.core import CORE
from ..helpers import get_define_value
@pytest.mark.parametrize(
("config", "define"),
[
("bk72xx_tracker.yaml", "BK72XX_BLE_SCAN_LISTENER_COUNT"),
("rp2_tracker.yaml", "RP2040_BLE_SCAN_LISTENER_COUNT"),
],
)
def test_tracker_requests_one_slot(
config: str,
define: str,
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
@@ -25,18 +36,27 @@ def test_tracker_requests_one_slot(
The neutral listener count must stay absent from the same build: no BLE
consumer registered through register_ble_device().
"""
generate_main(component_config_path("bk72xx_tracker.yaml"))
assert get_define_value("BK72XX_BLE_SCAN_LISTENER_COUNT") == "1"
generate_main(component_config_path(config))
assert get_define_value(define) == "1"
assert get_define_value("ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT") is None
@pytest.mark.parametrize(
("config", "define"),
[
("bk72xx_controller_only.yaml", "BK72XX_BLE_SCAN_LISTENER_COUNT"),
("rp2_controller_only.yaml", "RP2040_BLE_SCAN_LISTENER_COUNT"),
],
)
def test_controller_only_emits_no_count(
config: str,
define: str,
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""No consumer, no define — the guarded listener storage compiles out."""
generate_main(component_config_path("bk72xx_controller_only.yaml"))
assert get_define_value("BK72XX_BLE_SCAN_LISTENER_COUNT") is None
generate_main(component_config_path(config))
assert get_define_value(define) is None
def test_neutral_listener_count_emitted_when_requested() -> None: