diff --git a/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.h b/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.h index 435e953655..1905e36f94 100644 --- a/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.h +++ b/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.h @@ -91,7 +91,9 @@ class BK72xxBLETracker : public Component, // controller never solicits scan responses and never merges them; consumers // relying on scan-response fields (device names) get them only where the // receiver merges per address (Home Assistant does). No GATT client either. - return {.active_scan = false, .merges_scan_response = false, .gatt = false}; + // scan_mode_switch stays false for the same reason: with no active-scan + // path there is no mode to switch to. + return {.active_scan = false, .merges_scan_response = false, .gatt = false, .scan_mode_switch = false}; } bool request_scan_mode(bool active) override { // Passive-only controller: a passive request is already honored, an active diff --git a/esphome/components/ble_device_base/ble_hub.h b/esphome/components/ble_device_base/ble_hub.h index 6f7e580975..454478e0eb 100644 --- a/esphome/components/ble_device_base/ble_hub.h +++ b/esphome/components/ble_device_base/ble_hub.h @@ -59,6 +59,11 @@ struct HubCapabilities { /// GATT client connections are available (today: esp32 only, but a chip SDK /// gaining GATT support only has to flip this bit). bool gatt; + /// request_scan_mode() is honored at runtime. Distinct from active_scan: + /// a passive-only controller (bk72xx) can never switch, and a hub may + /// support active scanning yet still refuse the runtime switch + /// (esp32_ble_tracker drives its mode through its own tracker API). + bool scan_mode_switch; }; class BLEHub { @@ -86,9 +91,9 @@ class BLEHub { /// picks it up on its next start. The default cannot-change keeps hubs /// without a mode switch (and out-of-tree trackers) building unchanged. /// Independent of HubCapabilities::active_scan: that bit describes what the - /// CONTROLLER can do, this method describes whether the hub exposes a - /// runtime switch — a hub may support active scanning and still refuse - /// (esp32_ble_tracker drives its mode through its own tracker API). + /// CONTROLLER can do; whether this method honors requests is advertised by + /// HubCapabilities::scan_mode_switch, so consumers can gate features on the + /// switch without probing. virtual bool request_scan_mode(bool active) { return false; } }; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index b2db092eb0..fe2a5d8599 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -242,7 +242,10 @@ class ESP32BLETracker final : public Component, this->raw_advertisement_callback_ = callback; } ble_device_base::HubCapabilities get_capabilities() const override { - return {/* active_scan = */ true, /* merges_scan_response = */ true, /* gatt = */ true}; + // scan_mode_switch is false: the mode is driven through this tracker's own + // API (set_scan_active + restart), not the neutral request_scan_mode(). + return {/* active_scan = */ true, /* merges_scan_response = */ true, /* gatt = */ true, + /* scan_mode_switch = */ false}; } void get_adapter_mac(uint8_t out[6]) override; bool scan_running() override { return this->scanner_state_ == ScannerState::RUNNING; } diff --git a/esphome/components/ln882h_ble_tracker/ln882h_ble_tracker.h b/esphome/components/ln882h_ble_tracker/ln882h_ble_tracker.h index 00f1af16a4..43328a288d 100644 --- a/esphome/components/ln882h_ble_tracker/ln882h_ble_tracker.h +++ b/esphome/components/ln882h_ble_tracker/ln882h_ble_tracker.h @@ -72,7 +72,8 @@ class LN882HBLETracker : public Component, // The LN882H controller supports active scanning; adv + scan response arrive // as separate reports and are merged by this tracker (Bluedroid semantics). // The SDK's GATT client is not exposed. - return {.active_scan = true, .merges_scan_response = true, .gatt = false}; + // scan_mode_switch: request_scan_mode() is implemented (restart-if-running). + return {.active_scan = true, .merges_scan_response = true, .gatt = false, .scan_mode_switch = true}; } // The controller stores the address LSB-first (BLE convention); the contract // wants printable (MSB-first) order. diff --git a/esphome/components/rp2_ble_tracker/rp2_ble_tracker.h b/esphome/components/rp2_ble_tracker/rp2_ble_tracker.h index 4806f599bd..70ececb528 100644 --- a/esphome/components/rp2_ble_tracker/rp2_ble_tracker.h +++ b/esphome/components/rp2_ble_tracker/rp2_ble_tracker.h @@ -64,7 +64,7 @@ class RP2BLETracker : public Component, // than merging them into the advertisement — consumers relying on // scan-response fields (device names) get them only where the receiver // merges per address (Home Assistant does). No GATT path yet. - return {.active_scan = true, .merges_scan_response = false, .gatt = false}; + return {.active_scan = true, .merges_scan_response = false, .gatt = false, .scan_mode_switch = true}; } // The controller stores the address in printable (MSB-first) order, which is // exactly what the contract wants. diff --git a/tests/components/ble_device_base/test_scan_mode_request.cpp b/tests/components/ble_device_base/test_scan_mode_request.cpp index 2eeb602999..9125eb6f2f 100644 --- a/tests/components/ble_device_base/test_scan_mode_request.cpp +++ b/tests/components/ble_device_base/test_scan_mode_request.cpp @@ -29,7 +29,7 @@ class DefaultHub : public BLEHub { class SwitchingHub : public DefaultHub { public: - HubCapabilities get_capabilities() const override { return {true, false, false}; } + HubCapabilities get_capabilities() const override { return {true, false, false, /* scan_mode_switch = */ true}; } bool request_scan_mode(bool active) override { this->active_ = active; return true; @@ -57,6 +57,7 @@ TEST(BLEHubScanModeRequest, DefaultRefusesAndChangesNothing) { TEST(BLEHubScanModeRequest, OverrideHonorsAndApplies) { SwitchingHub hub; + EXPECT_TRUE(hub.get_capabilities().scan_mode_switch); EXPECT_TRUE(hub.request_scan_mode(true)); EXPECT_TRUE(hub.scan_active()); EXPECT_TRUE(hub.request_scan_mode(false)); @@ -66,6 +67,8 @@ TEST(BLEHubScanModeRequest, OverrideHonorsAndApplies) { TEST(BLEHubScanModeRequest, CapabilityAndSwitchAreIndependent) { CapableRefusingHub hub; EXPECT_TRUE(hub.get_capabilities().active_scan); + // The esp32 shape advertises no runtime switch, and the request refuses. + EXPECT_FALSE(hub.get_capabilities().scan_mode_switch); EXPECT_FALSE(hub.request_scan_mode(false)); EXPECT_TRUE(hub.scan_active()); }