8#include <esp_gap_ble_api.h>
9#include <esp_gatt_defs.h>
10#include <esp_gattc_api.h>
14static const char *
const TAG =
"esp32_ble_client";
19static constexpr uint16_t MEDIUM_MIN_CONN_INTERVAL = 0x07;
20static constexpr uint16_t MEDIUM_MAX_CONN_INTERVAL = 0x09;
24static constexpr uint16_t MEDIUM_CONN_TIMEOUT = 800;
27static constexpr uint16_t FAST_MIN_CONN_INTERVAL = 0x06;
28static constexpr uint16_t FAST_MAX_CONN_INTERVAL = 0x06;
29static constexpr uint16_t FAST_CONN_TIMEOUT = 1000;
30static constexpr uint32_t DISCONNECTING_TIMEOUT = 10000;
31static const esp_bt_uuid_t NOTIFY_DESC_UUID = {
32 .len = ESP_UUID_LEN_16,
35 .uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,
40 static uint8_t connection_index = 0;
46 ESPBTClient::set_state(st);
51 this->
set_state(espbt::ClientState::INIT);
54 if (this->
state() == espbt::ClientState::INIT) {
55 auto ret = esp_ble_gattc_app_register(this->
app_id);
57 ESP_LOGE(TAG,
"gattc app register failed. app_id=%d code=%d", this->
app_id, ret);
60 this->
set_state(espbt::ClientState::IDLE);
64 else if (this->
state() == espbt::ClientState::IDLE) {
66 }
else if (this->
state() == espbt::ClientState::DISCONNECTING &&
68 ESP_LOGE(TAG,
"[%d] [%s] Timeout waiting for CLOSE_EVT after disconnect, forcing IDLE", this->
connection_index_,
87 if (this->
status_ == ESP_GATT_NO_RESOURCES) {
88 ESP_LOGE(TAG,
" Failed due to no resources. Try to reduce number of BLE clients in config.");
89 }
else if (this->
status_ != ESP_GATT_OK) {
90 ESP_LOGW(TAG,
" Failed due to error code %d", this->
status_);
94#ifdef USE_ESP32_BLE_DEVICE
100 if (this->
state() != espbt::ClientState::IDLE)
104 if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG)
107 this->
set_state(espbt::ClientState::DISCOVERED);
116 if (this->
state() == espbt::ClientState::CONNECTING || this->
state() == espbt::ClientState::CONNECTED ||
117 this->
state() == espbt::ClientState::ESTABLISHED) {
121 }
else if (this->
state() == espbt::ClientState::DISCONNECTING) {
122 ESP_LOGW(TAG,
"[%d] [%s] Cannot connect, still waiting for CLOSE_EVT to complete disconnect",
131 this->
set_state(espbt::ClientState::CONNECTING);
136 this->
set_conn_params_(FAST_MIN_CONN_INTERVAL, FAST_MAX_CONN_INTERVAL, 0, FAST_CONN_TIMEOUT,
"fast");
137 }
else if (this->
connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
139 this->
set_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT,
"medium");
151 if (this->
state() == espbt::ClientState::IDLE || this->
state() == espbt::ClientState::DISCONNECTING) {
156 if (this->
state() == espbt::ClientState::CONNECTING || this->
conn_id_ == UNSET_CONN_ID) {
157 ESP_LOGD(TAG,
"[%d] [%s] Disconnect before connected, disconnect scheduled", this->
connection_index_,
168 if (this->
state() == espbt::ClientState::DISCONNECTING) {
172 if (this->
conn_id_ == UNSET_CONN_ID) {
173 this->
log_error_(
"conn id unset, cannot disconnect");
188 if (this->
state() == espbt::ClientState::DISCOVERED) {
190 this->
set_state(espbt::ClientState::IDLE);
197#ifdef USE_ESP32_BLE_DEVICE
200 this->services_.clear();
202#ifndef CONFIG_BT_GATTC_CACHE_NVS_FLASH
239 this->
set_state(espbt::ClientState::IDLE);
256 uint16_t timeout,
const char *param_type) {
257 esp_ble_conn_update_params_t conn_params = {{0}};
258 memcpy(conn_params.bda, this->remote_bda_,
sizeof(esp_bd_addr_t));
259 conn_params.min_int = min_interval;
260 conn_params.max_int = max_interval;
261 conn_params.latency = latency;
262 conn_params.timeout = timeout;
264 esp_err_t err = esp_ble_gap_update_conn_params(&conn_params);
272 const char *param_type) {
276 esp_err_t err = esp_ble_gap_set_prefer_conn_params(this->
remote_bda_, min_interval, max_interval, latency, timeout);
283 esp_ble_gattc_cb_param_t *param) {
284 if (event == ESP_GATTC_REG_EVT && this->
app_id != param->reg.app_id)
286 if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->
gattc_if_)
290 event, esp_gattc_if);
293 case ESP_GATTC_REG_EVT: {
294 if (param->reg.status == ESP_GATT_OK) {
299 this->
log_error_(
"gattc app registration failed status", param->reg.status);
300 this->
status_ = param->reg.status;
305 case ESP_GATTC_OPEN_EVT: {
306 if (!this->
check_addr(param->open.remote_bda))
315 if (this->
state() == espbt::ClientState::IDLE) {
316 ESP_LOGD(TAG,
"[%d] [%s] ESP_GATTC_OPEN_EVT in IDLE state (status=%d), ignoring", this->
connection_index_,
321 if (this->
state() != espbt::ClientState::CONNECTING) {
325 ESP_LOGE(TAG,
"[%d] [%s] ESP_GATTC_OPEN_EVT in %s state (status=%d)", this->
connection_index_,
328 if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
343 this->
set_state(espbt::ClientState::CONNECTED);
354 esp_ble_gattc_search_service(esp_gattc_if, param->open.conn_id,
nullptr);
357 case ESP_GATTC_CONNECT_EVT: {
358 if (!this->
check_addr(param->connect.remote_bda))
361 this->
conn_id_ = param->connect.conn_id;
366 auto ret = esp_ble_gattc_send_mtu_req(this->
gattc_if_, param->connect.conn_id);
372 case ESP_GATTC_DISCONNECT_EVT: {
373 if (!this->
check_addr(param->disconnect.remote_bda))
376 if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER &&
377 this->state() == espbt::ClientState::CONNECTED) {
381 param->disconnect.reason);
386 if (this->
state() == espbt::ClientState::IDLE) {
387 this->
log_event_(
"DISCONNECT_EVT after CLOSE_EVT, already IDLE");
402 case ESP_GATTC_CFG_MTU_EVT: {
403 if (this->
conn_id_ != param->cfg_mtu.conn_id)
405 if (param->cfg_mtu.status != ESP_GATT_OK) {
407 param->cfg_mtu.mtu, param->cfg_mtu.status);
412 param->cfg_mtu.status, param->cfg_mtu.mtu);
413 this->
mtu_ = param->cfg_mtu.mtu;
416 case ESP_GATTC_CLOSE_EVT: {
417 if (this->
conn_id_ != param->close.conn_id)
425 case ESP_GATTC_SEARCH_RES_EVT: {
426 if (this->
conn_id_ != param->search_res.conn_id)
434#ifdef USE_ESP32_BLE_DEVICE
436 ble_service->
uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
437 ble_service->
start_handle = param->search_res.start_handle;
438 ble_service->
end_handle = param->search_res.end_handle;
439 ble_service->
client =
this;
444 case ESP_GATTC_SEARCH_CMPL_EVT: {
445 if (this->
conn_id_ != param->search_cmpl.conn_id)
451 this->
update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT,
"medium");
452 }
else if (this->
connection_type_ != espbt::ConnectionType::V3_WITH_CACHE) {
453#ifdef USE_ESP32_BLE_DEVICE
454#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
456 char uuid_buf[espbt::UUID_STR_LEN];
457 svc->uuid.to_str(uuid_buf);
460 svc->start_handle, svc->end_handle);
469 case ESP_GATTC_READ_DESCR_EVT: {
470 if (this->
conn_id_ != param->write.conn_id)
475 case ESP_GATTC_WRITE_DESCR_EVT: {
476 if (this->
conn_id_ != param->write.conn_id)
481 case ESP_GATTC_WRITE_CHAR_EVT: {
482 if (this->
conn_id_ != param->write.conn_id)
487 case ESP_GATTC_READ_CHAR_EVT: {
488 if (this->
conn_id_ != param->read.conn_id)
493 case ESP_GATTC_NOTIFY_EVT: {
494 if (this->
conn_id_ != param->notify.conn_id)
499 case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
507 esp_gattc_descr_elem_t desc_result;
509 esp_gatt_status_t descr_status = esp_ble_gattc_get_descr_by_char_handle(
510 this->
gattc_if_, this->
conn_id_, param->reg_for_notify.handle, NOTIFY_DESC_UUID, &desc_result, &count);
511 if (descr_status != ESP_GATT_OK) {
515 esp_gattc_char_elem_t char_result;
516 esp_gatt_status_t char_status =
517 esp_ble_gattc_get_all_char(this->
gattc_if_, this->
conn_id_, param->reg_for_notify.handle,
518 param->reg_for_notify.handle, &char_result, &count, 0);
519 if (char_status != ESP_GATT_OK) {
528 uint16_t notify_en = char_result.properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY ? 1 : 2;
530 esp_ble_gattc_write_char_descr(this->
gattc_if_, this->
conn_id_, desc_result.handle,
sizeof(notify_en),
531 (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
532 ESP_LOGV(TAG,
"Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties);
539 case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
554 this->
defer(std::move(
f));
560 case ESP_GAP_BLE_SEC_REQ_EVT:
561 if (!this->
check_addr(param->ble_security.auth_cmpl.bd_addr))
564 esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr,
true);
567 case ESP_GAP_BLE_AUTH_CMPL_EVT:
568 if (!this->
check_addr(param->ble_security.auth_cmpl.bd_addr))
570 char addr_str[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
573 if (!param->ble_security.auth_cmpl.success) {
574 this->
log_error_(
"auth fail reason", param->ble_security.auth_cmpl.fail_reason);
578 param->ble_security.auth_cmpl.addr_type, param->ble_security.auth_cmpl.auth_mode);
596 return (
float) ((uint8_t) value[0]);
603 return (
float) ((uint8_t) value[1]);
617 return (
float)
encode_uint32(value[1], value[2], value[3], value[4]);
621 return (
float) ((int8_t) value[1]);
625 return (
float) ((int16_t) (value[1] << 8) + (int16_t) value[2]);
630 return (
float) ((int32_t) (value[1] << 16) + (int32_t) (value[2] << 8) + (int32_t) (value[3]));
635 return (
float) ((int32_t) (value[1] << 24) + (int32_t) (value[2] << 16) + (int32_t) (value[3] << 8) +
636 (int32_t) (value[4]));
639 ESP_LOGW(TAG,
"[%d] [%s] Cannot parse characteristic value of type 0x%x length %d", this->
connection_index_,
644#ifdef USE_ESP32_BLE_DEVICE
647 if (svc->uuid == uuid)
659 return svc->get_characteristic(chr);
663 return this->
get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr));
669 svc->parse_characteristics();
670 for (
auto *chr : svc->characteristics) {
671 if (chr->handle ==
handle)
680 if (chr !=
nullptr) {
682 chr->parse_descriptors();
683 for (
auto &desc : chr->descriptors) {
684 if (desc->uuid.get_uuid().uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG)
695 auto *ch = svc->get_characteristic(chr);
698 return ch->get_descriptor(descr);
702 return this->
get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr),
703 espbt::ESPBTUUID::from_uint16(descr));
709 svc->parse_characteristics();
710 for (
auto *chr : svc->characteristics) {
712 chr->parse_descriptors();
713 for (
auto *desc : chr->descriptors) {
714 if (desc->handle ==
handle)
void mark_failed()
Mark this component as failed.
ESPDEPRECATED("Use const char* overload instead. Removed in 2026.7.0", "2026.1.0") void defer(const std voi defer)(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call.
void enable_loop()
Enable this component's loop.
void disable_loop()
Disable this component's loop.
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.", "2026.2.0") void set_retry(const std uint32_t uint8_t std::function< RetryResult(uint8_t)> && f
espbt::ConnectionType connection_type_
void log_event_(const char *name)
esp_gatt_status_t status_
std::vector< BLEService * > services_
void log_gattc_lifecycle_event_(const char *name)
char address_str_[MAC_ADDRESS_PRETTY_BUFFER_SIZE]
uint32_t disconnecting_started_
void handle_connection_result_(esp_err_t ret)
esp_bd_addr_t remote_bda_
void log_gattc_warning_(const char *operation, esp_gatt_status_t status)
void log_connection_params_(const char *param_type)
void log_error_(const char *message)
const char * address_str() const
BLEDescriptor * get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr)
void unconditional_disconnect()
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override
void set_state(espbt::ClientState st) override
esp_err_t update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout, const char *param_type)
float get_setup_priority() const override
BLECharacteristic * get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr)
void disconnect() override
bool check_addr(esp_bd_addr_t &addr)
virtual void set_address(uint64_t address)
void set_idle_()
Transition to IDLE and reset conn_id — call when the connection is fully dead.
void run_later(std::function< void()> &&f)
virtual void on_disconnect_complete(esp_err_t reason)
Hook called once a connection has been fully torn down (after release_services() and set_idle_()),...
uint8_t connection_index_
BLEService * get_service(espbt::ESPBTUUID uuid)
void set_disconnecting_()
Transition to DISCONNECTING and start the safety timeout.
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override
esp_ble_addr_type_t remote_addr_type_
void log_gattc_data_event_(const char *name)
bool parse_device(const espbt::ESPBTDevice &device) override
float parse_char_value(uint8_t *value, uint16_t length)
BLEDescriptor * get_config_descriptor(uint16_t handle)
void set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout, const char *param_type)
void dump_config() override
void log_warning_(const char *message)
void print_bt_device_info(const ESPBTDevice &device)
ClientState state() const
void set_state_internal_(ClientState st)
Set state without IDLE handling - use for direct state transitions.
uint64_t address_uint64() const
esp_ble_addr_type_t get_address_type() const
const LogString * message
ESP32BLETracker * global_esp32_ble_tracker
const char * client_state_to_string(ClientState state)
constexpr float AFTER_BLUETOOTH
constexpr uint32_t encode_uint24(uint8_t byte1, uint8_t byte2, uint8_t byte3)
Encode a 24-bit value given three bytes in most to least significant byte order.
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
uint32_t IRAM_ATTR HOT millis()
char * format_mac_addr_upper(const uint8_t *mac, char *output)
Format MAC address as XX:XX:XX:XX:XX:XX (uppercase, colon separators)
spi_device_handle_t handle