3#if defined(USE_ESP32_BLE) && defined(USE_BLE_GATT_CLIENT)
7#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
20#include <esp_idf_version.h>
23#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 5) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0)) || \
24 ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 1)
25#define BLUEDROID_HAS_CANCEL_OPEN
32using ble_device_base::FAST_CONN_TIMEOUT;
33using ble_device_base::FAST_MAX_CONN_INTERVAL;
34using ble_device_base::FAST_MIN_CONN_INTERVAL;
35using ble_device_base::MEDIUM_CONN_TIMEOUT;
36using ble_device_base::MEDIUM_MAX_CONN_INTERVAL;
37using ble_device_base::MEDIUM_MIN_CONN_INTERVAL;
38using esp32_ble_tracker::ClientState;
39using esp32_ble_tracker::ConnectionType;
49 static uint8_t connection_index = 0;
58 auto st = this->
state();
59 if (st == ClientState::INIT) {
62 auto ret = esp_ble_gattc_app_register(this->
app_id);
64 ESP_LOGE(TAG,
"gattc app register failed: app_id=%d code=%d", this->
app_id,
ret);
66 LOG_STR(
"Registration failed; if the error was ESP_GATT_NO_RESOURCES, reduce the connection slots"));
76 }
else if (st == ClientState::CONNECTING) {
80 ESP_LOGE(TAG,
"[%d] Timeout waiting for teardown, forcing IDLE", this->
connection_index_);
90 if (this->
state() == ClientState::IDLE) {
99 auto st = this->
state();
100 if (st != ClientState::IDLE && st != ClientState::INIT) {
117 if (this->
gattc_if_ == ESP_GATT_IF_NONE) {
119 ESP_LOGW(TAG,
"[%d] Connect rejected, GATT app not registered", this->
connection_index_);
120 return ble_device_base::GATT_ERR_NOT_CONNECTED;
124 if (this->
state() != ClientState::IDLE) {
126 return ESP_GATT_BUSY;
132 this->
set_state(ClientState::DISCOVERED);
137 auto st = this->
state();
138 if (st == ClientState::CONNECTING || st == ClientState::CONNECTED || st == ClientState::ESTABLISHED) {
142 if (st == ClientState::DISCONNECTING) {
143 ESP_LOGW(TAG,
"[%d] Cannot connect, still waiting for CLOSE_EVT", this->
connection_index_);
155 this->
set_state(ClientState::CONNECTING);
159 esp_ble_gap_set_prefer_conn_params(this->
remote_bda_, FAST_MIN_CONN_INTERVAL,
160 FAST_MAX_CONN_INTERVAL, 0, FAST_CONN_TIMEOUT));
163 esp_ble_gap_set_prefer_conn_params(this->
remote_bda_, MEDIUM_MIN_CONN_INTERVAL,
164 MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT));
177 auto st = this->
state();
178 if (st == ClientState::DISCONNECTING) {
183 if (st == ClientState::IDLE) {
184 return ble_device_base::GATT_ERR_NOT_CONNECTED;
186 if (st == ClientState::DISCOVERED) {
189 return ble_device_base::GATT_ERR_NOT_CONNECTED;
208#ifdef BLUEDROID_HAS_CANCEL_OPEN
209 esp_ble_gattc_cancel_open_params_t params{};
211 memcpy(params.remote_bda, this->remote_bda_,
sizeof(esp_bd_addr_t));
213 if (this->
check_and_log_error_(
"esp_ble_gattc_cancel_open", esp_ble_gattc_cancel_open(¶ms)) == ESP_OK) {
232 ESP_LOGE(TAG,
"[%d] esp_ble_gattc_close error: %d", this->
connection_index_, err);
254 return ble_device_base::GATT_ERR_NOT_CONNECTED;
283 return ble_device_base::GATT_ERR_NOT_CONNECTED;
286 handle, ESP_GATT_AUTH_REQ_NONE));
291 return ble_device_base::GATT_ERR_NOT_CONNECTED;
295 "esp_ble_gattc_write_char",
296 esp_ble_gattc_write_char(this->
gattc_if_, this->
conn_id_, handle,
len,
const_cast<uint8_t *
>(data),
297 response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP,
298 ESP_GATT_AUTH_REQ_NONE));
303 return ble_device_base::GATT_ERR_NOT_CONNECTED;
306 "esp_ble_gattc_read_char_descr",
307 esp_ble_gattc_read_char_descr(this->
gattc_if_, this->
conn_id_, handle, ESP_GATT_AUTH_REQ_NONE));
312 return ble_device_base::GATT_ERR_NOT_CONNECTED;
315 "esp_ble_gattc_write_char_descr",
316 esp_ble_gattc_write_char_descr(this->
gattc_if_, this->
conn_id_, handle,
len,
const_cast<uint8_t *
>(data),
317 ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE));
322 return ble_device_base::GATT_ERR_NOT_CONNECTED;
335 return ble_device_base::GATT_ERR_NOT_CONNECTED;
337 return esp_ble_set_encryption(this->
remote_bda_, ESP_BLE_SEC_ENCRYPT);
349#ifndef CONFIG_BT_GATTC_CACHE_NVS_FLASH
362 return memcmp(addr, this->
remote_bda_,
sizeof(esp_bd_addr_t)) == 0;
374 this->
set_state(ClientState::DISCONNECTING);
380 uint16_t timeout,
const char *param_type) {
381 esp_ble_conn_update_params_t conn_params = {};
382 memcpy(conn_params.bda, this->remote_bda_,
sizeof(esp_bd_addr_t));
383 conn_params.min_int = min_interval;
384 conn_params.max_int = max_interval;
385 conn_params.latency = latency;
386 conn_params.timeout = timeout;
388 return this->
check_and_log_error_(
"esp_ble_gap_update_conn_params", esp_ble_gap_update_conn_params(&conn_params));
399 ESP_LOGW(TAG,
"[%d] %s failed, status=%d", this->
connection_index_, operation, code);
406 this->
update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT,
"medium");
407 if (
status != ESP_GATT_OK) {
413 uint16_t primary = 0;
414 uint16_t secondary = 0;
415 auto primary_status = esp_ble_gattc_get_attr_count(this->
gattc_if_, this->
conn_id_, ESP_GATT_DB_PRIMARY_SERVICE,
416 0x0001, 0xFFFF, 0, &primary);
417 auto secondary_status = esp_ble_gattc_get_attr_count(this->
gattc_if_, this->
conn_id_, ESP_GATT_DB_SECONDARY_SERVICE,
418 0x0001, 0xFFFF, 0, &secondary);
419 if (primary_status != ESP_GATT_OK || secondary_status != ESP_GATT_OK) {
421 auto count_status = primary_status != ESP_GATT_OK ? primary_status : secondary_status;
438#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
463 if (api_conn ==
nullptr) {
476 esp_gattc_service_elem_t service_result;
477 uint16_t svc_count = 1;
478 esp_gatt_status_t svc_status = esp_ble_gattc_get_service(this->
gattc_if_, this->
conn_id_,
nullptr, &service_result,
480 if (svc_status != ESP_GATT_OK || svc_count == 0) {
481 ESP_LOGE(TAG,
"[%d] [%s] Service walk failed (service %d), aborting stream", conn.
connection_index_,
486 uint16_t total_char_count = 0;
487 auto char_count_status =
488 esp_ble_gattc_get_attr_count(this->
gattc_if_, this->
conn_id_, ESP_GATT_DB_CHARACTERISTIC,
489 service_result.start_handle, service_result.end_handle, 0, &total_char_count);
490 if (char_count_status != ESP_GATT_OK) {
498 if (!resp.
services.empty() && current_size + estimated_size > MAX_PACKET_SIZE) {
503 auto &service_resp = resp.
services.back();
506 service_resp.handle = service_result.start_handle;
508 if (total_char_count > 0) {
509 service_resp.characteristics.init(total_char_count);
510 uint16_t char_offset = 0;
511 esp_gattc_char_elem_t char_result;
514 while (char_offset < total_char_count) {
516 auto char_status = esp_ble_gattc_get_all_char(this->
gattc_if_, this->
conn_id_, service_result.start_handle,
517 service_result.end_handle, &char_result, &cc, char_offset);
518 if (char_status != ESP_GATT_OK || cc == 0) {
525 service_resp.characteristics.emplace_back();
526 auto &characteristic_resp = service_resp.characteristics.back();
527 fill_gatt_uuid(characteristic_resp.uuid, characteristic_resp.short_uuid,
529 characteristic_resp.handle = char_result.char_handle;
530 characteristic_resp.properties = char_result.properties;
532 uint16_t total_desc_count = 0;
533 auto desc_count_status = esp_ble_gattc_get_attr_count(this->
gattc_if_, this->
conn_id_, ESP_GATT_DB_DESCRIPTOR,
534 0, 0, char_result.char_handle, &total_desc_count);
535 if (desc_count_status != ESP_GATT_OK) {
542 if (total_desc_count > 0) {
543 characteristic_resp.descriptors.init(total_desc_count);
544 uint16_t desc_offset = 0;
545 esp_gattc_descr_elem_t desc_result;
546 while (desc_offset < total_desc_count) {
548 auto desc_status = esp_ble_gattc_get_all_descr(this->
gattc_if_, this->
conn_id_, char_result.char_handle,
549 &desc_result, &dc, desc_offset);
550 if (desc_status != ESP_GATT_OK || dc == 0) {
555 characteristic_resp.descriptors.emplace_back();
556 auto &descriptor_resp = characteristic_resp.descriptors.back();
559 descriptor_resp.handle = desc_result.handle;
575 if (!api_conn->send_message(resp)) {
587 auto st = this->
state();
588 if (st == ClientState::IDLE) {
591 ESP_LOGD(TAG,
"[%d] OPEN_EVT in IDLE state (status=%d)", this->
connection_index_, param->open.status);
592 if (param->open.status == ESP_GATT_OK || param->open.status == ESP_GATT_ALREADY_OPEN) {
598 if (st != ClientState::CONNECTING) {
601 if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
609 this->
conn_id_ = param->open.conn_id;
619 this->
set_state(ClientState::ESTABLISHED);
630 esp_ble_gattc_search_service(this->
gattc_if_, param->open.conn_id,
nullptr)) == 0) {
644 if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER && this->state() == ClientState::CONNECTED) {
647 ESP_LOGD(TAG,
"[%d] DISCONNECT_EVT reason=0x%02x", this->
connection_index_, param->disconnect.reason);
649 if (this->
state() == ClientState::IDLE) {
667 esp_ble_gattc_cb_param_t *param) {
668 if (event == ESP_GATTC_REG_EVT && this->
app_id != param->reg.app_id)
670 if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->
gattc_if_)
674 case ESP_GATTC_REG_EVT: {
675 if (param->reg.status == ESP_GATT_OK) {
678 ESP_LOGE(TAG,
"[%d] gattc app registration failed, status=%d", this->
connection_index_, param->reg.status);
680 LOG_STR(
"Registration failed; if the error was ESP_GATT_NO_RESOURCES, reduce the connection slots"));
684 case ESP_GATTC_CONNECT_EVT: {
689 if (this->
state() != ClientState::CONNECTING)
691 this->
conn_id_ = param->connect.conn_id;
693 auto ret = esp_ble_gattc_send_mtu_req(this->
gattc_if_, param->connect.conn_id);
701 case ESP_GATTC_OPEN_EVT: {
704 if (param->open.status == ESP_GATT_OK || param->open.status == ESP_GATT_ALREADY_OPEN) {
705 ESP_LOGW(TAG,
"[%d] Closing link left by an abandoned open", this->
connection_index_);
714 case ESP_GATTC_CFG_MTU_EVT: {
715 if (this->
conn_id_ != param->cfg_mtu.conn_id)
717 if (param->cfg_mtu.status != ESP_GATT_OK) {
727 true, param->cfg_mtu.status == ESP_GATT_OK ? param->cfg_mtu.mtu : ble_device_base::DEFAULT_ATT_MTU, 0);
734 case ESP_GATTC_DISCONNECT_EVT: {
735 if (!this->
check_addr_(param->disconnect.remote_bda))
740 case ESP_GATTC_CLOSE_EVT: {
741 if (this->
conn_id_ != param->close.conn_id)
750 case ESP_GATTC_SEARCH_CMPL_EVT: {
751 if (this->
conn_id_ != param->search_cmpl.conn_id)
754 if (this->
state() == ClientState::DISCONNECTING) {
762 this->
set_state(ClientState::ESTABLISHED);
766 case ESP_GATTC_READ_CHAR_EVT:
767 case ESP_GATTC_READ_DESCR_EVT: {
768 if (this->
conn_id_ != param->read.conn_id)
770 bool ok = param->read.status == ESP_GATT_OK;
772 ok ? param->read.value_len : 0, ok ? 0 : param->read.status);
775 case ESP_GATTC_WRITE_CHAR_EVT:
776 case ESP_GATTC_WRITE_DESCR_EVT: {
777 if (this->
conn_id_ != param->write.conn_id)
780 param->write.status == ESP_GATT_OK ? 0 : param->write.status);
783 case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
785 param->reg_for_notify.status == ESP_GATT_OK ? 0 : param->reg_for_notify.status);
788 case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
790 param->unreg_for_notify.handle,
false,
791 param->unreg_for_notify.status == ESP_GATT_OK ? 0 : param->unreg_for_notify.status);
794 case ESP_GATTC_NOTIFY_EVT: {
795 if (this->
conn_id_ != param->notify.conn_id)
797 ESP_LOGV(TAG,
"[%d] NOTIFY_EVT handle=0x%2X", this->
connection_index_, param->notify.handle);
801#ifdef BLUEDROID_HAS_CANCEL_OPEN
802 case ESP_GATTC_CANCEL_OPEN_EVT: {
803 if (!this->
check_addr_(param->cancel_open.remote_bda))
805 if (param->cancel_open.status != ESP_GATT_OK) {
827 case ESP_GAP_BLE_SEC_REQ_EVT: {
828 if (!this->
check_addr_(param->ble_security.auth_cmpl.bd_addr))
833 esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr,
true));
839 case ESP_GAP_BLE_AUTH_CMPL_EVT: {
840 if (!this->
check_addr_(param->ble_security.auth_cmpl.bd_addr))
843 param->ble_security.auth_cmpl.success ? 0 : param->ble_security.auth_cmpl.fail_reason);
void mark_failed()
Mark this component as failed.
void enable_loop()
Enable this component's loop.
void disable_loop()
Disable this component's loop.
uint32_t calculate_size() const
std::vector< BluetoothGATTService > services
static ESPBTUUID from_uuid(esp_bt_uuid_t uuid)
Source compatibility with the historical esp32_ble API (esp32 builds only).
virtual void on_notify_state(uint16_t handle, bool enabled, int error)
virtual void on_notify_data(uint16_t handle, const uint8_t *data, uint16_t len)
virtual void on_write_result(uint16_t handle, int error)
virtual void on_service_discovery_done(int error)
virtual void on_pairing_result(int status)
virtual void on_connection_state(bool connected, uint16_t mtu, int error)
virtual void on_read_result(uint16_t handle, const uint8_t *data, uint16_t len, int error)
void cancel_pending_open_()
bool check_addr_(const esp_bd_addr_t &addr) const
void disconnect() override
uint32_t disconnecting_started_
int handle_search_cmpl_(esp_gatt_status_t status)
esp_err_t update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout, const char *param_type)
int read_descriptor(uint16_t handle)
SearchState search_state_
esp_bd_addr_t remote_bda_
void ble_before_disabled_event_handler() override
ble_device_base::GattClientListener * listener_
void handle_open_evt_(esp_ble_gattc_cb_param_t *param)
int write_descriptor(uint16_t handle, const uint8_t *data, uint16_t len)
uint8_t remote_addr_type_
int write_characteristic(uint16_t handle, const uint8_t *data, uint16_t len, bool response)
void handle_disconnect_evt_(esp_ble_gattc_cb_param_t *param)
bool cancel_gatt_disconnect()
static constexpr uint16_t UNSET_CONN_ID
esp32_ble_tracker::ConnectionType connection_type_
uint8_t connection_index_
void unconditional_disconnect_()
int update_connection_params(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout)
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override
void dump_config() override
void deliver_pending_search_()
void stream_service_batch(BluetoothConnection &conn)
In-place service streamer (the proxy wrapper detects and prefers it): builds one api response batch d...
int read_characteristic(uint16_t handle)
int check_and_log_error_(const char *operation, esp_err_t err)
int notify_characteristic(uint16_t handle, bool enable)
void set_disconnecting_()
void log_gattc_warning_(const char *operation, int code)
void abort_service_stream(conn_err_t err)
Streamer abort: latch the GATT cause, park the cursor, tear down.
bool batch_stalled_
Set while a refused batch is retrying, so only the first one warns.
void send_services_done_()
Send services-done and settle the cursor: DONE when it lands (or no subscriber), SERVICES_DONE_PENDIN...
void park_service_stream_()
Park the stream without services-done and free any held table: an interrupted stream must never be de...
bluetooth_proxy::BluetoothProxy * proxy_
uint8_t connection_index_
void note_batch_stalled_()
Warn on the stall's leading edge only.
char address_str_[MAC_ADDRESS_PRETTY_BUFFER_SIZE]
api::APIConnection * get_api_connection()
bool client_supports_efficient_uuids() const
Whether the subscribed API client understands 16/32-bit UUID fields.
ClientState state() const
virtual void set_state(ClientState st)
Set the client state with IDLE handling (clears want_disconnect_).
bool disconnect_pending() const
void uint64_to_mac_msb_first(uint64_t address, uint8_t out[6])
Unpack a uint64 BLE address into printable (MSB-first) byte order — the order bd_addr_t / esp_bd_addr...
size_t estimate_service_size(uint16_t char_count, bool use_efficient_uuids)
Estimate the wire size of a service (service overhead + its characteristics, assuming 128-bit UUIDs a...
ESPHOME_LOG_TAG(TAG, "bluetooth_connection")
BatchClose close_service_batch(api::BluetoothGATTGetServicesResponse &resp, size_t ¤t_size, int16_t &send_service, uint8_t connection_index, const char *address_str)
Close out the service just packed into resp (account its actual wire size, advance the cursor) and de...
void fill_gatt_uuid(std::array< uint64_t, 2 > &uuid_128, uint32_t &short_uuid, const ble_device_base::ESPBTUUID &uuid, bool use_efficient_uuids)
Fill the UUID in the appropriate wire format based on client support and UUID type (128-bit array for...
uint32_t IRAM_ATTR HOT millis()
spi_device_handle_t handle