mirror of
https://github.com/esphome/esphome.git
synced 2026-08-31 01:56:01 +00:00
Bind BLEHub to the build's tracker at compile time
This commit is contained in:
@@ -144,6 +144,9 @@ async def stop_scan_action_to_code(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
# Selects the BLEHub alias arm in ble_device_base/ble_hub_impl.h.
|
||||
cg.add_define("USE_BK72XX_BLE_TRACKER")
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ namespace esphome::bk72xx_ble_tracker {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class BK72xxBLETracker : public Component,
|
||||
public ble_device_base::BLEHub,
|
||||
public bk72xx_ble::BLEScanListener,
|
||||
public Parented<bk72xx_ble::BK72xxBLE>
|
||||
#ifdef USE_OTA_STATE_LISTENER
|
||||
@@ -93,15 +92,15 @@ class BK72xxBLETracker : public Component,
|
||||
void stop_scan();
|
||||
|
||||
// ---- ble_device_base::BLEHub contract ----
|
||||
void register_listener(ble_device_base::ESPBTDeviceListener *listener) override {
|
||||
void register_listener(ble_device_base::ESPBTDeviceListener *listener) {
|
||||
#ifdef ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT
|
||||
this->listeners_.push_back(listener);
|
||||
#endif
|
||||
}
|
||||
void set_raw_advertisement_callback(ble_device_base::RawAdvertisementCallback callback) override {
|
||||
void set_raw_advertisement_callback(ble_device_base::RawAdvertisementCallback callback) {
|
||||
this->raw_advertisement_callback_ = callback;
|
||||
}
|
||||
ble_device_base::HubCapabilities get_capabilities() const override {
|
||||
static constexpr ble_device_base::HubCapabilities get_capabilities() {
|
||||
// The Beken BDK exposes no active-scan path (passive scanning only), so the
|
||||
// controller never solicits scan responses and never merges them; consumers
|
||||
// relying on scan-response fields (device names) get them only where the
|
||||
@@ -110,21 +109,21 @@ class BK72xxBLETracker : public Component,
|
||||
// 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 {
|
||||
bool request_scan_mode(bool active) {
|
||||
// Passive-only controller: a passive request is already honored, an active
|
||||
// one cannot be.
|
||||
return !active;
|
||||
}
|
||||
// The controller stores the address LSB-first (BLE convention); the contract
|
||||
// wants printable (MSB-first) order.
|
||||
void get_adapter_mac(uint8_t out[6]) override {
|
||||
void get_adapter_mac(uint8_t out[6]) {
|
||||
uint8_t mac[6];
|
||||
this->parent_->get_mac_lsb_first(mac);
|
||||
for (int i = 0; i < 6; i++)
|
||||
out[i] = mac[5 - i];
|
||||
}
|
||||
bool scan_running() override { return this->scan_running_; }
|
||||
bool scan_active() override { return false; } // BK72xx scan is passive-only
|
||||
bool scan_running() { return this->scan_running_; }
|
||||
bool scan_active() { return false; } // BK72xx scan is passive-only
|
||||
|
||||
// ---- bk72xx_ble::BLEScanListener ----
|
||||
// Delivered by the controller's loop() on the ESPHome main task — the
|
||||
|
||||
@@ -3,15 +3,16 @@ ble_device_base — the platform-neutral BLE layer.
|
||||
|
||||
Owns the shared advertisement types (ESPBTUUID / ESPBTDevice / ServiceData /
|
||||
ESPBLEiBeacon / ESPBTDeviceListener, in ble_device.h) and the tracker contract
|
||||
(BLEHub, in ble_hub.h) on every platform.
|
||||
(BLEHub, in ble_hub.h; C++-side a per-platform alias bound in ble_hub_impl.h)
|
||||
on every platform.
|
||||
|
||||
BLE consumers (sensor components, bluetooth_proxy) bind to whichever tracker the
|
||||
configuration declares via `cv.use_id(BLEHub)` — ESPHome resolves any declared
|
||||
subclass, so there is no platform table here and no dependency in either
|
||||
direction. A sensor extends BLE_DEVICE_SCHEMA in its CONFIG_SCHEMA (so an
|
||||
explicit ble_hub_id: is a declared key even on strict schemas) and calls
|
||||
register_ble_device() in to_code; a tracker component subclasses BLEHub (C++
|
||||
and codegen class) and MUST call register_hub_provider() at import time —
|
||||
register_ble_device() in to_code; a tracker component declares BLEHub as its
|
||||
codegen-class parent and MUST call register_hub_provider() at import time —
|
||||
without it _require_hub rejects configs that bind through the generated id
|
||||
(an explicit ble_hub_id: bypasses the registry). Adding a new BLE chip
|
||||
requires only a new in-tree tracker component; out-of-tree BLE hubs are
|
||||
@@ -48,8 +49,10 @@ LISTENER_COUNT_DEFINE = "ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT"
|
||||
|
||||
ble_device_base_ns = cg.esphome_ns.namespace("ble_device_base")
|
||||
|
||||
# The neutral tracker contract. Every tracker's codegen class declares this as a
|
||||
# parent, which is what lets cv.use_id(BLEHub) resolve any of them.
|
||||
# The neutral tracker contract. Every tracker's codegen class declares this as
|
||||
# a parent, which is what lets cv.use_id(BLEHub) resolve any of them. Python
|
||||
# only: the C++ name is a per-platform alias for the build's tracker class
|
||||
# (ble_hub_impl.h), so generated code never names ble_device_base::BLEHub.
|
||||
BLEHub = ble_device_base_ns.class_("BLEHub")
|
||||
|
||||
# The neutral listener base (C++: ble_device_base::ESPBTDeviceListener).
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// Platform-neutral BLE advertisement triggers: ESPBTDeviceListener subclasses
|
||||
// registered on a BLEHub, exposed by each tracker under its own automation
|
||||
// names. parse_device()'s return feeds the "Found device" suppression.
|
||||
// The constructors are templated on the hub type instead of naming BLEHub:
|
||||
// the contract is duck-typed and this header must also build with no tracker
|
||||
// present (host unit tests).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ble_device.h"
|
||||
#include "ble_hub.h"
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -18,7 +20,7 @@ namespace esphome::ble_device_base {
|
||||
// on_ble_advertise: fires on every BLE advertisement, optionally filtered to one or more MACs.
|
||||
class ESPBTAdvertiseTrigger final : public Trigger<const ESPBTDevice &>, public ESPBTDeviceListener {
|
||||
public:
|
||||
explicit ESPBTAdvertiseTrigger(BLEHub *parent) { parent->register_listener(this); }
|
||||
template<typename Hub> explicit ESPBTAdvertiseTrigger(Hub *parent) { parent->register_listener(this); }
|
||||
|
||||
void set_addresses(std::initializer_list<uint64_t> addresses) { this->addresses_ = addresses; }
|
||||
|
||||
@@ -39,7 +41,7 @@ class ESPBTAdvertiseTrigger final : public Trigger<const ESPBTDevice &>, public
|
||||
// data for the given UUID. Optional single-MAC filter.
|
||||
class BLEServiceDataAdvertiseTrigger final : public Trigger<const adv_data_t &>, public ESPBTDeviceListener {
|
||||
public:
|
||||
explicit BLEServiceDataAdvertiseTrigger(BLEHub *parent) { parent->register_listener(this); }
|
||||
template<typename Hub> explicit BLEServiceDataAdvertiseTrigger(Hub *parent) { parent->register_listener(this); }
|
||||
|
||||
void set_service_uuid16(uint64_t uuid) { this->uuid_ = ESPBTUUID::from_uint16(static_cast<uint16_t>(uuid)); }
|
||||
void set_service_uuid32(uint64_t uuid) { this->uuid_ = ESPBTUUID::from_uint32(static_cast<uint32_t>(uuid)); }
|
||||
@@ -73,7 +75,7 @@ class BLEServiceDataAdvertiseTrigger final : public Trigger<const adv_data_t &>,
|
||||
// manufacturer data for the given ID. Optional single-MAC filter.
|
||||
class BLEManufacturerDataAdvertiseTrigger final : public Trigger<const adv_data_t &>, public ESPBTDeviceListener {
|
||||
public:
|
||||
explicit BLEManufacturerDataAdvertiseTrigger(BLEHub *parent) { parent->register_listener(this); }
|
||||
template<typename Hub> explicit BLEManufacturerDataAdvertiseTrigger(Hub *parent) { parent->register_listener(this); }
|
||||
|
||||
void set_manufacturer_uuid16(uint64_t uuid) { this->uuid_ = ESPBTUUID::from_uint16(static_cast<uint16_t>(uuid)); }
|
||||
void set_manufacturer_uuid32(uint64_t uuid) { this->uuid_ = ESPBTUUID::from_uint32(static_cast<uint32_t>(uuid)); }
|
||||
@@ -108,7 +110,7 @@ class BLEManufacturerDataAdvertiseTrigger final : public Trigger<const adv_data_
|
||||
// claims devices (parse_device always returns false).
|
||||
class BLEEndOfScanTrigger final : public Trigger<>, public ESPBTDeviceListener {
|
||||
public:
|
||||
explicit BLEEndOfScanTrigger(BLEHub *parent) { parent->register_listener(this); }
|
||||
template<typename Hub> explicit BLEEndOfScanTrigger(Hub *parent) { parent->register_listener(this); }
|
||||
|
||||
bool parse_device(const ESPBTDevice &device) override { return false; }
|
||||
void on_scan_end() override { this->trigger(); }
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
// ble_hub.h
|
||||
//
|
||||
// BLEHub — the platform-neutral BLE tracker contract.
|
||||
// The platform-neutral BLE tracker contract: the types every tracker and
|
||||
// consumer share, plus (below) the method surface every tracker provides.
|
||||
//
|
||||
// Every BLE tracker component (esp32_ble_tracker, bk72xx_ble_tracker,
|
||||
// ln882h_ble_tracker, future chips) implements this interface; every BLE
|
||||
// consumer (sensor components, bluetooth_proxy) binds to it — in YAML via
|
||||
// `cv.use_id(BLEHub)`, which resolves whichever tracker the config declares.
|
||||
// Adding a new BLE chip therefore requires only a new tracker component that
|
||||
// implements BLEHub: no consumer, registry, or base changes.
|
||||
// Exactly one tracker component exists per build (one chip, one controller),
|
||||
// so BLEHub is not an abstract interface: ble_hub_impl.h binds the name to
|
||||
// the build's tracker with a `using` alias, and every hub call is a direct,
|
||||
// inlinable member call — no vtable, no virtual dispatch. Trackers include
|
||||
// this header and implement the documented surface; consumers include
|
||||
// ble_hub_impl.h and bind in YAML via `cv.use_id(BLEHub)` (the Python-side
|
||||
// class, which resolves whichever tracker the config declares). Adding a new
|
||||
// BLE chip requires only a new tracker component that provides the surface
|
||||
// and an alias arm in ble_hub_impl.h.
|
||||
//
|
||||
// Chip differences are expressed as data (HubCapabilities), never as
|
||||
// platform conditionals in consumers.
|
||||
@@ -90,48 +94,39 @@ struct HubCapabilities {
|
||||
bool scan_mode_switch;
|
||||
};
|
||||
|
||||
class BLEHub {
|
||||
public:
|
||||
virtual ~BLEHub() = default;
|
||||
|
||||
/// Register a parsed-advertisement consumer (BLE sensors, automation triggers).
|
||||
virtual void register_listener(ESPBTDeviceListener *listener) = 0;
|
||||
|
||||
/// Wire the raw-advertisement stream (bluetooth_proxy). One consumer at a time.
|
||||
virtual void set_raw_advertisement_callback(RawAdvertisementCallback callback) = 0;
|
||||
|
||||
#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.
|
||||
virtual void get_adapter_mac(uint8_t out[6]) = 0;
|
||||
|
||||
virtual bool scan_running() = 0;
|
||||
/// True when the current/configured scan mode is active (scan requests sent).
|
||||
virtual bool scan_active() = 0;
|
||||
/// Request a scan-mode change (active = send scan requests). Returns false
|
||||
/// when the hub cannot honor the request; the caller reports the real state
|
||||
/// back to its subscriber. A hub that returns true applies the mode
|
||||
/// immediately: a running scan is restarted with the new mode, an idle one
|
||||
/// 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; 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; }
|
||||
};
|
||||
// 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.
|
||||
//
|
||||
// /// Register a parsed-advertisement consumer (BLE sensors, automation triggers).
|
||||
// void register_listener(ESPBTDeviceListener *listener);
|
||||
//
|
||||
// /// Wire the raw-advertisement stream (bluetooth_proxy). One consumer at a time.
|
||||
// void set_raw_advertisement_callback(RawAdvertisementCallback callback);
|
||||
//
|
||||
// /// Push subscriber for scanner-state transitions, invoked where the hub's
|
||||
// /// state changes. Provided only by hubs that push (today: esp32), gated on
|
||||
// /// USE_BLE_SCANNER_STATE_CALLBACK (bluetooth_proxy emits the define), so
|
||||
// /// subscriber-less builds carry no storage. Consumers of a hub without it
|
||||
// /// fall back to polling scan_running().
|
||||
// void set_scanner_state_callback(ScannerStateCallback callback);
|
||||
//
|
||||
// static constexpr HubCapabilities get_capabilities();
|
||||
//
|
||||
// /// Adapter MAC in printable (MSB-first) order, out[0] = MSB.
|
||||
// void get_adapter_mac(uint8_t out[6]);
|
||||
//
|
||||
// bool scan_running();
|
||||
// /// True when the current/configured scan mode is active (scan requests sent).
|
||||
// bool scan_active();
|
||||
// /// Request a scan-mode change (active = send scan requests). Returns false
|
||||
// /// when the hub cannot honor the request; the caller reports the real state
|
||||
// /// back to its subscriber. A hub that returns true applies the mode
|
||||
// /// immediately: a running scan is restarted with the new mode, an idle one
|
||||
// /// picks it up on its next start. A hub without a mode switch refuses
|
||||
// /// without changing any state. Independent of HubCapabilities::active_scan:
|
||||
// /// that bit describes what the 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.
|
||||
// bool request_scan_mode(bool active);
|
||||
|
||||
} // namespace esphome::ble_device_base
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// ble_hub_impl.h
|
||||
//
|
||||
// Binds ble_device_base::BLEHub to the build's tracker. Exactly one tracker
|
||||
// component exists per build, so the hub is a compile-time alias rather than
|
||||
// an abstract interface: every hub call is a direct, inlinable member call
|
||||
// and trackers carry no vtable for the contract. Consumers include this
|
||||
// header; trackers include ble_hub.h (the contract types and the documented
|
||||
// method surface). Each tracker's codegen emits its USE_*_BLE_TRACKER define.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#if defined(USE_ESP32_BLE_TRACKER)
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#elif defined(USE_RP2_BLE_TRACKER)
|
||||
#include "esphome/components/rp2_ble_tracker/rp2_ble_tracker.h"
|
||||
#elif defined(USE_BK72XX_BLE_TRACKER)
|
||||
#include "esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.h"
|
||||
#elif defined(USE_LN882H_BLE_TRACKER)
|
||||
#include "esphome/components/ln882h_ble_tracker/ln882h_ble_tracker.h"
|
||||
#endif
|
||||
|
||||
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;
|
||||
#endif
|
||||
|
||||
} // namespace esphome::ble_device_base
|
||||
@@ -374,10 +374,12 @@ async def _to_code_esp32(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_active(config[CONF_ACTIVE]))
|
||||
# Advertisements and scanner state arrive through the hub callbacks
|
||||
# (installed in setup()); the tracker stays typed for scan-mode calls.
|
||||
tracker = await cg.get_variable(config[esp32_ble_tracker.CONF_ESP32_BLE_ID])
|
||||
cg.add(var.set_parent(tracker))
|
||||
cg.add(var.set_ble_hub(tracker))
|
||||
|
||||
# Compiles the scanner-state push slot into the tracker and the matching
|
||||
# registration into the proxy; the other hubs are polled instead.
|
||||
cg.add_define("USE_BLE_SCANNER_STATE_CALLBACK")
|
||||
|
||||
# Define max connections for protobuf fixed array
|
||||
connection_count = len(config.get(CONF_CONNECTIONS, []))
|
||||
@@ -426,5 +428,3 @@ 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")
|
||||
|
||||
@@ -75,9 +75,13 @@ void BluetoothProxy::setup() {
|
||||
this->hub_->set_raw_advertisement_callback({this, [](void *self, const ble_device_base::RawAdvertisement &adv) {
|
||||
static_cast<BluetoothProxy *>(self)->on_raw_advertisement_(adv);
|
||||
}});
|
||||
#ifdef USE_BLE_SCANNER_STATE_CALLBACK
|
||||
// Only hubs that push scanner-state transitions compile the slot (today:
|
||||
// esp32); elsewhere loop() polls scan_running() instead.
|
||||
this->hub_->set_scanner_state_callback({this, [](void *self, ble_device_base::ScannerState state) {
|
||||
static_cast<BluetoothProxy *>(self)->send_bluetooth_scanner_state_(state);
|
||||
}});
|
||||
#endif
|
||||
}
|
||||
|
||||
// The hub delivers raw advertisements on the ESPHome main loop.
|
||||
@@ -465,13 +469,13 @@ void BluetoothProxy::bluetooth_set_connection_params(const api::BluetoothSetConn
|
||||
#ifdef USE_ESP32
|
||||
|
||||
void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
|
||||
if (this->parent_()->get_scan_active() == active) {
|
||||
if (this->hub_->get_scan_active() == active) {
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Setting scanner mode to %s", active ? "active" : "passive");
|
||||
this->parent_()->set_scan_active(active);
|
||||
this->parent_()->stop_scan();
|
||||
this->parent_()->set_scan_continuous(
|
||||
this->hub_->set_scan_active(active);
|
||||
this->hub_->stop_scan();
|
||||
this->hub_->set_scan_continuous(
|
||||
true); // Set this to true to automatically start scanning again when it has cleaned up.
|
||||
}
|
||||
|
||||
@@ -619,7 +623,7 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
|
||||
}
|
||||
this->api_connection_ = api_connection;
|
||||
#ifdef USE_ESP32
|
||||
this->send_bluetooth_scanner_state_(this->parent_()->get_scanner_state());
|
||||
this->send_bluetooth_scanner_state_(this->hub_->get_scanner_state());
|
||||
#else
|
||||
this->send_polled_scanner_state_();
|
||||
#endif
|
||||
|
||||
@@ -15,17 +15,13 @@
|
||||
|
||||
#include "esphome/components/bluetooth_connection/bluetooth_connection.h"
|
||||
|
||||
#include "esphome/components/ble_device_base/ble_hub_impl.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#include "esphome/components/bluetooth_connection/bluetooth_connection_esp32.h"
|
||||
|
||||
#else
|
||||
#include "esphome/components/ble_device_base/ble_hub.h"
|
||||
#ifdef USE_BLE_GATT_CLIENT
|
||||
#elif defined(USE_BLE_GATT_CLIENT)
|
||||
#include "esphome/components/bluetooth_connection/bluetooth_connection_hub.h"
|
||||
#endif
|
||||
#endif // USE_ESP32
|
||||
|
||||
namespace esphome::bluetooth_proxy {
|
||||
|
||||
@@ -75,11 +71,7 @@ class BluetoothProxy final : public Component {
|
||||
#endif
|
||||
public:
|
||||
BluetoothProxy();
|
||||
#ifdef USE_ESP32
|
||||
// Advertisements arrive through the hub's raw callback; parent_() below
|
||||
// recovers the tracker type for the esp32-only scan-mode calls.
|
||||
void set_parent(esp32_ble_tracker::ESP32BLETracker *parent) { this->hub_ = parent; }
|
||||
#endif // USE_ESP32
|
||||
void set_ble_hub(ble_device_base::BLEHub *hub) { this->hub_ = hub; }
|
||||
void dump_config() override;
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
@@ -88,7 +80,6 @@ class BluetoothProxy final : public Component {
|
||||
void register_connection(BluetoothConnection *connection);
|
||||
#endif // BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifndef USE_ESP32
|
||||
void set_ble_hub(ble_device_base::BLEHub *hub) { this->hub_ = hub; }
|
||||
// Run after the hub's setup() (the trackers use AFTER_WIFI): setup() below
|
||||
// snapshots scan_active()/scan_running() and installs the raw callback, and
|
||||
// the BLEHub contract does not promise those are settled any earlier than
|
||||
@@ -262,13 +253,6 @@ class BluetoothProxy final : public Component {
|
||||
std::array<BluetoothConnection *, BLUETOOTH_PROXY_MAX_CONNECTIONS> connections_{};
|
||||
#endif
|
||||
ble_device_base::BLEHub *hub_{nullptr};
|
||||
#ifdef USE_ESP32
|
||||
// set_parent() is the only writer of hub_ on esp32, so the downcast is
|
||||
// exact; ESP32BLETracker derives from BLEHub non-virtually.
|
||||
esp32_ble_tracker::ESP32BLETracker *parent_() {
|
||||
return static_cast<esp32_ble_tracker::ESP32BLETracker *>(this->hub_);
|
||||
}
|
||||
#endif
|
||||
|
||||
// BLE advertisement batching
|
||||
api::BluetoothLERawAdvertisementsResponse response_;
|
||||
|
||||
@@ -205,6 +205,9 @@ async def to_code(config):
|
||||
# available on esp32 (sensors with irk: worked without opting in).
|
||||
ble_device_base.request_irk_support()
|
||||
|
||||
# Selects the BLEHub alias arm in ble_device_base/ble_hub_impl.h.
|
||||
cg.add_define("USE_ESP32_BLE_TRACKER")
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
||||
@@ -161,7 +161,6 @@ class ESPBTClient : public ESPBTDeviceListener {
|
||||
};
|
||||
|
||||
class ESP32BLETracker final : public Component,
|
||||
public ble_device_base::BLEHub,
|
||||
#ifdef USE_OTA_STATE_LISTENER
|
||||
public ota::OTAGlobalStateListener,
|
||||
#endif
|
||||
@@ -186,19 +185,27 @@ class ESP32BLETracker final : public Component,
|
||||
void register_client(ESPBTClient *client);
|
||||
|
||||
// ---- ble_device_base::BLEHub (the platform-neutral tracker contract) ----
|
||||
void register_listener(ble_device_base::ESPBTDeviceListener *listener) override;
|
||||
void set_raw_advertisement_callback(ble_device_base::RawAdvertisementCallback callback) override {
|
||||
void register_listener(ble_device_base::ESPBTDeviceListener *listener);
|
||||
void set_raw_advertisement_callback(ble_device_base::RawAdvertisementCallback callback) {
|
||||
this->raw_advertisement_callback_ = callback;
|
||||
}
|
||||
ble_device_base::HubCapabilities get_capabilities() const override {
|
||||
#ifdef USE_BLE_SCANNER_STATE_CALLBACK
|
||||
void set_scanner_state_callback(ble_device_base::ScannerStateCallback callback) {
|
||||
this->scanner_state_callback_ = callback;
|
||||
}
|
||||
#endif
|
||||
static constexpr ble_device_base::HubCapabilities get_capabilities() {
|
||||
// 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; }
|
||||
bool scan_active() override { return this->scan_active_; }
|
||||
void get_adapter_mac(uint8_t out[6]);
|
||||
bool scan_running() { return this->scanner_state_ == ScannerState::RUNNING; }
|
||||
bool scan_active() { return this->scan_active_; }
|
||||
// The mode is driven through this tracker's own API (see get_capabilities);
|
||||
// the neutral request refuses without changing any state.
|
||||
bool request_scan_mode(bool active) { return false; }
|
||||
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
void print_bt_device_info(const ESPBTDevice &device);
|
||||
@@ -288,6 +295,9 @@ class ESP32BLETracker final : public Component,
|
||||
StaticVector<ble_device_base::ESPBTDeviceListener *, ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT> neutral_listeners_;
|
||||
#endif
|
||||
ble_device_base::RawAdvertisementCallback raw_advertisement_callback_{};
|
||||
#ifdef USE_BLE_SCANNER_STATE_CALLBACK
|
||||
ble_device_base::ScannerStateCallback scanner_state_callback_{};
|
||||
#endif
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
/// Per-period "Found device" DEBUG log with MAC dedup (shared ble_device_base impl)
|
||||
ble_device_base::DiscoveredDeviceLog discovered_log_;
|
||||
|
||||
@@ -127,6 +127,9 @@ async def stop_scan_action_to_code(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
# Selects the BLEHub alias arm in ble_device_base/ble_hub_impl.h.
|
||||
cg.add_define("USE_LN882H_BLE_TRACKER")
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace esphome::ln882h_ble_tracker {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class LN882HBLETracker : public Component,
|
||||
public ble_device_base::BLEHub,
|
||||
public Parented<ln882h_ble::LN882HBLE>,
|
||||
public ln882h_ble::BLEScanListener
|
||||
#ifdef USE_OTA_STATE_LISTENER
|
||||
@@ -75,15 +74,15 @@ class LN882HBLETracker : public Component,
|
||||
void stop_scan();
|
||||
|
||||
// ---- ble_device_base::BLEHub contract ----
|
||||
void register_listener(ble_device_base::ESPBTDeviceListener *listener) override {
|
||||
void register_listener(ble_device_base::ESPBTDeviceListener *listener) {
|
||||
#ifdef ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT
|
||||
this->listeners_.push_back(listener);
|
||||
#endif
|
||||
}
|
||||
void set_raw_advertisement_callback(ble_device_base::RawAdvertisementCallback callback) override {
|
||||
void set_raw_advertisement_callback(ble_device_base::RawAdvertisementCallback callback) {
|
||||
this->raw_advertisement_callback_ = callback;
|
||||
}
|
||||
ble_device_base::HubCapabilities get_capabilities() const override {
|
||||
static constexpr ble_device_base::HubCapabilities get_capabilities() {
|
||||
// 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.
|
||||
@@ -92,15 +91,15 @@ class LN882HBLETracker : public Component,
|
||||
}
|
||||
// The controller stores the address LSB-first (BLE convention); the contract
|
||||
// wants printable (MSB-first) order.
|
||||
void get_adapter_mac(uint8_t out[6]) override {
|
||||
void get_adapter_mac(uint8_t out[6]) {
|
||||
uint8_t mac[6];
|
||||
this->parent_->get_mac_lsb_first(mac);
|
||||
for (int i = 0; i < 6; i++)
|
||||
out[i] = mac[5 - i];
|
||||
}
|
||||
bool scan_running() override { return this->scan_running_; }
|
||||
bool scan_active() override { return this->scan_active_; }
|
||||
bool request_scan_mode(bool active) override;
|
||||
bool scan_running() { return this->scan_running_; }
|
||||
bool scan_active() { return this->scan_active_; }
|
||||
bool request_scan_mode(bool active);
|
||||
|
||||
// ---- ln882h_ble::BLEScanListener ----
|
||||
// Delivered by the controller's loop() on the ESPHome main task — the
|
||||
|
||||
@@ -55,6 +55,9 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
# Selects the BLEHub alias arm in ble_device_base/ble_hub_impl.h.
|
||||
cg.add_define("USE_RP2_BLE_TRACKER")
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
namespace esphome::rp2_ble_tracker {
|
||||
|
||||
class RP2BLETracker : public Component,
|
||||
public ble_device_base::BLEHub,
|
||||
public rp2040_ble::BLEScanListener,
|
||||
public Parented<rp2040_ble::RP2040BLE>
|
||||
#ifdef USE_OTA_STATE_LISTENER
|
||||
@@ -51,15 +50,15 @@ class RP2BLETracker : public Component,
|
||||
void stop_scan();
|
||||
|
||||
// ---- ble_device_base::BLEHub contract ----
|
||||
void register_listener(ble_device_base::ESPBTDeviceListener *listener) override {
|
||||
void register_listener(ble_device_base::ESPBTDeviceListener *listener) {
|
||||
#ifdef ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT
|
||||
this->listeners_.push_back(listener);
|
||||
#endif
|
||||
}
|
||||
void set_raw_advertisement_callback(ble_device_base::RawAdvertisementCallback callback) override {
|
||||
void set_raw_advertisement_callback(ble_device_base::RawAdvertisementCallback callback) {
|
||||
this->raw_advertisement_callback_ = callback;
|
||||
}
|
||||
ble_device_base::HubCapabilities get_capabilities() const override {
|
||||
static constexpr ble_device_base::HubCapabilities get_capabilities() {
|
||||
// BTstack delivers scan responses as separate advertisement reports rather
|
||||
// than merging them into the advertisement — consumers relying on
|
||||
// scan-response fields (device names) get them only where the receiver
|
||||
@@ -74,10 +73,10 @@ class RP2BLETracker : public Component,
|
||||
}
|
||||
// The controller stores the address in printable (MSB-first) order, which is
|
||||
// exactly what the contract wants.
|
||||
void get_adapter_mac(uint8_t out[6]) override { this->parent_->get_mac_msb_first(out); }
|
||||
bool scan_running() override { return this->scan_running_; }
|
||||
bool scan_active() override { return this->scan_active_; }
|
||||
bool request_scan_mode(bool active) override;
|
||||
void get_adapter_mac(uint8_t out[6]) { this->parent_->get_mac_msb_first(out); }
|
||||
bool scan_running() { return this->scan_running_; }
|
||||
bool scan_active() { return this->scan_active_; }
|
||||
bool request_scan_mode(bool active);
|
||||
|
||||
// ---- rp2040_ble::BLEScanListener ----
|
||||
// Delivered by the controller's loop() on the ESPHome main loop — the
|
||||
|
||||
@@ -305,6 +305,7 @@
|
||||
#define USE_ESP32_BLE_SERVER_DESCRIPTOR_ON_WRITE
|
||||
#define USE_ESP32_BLE_SERVER_ON_CONNECT
|
||||
#define USE_ESP32_BLE_SERVER_ON_DISCONNECT
|
||||
#define USE_ESP32_BLE_TRACKER
|
||||
#define ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT 1
|
||||
#define ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT 1
|
||||
#define ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT 1
|
||||
@@ -465,6 +466,7 @@
|
||||
#define USE_LOGGER_USB_CDC
|
||||
#define USE_SOCKET_IMPL_LWIP_TCP
|
||||
#define USE_RP2040_BLE
|
||||
#define USE_RP2_BLE_TRACKER
|
||||
#define RP2040_BLE_SCAN_LISTENER_COUNT 1
|
||||
#define ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT 1
|
||||
#define USE_BLE_GATT_CLIENT
|
||||
@@ -486,8 +488,10 @@
|
||||
|
||||
#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
|
||||
#define ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT 1
|
||||
#define USE_CAPTIVE_PORTAL
|
||||
|
||||
@@ -13,17 +13,14 @@ namespace esphome::ble_device_base::testing {
|
||||
//
|
||||
// The in-tree emit site (BK72xxBLETracker::on_scan_report) compiles against
|
||||
// the Beken SDK and cannot run host-side, so the guard-and-fire semantics are
|
||||
// pinned here through a minimal host BLEHub implementation instead.
|
||||
// pinned here through a minimal host hub instead. The contract is duck-typed
|
||||
// (BLEHub is a per-platform alias, not a base class), so the fake carries
|
||||
// only the slot surface under test.
|
||||
namespace {
|
||||
|
||||
class FakeHub : public BLEHub {
|
||||
class FakeHub {
|
||||
public:
|
||||
void register_listener(ESPBTDeviceListener *listener) override {}
|
||||
void set_raw_advertisement_callback(RawAdvertisementCallback callback) override { this->callback_ = callback; }
|
||||
HubCapabilities get_capabilities() const override { return {false, false, false}; }
|
||||
void get_adapter_mac(uint8_t out[6]) override {}
|
||||
bool scan_running() override { return false; }
|
||||
bool scan_active() override { return false; }
|
||||
void set_raw_advertisement_callback(RawAdvertisementCallback callback) { this->callback_ = callback; }
|
||||
|
||||
/// The emit path every tracker implements: fire only when a subscriber is set.
|
||||
void emit(const RawAdvertisement &adv) {
|
||||
|
||||
@@ -6,47 +6,45 @@
|
||||
|
||||
namespace esphome::ble_device_base::testing {
|
||||
|
||||
// Pins the request_scan_mode() contract: the base default refuses (so hubs
|
||||
// without a mode switch — and out-of-tree trackers — keep building and
|
||||
// callers report the real state), while an overriding hub both honors the
|
||||
// request and applies it.
|
||||
// Pins the request_scan_mode() contract documented in ble_hub.h: a hub
|
||||
// without a mode switch refuses without changing any state (so callers report
|
||||
// the real state back), while a switching hub both honors the request and
|
||||
// applies it. The contract is duck-typed (BLEHub is a per-platform alias),
|
||||
// so the shapes are pinned through minimal host hubs mirroring the in-tree
|
||||
// tracker stubs.
|
||||
namespace {
|
||||
|
||||
class DefaultHub : public BLEHub {
|
||||
class RefusingHub {
|
||||
public:
|
||||
void register_listener(ESPBTDeviceListener *listener) override {}
|
||||
void set_raw_advertisement_callback(RawAdvertisementCallback callback) override {}
|
||||
HubCapabilities get_capabilities() const override { return {false, false, false}; }
|
||||
void get_adapter_mac(uint8_t out[6]) override {}
|
||||
bool scan_running() override { return false; }
|
||||
// Backed by real state so "changes nothing" is observable: a base default
|
||||
// that silently mutated the hub would flip this and fail the assertion.
|
||||
bool scan_active() override { return this->active_; }
|
||||
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 DefaultHub {
|
||||
class SwitchingHub : public RefusingHub {
|
||||
public:
|
||||
HubCapabilities get_capabilities() const override { return {true, false, false, /* scan_mode_switch = */ true}; }
|
||||
bool request_scan_mode(bool active) override {
|
||||
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 default (mode is driven through its own tracker API).
|
||||
class CapableRefusingHub : public DefaultHub {
|
||||
// the refusing stub (mode is driven through its own tracker API).
|
||||
class CapableRefusingHub : public RefusingHub {
|
||||
public:
|
||||
HubCapabilities get_capabilities() const override { return {true, false, false}; }
|
||||
static constexpr HubCapabilities get_capabilities() { return {true, false, false}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(BLEHubScanModeRequest, DefaultRefusesAndChangesNothing) {
|
||||
DefaultHub hub;
|
||||
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.
|
||||
@@ -55,7 +53,7 @@ TEST(BLEHubScanModeRequest, DefaultRefusesAndChangesNothing) {
|
||||
EXPECT_TRUE(hub.scan_active());
|
||||
}
|
||||
|
||||
TEST(BLEHubScanModeRequest, OverrideHonorsAndApplies) {
|
||||
TEST(BLEHubScanModeRequest, SwitchingHubHonorsAndApplies) {
|
||||
SwitchingHub hub;
|
||||
EXPECT_TRUE(hub.get_capabilities().scan_mode_switch);
|
||||
EXPECT_TRUE(hub.request_scan_mode(true));
|
||||
|
||||
Reference in New Issue
Block a user