mirror of
https://github.com/esphome/esphome.git
synced 2026-08-29 01:03:29 +00:00
Revive backend-only builds, the slot ledger, and the table gate
This commit is contained in:
@@ -139,10 +139,8 @@ concept BLEGattConnectionContract = requires(T conn, GattClientListener *listene
|
||||
{ conn.update_connection_params(uint16_t{}, uint16_t{}, uint16_t{}, uint16_t{}) } -> std::same_as<int>;
|
||||
{ conn.get_service_table() } -> std::same_as<GattServiceTable>;
|
||||
{ conn.release_services() } -> std::same_as<void>;
|
||||
// Deferred-disconnect visibility and the connection-type hint; backends
|
||||
// without the underlying state carry inline no-ops.
|
||||
{ conn.disconnect_pending() } -> std::same_as<bool>;
|
||||
{ conn.cancel_pending_disconnect() } -> std::same_as<void>;
|
||||
// Connection-type hint for backends that tune parameters by it; others
|
||||
// carry an inline no-op.
|
||||
{ conn.set_connection_type(ConnectionType{}) } -> std::same_as<void>;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
"""Per-platform GATT connection backends and the helpers to embed one.
|
||||
|
||||
Backends: esp32 Bluedroid, rp2 BTstack. No user-facing configuration; the
|
||||
Bluetooth proxy's codegen declares and registers the backend instances
|
||||
through gatt_client_schema()/hub_connection_schema() + new_gatt_backend().
|
||||
Backends: esp32 Bluedroid, rp2 BTstack. No user-facing configuration; a
|
||||
consumer's codegen declares and registers the backend instances — the
|
||||
Bluetooth proxy through its per-slot connection wrappers (a streaming
|
||||
consumer), and the neutral ble_client through gatt_client_schema() +
|
||||
new_gatt_backend().
|
||||
"""
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
@@ -19,6 +21,15 @@ DOMAIN = "bluetooth_connection"
|
||||
|
||||
|
||||
def AUTO_LOAD() -> list[str]:
|
||||
"""ble_device_base plus the platform BLE stack the build's backend
|
||||
registers with, so consumers stay platform-blind. The platform-less arm
|
||||
serves tooling that resolves the manifest without a target."""
|
||||
if CORE.is_esp32:
|
||||
return ["ble_device_base", "esp32_ble_tracker"]
|
||||
if CORE.is_rp2:
|
||||
return ["ble_device_base", "rp2040_ble"]
|
||||
if CORE.target_platform is None:
|
||||
return ["ble_device_base", "esp32_ble_tracker", "rp2040_ble"]
|
||||
return ["ble_device_base"]
|
||||
|
||||
|
||||
@@ -92,6 +103,9 @@ _PLATFORM_BACKENDS: dict[str, _PlatformBackend] = {
|
||||
PLATFORM_RP2: _PlatformBackend(RP2GattClient, _rp2_schema_fragment, _rp2_register),
|
||||
}
|
||||
|
||||
# Gates dedicated-backend consumers (cv.only_on).
|
||||
GATT_CLIENT_PLATFORMS = list(_PLATFORM_BACKENDS)
|
||||
|
||||
|
||||
def _backend_entry(platform: str | None = None) -> _PlatformBackend:
|
||||
key = platform if platform is not None else CORE.target_platform
|
||||
@@ -122,14 +136,70 @@ def hub_connection_schema(platform: str | None = None) -> cv.Schema:
|
||||
)
|
||||
|
||||
|
||||
async def new_gatt_backend(config: ConfigType) -> cg.MockObj:
|
||||
@dataclass
|
||||
class _SlotLedger:
|
||||
"""GATT connection slots claimed this run, for the platform cap check."""
|
||||
|
||||
consumers: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
def _ledger() -> _SlotLedger:
|
||||
if DOMAIN not in CORE.data:
|
||||
CORE.data[DOMAIN] = _SlotLedger()
|
||||
return CORE.data[DOMAIN]
|
||||
|
||||
|
||||
def consume_gatt_slot(consumer: str, count: int = 1):
|
||||
"""Validator claiming GATT connection slots — the one spelling for every
|
||||
claimant (the proxy per configured slot, dedicated backends once). The
|
||||
neutral ledger feeds the platform cap check in FINAL_VALIDATE_SCHEMA;
|
||||
esp32 additionally charges the controller's connection budget."""
|
||||
|
||||
def validator(config: ConfigType) -> ConfigType:
|
||||
_ledger().consumers.extend([consumer] * count)
|
||||
if CORE.is_esp32:
|
||||
from esphome.components import esp32_ble
|
||||
|
||||
esp32_ble.consume_connection_slots(count, consumer)(config)
|
||||
return config
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
def _validate_slot_totals(config: ConfigType) -> ConfigType:
|
||||
# esp32 has its own controller budget (esp32_ble); the hub platforms cap
|
||||
# at the prebuilt stack's client count, and nothing else counts claims
|
||||
# across components (e.g. a proxy plus a radon_eye_rd200 on rp2).
|
||||
if (cap := HUB_MAX_CONNECTIONS.get(CORE.target_platform)) is None:
|
||||
return config
|
||||
claimed = _ledger().consumers
|
||||
if len(claimed) > cap:
|
||||
raise cv.Invalid(
|
||||
f"{CORE.target_platform} supports at most {cap} GATT client "
|
||||
f"connection(s); {len(claimed)} requested by: {', '.join(claimed)}"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _validate_slot_totals
|
||||
|
||||
|
||||
async def new_gatt_backend(
|
||||
config: ConfigType, *, service_table: bool = True
|
||||
) -> cg.MockObj:
|
||||
"""Instantiate the backend declared by gatt_client_schema() and register
|
||||
it with its platform stack. The connection slot is claimed at validation
|
||||
(the proxy's slot validators), not here.
|
||||
(the consume_gatt_slot validators), not here.
|
||||
|
||||
service_table compiles the on-demand service-table materializer into the
|
||||
backend; direct consumers need it, the streaming proxy does not, so
|
||||
proxy-only builds keep the smaller footprint.
|
||||
"""
|
||||
from esphome.components import ble_device_base
|
||||
|
||||
ble_device_base.request_gatt_client()
|
||||
if service_table:
|
||||
cg.add_define("USE_BLE_GATT_SERVICE_TABLE")
|
||||
backend = cg.new_Pvariable(config[CONF_BACKEND_ID])
|
||||
# The backend has no user-facing component options; an empty config keeps
|
||||
# the consumer's own keys (update_interval, ...) off it.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <esp_gattc_api.h>
|
||||
#endif
|
||||
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
|
||||
#include "esphome/components/api/api_pb2.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -44,7 +44,7 @@ BatchClose close_service_batch(api::BluetoothGATTGetServicesResponse &resp, size
|
||||
|
||||
} // namespace esphome::bluetooth_connection
|
||||
|
||||
#endif // BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#endif // BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
|
||||
#ifdef USE_ESP32
|
||||
namespace esphome::bluetooth_connection {
|
||||
|
||||
@@ -16,14 +16,16 @@
|
||||
#include <esp_err.h>
|
||||
#endif
|
||||
|
||||
// The connection-aware API request handlers are compiled: a GATT backend is
|
||||
// wired by codegen (one slot per connection). This is the single spelling of
|
||||
// that predicate - the hub wrapper and the API request handlers gate on it.
|
||||
// The proxy-serving surface is compiled: a proxy is present and a GATT
|
||||
// backend is wired by codegen (one slot per connection). This is the single
|
||||
// spelling of that predicate - the hub wrapper, the connection-aware API
|
||||
// request handlers, and the Bluedroid in-place streamer all gate on it.
|
||||
// Advertisement-only builds get the clean-error handlers; address-scoped
|
||||
// maintenance (unpair, cache clear) still works there through the
|
||||
// per-platform free functions below.
|
||||
#ifdef USE_BLE_GATT_CLIENT
|
||||
#define BLUETOOTH_CONNECTION_HAS_GATT
|
||||
// per-platform free functions below. Backend-only builds (a dedicated-backend
|
||||
// consumer without bluetooth_proxy) compile none of this API surface.
|
||||
#if defined(USE_BLE_GATT_CLIENT) && defined(USE_BLUETOOTH_PROXY)
|
||||
#define BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
#endif
|
||||
|
||||
namespace esphome::api {
|
||||
@@ -139,7 +141,7 @@ inline void fill_gatt_uuid(std::array<uint64_t, 2> &uuid_128, uint32_t &short_uu
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
/// Result of close_service_batch: keep filling the batch or send it now.
|
||||
/// An oversized service is packed alone; a failed (backpressured) send is
|
||||
/// retried from the batch start, so no service is silently skipped.
|
||||
@@ -151,6 +153,6 @@ enum class BatchClose : uint8_t { CONTINUE, SEND };
|
||||
/// cannot drift.
|
||||
BatchClose close_service_batch(api::BluetoothGATTGetServicesResponse &resp, size_t ¤t_size, int16_t &send_service,
|
||||
uint8_t connection_index, const char *address_str);
|
||||
#endif // BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#endif // BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
|
||||
} // namespace esphome::bluetooth_connection
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
#if defined(USE_ESP32_BLE) && defined(USE_BLE_GATT_CLIENT)
|
||||
|
||||
#include "bluetooth_connection.h"
|
||||
|
||||
// The in-place streamer serves the proxy's service-discovery API; backend-only
|
||||
// builds compile without the proxy headers or the streamer.
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#include "bluetooth_connection.h"
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
#include "bluetooth_connection_hub.h"
|
||||
|
||||
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
|
||||
@@ -485,7 +486,7 @@ void BluedroidGattClient::handle_search_cmpl_() {
|
||||
this->listener_->on_service_discovery_done(0);
|
||||
}
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
void BluedroidGattClient::stream_service_batch(BluetoothConnection &conn) {
|
||||
if (this->services_released_ || conn.send_service_ >= this->service_total_) {
|
||||
conn.send_service_ = DONE_SENDING_SERVICES;
|
||||
@@ -624,7 +625,7 @@ void BluedroidGattClient::stream_service_batch(BluetoothConnection &conn) {
|
||||
conn.send_service_ = batch_start;
|
||||
}
|
||||
}
|
||||
#endif // USE_BLUETOOTH_PROXY
|
||||
#endif // BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
|
||||
// ---- events ----
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
#if defined(USE_ESP32_BLE) && defined(USE_BLE_GATT_CLIENT)
|
||||
|
||||
#include "bluetooth_connection.h"
|
||||
|
||||
#include "esphome/components/ble_device_base/ble_gatt_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/component.h"
|
||||
@@ -23,7 +25,7 @@
|
||||
namespace esphome::bluetooth_connection {
|
||||
|
||||
class BluedroidGattClient;
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
class BluetoothConnection;
|
||||
#endif
|
||||
|
||||
@@ -82,7 +84,7 @@ class BluedroidGattClient final : public Component {
|
||||
#endif
|
||||
void release_services();
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
/// In-place service streamer (the proxy wrapper detects and prefers it):
|
||||
/// builds one api response batch directly from Bluedroid's cached database,
|
||||
/// so the streaming peak is the response itself - the old esp32 model.
|
||||
@@ -90,8 +92,6 @@ class BluedroidGattClient final : public Component {
|
||||
#endif
|
||||
|
||||
void set_connection_type(ble_device_base::ConnectionType ct) { this->connection_type_ = ct; }
|
||||
bool disconnect_pending() const { return this->shim_.disconnect_pending(); }
|
||||
void cancel_pending_disconnect() { this->shim_.cancel_pending_disconnect(); }
|
||||
|
||||
protected:
|
||||
friend class BluedroidTrackerShim;
|
||||
|
||||
@@ -44,8 +44,6 @@ class StubGattBackend {
|
||||
return ble_device_base::GATT_ERR_NOT_CONNECTED;
|
||||
}
|
||||
ble_device_base::GattServiceTable get_service_table() { return {}; }
|
||||
bool disconnect_pending() const { return false; }
|
||||
void cancel_pending_disconnect() {}
|
||||
void set_connection_type(ble_device_base::ConnectionType ct) {}
|
||||
void release_services() {}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// The proxy's per-slot connection wrapper, shared by every platform.
|
||||
#include "bluetooth_connection_hub.h"
|
||||
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
|
||||
#include "esphome/components/api/api_pb2.h"
|
||||
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
|
||||
@@ -436,4 +436,4 @@ void BluetoothConnection::send_service_for_discovery_() {
|
||||
|
||||
} // namespace esphome::bluetooth_connection
|
||||
|
||||
#endif // BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#endif // BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
// The wrapper exists to serve the proxy's API surface; direct consumers
|
||||
// drive the backend themselves, so backend-only builds compile this header
|
||||
// empty.
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
|
||||
#include "esphome/components/ble_device_base/ble_client_state.h"
|
||||
#include "bluetooth_connection_gatt_backend.h"
|
||||
@@ -147,4 +147,4 @@ class BluetoothConnection final : public ble_device_base::GattClientListener {
|
||||
|
||||
} // namespace esphome::bluetooth_connection
|
||||
|
||||
#endif // BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#endif // BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
|
||||
@@ -92,10 +92,7 @@ class RP2GattClient final : public Component, public Parented<rp2040_ble::RP2040
|
||||
int pair();
|
||||
int update_connection_params(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout);
|
||||
ble_device_base::GattServiceTable get_service_table();
|
||||
// No deferred-disconnect state (disconnect is one call) and no
|
||||
// connection-type branching on this backend.
|
||||
bool disconnect_pending() const { return false; }
|
||||
void cancel_pending_disconnect() {}
|
||||
// No connection-type branching on this backend.
|
||||
void set_connection_type(ble_device_base::ConnectionType ct) {}
|
||||
void release_services();
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ def _esp32_config_schema() -> cv.All:
|
||||
)
|
||||
elif config[CONF_ACTIVE]:
|
||||
connection_slots: int = config[CONF_CONNECTION_SLOTS]
|
||||
esp32_ble.consume_connection_slots(connection_slots, "bluetooth_proxy")(
|
||||
bluetooth_connection.consume_gatt_slot("bluetooth_proxy", connection_slots)(
|
||||
config
|
||||
)
|
||||
|
||||
@@ -160,6 +160,9 @@ def _rp2_config_schema() -> cv.All:
|
||||
# their ids exist for codegen (the esp32 arm's `connections` pattern).
|
||||
if not config[CONF_ACTIVE]:
|
||||
return config
|
||||
bluetooth_connection.consume_gatt_slot(
|
||||
"bluetooth_proxy", config[CONF_CONNECTION_SLOTS]
|
||||
)(config)
|
||||
return {
|
||||
**config,
|
||||
CONF_CONNECTIONS: [
|
||||
@@ -201,7 +204,9 @@ async def _connections_to_code(var: cg.MockObj, config: ConfigType) -> None:
|
||||
"""One wrapper + backend pair per slot; the platform-specific backend
|
||||
registration lives in bluetooth_connection.new_gatt_backend()."""
|
||||
for connection_conf in config.get(CONF_CONNECTIONS, []):
|
||||
backend = await bluetooth_connection.new_gatt_backend(connection_conf)
|
||||
backend = await bluetooth_connection.new_gatt_backend(
|
||||
connection_conf, service_table=False
|
||||
)
|
||||
connection = cg.new_Pvariable(connection_conf[CONF_ID])
|
||||
cg.add(connection.set_backend(backend))
|
||||
cg.add(var.register_connection(connection))
|
||||
|
||||
@@ -106,7 +106,7 @@ void BluetoothProxy::on_raw_advertisement_(const ble_device_base::RawAdvertiseme
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
void BluetoothProxy::log_connection_request_ignored_(BluetoothConnection *connection, ClientState state) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, state: %s", connection->get_connection_index(),
|
||||
connection->address_str(), ble_device_base::client_state_to_string(state));
|
||||
@@ -115,7 +115,7 @@ void BluetoothProxy::log_connection_request_ignored_(BluetoothConnection *connec
|
||||
void BluetoothProxy::log_connection_info_(BluetoothConnection *connection, const char *message) {
|
||||
ESP_LOGI(TAG, "[%d] [%s] Connecting %s", connection->get_connection_index(), connection->address_str(), message);
|
||||
}
|
||||
#endif // BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#endif // BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
|
||||
void BluetoothProxy::log_not_connected_gatt_(const char *action, const char *type) {
|
||||
ESP_LOGW(TAG, "Cannot %s GATT %s, not connected", action, type);
|
||||
@@ -139,7 +139,7 @@ void BluetoothProxy::dump_config() {
|
||||
this->get_bluetooth_mac_address_pretty(mac_str);
|
||||
const char *mac_out = mac_str[0] != '\0' ? mac_str : "unavailable (adapter not up yet)";
|
||||
const char *scan_mode = this->configured_scan_active_ ? "active" : "passive";
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Bluetooth Proxy:\n"
|
||||
" Active: %s\n"
|
||||
@@ -157,7 +157,7 @@ void BluetoothProxy::dump_config() {
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
|
||||
// maybe_unused: in a passive proxy (active: false) MAX is 0, the body is removed, and connection is unused.
|
||||
void BluetoothProxy::register_connection([[maybe_unused]] BluetoothConnection *connection) {
|
||||
@@ -426,7 +426,7 @@ void BluetoothProxy::bluetooth_set_connection_params(const api::BluetoothSetConn
|
||||
this->api_connection_->send_message(resp);
|
||||
}
|
||||
|
||||
#endif // BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#endif // BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -469,7 +469,7 @@ void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
|
||||
#endif // USE_ESP32
|
||||
|
||||
void BluetoothProxy::loop() {
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
// Stream pending service-discovery batches every iteration; the streamer
|
||||
// handles a vanished API connection itself.
|
||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||
@@ -484,7 +484,7 @@ void BluetoothProxy::loop() {
|
||||
this->last_advertisement_flush_time_ = now;
|
||||
|
||||
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
// The API subscriber is gone: tear down any connections it left behind
|
||||
// (disconnect() on an already-disconnecting slot is a no-op).
|
||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||
@@ -508,7 +508,7 @@ void BluetoothProxy::loop() {
|
||||
this->flush_pending_advertisements_();
|
||||
}
|
||||
|
||||
#ifndef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifndef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
|
||||
// Advertisement-only proxy. GATT client connections are excluded at compile
|
||||
// time (no connection backend on this platform, or active: false), so every
|
||||
@@ -580,7 +580,7 @@ void BluetoothProxy::bluetooth_set_connection_params(const api::BluetoothSetConn
|
||||
this->api_connection_->send_message(resp);
|
||||
}
|
||||
|
||||
#endif // !BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#endif // !BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
|
||||
void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags) {
|
||||
if (this->api_connection_ != nullptr && this->api_connection_ != api_connection) {
|
||||
|
||||
@@ -26,7 +26,7 @@ using bluetooth_connection::conn_err_t;
|
||||
using bluetooth_connection::GATT_NOT_CONNECTED;
|
||||
using bluetooth_connection::INIT_SENDING_SERVICES;
|
||||
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
using BluetoothConnection = bluetooth_connection::BluetoothConnection;
|
||||
using ClientState = ble_device_base::ClientState;
|
||||
#endif
|
||||
@@ -58,7 +58,7 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
|
||||
};
|
||||
|
||||
class BluetoothProxy final : public Component {
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
// Allow the connection to update connections_free_response_
|
||||
friend bluetooth_connection::BluetoothConnection;
|
||||
#endif
|
||||
@@ -69,9 +69,9 @@ class BluetoothProxy final : public Component {
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
void register_connection(BluetoothConnection *connection);
|
||||
#endif // BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#endif // BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
#ifndef USE_ESP32
|
||||
// Run after the hub's setup() (the trackers use AFTER_WIFI): setup() below
|
||||
// snapshots scan_active()/scan_running() and installs the raw callback, and
|
||||
@@ -189,7 +189,7 @@ class BluetoothProxy final : public Component {
|
||||
}
|
||||
void log_advertisement_flush_();
|
||||
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
|
||||
void log_connection_request_ignored_(BluetoothConnection *connection, ClientState state);
|
||||
void log_connection_info_(BluetoothConnection *connection, const char *message);
|
||||
@@ -197,7 +197,7 @@ class BluetoothProxy final : public Component {
|
||||
void log_not_connected_gatt_(const char *action, const char *type);
|
||||
void handle_gatt_not_connected_(uint64_t address, uint16_t handle, const char *action, const char *type);
|
||||
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
/// Keep the pre-allocated connections-free message in step when a
|
||||
/// connection slot changes address (0 = free). Called from the connection
|
||||
/// classes' set_address().
|
||||
@@ -237,7 +237,7 @@ class BluetoothProxy final : public Component {
|
||||
// Group 1: Pointers (4 bytes each, naturally aligned)
|
||||
api::APIConnection *api_connection_{nullptr};
|
||||
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
#ifdef BLUETOOTH_CONNECTION_SERVES_PROXY
|
||||
// Group 2: Fixed-size array of connection pointers
|
||||
std::array<BluetoothConnection *, BLUETOOTH_PROXY_MAX_CONNECTIONS> connections_{};
|
||||
#endif
|
||||
|
||||
@@ -307,6 +307,7 @@
|
||||
#define USE_ESP32_BLE_SERVER_ON_DISCONNECT
|
||||
#define USE_ESP32_BLE_TRACKER
|
||||
#define USE_BLE_GATT_CLIENT
|
||||
#define USE_BLE_GATT_SERVICE_TABLE
|
||||
#define ESPHOME_BLE_GATT_CLIENT_COUNT 1
|
||||
#define ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT 1
|
||||
#define ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT 1
|
||||
|
||||
@@ -177,12 +177,22 @@ def test_rp2_rejects_esp32_only_keys_by_name(
|
||||
bluetooth_proxy.CONFIG_SCHEMA({"connections": [{}]})
|
||||
|
||||
|
||||
def test_bluetooth_connection_auto_load_covers_its_includes() -> None:
|
||||
# Every backend builds on ble_device_base alone; the Bluedroid backend
|
||||
# talks to IDF directly, so esp32_ble_client is no longer in the closure.
|
||||
for platform in ("esp32", "rp2", None):
|
||||
_set_platform(platform)
|
||||
assert bluetooth_connection.AUTO_LOAD() == ["ble_device_base"]
|
||||
def test_bluetooth_connection_auto_load_matches_the_platform_stack() -> None:
|
||||
# The backend registers with its platform BLE stack, so that dependency
|
||||
# lives here and consumers (proxy, radon_eye_rd200) stay platform-blind;
|
||||
# the platform-less arm is the union for manifest-resolving tooling.
|
||||
_set_platform("esp32")
|
||||
assert bluetooth_connection.AUTO_LOAD() == ["ble_device_base", "esp32_ble_tracker"]
|
||||
_set_platform("rp2")
|
||||
assert bluetooth_connection.AUTO_LOAD() == ["ble_device_base", "rp2040_ble"]
|
||||
_set_platform("ln882x")
|
||||
assert bluetooth_connection.AUTO_LOAD() == ["ble_device_base"]
|
||||
_set_platform(None)
|
||||
assert bluetooth_connection.AUTO_LOAD() == [
|
||||
"ble_device_base",
|
||||
"esp32_ble_tracker",
|
||||
"rp2040_ble",
|
||||
]
|
||||
|
||||
|
||||
def test_every_registered_hub_platform_has_a_schema_arm() -> None:
|
||||
@@ -193,9 +203,9 @@ def test_every_registered_hub_platform_has_a_schema_arm() -> None:
|
||||
registered = set(bluetooth_connection.HUB_MAX_CONNECTIONS)
|
||||
assert registered <= set(bluetooth_proxy._GATT_HUB_SCHEMAS)
|
||||
assert registered <= set(bluetooth_proxy._HUB_PLATFORMS)
|
||||
# Hub platforms must also be in the backend registry the shared codegen
|
||||
# Hub platforms must also be in the backend registry the shared consumer
|
||||
# helpers dispatch on.
|
||||
assert registered <= set(bluetooth_connection._PLATFORM_BACKENDS)
|
||||
assert registered <= set(bluetooth_connection.GATT_CLIENT_PLATFORMS)
|
||||
# The outer walkable schema's bound must stay the loosest platform cap.
|
||||
assert (
|
||||
max(bluetooth_connection.HUB_MAX_CONNECTIONS.values())
|
||||
|
||||
@@ -50,8 +50,6 @@ class MinimalConnection {
|
||||
}
|
||||
GattServiceTable get_service_table() { return {}; }
|
||||
void release_services() {}
|
||||
bool disconnect_pending() const { return false; }
|
||||
void cancel_pending_disconnect() {}
|
||||
void set_connection_type(ConnectionType ct) {}
|
||||
|
||||
protected:
|
||||
|
||||
Reference in New Issue
Block a user