From c7d6b4aaa4643d97ea21e097872d2f03178adc3f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 8 Aug 2026 19:13:07 -0500 Subject: [PATCH 1/3] [esp32_ble_tracker] Retire the raw listener path and parser-type enum (#18177) --- .../bluetooth_connection_esp32.cpp | 6 -- .../bluetooth_connection_esp32.h | 3 +- .../bluetooth_proxy/bluetooth_proxy.h | 23 +------- esphome/components/esp32_ble/ble.cpp | 16 +++++- esphome/components/esp32_ble/ble.h | 2 + .../esp32_ble_tracker/esp32_ble_tracker.cpp | 57 ++----------------- .../esp32_ble_tracker/esp32_ble_tracker.h | 17 ++---- 7 files changed, 30 insertions(+), 94 deletions(-) diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_esp32.cpp b/esphome/components/bluetooth_connection/bluetooth_connection_esp32.cpp index be6fa4c6c5..f5c59ca43a 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_esp32.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection_esp32.cpp @@ -479,12 +479,6 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl return this->check_and_log_error_("esp_ble_gattc_unregister_for_notify", err); } -esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() { - // RAW keeps the tracker from building parsed ESPBTDevice objects for the - // proxy's connections (the proxy itself consumes the hub raw callback). - return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS; -} - } // namespace esphome::bluetooth_connection #endif // USE_ESP32 diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_esp32.h b/esphome/components/bluetooth_connection/bluetooth_connection_esp32.h index 531ff311a7..fb60d93e9c 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_esp32.h +++ b/esphome/components/bluetooth_connection/bluetooth_connection_esp32.h @@ -21,7 +21,8 @@ class BluetoothConnection final : public esp32_ble_client::BLEClientBase { bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; - esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override; + // The proxy's connections never consume parsed ESPBTDevice objects. + bool wants_parsed_advertisements() override { return false; } esp_err_t read_characteristic(uint16_t handle); esp_err_t write_characteristic(uint16_t handle, const uint8_t *data, size_t length, bool response); diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 86d45c144a..9fc975680e 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -20,10 +20,6 @@ #include "esphome/components/bluetooth_connection/bluetooth_connection_esp32.h" -#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID -#include -#endif -#include #else #include "esphome/components/ble_device_base/ble_hub.h" #ifdef USE_BLE_GATT_CLIENT @@ -179,28 +175,15 @@ class BluetoothProxy final : public Component { } void get_bluetooth_mac_address_pretty(std::span output) { -#ifdef USE_ESP32 - const uint8_t *mac = esp_bt_dev_get_address(); - if (mac != nullptr) { - format_mac_addr_upper(mac, output.data()); - } else { - output[0] = '\0'; - } -#else uint8_t mac[6] = {}; this->hub_->get_adapter_mac(mac); - // Mirror the esp32 arm's unavailable -> empty-string fallback: some hubs - // (rp2040's BTstack) only learn the address once the link layer is up, and - // report all-zero until then. - bool nonzero = false; - for (uint8_t b : mac) - nonzero |= b != 0; - if (nonzero) { + // Unavailable -> empty string: some hubs (rp2040's BTstack) only learn + // the address once the link layer is up, and report all-zero until then. + if (mac_address_is_valid(mac)) { format_mac_addr_upper(mac, output.data()); } else { output[0] = '\0'; } -#endif } protected: diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index fb75e8837f..d11683ab35 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -674,11 +674,23 @@ void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gat } #endif +void ESP32BLE::get_mac_msb_first(uint8_t out[6]) const { + // The running stack owns the address (on hosted controllers it lives in + // the remote chip's efuse); null before init becomes all-zero. + const uint8_t *mac = esp_bt_dev_get_address(); + if (mac != nullptr) { + memcpy(out, mac, 6); + } else { + memset(out, 0, 6); + } +} + float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; } void ESP32BLE::dump_config() { - const uint8_t *mac_address = esp_bt_dev_get_address(); - if (mac_address) { + uint8_t mac_address[6]; + this->get_mac_msb_first(mac_address); + if (mac_address_is_valid(mac_address)) { const char *io_capability_s; switch (this->io_cap_) { case ESP_IO_CAP_OUT: diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index c85ddfc983..45cfd8ee71 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -108,6 +108,8 @@ class ESP32BLE final : public Component { void setup() override; void loop() override; void dump_config() override; + /// Adapter MAC in printable (MSB-first) order; all-zero until the stack is up. + void get_mac_msb_first(uint8_t out[6]) const; float get_setup_priority() const override; void set_name(const char *name) { this->name_ = name; } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index e51b293bfe..0950bfeb70 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -271,7 +271,9 @@ void ESP32BLETracker::register_client(ESPBTClient *client) { // Safe because ESP32BLETracker (singleton) outlives all registered clients. client->set_tracker_state_version(&this->state_version_); this->clients_.push_back(client); - this->recalculate_advertisement_parser_types(); + // Registration is add-only, so the flag is a monotonic OR. + if (client->wants_parsed_advertisements()) + this->parse_advertisements_ = true; #endif } @@ -283,48 +285,11 @@ void ESP32BLETracker::register_listener(ble_device_base::ESPBTDeviceListener *li #endif } -void ESP32BLETracker::get_adapter_mac(uint8_t out[6]) { - get_mac_address_raw(out); // WiFi base MAC, MSB-first - // BT MAC = base MAC + 2 on the last octet only, wrapping without carry — - // exactly ESP-IDF's esp_read_mac(ESP_MAC_BT): mac[5] += MAC_ADDR_UNIVERSE_BT_OFFSET. - out[5] += 2; -} - void ESP32BLETracker::register_listener(ESPBTDeviceListener *listener) { #ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT listener->set_parent(this); this->listeners_.push_back(listener); - this->recalculate_advertisement_parser_types(); -#endif -} - -void ESP32BLETracker::recalculate_advertisement_parser_types() { - this->raw_advertisements_ = false; - this->parse_advertisements_ = false; -#ifdef ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT - // Neutral (BLEHub) listeners are parsed-advertisement consumers and are not in - // listeners_; without this, any later esp32-path registration (e.g. the proxy's - // GATT clients) would recompute the flags and silently drop parsed dispatch. - if (!this->neutral_listeners_.empty()) - this->parse_advertisements_ = true; -#endif -#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT - for (auto *listener : this->listeners_) { - if (listener->get_advertisement_parser_type() == AdvertisementParserType::PARSED_ADVERTISEMENTS) { - this->parse_advertisements_ = true; - } else { - this->raw_advertisements_ = true; - } - } -#endif -#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT - for (auto *client : this->clients_) { - if (client->get_advertisement_parser_type() == AdvertisementParserType::PARSED_ADVERTISEMENTS) { - this->parse_advertisements_ = true; - } else { - this->raw_advertisements_ = true; - } - } + this->parse_advertisements_ = true; #endif } @@ -478,20 +443,6 @@ void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) { this->raw_advertisement_callback_.invoke(adv); } - // Process raw advertisements - if (this->raw_advertisements_) { -#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT - for (auto *listener : this->listeners_) { - listener->parse_devices(&scan_result, 1); - } -#endif -#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT - for (auto *client : this->clients_) { - client->parse_devices(&scan_result, 1); - } -#endif - } - // Process parsed advertisements if (this->parse_advertisements_) { #ifdef USE_ESP32_BLE_DEVICE diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index c570c28122..ee1b1429c0 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -35,11 +35,6 @@ using namespace esp32_ble; using adv_data_t = ble_device_base::adv_data_t; -enum AdvertisementParserType { - PARSED_ADVERTISEMENTS, - RAW_ADVERTISEMENTS, -}; - #ifdef USE_ESP32_BLE_UUID using ServiceData = ble_device_base::ServiceData; #endif @@ -63,10 +58,6 @@ class ESPBTDeviceListener : public ble_device_base::ESPBTDeviceListener { // Raw-only build: no parsed-device support is compiled in. bool parse_device(const ble_device_base::ESPBTDevice &device) override { return false; } #endif - virtual bool parse_devices(const BLEScanResult *scan_results, size_t count) { return false; }; - virtual AdvertisementParserType get_advertisement_parser_type() { - return AdvertisementParserType::PARSED_ADVERTISEMENTS; - }; void set_parent(ESP32BLETracker *parent) { parent_ = parent; } protected: @@ -123,6 +114,10 @@ class BLEScannerStateListener { /// The pointer may be null if the client is not registered with a tracker. class ESPBTClient : public ESPBTDeviceListener { public: + /// False keeps the tracker from building parsed ESPBTDevice objects on + /// this client's account (raw consumers use the hub callback). + virtual bool wants_parsed_advertisements() { return true; } + virtual bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) = 0; virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; @@ -199,7 +194,6 @@ class ESP32BLETracker final : public Component, // esp32-flavored path (unmigrated esp32 sensors; sets the tracker back-pointer). void register_listener(ESPBTDeviceListener *listener); void register_client(ESPBTClient *client); - void recalculate_advertisement_parser_types(); // ---- ble_device_base::BLEHub (the platform-neutral tracker contract) ---- void register_listener(ble_device_base::ESPBTDeviceListener *listener) override; @@ -212,7 +206,7 @@ class ESP32BLETracker final : public Component, return {/* active_scan = */ true, /* merges_scan_response = */ true, /* gatt = */ true, /* scan_mode_switch = */ false}; } - void get_adapter_mac(uint8_t out[6]) override; + void get_adapter_mac(uint8_t out[6]) override { this->parent_->get_mac_msb_first(out); } bool scan_running() override { return this->scanner_state_ == ScannerState::RUNNING; } bool scan_active() override { return this->scan_active_; } @@ -355,7 +349,6 @@ class ESP32BLETracker final : public Component, bool scan_continuous_before_ota_{false}; #endif bool ble_was_disabled_{true}; - bool raw_advertisements_{false}; bool parse_advertisements_{false}; #ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE bool coex_prefer_ble_{false}; From ae5b6d271f448ffd04ccec747650845bff8bc4e7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 8 Aug 2026 00:56:59 -0500 Subject: [PATCH 2/3] Retire the scanner-state listener interface --- .../components/esp32_ble_tracker/__init__.py | 17 -------------- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 5 ---- .../esp32_ble_tracker/esp32_ble_tracker.h | 23 ------------------- esphome/core/defines.h | 1 - .../ble_device_base/test_slot_counter.py | 8 ------- 5 files changed, 54 deletions(-) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 646ce79233..0549a5f218 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -69,9 +69,6 @@ def _get_required_features() -> set[BLEFeatures]: # registered listener, client, or scanner state listener. _request_listener_slot = cg.slot_counter("ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT") _request_client_slot = cg.slot_counter("ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT") -_request_scanner_state_listener_slot = cg.slot_counter( - "ESPHOME_ESP32_BLE_TRACKER_SCANNER_STATE_LISTENER_COUNT" -) def register_ble_features(features: set[BLEFeatures]) -> None: @@ -386,17 +383,3 @@ async def register_raw_client( paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) cg.add(paren.register_client(var)) return var - - -async def register_scanner_state_listener( - var: cg.SafeExpType, config: ConfigType -) -> cg.SafeExpType: - """Register a listener for scanner state changes. - - The slot request here is what sizes the tracker's listener storage; a - build with no registrations compiles the storage out entirely. - """ - _request_scanner_state_listener_slot() - paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) - cg.add(paren.add_scanner_state_listener(var)) - return var diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 0950bfeb70..18b6cf022d 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -392,11 +392,6 @@ void ESP32BLETracker::set_scanner_state_(ScannerState state) { this->scanner_state_callback_.invoke(state); } #endif -#ifdef ESPHOME_ESP32_BLE_TRACKER_SCANNER_STATE_LISTENER_COUNT - for (auto *listener : this->scanner_state_listeners_) { - listener->on_scanner_state(state); - } -#endif } void ESP32BLETracker::dump_config() { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index ee1b1429c0..9031d86c97 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -89,16 +89,6 @@ using ble_device_base::client_state_to_string; // Neutral scanner lifecycle re-exported for backward compatibility. using ScannerState = ble_device_base::ScannerState; -/** Listener interface for BLE scanner state changes. - * - * Components can implement this interface to receive scanner state updates - * without the overhead of std::function callbacks. - */ -class BLEScannerStateListener { - public: - virtual void on_scanner_state(ScannerState state) = 0; -}; - /// Base class for BLE GATT clients that connect to remote devices. /// /// State Change Tracking Design: @@ -226,15 +216,6 @@ class ESP32BLETracker final : public Component, void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override; #endif -#ifdef ESPHOME_ESP32_BLE_TRACKER_SCANNER_STATE_LISTENER_COUNT - /// Add a listener for scanner state changes. Only compiled when a consumer - /// requested a slot in codegen: register through - /// esp32_ble_tracker.register_scanner_state_listener() in your component's - /// to_code, which requests the slot and emits this call. - void add_scanner_state_listener(BLEScannerStateListener *listener) { - this->scanner_state_listeners_.push_back(listener); - } -#endif ScannerState get_scanner_state() const { return this->scanner_state_; } protected: @@ -300,10 +281,6 @@ class ESP32BLETracker final : public Component, #endif #ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT StaticVector clients_; -#endif -#ifdef ESPHOME_ESP32_BLE_TRACKER_SCANNER_STATE_LISTENER_COUNT - StaticVector - scanner_state_listeners_; #endif // Parsed listeners registered through the neutral BLEHub contract (migrated // sensors); dispatched alongside listeners_. diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 7ddc607c5c..fd351356df 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -307,7 +307,6 @@ #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_TRACKER_SCANNER_STATE_LISTENER_COUNT 1 #define ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT 1 #define ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT 2 #define ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT 1 diff --git a/tests/component_tests/ble_device_base/test_slot_counter.py b/tests/component_tests/ble_device_base/test_slot_counter.py index daa2884588..c174673155 100644 --- a/tests/component_tests/ble_device_base/test_slot_counter.py +++ b/tests/component_tests/ble_device_base/test_slot_counter.py @@ -95,10 +95,6 @@ def test_esp32_tracker_handler_counts( assert get_define_value("ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT") == "1" assert get_define_value("ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT") is None # No consumer subscribed to scanner state, so the storage compiles out. - assert ( - get_define_value("ESPHOME_ESP32_BLE_TRACKER_SCANNER_STATE_LISTENER_COUNT") - is None - ) assert get_define_value("ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT") is None assert get_define_value("ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT") is None @@ -111,10 +107,6 @@ def test_esp32_bluetooth_proxy_requests_client_slots_only( active: true); advertisements and scanner state arrive through the hub callbacks, so no listener or scanner-state slot exists.""" generate_main(component_config_path("esp32_bluetooth_proxy.yaml")) - assert ( - get_define_value("ESPHOME_ESP32_BLE_TRACKER_SCANNER_STATE_LISTENER_COUNT") - is None - ) assert get_define_value("ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT") is None assert get_define_value("ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT") == "3" From ce2e113a6daef35fd5cd93813a8da50f4d2dac15 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 8 Aug 2026 01:31:22 -0500 Subject: [PATCH 3/3] Update stale comments left by the listener removal --- esphome/components/esp32_ble_tracker/__init__.py | 2 +- tests/component_tests/ble_device_base/test_slot_counter.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 0549a5f218..b1ad07dfdd 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -66,7 +66,7 @@ def _get_required_features() -> set[BLEFeatures]: # Slot counters sizing the tracker's StaticVector storage; one request per -# registered listener, client, or scanner state listener. +# registered listener or client. _request_listener_slot = cg.slot_counter("ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT") _request_client_slot = cg.slot_counter("ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT") diff --git a/tests/component_tests/ble_device_base/test_slot_counter.py b/tests/component_tests/ble_device_base/test_slot_counter.py index c174673155..e784c9871e 100644 --- a/tests/component_tests/ble_device_base/test_slot_counter.py +++ b/tests/component_tests/ble_device_base/test_slot_counter.py @@ -94,7 +94,7 @@ def test_esp32_tracker_handler_counts( assert get_define_value("ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT") == "1" assert get_define_value("ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT") == "1" assert get_define_value("ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT") is None - # No consumer subscribed to scanner state, so the storage compiles out. + # No advertisement listener or client is registered, so both storages compile out. assert get_define_value("ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT") is None assert get_define_value("ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT") is None @@ -105,7 +105,7 @@ def test_esp32_bluetooth_proxy_requests_client_slots_only( ) -> None: """The proxy requests a client slot per connection (three by default with active: true); advertisements and scanner state arrive through the hub - callbacks, so no listener or scanner-state slot exists.""" + callbacks, so no listener slot exists.""" generate_main(component_config_path("esp32_bluetooth_proxy.yaml")) assert get_define_value("ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT") is None assert get_define_value("ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT") == "3"