diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 45c306c23f..7bcc350a31 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -67,6 +67,9 @@ bool BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es // A bridge-initiated notify registration must bypass BLEClientBase's // REG_FOR_NOTIFY handling: its automatic CCCD write would double the // node's own (the neutral contract makes the CCCD the node's job). + // The match is by handle: a legacy and a neutral node subscribing the SAME + // characteristic on one client is unsupported during the migration window + // (each migration removes the legacy half). if (event == ESP_GATTC_REG_FOR_NOTIFY_EVT && esp_gattc_if == this->gattc_if_ && this->take_pending_gatt_reg_(param->reg_for_notify.handle)) { if (this->pending_notify_regs_ > 0) @@ -87,7 +90,12 @@ bool BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es // Before the legacy fan-out so gatt nodes resolve their handles before any // legacy trigger (e.g. on_connect at SEARCH_CMPL) fires - the neutral // engine's order. - this->dispatch_gatt_event_(event, param); + if (!this->dispatch_gatt_event_(event, param)) { + // Failed discovery: the link is coming down, so the legacy fan-out must + // not observe the event as a connection (the on_connect trigger would + // fire into the teardown). + return true; + } #endif for (auto *node : this->nodes_) node->gattc_event_handler(event, esp_gattc_if, param); @@ -162,13 +170,12 @@ bool BLEClient::take_pending_gatt_reg_(uint16_t handle) { return false; } -void BLEClient::dispatch_gatt_event_(esp_gattc_cb_event_t event, esp_ble_gattc_cb_param_t *param) { +bool BLEClient::dispatch_gatt_event_(esp_gattc_cb_event_t event, esp_ble_gattc_cb_param_t *param) { if (this->gatt_nodes_.empty()) - return; + return true; switch (event) { case ESP_GATTC_SEARCH_CMPL_EVT: - this->handle_gatt_search_cmpl_(param->search_cmpl.status); - break; + return this->handle_gatt_search_cmpl_(param->search_cmpl.status); case ESP_GATTC_READ_CHAR_EVT: case ESP_GATTC_READ_DESCR_EVT: { bool ok = param->read.status == ESP_GATT_OK; @@ -199,9 +206,12 @@ void BLEClient::dispatch_gatt_event_(esp_gattc_cb_event_t event, esp_ble_gattc_c default: break; } + return true; } -void BLEClient::handle_gatt_search_cmpl_(esp_gatt_status_t status) { +// Returns whether the discovery stands: false tears the link down and the +// caller suppresses the legacy fan-out of the event. +bool BLEClient::handle_gatt_search_cmpl_(esp_gatt_status_t status) { // The base establishes without reading the search status; the neutral // contract treats a failed or empty discovery as a failed connection. uint16_t service_total = 0; @@ -217,7 +227,7 @@ void BLEClient::handle_gatt_search_cmpl_(esp_gatt_status_t status) { this->address_str(), status, service_total); this->register_gatt_failure_(); this->disconnect(); - return; + return false; } this->gatt_connected_ = true; auto view = table.view(); @@ -227,7 +237,7 @@ void BLEClient::handle_gatt_search_cmpl_(esp_gatt_status_t status) { // The node tore the link down; remaining nodes get on_disconnected // with no preceding on_connected, so leave a trace of why. ESP_LOGW(TAG, "[%s] A node aborted the connection during setup", this->address_str()); - return; + return false; } } // Promote so the legacy release condition can fire; subscriptions the @@ -236,6 +246,7 @@ void BLEClient::handle_gatt_search_cmpl_(esp_gatt_status_t status) { node->node_state = espbt::ClientState::ESTABLISHED; this->gatt_consecutive_failures_ = 0; this->gatt_hold_off_ms_ = 0; + return true; } void BLEClient::register_gatt_failure_() { diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index bd48594ea8..38a1f70dbe 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -71,8 +71,8 @@ class BLEClient final : public BLEClientBase { #ifdef USE_BLE_CLIENT_GATT_NODES int check_gatt_op_(const char *operation, esp_err_t err); void register_gatt_failure_(); - void dispatch_gatt_event_(esp_gattc_cb_event_t event, esp_ble_gattc_cb_param_t *param); - void handle_gatt_search_cmpl_(esp_gatt_status_t status); + bool dispatch_gatt_event_(esp_gattc_cb_event_t event, esp_ble_gattc_cb_param_t *param); + bool handle_gatt_search_cmpl_(esp_gatt_status_t status); bool take_pending_gatt_reg_(uint16_t handle); void on_disconnect_complete(esp_err_t reason) override; #endif