diff --git a/esphome/components/ble_device_base/ble_gatt_client.h b/esphome/components/ble_device_base/ble_gatt_client.h index 6ba078f2af..1df1de5b09 100644 --- a/esphome/components/ble_device_base/ble_gatt_client.h +++ b/esphome/components/ble_device_base/ble_gatt_client.h @@ -140,10 +140,8 @@ concept BLEGattConnectionContract = requires(T conn, GattClientListener *listene { conn.update_connection_params(uint16_t{}, uint16_t{}, uint16_t{}, uint16_t{}) } -> std::same_as; { conn.get_service_table() } -> std::same_as; { conn.release_services() } -> std::same_as; - // Deferred-disconnect visibility and the connection-type hint; backends - // without the underlying state carry inline no-ops. - { conn.disconnect_pending() } -> std::same_as; - { conn.cancel_pending_disconnect() } -> std::same_as; + // Connection-type hint for backends that tune parameters by it; others + // carry an inline no-op. { conn.set_connection_type(ConnectionType{}) } -> std::same_as; }; diff --git a/esphome/components/bluetooth_connection/__init__.py b/esphome/components/bluetooth_connection/__init__.py index ca34fcade6..7523bd9050 100644 --- a/esphome/components/bluetooth_connection/__init__.py +++ b/esphome/components/bluetooth_connection/__init__.py @@ -19,6 +19,15 @@ DOMAIN = "bluetooth_connection" def AUTO_LOAD() -> list[str]: + """ble_device_base plus the platform BLE stack the build's backend + registers with (the Bluedroid header includes the tracker's), so + consumers need not know. The platform-less arm serves manifest tooling.""" + 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"] @@ -116,9 +125,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) ) @@ -131,9 +144,11 @@ async def new_gatt_backend(config: ConfigType) -> cg.MockObj: ble_device_base.request_gatt_client() 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 diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp index e3f5a9254f..bacf66fafd 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp @@ -529,13 +529,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; } @@ -568,6 +571,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; @@ -587,6 +591,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) { @@ -604,6 +609,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; @@ -668,7 +674,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(); diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_gatt_backend.h b/esphome/components/bluetooth_connection/bluetooth_connection_gatt_backend.h index 7dd79414db..5bb8214cab 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_gatt_backend.h +++ b/esphome/components/bluetooth_connection/bluetooth_connection_gatt_backend.h @@ -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() {} }; diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_rp2.h b/esphome/components/bluetooth_connection/bluetooth_connection_rp2.h index 8aa9f80905..2bf88d7db9 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_rp2.h +++ b/esphome/components/bluetooth_connection/bluetooth_connection_rp2.h @@ -94,8 +94,6 @@ class RP2GattClient final : public Component, public Parented 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"] + # 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") + 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: diff --git a/tests/components/ble_device_base/test_gatt_client_contract.cpp b/tests/components/ble_device_base/test_gatt_client_contract.cpp index 35989512c1..17c166ebb6 100644 --- a/tests/components/ble_device_base/test_gatt_client_contract.cpp +++ b/tests/components/ble_device_base/test_gatt_client_contract.cpp @@ -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: