diff --git a/esphome/components/ble_device_base/ble_hub.h b/esphome/components/ble_device_base/ble_hub.h index a7ed976520..2cbf5aa9a5 100644 --- a/esphome/components/ble_device_base/ble_hub.h +++ b/esphome/components/ble_device_base/ble_hub.h @@ -86,8 +86,8 @@ struct HubCapabilities { bool scan_mode_switch; }; -// The BLEHub method surface. Duck-typed: a tracker that misses one of these -// fails to compile at the consumer's call site, not at a class definition. +// The BLEHub method surface, enforced by the BLEHubContract concept below +// (asserted where ble_hub_impl.h binds the alias). // // /// Register a parsed-advertisement consumer (BLE sensors, automation triggers). // void register_listener(ESPBTDeviceListener *listener); @@ -118,4 +118,18 @@ struct HubCapabilities { // /// consumers can gate features on the switch without probing. // bool request_scan_mode(bool active); +// set_scanner_state_callback is deliberately absent: it exists only on push +// hubs under USE_BLE_SCANNER_STATE_CALLBACK. +template +concept BLEHubContract = requires(T hub, ESPBTDeviceListener *listener, RawAdvertisementCallback raw_callback, + uint8_t *mac) { + hub.register_listener(listener); + hub.set_raw_advertisement_callback(raw_callback); + T::get_capabilities(); + hub.get_adapter_mac(mac); + hub.scan_running(); + hub.scan_active(); + hub.request_scan_mode(true); +}; + } // namespace esphome::ble_device_base diff --git a/esphome/components/ble_device_base/ble_hub_impl.h b/esphome/components/ble_device_base/ble_hub_impl.h index e5062d2f25..87214ca7f7 100644 --- a/esphome/components/ble_device_base/ble_hub_impl.h +++ b/esphome/components/ble_device_base/ble_hub_impl.h @@ -6,28 +6,30 @@ #pragma once +#include "ble_hub.h" #include "esphome/core/defines.h" #if defined(USE_ESP32_BLE_TRACKER) #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#define ESPHOME_BLE_HUB_TYPE esp32_ble_tracker::ESP32BLETracker #elif defined(USE_RP2_BLE_TRACKER) #include "esphome/components/rp2_ble_tracker/rp2_ble_tracker.h" +#define ESPHOME_BLE_HUB_TYPE rp2_ble_tracker::RP2BLETracker #elif defined(USE_BK72XX_BLE_TRACKER) #include "esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.h" +#define ESPHOME_BLE_HUB_TYPE bk72xx_ble_tracker::BK72xxBLETracker #elif defined(USE_LN882H_BLE_TRACKER) #include "esphome/components/ln882h_ble_tracker/ln882h_ble_tracker.h" +#define ESPHOME_BLE_HUB_TYPE ln882h_ble_tracker::LN882HBLETracker #endif +// No #else on purpose: builds without a tracker (host unit tests) get no BLEHub. namespace esphome::ble_device_base { -#if defined(USE_ESP32_BLE_TRACKER) -using BLEHub = esp32_ble_tracker::ESP32BLETracker; -#elif defined(USE_RP2_BLE_TRACKER) -using BLEHub = rp2_ble_tracker::RP2BLETracker; -#elif defined(USE_BK72XX_BLE_TRACKER) -using BLEHub = bk72xx_ble_tracker::BK72xxBLETracker; -#elif defined(USE_LN882H_BLE_TRACKER) -using BLEHub = ln882h_ble_tracker::LN882HBLETracker; +#ifdef ESPHOME_BLE_HUB_TYPE +using BLEHub = ESPHOME_BLE_HUB_TYPE; +static_assert(BLEHubContract, "The build's BLE tracker is missing part of the BLEHub surface (ble_hub.h)"); +#undef ESPHOME_BLE_HUB_TYPE #endif } // namespace esphome::ble_device_base diff --git a/esphome/core/defines.h b/esphome/core/defines.h index ed5bbd495c..ad24d27369 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -252,12 +252,12 @@ // 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 -// std::array instantiation a real build produces. +// count (default 3) and the scanner-state push slot, _to_code_ble_hub() emits +// the slot count (1 on rp2, 0 on advertisement-only hubs) — so static analysis +// checks the same instantiations a real build produces. #ifdef USE_ESP32 +#define USE_BLE_SCANNER_STATE_CALLBACK #define BLUETOOTH_PROXY_MAX_CONNECTIONS 3 #elif defined(USE_RP2) #define BLUETOOTH_PROXY_MAX_CONNECTIONS 1 @@ -488,11 +488,17 @@ #ifdef USE_LIBRETINY #define USE_BK72XX_BLE -#define USE_BK72XX_BLE_TRACKER #define BK72XX_BLE_SCAN_LISTENER_COUNT 1 #define USE_LN882H_BLE -#define USE_LN882H_BLE_TRACKER #define LN882H_BLE_SCAN_LISTENER_COUNT 1 +// One tracker arm per build: ln882x gets its real hub; bk72xx also stands in +// for hub-less LibreTiny chips (rtl87xx) so bluetooth_proxy.h has a BLEHub +// to parse against. +#ifdef USE_LN882X +#define USE_LN882H_BLE_TRACKER +#else +#define USE_BK72XX_BLE_TRACKER +#endif #define ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT 1 #define USE_CAPTIVE_PORTAL #define USE_WIFI_SCAN_RESULTS_LOCK diff --git a/tests/components/ble_device_base/test_scan_mode_request.cpp b/tests/components/ble_device_base/test_scan_mode_request.cpp deleted file mode 100644 index 6d3ce6c382..0000000000 --- a/tests/components/ble_device_base/test_scan_mode_request.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include - -#include - -#include "esphome/components/ble_device_base/ble_hub.h" - -namespace esphome::ble_device_base::testing { - -// Pins the request_scan_mode() contract documented in ble_hub.h: a hub -// without a mode switch refuses without changing any state, a switching hub -// honors and applies. Minimal host hubs mirror the in-tree tracker stubs. -namespace { - -class RefusingHub { - public: - static constexpr HubCapabilities get_capabilities() { return {false, false, false}; } - bool scan_active() { return this->active_; } - // The refusing shape every non-switching tracker provides: no state touched. - bool request_scan_mode(bool active) { return false; } - - protected: - bool active_{true}; -}; - -class SwitchingHub : public RefusingHub { - public: - static constexpr HubCapabilities get_capabilities() { return {true, false, false, /* scan_mode_switch = */ true}; } - bool request_scan_mode(bool active) { - this->active_ = active; - return true; - } -}; - -// The esp32 shape: the controller supports active scanning but the hub keeps -// the refusing stub (mode is driven through its own tracker API). -class CapableRefusingHub : public RefusingHub { - public: - static constexpr HubCapabilities get_capabilities() { return {true, false, false}; } -}; - -} // namespace - -TEST(BLEHubScanModeRequest, RefusingHubChangesNothing) { - RefusingHub hub; - EXPECT_TRUE(hub.scan_active()); - EXPECT_FALSE(hub.request_scan_mode(false)); - // Refused, not applied-and-reported-false: the state is untouched. - EXPECT_TRUE(hub.scan_active()); - EXPECT_FALSE(hub.request_scan_mode(true)); - EXPECT_TRUE(hub.scan_active()); -} - -TEST(BLEHubScanModeRequest, SwitchingHubHonorsAndApplies) { - 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)); - EXPECT_FALSE(hub.scan_active()); -} - -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()); -} - -} // namespace esphome::ble_device_base::testing