From 1bbe8b415faa09118cd6da2f9fe6fceaab9d6f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edvard=20Filistovi=C4=8D?= Date: Fri, 7 Aug 2026 18:01:50 +0300 Subject: [PATCH] [bluetooth_proxy] Only advance the scanner-state detector when the frame was sent (#18154) --- .../bluetooth_proxy/bluetooth_proxy.cpp | 17 +++++++++-------- .../bluetooth_proxy/test_platform_gates.py | 7 +++++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 9002727bbf..66f22c9a90 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -59,7 +59,6 @@ void BluetoothProxy::setup() { // Capture the configured scan mode from YAML before any API changes this->configured_scan_active_ = this->hub_->scan_active(); - this->last_scan_running_ = this->hub_->scan_running(); // The hub delivers raw advertisements on the ESPHome main loop: // mac is least-significant octet first (BLE controller convention). @@ -93,19 +92,21 @@ void BluetoothProxy::on_raw_advertisement_(const ble_device_base::RawAdvertiseme } void BluetoothProxy::send_bluetooth_scanner_state_() { - // Records what goes on the wire so loop()'s change detector cannot report the - // same transition twice; every caller relies on this instead of updating - // last_scan_running_ itself. - this->last_scan_running_ = this->hub_->scan_running(); + // One read feeds both the frame and the change detector; the detector only + // advances if the frame was accepted, so a dropped send (WOULD_BLOCK on a + // full TX buffer) is retried from loop() instead of leaving a stale state. + const bool running = this->hub_->scan_running(); api::BluetoothScannerStateResponse resp; - resp.state = this->last_scan_running_ ? api::enums::BluetoothScannerState::BLUETOOTH_SCANNER_STATE_RUNNING - : api::enums::BluetoothScannerState::BLUETOOTH_SCANNER_STATE_IDLE; + resp.state = running ? api::enums::BluetoothScannerState::BLUETOOTH_SCANNER_STATE_RUNNING + : api::enums::BluetoothScannerState::BLUETOOTH_SCANNER_STATE_IDLE; resp.mode = this->hub_->scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE : api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE; resp.configured_mode = this->configured_scan_active_ ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE : api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE; - this->api_connection_->send_message(resp); + if (this->api_connection_->send_message(resp)) { + this->last_scan_running_ = running; + } } #endif // USE_ESP32 diff --git a/tests/component_tests/bluetooth_proxy/test_platform_gates.py b/tests/component_tests/bluetooth_proxy/test_platform_gates.py index 682f98bf6b..c474b5fa81 100644 --- a/tests/component_tests/bluetooth_proxy/test_platform_gates.py +++ b/tests/component_tests/bluetooth_proxy/test_platform_gates.py @@ -23,6 +23,13 @@ HUB_PLATFORM_FRAMEWORKS = [ ] +def test_hub_platform_list_covers_every_hub_platform() -> None: + # A platform added to _HUB_PLATFORMS (bk72xx is planned) would otherwise + # get no gate coverage at all. + covered = {pf.value[0] for pf in HUB_PLATFORM_FRAMEWORKS} + assert covered == set(bluetooth_proxy._HUB_PLATFORMS) + + def _set_platform(platform: str | None) -> None: # For arms set_core_config cannot express (bare platform, no framework). CORE.data.setdefault(KEY_CORE, {})[KEY_TARGET_PLATFORM] = platform