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";
18using ble_device_base::FAST_CONN_TIMEOUT;
19using ble_device_base::FAST_MAX_CONN_INTERVAL;
20using ble_device_base::FAST_MIN_CONN_INTERVAL;
21using ble_device_base::MEDIUM_CONN_TIMEOUT;
22using ble_device_base::MEDIUM_MAX_CONN_INTERVAL;
23using ble_device_base::MEDIUM_MIN_CONN_INTERVAL;
24static constexpr uint32_t DISCONNECTING_TIMEOUT = 10000;
25static const esp_bt_uuid_t NOTIFY_DESC_UUID = {
26 .len = ESP_UUID_LEN_16,
29 .uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,
34 static uint8_t connection_index = 0;
40 ESPBTClient::set_state(st);
45 this->
set_state(espbt::ClientState::INIT);
48 if (this->
state() == espbt::ClientState::INIT) {
49 auto ret = esp_ble_gattc_app_register(this->
app_id);
51 ESP_LOGE(TAG,
"gattc app register failed. app_id=%d code=%d", this->
app_id,
ret);
54 this->
set_state(espbt::ClientState::IDLE);
58 else if (this->
state() == espbt::ClientState::IDLE) {
60 }
else if (this->
state() == espbt::ClientState::DISCONNECTING &&
62 ESP_LOGE(TAG,
"[%d] [%s] Timeout waiting for CLOSE_EVT after disconnect, forcing IDLE", this->
connection_index_,
81 if (this->
status_ == ESP_GATT_NO_RESOURCES) {
82 ESP_LOGE(TAG,
" Failed due to no resources. Try to reduce number of BLE clients in config.");
83 }
else if (this->
status_ != ESP_GATT_OK) {
84 ESP_LOGW(TAG,
" Failed due to error code %d", this->
status_);
88#ifdef USE_ESP32_BLE_DEVICE
94 if (this->
state() != espbt::ClientState::IDLE)
98 if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG)
101 this->
set_state(espbt::ClientState::DISCOVERED);
110 if (this->
state() == espbt::ClientState::CONNECTING || this->
state() == espbt::ClientState::CONNECTED ||
111 this->
state() == espbt::ClientState::ESTABLISHED) {
113 espbt::client_state_to_string(this->
state()));
115 }
else if (this->
state() == espbt::ClientState::DISCONNECTING) {
116 ESP_LOGW(TAG,
"[%d] [%s] Cannot connect, still waiting for CLOSE_EVT to complete disconnect",
128 this->
set_state(espbt::ClientState::CONNECTING);
133 this->
set_conn_params_(FAST_MIN_CONN_INTERVAL, FAST_MAX_CONN_INTERVAL, 0, FAST_CONN_TIMEOUT,
"fast");
134 }
else if (this->
connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
136 this->
set_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT,
"medium");
148 if (this->
state() == espbt::ClientState::IDLE || this->
state() == espbt::ClientState::DISCONNECTING) {
150 espbt::client_state_to_string(this->
state()));
153 if (this->
state() == espbt::ClientState::CONNECTING || this->
conn_id_ == UNSET_CONN_ID) {
154 ESP_LOGD(TAG,
"[%d] [%s] Disconnect before connected, disconnect scheduled", this->
connection_index_,
165 if (this->
state() == espbt::ClientState::DISCONNECTING) {
169 if (this->
conn_id_ == UNSET_CONN_ID) {
170 this->
log_error_(
"conn id unset, cannot disconnect");
185 if (this->
state() == espbt::ClientState::DISCOVERED) {
187 this->
set_state(espbt::ClientState::IDLE);
194#ifdef USE_ESP32_BLE_DEVICE
197 this->services_.clear();
199#ifndef CONFIG_BT_GATTC_CACHE_NVS_FLASH
207 esp_err_t err = esp_ble_gattc_register_for_notify(this->
gattc_if_, this->
remote_bda_, char_handle);
213 this->
log_warning_(
"Too many outstanding notify registrations to track");
252 this->
set_state(espbt::ClientState::IDLE);
269 uint16_t timeout,
const char *param_type) {
270 esp_ble_conn_update_params_t conn_params = {{0}};
271 memcpy(conn_params.bda, this->remote_bda_,
sizeof(esp_bd_addr_t));
272 conn_params.min_int = min_interval;
273 conn_params.max_int = max_interval;
274 conn_params.latency = latency;
275 conn_params.timeout = timeout;
277 esp_err_t err = esp_ble_gap_update_conn_params(&conn_params);
285 const char *param_type) {
289 esp_err_t err = esp_ble_gap_set_prefer_conn_params(this->
remote_bda_, min_interval, max_interval, latency, timeout);
296 esp_ble_gattc_cb_param_t *param) {
297 if (event == ESP_GATTC_REG_EVT && this->
app_id != param->reg.app_id)
299 if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->
gattc_if_)
303 event, esp_gattc_if);
306 case ESP_GATTC_REG_EVT: {
307 if (param->reg.status == ESP_GATT_OK) {
312 this->
log_error_(
"gattc app registration failed status", param->reg.status);
313 this->
status_ = param->reg.status;
318 case ESP_GATTC_OPEN_EVT: {
319 if (!this->
check_addr(param->open.remote_bda))
328 if (this->
state() == espbt::ClientState::IDLE) {
329 ESP_LOGD(TAG,
"[%d] [%s] ESP_GATTC_OPEN_EVT in IDLE state (status=%d), ignoring", this->
connection_index_,
334 if (this->
state() != espbt::ClientState::CONNECTING) {
338 ESP_LOGE(TAG,
"[%d] [%s] ESP_GATTC_OPEN_EVT in %s state (status=%d)", this->
connection_index_,
339 this->
address_str_, espbt::client_state_to_string(this->
state()), param->open.status);
341 if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
356 this->
set_state(espbt::ClientState::CONNECTED);
367 esp_ble_gattc_search_service(esp_gattc_if, param->open.conn_id,
nullptr);
370 case ESP_GATTC_CONNECT_EVT: {
371 if (!this->
check_addr(param->connect.remote_bda))
374 this->
conn_id_ = param->connect.conn_id;
379 auto ret = esp_ble_gattc_send_mtu_req(this->
gattc_if_, param->connect.conn_id);
385 case ESP_GATTC_DISCONNECT_EVT: {
386 if (!this->
check_addr(param->disconnect.remote_bda))
389 if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER &&
390 this->state() == espbt::ClientState::CONNECTED) {
394 param->disconnect.reason);
399 if (this->
state() == espbt::ClientState::IDLE) {
400 this->
log_event_(
"DISCONNECT_EVT after CLOSE_EVT, already IDLE");
415 case ESP_GATTC_CFG_MTU_EVT: {
416 if (this->
conn_id_ != param->cfg_mtu.conn_id)
418 if (param->cfg_mtu.status != ESP_GATT_OK) {
420 param->cfg_mtu.mtu, param->cfg_mtu.status);
425 param->cfg_mtu.status, param->cfg_mtu.mtu);
426 this->
mtu_ = param->cfg_mtu.mtu;
429 case ESP_GATTC_CLOSE_EVT: {
430 if (this->
conn_id_ != param->close.conn_id)
438 case ESP_GATTC_SEARCH_RES_EVT: {
439 if (this->
conn_id_ != param->search_res.conn_id)
447#ifdef USE_ESP32_BLE_DEVICE
449 ble_service->
uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
450 ble_service->
start_handle = param->search_res.start_handle;
451 ble_service->
end_handle = param->search_res.end_handle;
452 ble_service->
client =
this;
457 case ESP_GATTC_SEARCH_CMPL_EVT: {
458 if (this->
conn_id_ != param->search_cmpl.conn_id)
464 this->
update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT,
"medium");
465 }
else if (this->
connection_type_ != espbt::ConnectionType::V3_WITH_CACHE) {
466#ifdef USE_ESP32_BLE_DEVICE
467#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
469 char uuid_buf[espbt::UUID_STR_LEN];
470 svc->uuid.to_str(uuid_buf);
473 svc->start_handle, svc->end_handle);
482 case ESP_GATTC_READ_DESCR_EVT: {
483 if (this->
conn_id_ != param->write.conn_id)
488 case ESP_GATTC_WRITE_DESCR_EVT: {
489 if (this->
conn_id_ != param->write.conn_id)
494 case ESP_GATTC_WRITE_CHAR_EVT: {
495 if (this->
conn_id_ != param->write.conn_id)
500 case ESP_GATTC_READ_CHAR_EVT: {
501 if (this->
conn_id_ != param->read.conn_id)
506 case ESP_GATTC_NOTIFY_EVT: {
507 if (this->
conn_id_ != param->notify.conn_id)
512 case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
525 this->
log_warning_(
"REG_FOR_NOTIFY after services released, notifications not enabled");
528 esp_gattc_descr_elem_t desc_result;
530 esp_gatt_status_t descr_status = esp_ble_gattc_get_descr_by_char_handle(
531 this->
gattc_if_, this->
conn_id_, param->reg_for_notify.handle, NOTIFY_DESC_UUID, &desc_result, &count);
532 if (descr_status != ESP_GATT_OK) {
536 esp_gattc_char_elem_t char_result;
537 esp_gatt_status_t char_status =
538 esp_ble_gattc_get_all_char(this->
gattc_if_, this->
conn_id_, param->reg_for_notify.handle,
539 param->reg_for_notify.handle, &char_result, &count, 0);
540 if (char_status != ESP_GATT_OK) {
549 uint16_t notify_en = char_result.properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY ? 1 : 2;
551 esp_ble_gattc_write_char_descr(this->
gattc_if_, this->
conn_id_, desc_result.handle,
sizeof(notify_en),
552 (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
553 ESP_LOGV(TAG,
"Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties);
560 case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
575 this->
defer(std::move(f));
581 case ESP_GAP_BLE_SEC_REQ_EVT:
582 if (!this->
check_addr(param->ble_security.auth_cmpl.bd_addr))
585 esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr,
true);
588 case ESP_GAP_BLE_AUTH_CMPL_EVT:
589 if (!this->
check_addr(param->ble_security.auth_cmpl.bd_addr))
591 char addr_str[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
594 if (!param->ble_security.auth_cmpl.success) {
595 this->
log_error_(
"auth fail reason", param->ble_security.auth_cmpl.fail_reason);
599 param->ble_security.auth_cmpl.addr_type, param->ble_security.auth_cmpl.auth_mode);
617 return (
float) ((uint8_t) value[0]);
624 return (
float) ((uint8_t) value[1]);
638 return (
float)
encode_uint32(value[1], value[2], value[3], value[4]);
642 return (
float) ((int8_t) value[1]);
646 return (
float) ((int16_t) (value[1] << 8) + (int16_t) value[2]);
651 return (
float) ((int32_t) (value[1] << 16) + (int32_t) (value[2] << 8) + (int32_t) (value[3]));
656 return (
float) ((int32_t) (value[1] << 24) + (int32_t) (value[2] << 16) + (int32_t) (value[3] << 8) +
657 (int32_t) (value[4]));
660 ESP_LOGW(TAG,
"[%d] [%s] Cannot parse characteristic value of type 0x%x length %d", this->
connection_index_,
665#ifdef USE_ESP32_BLE_DEVICE
668 if (svc->uuid == uuid)
680 return svc->get_characteristic(chr);
684 return this->
get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr));
690 svc->parse_characteristics();
691 for (
auto *chr : svc->characteristics) {
692 if (chr->handle ==
handle)
701 if (chr !=
nullptr) {
703 chr->parse_descriptors();
704 for (
auto &desc : chr->descriptors) {
705 if (desc->uuid.get_uuid().uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG)
716 auto *ch = svc->get_characteristic(chr);
719 return ch->get_descriptor(descr);
723 return this->
get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr),
724 espbt::ESPBTUUID::from_uint16(descr));
730 svc->parse_characteristics();
731 for (
auto *chr : svc->characteristics) {
733 chr->parse_descriptors();
734 for (
auto *desc : chr->descriptors) {
735 if (desc->handle ==
handle)
void mark_failed()
Mark this component as failed.
void enable_loop()
Enable this component's loop.
void defer(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call with a const char* name.
void disable_loop()
Disable this component's loop.
esp_ble_addr_type_t get_address_type() const
uint64_t address_uint64() const
Return MAC as packed uint64 (byte 0 in LSB — matches esp32's address_uint64).
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)
uint8_t pending_notify_regs_
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 register_for_notify(uint16_t char_handle)
Register for notifications, holding the service release until the registration completes.
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.
const LogString * message
ESP32BLETracker * global_esp32_ble_tracker
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