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

This commit is contained in:
J. Nick Koston
2026-08-09 15:55:00 -05:00
8 changed files with 35 additions and 0 deletions
@@ -111,6 +111,9 @@ class GattClientListener {
// with a platform stack's own void disconnect() on one backend class).
// Nonzero means nothing to tear down and no completion will follow; an
// accepted teardown (0) always reaches a terminal on_connection_state.
// - cancel_gatt_disconnect: true cancels a scheduled teardown that has not
// started closing - the in-flight connect resumes and completes normally.
// False once the teardown owns the link (or nothing was scheduled).
// - notify_characteristic: local registration only; the CCCD write is the
// API client's responsibility (a plain write_descriptor).
// - get_service_table/release_services: backend-owned transient storage,
@@ -129,6 +132,7 @@ concept BLEGattConnectionContract = requires(T conn, GattClientListener *listene
conn.set_listener(listener);
{ conn.connect(uint64_t{}, uint8_t{}) } -> std::same_as<int>;
{ conn.gatt_disconnect() } -> std::same_as<int>;
{ conn.cancel_gatt_disconnect() } -> std::same_as<bool>;
{ conn.discover_services() } -> std::same_as<int>;
{ conn.read_characteristic(uint16_t{}) } -> std::same_as<int>;
{ conn.write_characteristic(uint16_t{}, data, uint16_t{}, true) } -> std::same_as<int>;
@@ -197,6 +197,17 @@ void BluedroidGattClient::unconditional_disconnect_() {
this->set_disconnecting_();
}
bool BluedroidGattClient::cancel_gatt_disconnect() {
// Only a scheduled teardown (want_disconnect_ latched while the open is
// still in flight) is cancellable; once closing started the terminal
// report settles the race.
if (this->state() != ClientState::CONNECTING || !this->disconnect_pending()) {
return false;
}
this->want_disconnect_ = false;
return true;
}
int BluedroidGattClient::discover_services() {
if (this->conn_id_ == UNSET_CONN_ID) {
return ble_device_base::GATT_ERR_NOT_CONNECTED;
@@ -65,6 +65,7 @@ class BluedroidGattClient final : public esp32_ble_tracker::ESPBTClient, public
// ---- ble_device_base::BLEGattConnection contract ----
int connect(uint64_t address, uint8_t addr_type);
int gatt_disconnect();
bool cancel_gatt_disconnect();
int discover_services();
int read_characteristic(uint16_t handle);
int write_characteristic(uint16_t handle, const uint8_t *data, uint16_t len, bool response);
@@ -29,6 +29,7 @@ class StubGattBackend {
void set_listener(ble_device_base::GattClientListener *listener) {}
int connect(uint64_t address, uint8_t addr_type) { return ble_device_base::GATT_ERR_NOT_CONNECTED; }
int gatt_disconnect() { return ble_device_base::GATT_ERR_NOT_CONNECTED; }
bool cancel_gatt_disconnect() { return false; }
int discover_services() { return ble_device_base::GATT_ERR_NOT_CONNECTED; }
int read_characteristic(uint16_t handle) { return ble_device_base::GATT_ERR_NOT_CONNECTED; }
int write_characteristic(uint16_t handle, const uint8_t *data, uint16_t len, bool response) {
@@ -53,6 +53,16 @@ class BluetoothConnection final : public ble_device_base::GattClientListener {
/// takes.
void initiate_connection(uint8_t address_type);
void disconnect();
/// A connect request racing a scheduled teardown: true when the backend
/// had not started closing - the in-flight open resumes and reports
/// connected. False once the teardown owns the link.
bool cancel_teardown() {
if (this->state_ == ClientState::DISCONNECTING && this->backend_->cancel_gatt_disconnect()) {
this->state_ = ClientState::CONNECTING;
return true;
}
return false;
}
bool is_paired() const { return this->paired_; }
void set_unpaired() { this->paired_ = false; }
conn_err_t pair() { return this->backend_->pair(); }
@@ -83,6 +83,9 @@ class RP2GattClient final : public Component, public Parented<rp2040_ble::RP2040
// ---- ble_device_base::BLEGattConnection contract ----
int connect(uint64_t address, uint8_t addr_type);
int gatt_disconnect();
// Teardown starts inside gatt_disconnect() on this backend; nothing is
// ever scheduled, so there is nothing to cancel.
bool cancel_gatt_disconnect() { return false; }
int discover_services();
int read_characteristic(uint16_t handle);
int write_characteristic(uint16_t handle, const uint8_t *data, uint16_t len, bool response);
@@ -240,6 +240,10 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
this->send_device_connection(msg.address, true);
this->send_connections_free();
return;
} else if (connection->state() == ClientState::DISCONNECTING && connection->cancel_teardown()) {
ESP_LOGW(TAG, "[%d] [%s] Connection request while pending disconnect, cancelling pending disconnect",
connection->get_connection_index(), connection->address_str());
return;
} else if (connection->state() != ClientState::INIT) {
// Covers CONNECTING too: a repeat request during a connect attempt is
// ignored the same way.
@@ -31,6 +31,7 @@ class MinimalConnection {
this->listener_->on_connection_state(true, 517, 0);
return 0;
}
bool cancel_gatt_disconnect() { return false; }
int gatt_disconnect() { return 0; }
int discover_services() {
this->listener_->on_service_discovery_done(0);