1
0
mirror of https://github.com/esphome/esphome.git synced 2025-02-26 23:08:13 +00:00

Fix bluetooth race when disconnect called while still connecting (#8297)

This commit is contained in:
J. Nick Koston 2025-02-25 20:13:30 +00:00 committed by Jesse Hills
parent 20c9c410af
commit eca0c21966
No known key found for this signature in database
GPG Key ID: BEAAE804EFD8E83A
3 changed files with 82 additions and 5 deletions

View File

@ -123,12 +123,49 @@ void BLEClientBase::connect() {
esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); } esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
void BLEClientBase::disconnect() { void BLEClientBase::disconnect() {
if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING) if (this->state_ == espbt::ClientState::IDLE) {
ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already idle.", this->connection_index_,
this->address_str_.c_str());
return; return;
ESP_LOGI(TAG, "[%d] [%s] Disconnecting.", this->connection_index_, this->address_str_.c_str()); }
if (this->state_ == espbt::ClientState::DISCONNECTING) {
ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already disconnecting.", this->connection_index_,
this->address_str_.c_str());
return;
}
if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) {
ESP_LOGW(TAG, "[%d] [%s] Disconnecting before connected, disconnect scheduled.", this->connection_index_,
this->address_str_.c_str());
this->want_disconnect_ = true;
return;
}
this->unconditional_disconnect();
}
void BLEClientBase::unconditional_disconnect() {
// Disconnect without checking the state.
ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_.c_str(),
this->conn_id_);
if (this->state_ == espbt::ClientState::DISCONNECTING) {
ESP_LOGE(TAG, "[%d] [%s] Tried to disconnect while already disconnecting.", this->connection_index_,
this->address_str_.c_str());
return;
}
if (this->conn_id_ == UNSET_CONN_ID) {
ESP_LOGE(TAG, "[%d] [%s] No connection ID set, cannot disconnect.", this->connection_index_,
this->address_str_.c_str());
return;
}
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_); auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_close error, err=%d", this->connection_index_, this->address_str_.c_str(), //
// This is a fatal error, but we can't do anything about it
// and it likely means the BLE stack is in a bad state.
//
// In the future we might consider App.reboot() here since
// the BLE stack is in an indeterminate state.
//
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_close error, err=%d", this->connection_index_, this->address_str_.c_str(),
err); err);
} }
@ -184,12 +221,38 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
this->log_event_("ESP_GATTC_OPEN_EVT"); this->log_event_("ESP_GATTC_OPEN_EVT");
this->conn_id_ = param->open.conn_id; this->conn_id_ = param->open.conn_id;
this->service_count_ = 0; this->service_count_ = 0;
if (this->state_ != espbt::ClientState::CONNECTING) {
// This should not happen but lets log it in case it does
// because it means we have a bad assumption about how the
// ESP BT stack works.
if (this->state_ == espbt::ClientState::CONNECTED) {
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while already connected, status=%d", this->connection_index_,
this->address_str_.c_str(), param->open.status);
} else if (this->state_ == espbt::ClientState::ESTABLISHED) {
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while already established, status=%d",
this->connection_index_, this->address_str_.c_str(), param->open.status);
} else if (this->state_ == espbt::ClientState::DISCONNECTING) {
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while disconnecting, status=%d", this->connection_index_,
this->address_str_.c_str(), param->open.status);
} else {
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while not in connecting state, status=%d",
this->connection_index_, this->address_str_.c_str(), param->open.status);
}
}
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
ESP_LOGW(TAG, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(), ESP_LOGW(TAG, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(),
param->open.status); param->open.status);
this->set_state(espbt::ClientState::IDLE); this->set_state(espbt::ClientState::IDLE);
break; break;
} }
if (this->want_disconnect_) {
// Disconnect was requested after connecting started,
// but before the connection was established. Now that we have
// this->conn_id_ set, we can disconnect it.
this->unconditional_disconnect();
this->conn_id_ = UNSET_CONN_ID;
break;
}
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id); auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id);
if (ret) { if (ret) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_, ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_,
@ -241,6 +304,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
this->log_event_("ESP_GATTC_CLOSE_EVT"); this->log_event_("ESP_GATTC_CLOSE_EVT");
this->release_services(); this->release_services();
this->set_state(espbt::ClientState::IDLE); this->set_state(espbt::ClientState::IDLE);
this->conn_id_ = UNSET_CONN_ID;
break; break;
} }
case ESP_GATTC_SEARCH_RES_EVT: { case ESP_GATTC_SEARCH_RES_EVT: {

View File

@ -21,6 +21,8 @@ namespace esp32_ble_client {
namespace espbt = esphome::esp32_ble_tracker; namespace espbt = esphome::esp32_ble_tracker;
static const int UNSET_CONN_ID = 0xFFFF;
class BLEClientBase : public espbt::ESPBTClient, public Component { class BLEClientBase : public espbt::ESPBTClient, public Component {
public: public:
void setup() override; void setup() override;
@ -37,6 +39,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
void connect() override; void connect() override;
esp_err_t pair(); esp_err_t pair();
void disconnect() override; void disconnect() override;
void unconditional_disconnect();
void release_services(); void release_services();
bool connected() { return this->state_ == espbt::ClientState::ESTABLISHED; } bool connected() { return this->state_ == espbt::ClientState::ESTABLISHED; }
@ -94,7 +97,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
int gattc_if_; int gattc_if_;
esp_bd_addr_t remote_bda_; esp_bd_addr_t remote_bda_;
esp_ble_addr_type_t remote_addr_type_{BLE_ADDR_TYPE_PUBLIC}; esp_ble_addr_type_t remote_addr_type_{BLE_ADDR_TYPE_PUBLIC};
uint16_t conn_id_{0xFFFF}; uint16_t conn_id_{UNSET_CONN_ID};
uint64_t address_{0}; uint64_t address_{0};
bool auto_connect_{false}; bool auto_connect_{false};
std::string address_str_{}; std::string address_str_{};

View File

@ -173,12 +173,22 @@ class ESPBTClient : public ESPBTDeviceListener {
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
virtual void connect() = 0; virtual void connect() = 0;
virtual void disconnect() = 0; virtual void disconnect() = 0;
virtual void set_state(ClientState st) { this->state_ = st; } virtual void set_state(ClientState st) {
this->state_ = st;
if (st == ClientState::IDLE) {
this->want_disconnect_ = false;
}
}
ClientState state() const { return state_; } ClientState state() const { return state_; }
int app_id; int app_id;
protected: protected:
ClientState state_{ClientState::INIT}; ClientState state_{ClientState::INIT};
// want_disconnect_ is set to true when a disconnect is requested
// while the client is connecting. This is used to disconnect the
// client as soon as we get the connection id (conn_id_) from the
// ESP_GATTC_OPEN_EVT event.
bool want_disconnect_{false};
}; };
class ESP32BLETracker : public Component, class ESP32BLETracker : public Component,