mirror of
https://github.com/esphome/esphome.git
synced 2026-09-11 15:27:33 +00:00
Deliver scanner state through a hub push callback
This commit is contained in:
@@ -49,6 +49,27 @@ struct RawAdvertisementCallback {
|
||||
void invoke(const RawAdvertisement &adv) const { this->fn(this->instance, adv); }
|
||||
};
|
||||
|
||||
/// Scanner lifecycle, wire-value aligned with the api enum so consumers cast
|
||||
/// directly (pinned by static_asserts at the cast sites).
|
||||
enum class ScannerState : uint8_t {
|
||||
IDLE = 0,
|
||||
STARTING = 1,
|
||||
RUNNING = 2,
|
||||
FAILED = 3,
|
||||
STOPPING = 4,
|
||||
STOPPED = 5,
|
||||
};
|
||||
|
||||
/// 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().
|
||||
struct ScannerStateCallback {
|
||||
void *instance{nullptr};
|
||||
void (*fn)(void *instance, ScannerState state){nullptr};
|
||||
bool is_set() const { return this->fn != nullptr; }
|
||||
void invoke(ScannerState state) const { this->fn(this->instance, state); }
|
||||
};
|
||||
|
||||
/// What a tracker's controller/SDK can do — consumers branch on data, not #ifdefs.
|
||||
struct HubCapabilities {
|
||||
/// Controller can send scan requests (active scanning).
|
||||
@@ -78,6 +99,10 @@ 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).
|
||||
void set_scanner_state_callback(ScannerStateCallback callback) { this->scanner_state_callback_ = callback; }
|
||||
|
||||
virtual HubCapabilities get_capabilities() const = 0;
|
||||
|
||||
/// Adapter MAC in printable (MSB-first) order, out[0] = MSB.
|
||||
@@ -97,6 +122,9 @@ 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
|
||||
|
||||
@@ -374,12 +374,10 @@ async def _to_code_esp32(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_active(config[CONF_ACTIVE]))
|
||||
# Advertisements arrive through the hub raw callback (installed in
|
||||
# setup()); only the scanner-state listener still registers with the
|
||||
# tracker directly.
|
||||
# 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))
|
||||
await esp32_ble_tracker.register_scanner_state_listener(var, config)
|
||||
|
||||
# Define max connections for protobuf fixed array
|
||||
connection_count = len(config.get(CONF_CONNECTIONS, []))
|
||||
|
||||
@@ -24,15 +24,18 @@ static_assert(sizeof(((api::BluetoothLERawAdvertisement *) nullptr)->data) == 62
|
||||
|
||||
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
|
||||
|
||||
#ifdef USE_ESP32
|
||||
// The neutral enum's values are the wire values.
|
||||
static_assert(static_cast<uint32_t>(ble_device_base::ScannerState::IDLE) == api::enums::BLUETOOTH_SCANNER_STATE_IDLE);
|
||||
static_assert(static_cast<uint32_t>(ble_device_base::ScannerState::STOPPED) ==
|
||||
api::enums::BLUETOOTH_SCANNER_STATE_STOPPED);
|
||||
|
||||
void BluetoothProxy::on_scanner_state(esp32_ble_tracker::ScannerState state) {
|
||||
void BluetoothProxy::on_scanner_state_(ble_device_base::ScannerState state) {
|
||||
if (this->api_connection_ != nullptr) {
|
||||
this->send_bluetooth_scanner_state_(state);
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state) {
|
||||
bool BluetoothProxy::send_bluetooth_scanner_state_(ble_device_base::ScannerState state) {
|
||||
api::BluetoothScannerStateResponse resp;
|
||||
resp.state = static_cast<api::enums::BluetoothScannerState>(state);
|
||||
resp.mode = this->hub_->scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
|
||||
@@ -40,30 +43,21 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta
|
||||
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);
|
||||
return this->api_connection_->send_message(resp);
|
||||
}
|
||||
|
||||
#else // !USE_ESP32
|
||||
|
||||
void BluetoothProxy::send_bluetooth_scanner_state_() {
|
||||
#ifndef USE_ESP32
|
||||
void BluetoothProxy::send_polled_scanner_state_() {
|
||||
// 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 = 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;
|
||||
if (this->api_connection_->send_message(resp)) {
|
||||
if (this->send_bluetooth_scanner_state_(running ? ble_device_base::ScannerState::RUNNING
|
||||
: ble_device_base::ScannerState::IDLE)) {
|
||||
this->last_scan_running_ = running;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // USE_ESP32
|
||||
#endif // !USE_ESP32
|
||||
|
||||
void BluetoothProxy::setup() {
|
||||
// BLUETOOTH_PROXY_MAX_CONNECTIONS is 0 on an advertisement-only proxy.
|
||||
@@ -76,6 +70,9 @@ 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);
|
||||
}});
|
||||
this->hub_->set_scanner_state_callback({this, [](void *self, ble_device_base::ScannerState state) {
|
||||
static_cast<BluetoothProxy *>(self)->on_scanner_state_(state);
|
||||
}});
|
||||
}
|
||||
|
||||
// The hub delivers raw advertisements on the ESPHome main loop.
|
||||
@@ -507,7 +504,7 @@ void BluetoothProxy::loop() {
|
||||
|
||||
// The hub has no scanner-state listener interface; poll and report on change.
|
||||
if (this->hub_->scan_running() != this->last_scan_running_) {
|
||||
this->send_bluetooth_scanner_state_();
|
||||
this->send_polled_scanner_state_();
|
||||
}
|
||||
|
||||
this->flush_pending_advertisements_();
|
||||
@@ -595,7 +592,7 @@ void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
|
||||
// Reports the mode change; the sender also refreshes last_scan_running_, so
|
||||
// a failed restart (scan_running_ dropped by the tracker) is not reported
|
||||
// again by loop() on the next tick.
|
||||
this->send_bluetooth_scanner_state_();
|
||||
this->send_polled_scanner_state_();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,9 +613,9 @@ 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_(static_cast<ble_device_base::ScannerState>(this->parent_->get_scanner_state()));
|
||||
#else
|
||||
this->send_bluetooth_scanner_state_();
|
||||
this->send_polled_scanner_state_();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -72,11 +72,7 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
|
||||
SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
|
||||
};
|
||||
|
||||
#ifdef USE_ESP32
|
||||
class BluetoothProxy final : public esp32_ble_tracker::BLEScannerStateListener, public Component {
|
||||
#else
|
||||
class BluetoothProxy final : public Component {
|
||||
#endif
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
// Allow the connection to update connections_free_response_
|
||||
friend bluetooth_connection::BluetoothConnection;
|
||||
@@ -138,11 +134,6 @@ class BluetoothProxy final : public Component {
|
||||
void set_active(bool active) { this->active_ = active; }
|
||||
bool has_active() { return this->active_; }
|
||||
|
||||
#ifdef USE_ESP32
|
||||
/// BLEScannerStateListener interface
|
||||
void on_scanner_state(esp32_ble_tracker::ScannerState state) override;
|
||||
#endif
|
||||
|
||||
uint32_t get_legacy_version() const {
|
||||
if (!this->active_) {
|
||||
return LEGACY_PASSIVE_ONLY_VERSION;
|
||||
@@ -216,10 +207,10 @@ class BluetoothProxy final : public Component {
|
||||
}
|
||||
|
||||
protected:
|
||||
#ifdef USE_ESP32
|
||||
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
|
||||
#else
|
||||
void send_bluetooth_scanner_state_();
|
||||
void on_scanner_state_(ble_device_base::ScannerState state);
|
||||
bool send_bluetooth_scanner_state_(ble_device_base::ScannerState state);
|
||||
#ifndef USE_ESP32
|
||||
void send_polled_scanner_state_();
|
||||
#endif
|
||||
void on_raw_advertisement_(const ble_device_base::RawAdvertisement &raw);
|
||||
|
||||
|
||||
@@ -419,9 +419,16 @@ 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<int>(ScannerState::IDLE) == static_cast<int>(ble_device_base::ScannerState::IDLE));
|
||||
static_assert(static_cast<int>(ScannerState::STOPPING) == static_cast<int>(ble_device_base::ScannerState::STOPPING));
|
||||
|
||||
void ESP32BLETracker::set_scanner_state_(ScannerState state) {
|
||||
this->scanner_state_ = state;
|
||||
this->state_version_++;
|
||||
if (this->scanner_state_callback_.is_set()) {
|
||||
this->scanner_state_callback_.invoke(static_cast<ble_device_base::ScannerState>(state));
|
||||
}
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_SCANNER_STATE_LISTENER_COUNT
|
||||
for (auto *listener : this->scanner_state_listeners_) {
|
||||
listener->on_scanner_state(state);
|
||||
|
||||
@@ -107,13 +107,13 @@ def test_esp32_bluetooth_proxy_requests_scanner_state_slot(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""The proxy requests one scanner state slot and a client slot per
|
||||
connection (three by default with active: true); advertisements arrive
|
||||
through the hub raw callback, so no listener slot exists."""
|
||||
"""The proxy requests a client slot per connection (three by default with
|
||||
active: true); advertisements and scanner state arrive through the hub
|
||||
callbacks, so no listener or scanner-state slot exists."""
|
||||
generate_main(component_config_path("esp32_bluetooth_proxy.yaml"))
|
||||
assert (
|
||||
get_define_value("ESPHOME_ESP32_BLE_TRACKER_SCANNER_STATE_LISTENER_COUNT")
|
||||
== "1"
|
||||
is None
|
||||
)
|
||||
assert get_define_value("ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT") is None
|
||||
assert get_define_value("ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT") == "3"
|
||||
|
||||
Reference in New Issue
Block a user