diff --git a/esphome/components/ble_device_base/ble_client_state.h b/esphome/components/ble_device_base/ble_client_state.h index 7a0a8a3e40..013853b2e2 100644 --- a/esphome/components/ble_device_base/ble_client_state.h +++ b/esphome/components/ble_device_base/ble_client_state.h @@ -15,13 +15,13 @@ namespace esphome::ble_device_base { /// ATT code range so they cannot be mistaken for spec errors. -1 is /// understood by API clients as "not connected". Shared by every GATT /// client backend. +static constexpr int GATT_ERR_NOT_CONNECTED = -1; +static constexpr int GATT_ERR_NO_MEMORY = -2; + /// Safety net shared by every GATT backend: force IDLE when the stack never /// delivers its disconnect completion. static constexpr uint32_t GATT_DISCONNECT_TIMEOUT_MS = 10000; -static constexpr int GATT_ERR_NOT_CONNECTED = -1; -static constexpr int GATT_ERR_NO_MEMORY = -2; - // Preferred connection parameters shared by every platform's GATT client so // the backends cannot drift (units: interval 1.25 ms, timeout 10 ms; latency // 0). FAST covers connection setup and service discovery; MEDIUM is the diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp index 0bcaae7717..71e5356244 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp @@ -135,13 +135,18 @@ void BluedroidGattClient::tracker_connect_() { int BluedroidGattClient::disconnect() { auto st = this->state_(); - if (st == ClientState::IDLE || st == ClientState::DISCONNECTING) { + if (st == ClientState::DISCONNECTING) { return 0; } + // Nothing was opened, so no completion event will follow: report + // not-connected and the hub frees the slot at once (rp2 convention). + if (st == ClientState::IDLE) { + return ble_device_base::GATT_ERR_NOT_CONNECTED; + } if (st == ClientState::DISCOVERED) { - // Never opened: nothing to close. + // Parked for the tracker promote loop, never opened. this->set_state_(ClientState::IDLE); - return 0; + return ble_device_base::GATT_ERR_NOT_CONNECTED; } if (st == ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) { ESP_LOGD(TAG, "[%d] Disconnect scheduled", this->connection_index_); @@ -279,10 +284,20 @@ void BluedroidGattClient::handle_search_cmpl_() { this->update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium"); uint16_t primary = 0; uint16_t secondary = 0; - esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_PRIMARY_SERVICE, 0x0001, 0xFFFF, 0, - &primary); - esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_SECONDARY_SERVICE, 0x0001, 0xFFFF, 0, - &secondary); + auto primary_status = esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_PRIMARY_SERVICE, + 0x0001, 0xFFFF, 0, &primary); + auto secondary_status = esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_SECONDARY_SERVICE, + 0x0001, 0xFFFF, 0, &secondary); + if (primary_status != ESP_GATT_OK || secondary_status != ESP_GATT_OK) { + // A failed count must not become an authoritative empty database - V3 + // clients cache the streamed result permanently. + auto status = primary_status != ESP_GATT_OK ? primary_status : secondary_status; + this->log_gattc_warning_("esp_ble_gattc_get_attr_count", status); + if (this->listener_ != nullptr) { + this->listener_->on_service_discovery_done(status); + } + return; + } this->service_total_ = primary + secondary; this->listener_->on_service_discovery_done(0); } @@ -358,6 +373,7 @@ void BluedroidGattClient::stream_service_batch(BluetoothConnection &conn) { } if (char_status != ESP_GATT_OK || cc == 0) { if (char_status != ESP_GATT_OK) { + this->log_gattc_warning_("esp_ble_gattc_get_all_char", char_status); conn.send_service_ = DONE_SENDING_SERVICES; return; } @@ -371,8 +387,15 @@ void BluedroidGattClient::stream_service_batch(BluetoothConnection &conn) { characteristic_resp.properties = char_result.properties; uint16_t total_desc_count = 0; - esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, - char_result.char_handle, &total_desc_count); + auto desc_count_status = esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, + 0, 0, char_result.char_handle, &total_desc_count); + if (desc_count_status != ESP_GATT_OK) { + // Abort rather than stream the characteristic descriptor-less: a + // 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; + return; + } if (total_desc_count > 0) { characteristic_resp.descriptors.init(total_desc_count); uint16_t desc_offset = 0; @@ -386,6 +409,7 @@ void BluedroidGattClient::stream_service_batch(BluetoothConnection &conn) { } if (desc_status != ESP_GATT_OK || dc == 0) { if (desc_status != ESP_GATT_OK) { + this->log_gattc_warning_("esp_ble_gattc_get_all_descr", desc_status); conn.send_service_ = DONE_SENDING_SERVICES; return; } diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_gatt_backend.h b/esphome/components/bluetooth_connection/bluetooth_connection_gatt_backend.h index 0a54622d8e..b164902c12 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_gatt_backend.h +++ b/esphome/components/bluetooth_connection/bluetooth_connection_gatt_backend.h @@ -15,7 +15,7 @@ #if defined(USE_RP2040_BLE) #include "bluetooth_connection_rp2.h" #define ESPHOME_BLE_GATT_CONNECTION_TYPE bluetooth_connection::RP2GattClient -#elif defined(USE_ESP32) +#elif defined(USE_ESP32_BLE) #include "bluetooth_connection_bluedroid.h" #define ESPHOME_BLE_GATT_CONNECTION_TYPE bluetooth_connection::BluedroidGattClient #elif defined(USE_BLE_GATT_CLIENT_STUB_BACKEND) diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp b/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp index db110706fa..c2cf849348 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp @@ -67,8 +67,8 @@ void BluetoothConnection::disconnect() { void BluetoothConnection::check_disconnect_timeout_() { // Safety net mirroring the esp32 base class: if the backend's disconnect // completion is lost, force the slot free instead of leaking it. - static constexpr uint32_t DISCONNECT_TIMEOUT_MS = 10000; - if (this->state_ == ClientState::DISCONNECTING && millis() - this->disconnecting_started_ > DISCONNECT_TIMEOUT_MS) { + if (this->state_ == ClientState::DISCONNECTING && + millis() - this->disconnecting_started_ > ble_device_base::GATT_DISCONNECT_TIMEOUT_MS) { ESP_LOGW(TAG, "[%d] [%s] Disconnect timeout, freeing slot", this->connection_index_, this->address_str_); this->reset_connection_(GATT_NOT_CONNECTED); }