Merge branch 'esp32-gatt-backend' into esp32-proxy-flip

This commit is contained in:
J. Nick Koston
2026-08-08 23:21:58 -05:00
4 changed files with 39 additions and 15 deletions
@@ -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
@@ -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;
}
@@ -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)
@@ -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);
}