Apply the simplify findings and fix the passive esp32 build

This commit is contained in:
J. Nick Koston
2026-08-08 22:32:36 -05:00
parent 3c7a1062c8
commit 9dafff6a61
11 changed files with 87 additions and 101 deletions
@@ -16,10 +16,11 @@
#include <esp_err.h>
#endif
// A GATT connection backend exists in this build: esp32 (Bluedroid) or a hub
// platform with the neutral GATT client compiled in. Single-sourced here so
// the proxy and this component cannot drift.
#if defined(USE_ESP32) || defined(USE_BLE_GATT_CLIENT)
// The connection-aware API request handlers are compiled: a GATT backend is
// wired by codegen (one slot per connection). 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
#endif
@@ -246,9 +246,7 @@ void BluedroidGattClient::set_disconnecting_() {
}
void BluedroidGattClient::report_connection_state_(bool connected, int error) {
if (this->listener_ != nullptr) {
this->listener_->on_connection_state(connected, this->mtu_, error);
}
this->listener_->on_connection_state(connected, this->mtu_, error);
}
esp_err_t BluedroidGattClient::update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency,
@@ -286,9 +284,7 @@ void BluedroidGattClient::handle_search_cmpl_() {
esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_SECONDARY_SERVICE, 0x0001, 0xFFFF, 0,
&secondary);
this->service_total_ = primary + secondary;
if (this->listener_ != nullptr) {
this->listener_->on_service_discovery_done(0);
}
this->listener_->on_service_discovery_done(0);
}
void BluedroidGattClient::stream_service_batch(BluetoothConnection &conn) {
@@ -554,46 +550,35 @@ bool BluedroidGattClient::handle_gattc_event_(esp_gattc_cb_event_t event, esp_ga
case ESP_GATTC_READ_DESCR_EVT: {
if (this->conn_id_ != param->read.conn_id)
return false;
if (this->listener_ != nullptr) {
bool ok = param->read.status == ESP_GATT_OK;
this->listener_->on_read_result(param->read.handle, ok ? param->read.value : nullptr,
ok ? param->read.value_len : 0, ok ? 0 : param->read.status);
}
bool ok = param->read.status == ESP_GATT_OK;
this->listener_->on_read_result(param->read.handle, ok ? param->read.value : nullptr,
ok ? param->read.value_len : 0, ok ? 0 : param->read.status);
break;
}
case ESP_GATTC_WRITE_CHAR_EVT:
case ESP_GATTC_WRITE_DESCR_EVT: {
if (this->conn_id_ != param->write.conn_id)
return false;
if (this->listener_ != nullptr) {
this->listener_->on_write_result(param->write.handle,
param->write.status == ESP_GATT_OK ? 0 : param->write.status);
}
this->listener_->on_write_result(param->write.handle,
param->write.status == ESP_GATT_OK ? 0 : param->write.status);
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
if (this->listener_ != nullptr) {
this->listener_->on_notify_state(
param->reg_for_notify.handle, true,
param->reg_for_notify.status == ESP_GATT_OK ? 0 : param->reg_for_notify.status);
}
this->listener_->on_notify_state(param->reg_for_notify.handle, true,
param->reg_for_notify.status == ESP_GATT_OK ? 0 : param->reg_for_notify.status);
break;
}
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
if (this->listener_ != nullptr) {
this->listener_->on_notify_state(
param->unreg_for_notify.handle, false,
param->unreg_for_notify.status == ESP_GATT_OK ? 0 : param->unreg_for_notify.status);
}
this->listener_->on_notify_state(
param->unreg_for_notify.handle, false,
param->unreg_for_notify.status == ESP_GATT_OK ? 0 : param->unreg_for_notify.status);
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (this->conn_id_ != param->notify.conn_id)
return false;
ESP_LOGV(TAG, "[%d] NOTIFY_EVT handle=0x%2X", this->connection_index_, param->notify.handle);
if (this->listener_ != nullptr) {
this->listener_->on_notify_data(param->notify.handle, param->notify.value, param->notify.value_len);
}
this->listener_->on_notify_data(param->notify.handle, param->notify.value, param->notify.value_len);
break;
}
default:
@@ -614,10 +599,8 @@ void BluedroidGattClient::handle_gap_event_(esp_gap_ble_cb_event_t event, esp_bl
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
if (!this->check_addr_(param->ble_security.auth_cmpl.bd_addr))
break;
if (this->listener_ != nullptr) {
this->listener_->on_pairing_result(
param->ble_security.auth_cmpl.success ? 0 : param->ble_security.auth_cmpl.fail_reason);
}
this->listener_->on_pairing_result(
param->ble_security.auth_cmpl.success ? 0 : param->ble_security.auth_cmpl.fail_reason);
break;
}
default:
@@ -52,6 +52,7 @@ class BluedroidGattClient final : public Component {
void dump_config() override;
float get_setup_priority() const override { return setup_priority::AFTER_BLUETOOTH; }
// Wired by codegen before setup and invariant for the device lifetime.
void set_listener(BluetoothConnection *listener) { this->listener_ = listener; }
esp32_ble_tracker::ESPBTClient *tracker_client() { return &this->shim_; }
@@ -12,23 +12,15 @@
namespace esphome::bluetooth_connection {
namespace {
void address_to_bda(uint64_t address, esp_bd_addr_t &bda) {
for (uint8_t i = 0; i < 6; i++) {
bda[i] = (address >> ((5 - i) * 8)) & 0xFF;
}
}
} // namespace
conn_err_t unpair_device(uint64_t address) {
esp_bd_addr_t bda;
address_to_bda(address, bda);
ble_device_base::uint64_to_mac_msb_first(address, bda);
return esp_ble_remove_bond_device(bda);
}
conn_err_t clear_gatt_cache(uint64_t address) {
esp_bd_addr_t bda;
address_to_bda(address, bda);
ble_device_base::uint64_to_mac_msb_first(address, bda);
esp_ble_gattc_cache_clean(bda);
return CONN_OK;
}
@@ -46,6 +46,9 @@ 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,8 +1,7 @@
// Hub-platform BluetoothConnection: drives the build's GATT backend (the
// BluetoothConnection: drives the build's GATT backend (the
// ble_device_base::BLEGattConnection alias) and translates its events into
// the same API messages the esp32 class emits.
// Presents the identical method surface, so the proxy's GATT dispatch
// compiles against either class unchanged.
// the proxy's API messages. One wrapper for every platform; per-backend
// differences live behind the alias and the streamer cut-through.
#pragma once
@@ -52,11 +51,8 @@ class BluetoothConnection final {
bool is_paired() const { return this->paired_; }
void set_unpaired() { this->paired_ = false; }
conn_err_t pair() { return this->backend_->pair(); }
// Backends with deferred-disconnect state (bluedroid) answer through the
// detected forwards; the rest have nothing to track. Templates so the
// discarded branch is not odr-checked against backends without the methods.
bool disconnect_pending() const { return disconnect_pending_(this->backend_); }
void cancel_pending_disconnect() { cancel_pending_(this->backend_); }
bool disconnect_pending() const { return this->backend_->disconnect_pending(); }
void cancel_pending_disconnect() { this->backend_->cancel_pending_disconnect(); }
void set_address(uint64_t address);
uint64_t get_address() const { return this->address_; }
@@ -69,8 +65,8 @@ class BluetoothConnection final {
void set_connection_type(ConnectionType ct) {
this->connection_type_ = ct;
// The bluedroid backend branches on the type itself (prefer-params and
// the with-cache report at OPEN_EVT).
forward_connection_type_(this->backend_, ct);
// the with-cache report at OPEN_EVT); the others ignore it.
this->backend_->set_connection_type(ct);
}
// Latched at discovery completion rather than read from the backend table:
// streaming frees the table, and this must stay true for the connection's
@@ -79,14 +75,17 @@ class BluetoothConnection final {
bool has_gatt_services() const { return this->services_discovered_; }
/// Stream any pending service-discovery batch and police the disconnect
/// safety timeout. Called from the proxy's loop — hub connections have no
/// Component loop of their own (the esp32 class streams from its own
/// loop() and has the same 10 s safety net in its base class).
/// safety timeout. Called from the proxy's loop — the wrapper has no
/// Component loop of its own.
void process_pending_services() {
if (this->send_service_ >= 0) {
this->stream_pending_(this->backend_);
}
this->check_disconnect_timeout_();
// Inline state gate: this runs per loop iteration for every slot, and the
// 10 s safety net only matters while DISCONNECTING.
if (this->state_ == ClientState::DISCONNECTING) {
this->check_disconnect_timeout_();
}
}
// ---- backend event sink (called directly by the backend, main loop) ----
@@ -108,23 +107,6 @@ class BluetoothConnection final {
// response in place from its stack cache; the rest use the table streamer.
// Template so the discarded branch is not odr-checked against backends
// that lack the method.
template<typename Backend> static bool disconnect_pending_(Backend *backend) {
if constexpr (requires { backend->disconnect_pending(); }) {
return backend->disconnect_pending();
} else {
return false;
}
}
template<typename Backend> static void cancel_pending_(Backend *backend) {
if constexpr (requires { backend->cancel_pending_disconnect(); }) {
backend->cancel_pending_disconnect();
}
}
template<typename Backend> static void forward_connection_type_(Backend *backend, ConnectionType ct) {
if constexpr (requires { backend->set_connection_type(ct); }) {
backend->set_connection_type(ct);
}
}
template<typename Backend> void stream_pending_(Backend *backend) {
if constexpr (requires { backend->stream_service_batch(*this); }) {
backend->stream_service_batch(*this);
@@ -94,6 +94,11 @@ 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() {}
void set_connection_type(ble_device_base::ConnectionType ct) {}
void release_services();
protected:
+20 -13
View File
@@ -213,20 +213,31 @@ def _rp2_config_schema() -> cv.All:
return cv.All(schema, populate_connections)
async def _rp2_connections_to_code(var: cg.MockObj, config: ConfigType) -> None:
from esphome.components import rp2040_ble
# One wrapper + backend pair per slot (the esp32 arm's pattern).
for connection_conf in config[CONF_CONNECTIONS]:
async def _connections_to_code(
var: cg.MockObj, config: ConfigType, register_backend
) -> None:
"""One wrapper + backend pair per slot; register_backend supplies the
platform's backend registration (tracker client on esp32, controller
parent on rp2)."""
for connection_conf in config.get(CONF_CONNECTIONS, []):
ble_device_base.request_gatt_client()
backend = cg.new_Pvariable(connection_conf[CONF_BACKEND_ID])
await cg.register_component(backend, connection_conf)
await cg.register_parented(backend, config[rp2040_ble.CONF_RP2040_BLE_ID])
await register_backend(backend, connection_conf, config)
connection = cg.new_Pvariable(connection_conf[CONF_ID])
cg.add(connection.set_backend(backend))
cg.add(var.register_connection(connection))
async def _rp2_connections_to_code(var: cg.MockObj, config: ConfigType) -> None:
from esphome.components import rp2040_ble
async def register_backend(backend, connection_conf, config):
await cg.register_parented(backend, config[rp2040_ble.CONF_RP2040_BLE_ID])
await _connections_to_code(var, config, register_backend)
# Per-platform schema builders and connection codegen; every key of
# bluetooth_connection.HUB_MAX_CONNECTIONS needs an entry in both (pinned by
# tests/component_tests/bluetooth_proxy/).
@@ -386,17 +397,13 @@ async def _to_code_esp32(config: ConfigType) -> None:
connection_count = len(config.get(CONF_CONNECTIONS, []))
cg.add_define("BLUETOOTH_PROXY_MAX_CONNECTIONS", connection_count)
for connection_conf in config.get(CONF_CONNECTIONS, []):
ble_device_base.request_gatt_client()
backend = cg.new_Pvariable(connection_conf[CONF_BACKEND_ID])
await cg.register_component(backend, connection_conf)
async def register_backend(backend, connection_conf, _config):
# The tracker promote loop drives connect timing through the shim.
await esp32_ble_tracker.register_raw_client(
backend.tracker_client(), connection_conf
)
connection = cg.new_Pvariable(connection_conf[CONF_ID])
cg.add(connection.set_backend(backend))
cg.add(var.register_connection(connection))
await _connections_to_code(var, config, register_backend)
if config.get(CONF_CACHE_SERVICES):
add_idf_sdkconfig_option("CONFIG_BT_GATTC_CACHE_NVS_FLASH", True)
@@ -477,9 +477,8 @@ void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
void BluetoothProxy::loop() {
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
// Stream pending service-discovery batches every iteration (esp32 parity:
// its connections stream from their own per-iteration Component loop).
// send_service_for_discovery_() handles a vanished API connection itself.
// 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++) {
this->connections_[i]->process_pending_services();
}
@@ -539,12 +538,18 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR:
this->send_device_pairing(msg.address, false, GATT_NOT_CONNECTED);
break;
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
this->send_device_unpairing(msg.address, false, GATT_NOT_CONNECTED);
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: {
// Address-scoped maintenance needs no connection slot: real on esp32
// (Bluedroid bond table), the stub elsewhere keeps the old error reply.
conn_err_t ret = bluetooth_connection::unpair_device(msg.address);
this->send_device_unpairing(msg.address, ret == CONN_OK, ret);
break;
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE:
this->send_device_clear_cache(msg.address, false, GATT_NOT_CONNECTED);
}
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: {
conn_err_t ret = bluetooth_connection::clear_gatt_cache(msg.address);
this->send_device_clear_cache(msg.address, ret == CONN_OK, ret);
break;
}
}
}
@@ -5,8 +5,6 @@
#ifdef USE_BLUETOOTH_PROXY
#include <array>
#include <map>
#include <vector>
#include "esphome/components/api/api_connection.h"
#include "esphome/components/api/api_pb2.h"
@@ -27,7 +25,6 @@ namespace esphome::bluetooth_proxy {
// re-exported here so the proxy code reads unqualified.
using bluetooth_connection::CONN_OK;
using bluetooth_connection::conn_err_t;
using bluetooth_connection::DONE_SENDING_SERVICES;
using bluetooth_connection::GATT_NOT_CONNECTED;
using bluetooth_connection::INIT_SENDING_SERVICES;
@@ -0,0 +1,10 @@
# Advertisement-only proxy on esp32 by explicit choice: no GATT backend is
# compiled (USE_BLE_GATT_CLIENT unset), which pins the HAS_GATT gating and the
# address-scoped maintenance path that a connections build never exercises.
packages:
common: !include common.yaml
esp32_ble_tracker:
bluetooth_proxy:
active: false