Merge branch 'esp32-gatt-backend' into neutral-ble-client

# Conflicts:
#	esphome/components/bluetooth_connection/__init__.py
#	esphome/components/bluetooth_connection/bluetooth_connection_rp2.h
#	tests/component_tests/bluetooth_proxy/test_platform_gates.py
This commit is contained in:
J. Nick Koston
2026-08-09 03:09:32 -05:00
5 changed files with 33 additions and 17 deletions
@@ -22,8 +22,9 @@ 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."""
registers with (the Bluedroid header includes the tracker's), 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:
@@ -130,9 +131,13 @@ def gatt_client_schema(platform: str | None = None) -> cv.Schema:
def hub_connection_schema(platform: str | None = None) -> cv.Schema:
"""Per-slot schema for the proxy's connection wrappers: the wrapper id on
top of the backend fragment. Same platform rules as gatt_client_schema()."""
return gatt_client_schema(platform).extend(
{cv.GenerateID(): cv.declare_id(HubBluetoothConnection)}
top of the backend fragment, plus the component keys (setup_priority and
friends now apply to the backend, the slot's real Component). Same
platform rules as gatt_client_schema()."""
return (
gatt_client_schema(platform)
.extend({cv.GenerateID(): cv.declare_id(HubBluetoothConnection)})
.extend(cv.COMPONENT_SCHEMA)
)
@@ -201,9 +206,11 @@ async def new_gatt_backend(
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.
await cg.register_component(backend, {})
# The backend is the slot's real Component: component keys from the
# connection entry (setup_priority, ...) apply to it. Consumers whose own
# schema carries keys that register_component would misapply to the
# backend (e.g. a polling interval) must not put them in this config.
await cg.register_component(backend, config)
await _backend_entry().register(backend, config)
return backend
@@ -530,13 +530,16 @@ void BluedroidGattClient::stream_service_batch(BluetoothConnection &conn) {
ESP_LOGE(TAG, "[%d] [%s] Service walk failed (service %d), aborting stream", conn.connection_index_,
conn.address_str_, conn.send_service_);
conn.send_service_ = DONE_SENDING_SERVICES;
conn.disconnect();
return;
}
uint16_t total_char_count = 0;
if (esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC,
service_result.start_handle, service_result.end_handle, 0,
&total_char_count) != ESP_GATT_OK) {
this->log_gattc_warning_("esp_ble_gattc_get_attr_count", ESP_GATT_ERROR);
conn.send_service_ = DONE_SENDING_SERVICES;
conn.disconnect();
return;
}
@@ -569,6 +572,7 @@ void BluedroidGattClient::stream_service_batch(BluetoothConnection &conn) {
if (char_status != ESP_GATT_OK) {
this->log_gattc_warning_("esp_ble_gattc_get_all_char", char_status);
conn.send_service_ = DONE_SENDING_SERVICES;
conn.disconnect();
return;
}
break;
@@ -588,6 +592,7 @@ void BluedroidGattClient::stream_service_batch(BluetoothConnection &conn) {
// missing CCCD in a cached database breaks notifications for good.
this->log_gattc_warning_("esp_ble_gattc_get_attr_count", desc_count_status);
conn.send_service_ = DONE_SENDING_SERVICES;
conn.disconnect();
return;
}
if (total_desc_count > 0) {
@@ -605,6 +610,7 @@ void BluedroidGattClient::stream_service_batch(BluetoothConnection &conn) {
if (desc_status != ESP_GATT_OK) {
this->log_gattc_warning_("esp_ble_gattc_get_all_descr", desc_status);
conn.send_service_ = DONE_SENDING_SERVICES;
conn.disconnect();
return;
}
break;
@@ -669,7 +675,9 @@ void BluedroidGattClient::handle_open_evt_(esp_ble_gattc_cb_param_t *param) {
// suppressed by seen_mtu_ (HA tolerates a post-connect MTU of 23 here,
// matching the previous esp32 behavior).
this->seen_mtu_ = true;
this->report_connection_state_(true, 0, 0);
// Wire parity with the old class: no MTU exchange happened yet, and HA
// has always been handed the default 23 on the cached path.
this->report_connection_state_(true, 23, 0);
// Settled: only the disconnect safety net needs the loop, and
// set_disconnecting_() re-enables it.
this->disable_loop();
@@ -92,7 +92,8 @@ 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 connection-type branching on this backend.
// No deferred-disconnect state (disconnect is one call) and no
// connection-type branching on this backend.
void set_connection_type(ble_device_base::ConnectionType ct) {}
void release_services();
-1
View File
@@ -473,7 +473,6 @@
#define RP2040_BLE_SCAN_LISTENER_COUNT 1
#define ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT 1
#define USE_BLE_GATT_CLIENT
#define USE_BLE_GATT_SERVICE_TABLE
#define ESPHOME_BLE_GATT_CLIENT_COUNT 1
#define USE_RP2040_VARIANT_RP2040
#define USE_SPI
@@ -177,10 +177,11 @@ def test_rp2_rejects_esp32_only_keys_by_name(
bluetooth_proxy.CONFIG_SCHEMA({"connections": [{}]})
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.
def test_bluetooth_connection_auto_load_covers_its_includes() -> None:
# The backend registers with its platform BLE stack (and the Bluedroid
# header includes the tracker's), so that closure lives here and
# consumers 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")
@@ -203,9 +204,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 consumer
# Hub platforms must also be in the backend registry the shared codegen
# helpers dispatch on.
assert registered <= set(bluetooth_connection.GATT_CLIENT_PLATFORMS)
assert registered <= set(bluetooth_connection._PLATFORM_BACKENDS)
# The outer walkable schema's bound must stay the loosest platform cap.
assert (
max(bluetooth_connection.HUB_MAX_CONNECTIONS.values())