diff --git a/esphome/components/bluetooth_connection/__init__.py b/esphome/components/bluetooth_connection/__init__.py index fab9eebd07..d2dfd70b50 100644 --- a/esphome/components/bluetooth_connection/__init__.py +++ b/esphome/components/bluetooth_connection/__init__.py @@ -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 diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp index 36945824b8..ad4702dfd6 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp @@ -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(); diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_rp2.h b/esphome/components/bluetooth_connection/bluetooth_connection_rp2.h index 05fcf276a0..2bf88d7db9 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_rp2.h +++ b/esphome/components/bluetooth_connection/bluetooth_connection_rp2.h @@ -92,7 +92,8 @@ class RP2GattClient final : public Component, public Parented 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())