From d9f0f403739c5691184df088e4017b9e5a5d2858 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 8 Aug 2026 00:52:12 -0500 Subject: [PATCH] Simplify scanner state: aliased enum, define-gated slot, one guarded sender --- esphome/components/ble_device_base/ble_hub.h | 21 +++++++++++------ .../components/bluetooth_proxy/__init__.py | 2 ++ .../bluetooth_proxy/bluetooth_proxy.cpp | 23 +++++++++++-------- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 8 +++---- .../esp32_ble_tracker/esp32_ble_tracker.h | 14 ++--------- esphome/core/defines.h | 1 + 6 files changed, 36 insertions(+), 33 deletions(-) diff --git a/esphome/components/ble_device_base/ble_hub.h b/esphome/components/ble_device_base/ble_hub.h index fdee047185..72b6aba086 100644 --- a/esphome/components/ble_device_base/ble_hub.h +++ b/esphome/components/ble_device_base/ble_hub.h @@ -61,8 +61,9 @@ enum class ScannerState : uint8_t { }; /// Subscriber slot for scanner-state transitions; same shape as -/// RawAdvertisementCallback. Hubs that cannot push simply never invoke it and -/// the consumer falls back to polling scan_running(). +/// RawAdvertisementCallback, delivered on the ESPHome main loop. Hubs that +/// cannot push drop the registration and the consumer falls back to polling +/// scan_running(). struct ScannerStateCallback { void *instance{nullptr}; void (*fn)(void *instance, ScannerState state){nullptr}; @@ -99,10 +100,19 @@ class BLEHub { /// Wire the raw-advertisement stream (bluetooth_proxy). One consumer at a time. virtual void set_raw_advertisement_callback(RawAdvertisementCallback callback) = 0; - /// Push subscriber for scanner-state transitions (stored here; hubs invoke - /// scanner_state_callback_ where their state changes, if they can push). +#ifdef USE_BLE_SCANNER_STATE_CALLBACK + /// Push subscriber for scanner-state transitions; hubs that can push + /// invoke scanner_state_callback_ where their state changes. Compiled only + /// when a subscriber exists (bluetooth_proxy emits the define), so + /// subscriber-less builds carry no storage. void set_scanner_state_callback(ScannerStateCallback callback) { this->scanner_state_callback_ = callback; } + protected: + ScannerStateCallback scanner_state_callback_{}; + + public: +#endif // USE_BLE_SCANNER_STATE_CALLBACK + virtual HubCapabilities get_capabilities() const = 0; /// Adapter MAC in printable (MSB-first) order, out[0] = MSB. @@ -122,9 +132,6 @@ class BLEHub { /// HubCapabilities::scan_mode_switch, so consumers can gate features on the /// switch without probing. virtual bool request_scan_mode(bool active) { return false; } - - protected: - ScannerStateCallback scanner_state_callback_{}; }; } // namespace esphome::ble_device_base diff --git a/esphome/components/bluetooth_proxy/__init__.py b/esphome/components/bluetooth_proxy/__init__.py index ce20dc6cb5..8d9aa88bd8 100644 --- a/esphome/components/bluetooth_proxy/__init__.py +++ b/esphome/components/bluetooth_proxy/__init__.py @@ -426,3 +426,5 @@ async def to_code(config: ConfigType) -> None: cg.add_define("BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE", 16) cg.add_define("USE_BLUETOOTH_PROXY") + # Compiles the scanner-state push slot into the hub (see ble_hub.h). + cg.add_define("USE_BLE_SCANNER_STATE_CALLBACK") diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index e78d0c4781..9f481b47f8 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -27,16 +27,20 @@ BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; } // The neutral enum's values are the wire values. static_assert(static_cast(ble_device_base::ScannerState::IDLE) == api::enums::BLUETOOTH_SCANNER_STATE_IDLE); +static_assert(static_cast(ble_device_base::ScannerState::STARTING) == + api::enums::BLUETOOTH_SCANNER_STATE_STARTING); +static_assert(static_cast(ble_device_base::ScannerState::RUNNING) == + api::enums::BLUETOOTH_SCANNER_STATE_RUNNING); +static_assert(static_cast(ble_device_base::ScannerState::FAILED) == + api::enums::BLUETOOTH_SCANNER_STATE_FAILED); +static_assert(static_cast(ble_device_base::ScannerState::STOPPING) == + api::enums::BLUETOOTH_SCANNER_STATE_STOPPING); static_assert(static_cast(ble_device_base::ScannerState::STOPPED) == api::enums::BLUETOOTH_SCANNER_STATE_STOPPED); -void BluetoothProxy::on_scanner_state_(ble_device_base::ScannerState state) { - if (this->api_connection_ != nullptr) { - this->send_bluetooth_scanner_state_(state); - } -} - bool BluetoothProxy::send_bluetooth_scanner_state_(ble_device_base::ScannerState state) { + if (this->api_connection_ == nullptr) + return false; api::BluetoothScannerStateResponse resp; resp.state = static_cast(state); resp.mode = this->hub_->scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE @@ -72,7 +76,7 @@ void BluetoothProxy::setup() { static_cast(self)->on_raw_advertisement_(adv); }}); this->hub_->set_scanner_state_callback({this, [](void *self, ble_device_base::ScannerState state) { - static_cast(self)->on_scanner_state_(state); + static_cast(self)->send_bluetooth_scanner_state_(state); }}); } @@ -503,7 +507,8 @@ void BluetoothProxy::loop() { return; } - // The hub has no scanner-state listener interface; poll and report on change. + // This hub doesn't push scanner-state transitions; poll and report on + // change. A hub gaining push must also refresh last_scan_running_ here. if (this->hub_->scan_running() != this->last_scan_running_) { this->send_polled_scanner_state_(); } @@ -614,7 +619,7 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection } this->api_connection_ = api_connection; #ifdef USE_ESP32 - this->send_bluetooth_scanner_state_(static_cast(this->parent_()->get_scanner_state())); + this->send_bluetooth_scanner_state_(this->parent_()->get_scanner_state()); #else this->send_polled_scanner_state_(); #endif diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 1c04043ad7..e51b293bfe 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -419,16 +419,14 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i #endif } -// The neutral enum mirrors this tracker's wire-aligned values. -static_assert(static_cast(ScannerState::IDLE) == static_cast(ble_device_base::ScannerState::IDLE)); -static_assert(static_cast(ScannerState::STOPPING) == static_cast(ble_device_base::ScannerState::STOPPING)); - void ESP32BLETracker::set_scanner_state_(ScannerState state) { this->scanner_state_ = state; this->state_version_++; +#ifdef USE_BLE_SCANNER_STATE_CALLBACK if (this->scanner_state_callback_.is_set()) { - this->scanner_state_callback_.invoke(static_cast(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); diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 88642fff6b..c570c28122 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -95,18 +95,8 @@ using ClientState = ble_device_base::ClientState; using ConnectionType = ble_device_base::ConnectionType; using ble_device_base::client_state_to_string; -enum class ScannerState { - // Scanner is idle, init state - IDLE, - // Scanner is starting - STARTING, - // Scanner is running - RUNNING, - // Scanner failed to start - FAILED, - // Scanner is stopping - STOPPING, -}; +// Neutral scanner lifecycle re-exported for backward compatibility. +using ScannerState = ble_device_base::ScannerState; /** Listener interface for BLE scanner state changes. * diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 1685467a4b..7ddc607c5c 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -252,6 +252,7 @@ // platforms whose API/network types the proxy header cannot assume. #if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_RP2) #define USE_BLUETOOTH_PROXY +#define USE_BLE_SCANNER_STATE_CALLBACK // Mirror the codegen values per platform: _to_code_esp32() emits the connection // count (default 3), _to_code_ble_hub() emits the slot count (1 on rp2, 0 on // advertisement-only hubs) — so static analysis checks the same