ESPHome 2026.2.1
Loading...
Searching...
No Matches
ble_client_base.cpp
Go to the documentation of this file.
1#include "ble_client_base.h"
2
4#include "esphome/core/log.h"
5
6#ifdef USE_ESP32
7
8#include <esp_gap_ble_api.h>
9#include <esp_gatt_defs.h>
10#include <esp_gattc_api.h>
11
13
14static const char *const TAG = "esp32_ble_client";
15
16// Intermediate connection parameters for standard operation
17// ESP-IDF defaults (12.5-15ms) are too slow for stable connections through WiFi-based BLE proxies,
18// causing disconnections. These medium parameters balance responsiveness with bandwidth usage.
19static const uint16_t MEDIUM_MIN_CONN_INTERVAL = 0x07; // 7 * 1.25ms = 8.75ms
20static const uint16_t MEDIUM_MAX_CONN_INTERVAL = 0x09; // 9 * 1.25ms = 11.25ms
21// The timeout value was increased from 6s to 8s to address stability issues observed
22// in certain BLE devices when operating through WiFi-based BLE proxies. The longer
23// timeout reduces the likelihood of disconnections during periods of high latency.
24static const uint16_t MEDIUM_CONN_TIMEOUT = 800; // 800 * 10ms = 8s
25
26// Fastest connection parameters for devices with short discovery timeouts
27static const uint16_t FAST_MIN_CONN_INTERVAL = 0x06; // 6 * 1.25ms = 7.5ms (BLE minimum)
28static const uint16_t FAST_MAX_CONN_INTERVAL = 0x06; // 6 * 1.25ms = 7.5ms
29static const uint16_t FAST_CONN_TIMEOUT = 1000; // 1000 * 10ms = 10s
30static const esp_bt_uuid_t NOTIFY_DESC_UUID = {
31 .len = ESP_UUID_LEN_16,
32 .uuid =
33 {
34 .uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,
35 },
36};
37
39 static uint8_t connection_index = 0;
40 this->connection_index_ = connection_index++;
41}
42
44 ESP_LOGV(TAG, "[%d] [%s] Set state %d", this->connection_index_, this->address_str_, (int) st);
45 ESPBTClient::set_state(st);
46}
47
49 if (!esp32_ble::global_ble->is_active()) {
50 this->set_state(espbt::ClientState::INIT);
51 return;
52 }
53 if (this->state() == espbt::ClientState::INIT) {
54 auto ret = esp_ble_gattc_app_register(this->app_id);
55 if (ret) {
56 ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
57 this->mark_failed();
58 }
59 this->set_state(espbt::ClientState::IDLE);
60 }
61 // If idle, we can disable the loop as connect()
62 // will enable it again when a connection is needed.
63 else if (this->state() == espbt::ClientState::IDLE) {
64 this->disable_loop();
65 }
66}
67
69
71 ESP_LOGCONFIG(TAG,
72 " Address: %s\n"
73 " Auto-Connect: %s\n"
74 " State: %s",
75 this->address_str(), TRUEFALSE(this->auto_connect_), espbt::client_state_to_string(this->state()));
76 if (this->status_ == ESP_GATT_NO_RESOURCES) {
77 ESP_LOGE(TAG, " Failed due to no resources. Try to reduce number of BLE clients in config.");
78 } else if (this->status_ != ESP_GATT_OK) {
79 ESP_LOGW(TAG, " Failed due to error code %d", this->status_);
80 }
81}
82
83#ifdef USE_ESP32_BLE_DEVICE
85 if (!this->auto_connect_)
86 return false;
87 if (this->address_ == 0 || device.address_uint64() != this->address_)
88 return false;
89 if (this->state() != espbt::ClientState::IDLE)
90 return false;
91
92 this->log_event_("Found device");
93 if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG)
95
96 this->set_state(espbt::ClientState::DISCOVERED);
97 this->set_address(device.address_uint64());
98 this->remote_addr_type_ = device.get_address_type();
99 return true;
100}
101#endif
102
104 // Prevent duplicate connection attempts
105 if (this->state() == espbt::ClientState::CONNECTING || this->state() == espbt::ClientState::CONNECTED ||
106 this->state() == espbt::ClientState::ESTABLISHED) {
107 ESP_LOGW(TAG, "[%d] [%s] Connection already in progress, state=%s", this->connection_index_, this->address_str_,
109 return;
110 }
111 ESP_LOGI(TAG, "[%d] [%s] 0x%02x Connecting", this->connection_index_, this->address_str_, this->remote_addr_type_);
112 this->paired_ = false;
113 // Enable loop for state processing
114 this->enable_loop();
115 // Immediately transition to CONNECTING to prevent duplicate connection attempts
116 this->set_state(espbt::ClientState::CONNECTING);
117
118 // Determine connection parameters based on connection type
119 if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
120 // V3 without cache needs fast params for service discovery
121 this->set_conn_params_(FAST_MIN_CONN_INTERVAL, FAST_MAX_CONN_INTERVAL, 0, FAST_CONN_TIMEOUT, "fast");
122 } else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
123 // V3 with cache can use medium params
124 this->set_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium");
125 }
126 // For V1/Legacy, don't set params - use ESP-IDF defaults
127
128 // Open the connection
129 auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
130 this->handle_connection_result_(ret);
131}
132
133esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
134
136 if (this->state() == espbt::ClientState::IDLE || this->state() == espbt::ClientState::DISCONNECTING) {
137 ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already %s", this->connection_index_, this->address_str_,
139 return;
140 }
141 if (this->state() == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) {
142 ESP_LOGD(TAG, "[%d] [%s] Disconnect before connected, disconnect scheduled", this->connection_index_,
143 this->address_str_);
144 this->want_disconnect_ = true;
145 return;
146 }
148}
149
151 // Disconnect without checking the state.
152 ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_, this->conn_id_);
153 if (this->state() == espbt::ClientState::DISCONNECTING) {
154 this->log_error_("Already disconnecting");
155 return;
156 }
157 if (this->conn_id_ == UNSET_CONN_ID) {
158 this->log_error_("conn id unset, cannot disconnect");
159 return;
160 }
161 auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
162 if (err != ESP_OK) {
163 //
164 // This is a fatal error, but we can't do anything about it
165 // and it likely means the BLE stack is in a bad state.
166 //
167 // In the future we might consider App.reboot() here since
168 // the BLE stack is in an indeterminate state.
169 //
170 this->log_gattc_warning_("esp_ble_gattc_close", err);
171 }
172
173 if (this->state() == espbt::ClientState::DISCOVERED) {
174 this->set_address(0);
175 this->set_state(espbt::ClientState::IDLE);
176 } else {
177 this->set_state(espbt::ClientState::DISCONNECTING);
178 }
179}
180
182#ifdef USE_ESP32_BLE_DEVICE
183 for (auto &svc : this->services_)
184 delete svc; // NOLINT(cppcoreguidelines-owning-memory)
185 this->services_.clear();
186#endif
187#ifndef CONFIG_BT_GATTC_CACHE_NVS_FLASH
188 esp_ble_gattc_cache_clean(this->remote_bda_);
189#endif
190}
191
192void BLEClientBase::log_event_(const char *name) {
193 ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_, name);
194}
195
197 ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_%s_EVT", this->connection_index_, this->address_str_, name);
198}
199
201 // Data transfer events are logged at VERBOSE level because logging to UART creates
202 // delays that cause timing issues during time-sensitive BLE operations. This is
203 // especially problematic during pairing or firmware updates which require rapid
204 // writes to many characteristics - the log spam can cause these operations to fail.
205 ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_%s_EVT", this->connection_index_, this->address_str_, name);
206}
207
208void BLEClientBase::log_gattc_warning_(const char *operation, esp_gatt_status_t status) {
209 ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_, operation, status);
210}
211
212void BLEClientBase::log_gattc_warning_(const char *operation, esp_err_t err) {
213 ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_, operation, err);
214}
215
216void BLEClientBase::log_connection_params_(const char *param_type) {
217 ESP_LOGD(TAG, "[%d] [%s] %s conn params", this->connection_index_, this->address_str_, param_type);
218}
219
221 if (ret) {
222 this->log_gattc_warning_("esp_ble_gattc_open", ret);
223 this->set_state(espbt::ClientState::IDLE);
224 }
225}
226
228 ESP_LOGE(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_, message);
229}
230
231void BLEClientBase::log_error_(const char *message, int code) {
232 ESP_LOGE(TAG, "[%d] [%s] %s=%d", this->connection_index_, this->address_str_, message, code);
233}
234
236 ESP_LOGW(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_, message);
237}
238
239void BLEClientBase::update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency,
240 uint16_t timeout, const char *param_type) {
241 esp_ble_conn_update_params_t conn_params = {{0}};
242 memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t));
243 conn_params.min_int = min_interval;
244 conn_params.max_int = max_interval;
245 conn_params.latency = latency;
246 conn_params.timeout = timeout;
247 this->log_connection_params_(param_type);
248 esp_err_t err = esp_ble_gap_update_conn_params(&conn_params);
249 if (err != ESP_OK) {
250 this->log_gattc_warning_("esp_ble_gap_update_conn_params", err);
251 }
252}
253
254void BLEClientBase::set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
255 const char *param_type) {
256 // Set preferred connection parameters before connecting
257 // These will be used when establishing the connection
258 this->log_connection_params_(param_type);
259 esp_err_t err = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, min_interval, max_interval, latency, timeout);
260 if (err != ESP_OK) {
261 this->log_gattc_warning_("esp_ble_gap_set_prefer_conn_params", err);
262 }
263}
264
265bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
266 esp_ble_gattc_cb_param_t *param) {
267 if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
268 return false;
269 if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_)
270 return false;
271
272 ESP_LOGV(TAG, "[%d] [%s] gattc_event_handler: event=%d gattc_if=%d", this->connection_index_, this->address_str_,
273 event, esp_gattc_if);
274
275 switch (event) {
276 case ESP_GATTC_REG_EVT: {
277 if (param->reg.status == ESP_GATT_OK) {
278 ESP_LOGV(TAG, "[%d] [%s] gattc registered app id %d", this->connection_index_, this->address_str_,
279 this->app_id);
280 this->gattc_if_ = esp_gattc_if;
281 } else {
282 this->log_error_("gattc app registration failed status", param->reg.status);
283 this->status_ = param->reg.status;
284 this->mark_failed();
285 }
286 break;
287 }
288 case ESP_GATTC_OPEN_EVT: {
289 if (!this->check_addr(param->open.remote_bda))
290 return false;
291 this->log_gattc_lifecycle_event_("OPEN");
292 // conn_id was already set in ESP_GATTC_CONNECT_EVT
293 this->service_count_ = 0;
294
295 // ESP-IDF's BLE stack may send ESP_GATTC_OPEN_EVT after esp_ble_gattc_open() returns an
296 // error, if the error occurred at the BTA/GATT layer. This can result in the event
297 // arriving after we've already transitioned to IDLE state.
298 if (this->state() == espbt::ClientState::IDLE) {
299 ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in IDLE state (status=%d), ignoring", this->connection_index_,
300 this->address_str_, param->open.status);
301 break;
302 }
303
304 if (this->state() != espbt::ClientState::CONNECTING) {
305 // This should not happen but lets log it in case it does
306 // because it means we have a bad assumption about how the
307 // ESP BT stack works.
308 ESP_LOGE(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in %s state (status=%d)", this->connection_index_,
309 this->address_str_, espbt::client_state_to_string(this->state()), param->open.status);
310 }
311 if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
312 this->log_gattc_warning_("Connection open", param->open.status);
313 this->set_state(espbt::ClientState::IDLE);
314 break;
315 }
316 if (this->want_disconnect_) {
317 // Disconnect was requested after connecting started,
318 // but before the connection was established. Now that we have
319 // this->conn_id_ set, we can disconnect it.
321 this->conn_id_ = UNSET_CONN_ID;
322 break;
323 }
324 // MTU negotiation already started in ESP_GATTC_CONNECT_EVT
325 this->set_state(espbt::ClientState::CONNECTED);
326 ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_);
327 if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
328 // Cached connections already connected with medium parameters, no update needed
329 // only set our state, subclients might have more stuff to do yet.
330 this->set_state_internal_(espbt::ClientState::ESTABLISHED);
331 break;
332 }
333 // For V3_WITHOUT_CACHE, we already set fast params before connecting
334 // No need to update them again here
335 this->log_event_("Searching for services");
336 esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
337 break;
338 }
339 case ESP_GATTC_CONNECT_EVT: {
340 if (!this->check_addr(param->connect.remote_bda))
341 return false;
342 this->log_gattc_lifecycle_event_("CONNECT");
343 this->conn_id_ = param->connect.conn_id;
344 // Start MTU negotiation immediately as recommended by ESP-IDF examples
345 // (gatt_client, ble_throughput) which call esp_ble_gattc_send_mtu_req in
346 // ESP_GATTC_CONNECT_EVT instead of waiting for ESP_GATTC_OPEN_EVT.
347 // This saves ~3ms in the connection process.
348 auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->connect.conn_id);
349 if (ret) {
350 this->log_gattc_warning_("esp_ble_gattc_send_mtu_req", ret);
351 }
352 break;
353 }
354 case ESP_GATTC_DISCONNECT_EVT: {
355 if (!this->check_addr(param->disconnect.remote_bda))
356 return false;
357 // Check if we were disconnected while waiting for service discovery
358 if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER &&
359 this->state() == espbt::ClientState::CONNECTED) {
360 this->log_warning_("Remote closed during discovery");
361 } else {
362 ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_, this->address_str_,
363 param->disconnect.reason);
364 }
365 this->release_services();
366 this->set_state(espbt::ClientState::IDLE);
367 break;
368 }
369
370 case ESP_GATTC_CFG_MTU_EVT: {
371 if (this->conn_id_ != param->cfg_mtu.conn_id)
372 return false;
373 if (param->cfg_mtu.status != ESP_GATT_OK) {
374 ESP_LOGW(TAG, "[%d] [%s] cfg_mtu failed, mtu %d, status %d", this->connection_index_, this->address_str_,
375 param->cfg_mtu.mtu, param->cfg_mtu.status);
376 // No state change required here - disconnect event will follow if needed.
377 break;
378 }
379 ESP_LOGD(TAG, "[%d] [%s] cfg_mtu status %d, mtu %d", this->connection_index_, this->address_str_,
380 param->cfg_mtu.status, param->cfg_mtu.mtu);
381 this->mtu_ = param->cfg_mtu.mtu;
382 break;
383 }
384 case ESP_GATTC_CLOSE_EVT: {
385 if (this->conn_id_ != param->close.conn_id)
386 return false;
387 this->log_gattc_lifecycle_event_("CLOSE");
388 this->release_services();
389 this->set_state(espbt::ClientState::IDLE);
390 this->conn_id_ = UNSET_CONN_ID;
391 break;
392 }
393 case ESP_GATTC_SEARCH_RES_EVT: {
394 if (this->conn_id_ != param->search_res.conn_id)
395 return false;
396 this->service_count_++;
397 if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
398 // V3 clients don't need services initialized since
399 // as they use the ESP APIs to get services.
400 break;
401 }
402#ifdef USE_ESP32_BLE_DEVICE
403 BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory)
404 ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
405 ble_service->start_handle = param->search_res.start_handle;
406 ble_service->end_handle = param->search_res.end_handle;
407 ble_service->client = this;
408 this->services_.push_back(ble_service);
409#endif
410 break;
411 }
412 case ESP_GATTC_SEARCH_CMPL_EVT: {
413 if (this->conn_id_ != param->search_cmpl.conn_id)
414 return false;
415 this->log_gattc_lifecycle_event_("SEARCH_CMPL");
416 // For V3_WITHOUT_CACHE, switch back to medium connection parameters after service discovery
417 // This balances performance with bandwidth usage after the critical discovery phase
418 if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
419 this->update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium");
420 } else if (this->connection_type_ != espbt::ConnectionType::V3_WITH_CACHE) {
421#ifdef USE_ESP32_BLE_DEVICE
422#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
423 for (auto &svc : this->services_) {
424 char uuid_buf[espbt::UUID_STR_LEN];
425 svc->uuid.to_str(uuid_buf);
426 ESP_LOGV(TAG,
427 "[%d] [%s] Service UUID: %s\n"
428 "[%d] [%s] start_handle: 0x%x end_handle: 0x%x",
429 this->connection_index_, this->address_str_, uuid_buf, this->connection_index_, this->address_str_,
430 svc->start_handle, svc->end_handle);
431 }
432#endif
433#endif
434 }
435 ESP_LOGI(TAG, "[%d] [%s] Service discovery complete", this->connection_index_, this->address_str_);
436 this->set_state_internal_(espbt::ClientState::ESTABLISHED);
437 break;
438 }
439 case ESP_GATTC_READ_DESCR_EVT: {
440 if (this->conn_id_ != param->write.conn_id)
441 return false;
442 this->log_gattc_data_event_("READ_DESCR");
443 break;
444 }
445 case ESP_GATTC_WRITE_DESCR_EVT: {
446 if (this->conn_id_ != param->write.conn_id)
447 return false;
448 this->log_gattc_data_event_("WRITE_DESCR");
449 break;
450 }
451 case ESP_GATTC_WRITE_CHAR_EVT: {
452 if (this->conn_id_ != param->write.conn_id)
453 return false;
454 this->log_gattc_data_event_("WRITE_CHAR");
455 break;
456 }
457 case ESP_GATTC_READ_CHAR_EVT: {
458 if (this->conn_id_ != param->read.conn_id)
459 return false;
460 this->log_gattc_data_event_("READ_CHAR");
461 break;
462 }
463 case ESP_GATTC_NOTIFY_EVT: {
464 if (this->conn_id_ != param->notify.conn_id)
465 return false;
466 this->log_gattc_data_event_("NOTIFY");
467 break;
468 }
469 case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
470 this->log_gattc_data_event_("REG_FOR_NOTIFY");
471 if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
472 this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
473 // Client is responsible for flipping the descriptor value
474 // when using the cache
475 break;
476 }
477 esp_gattc_descr_elem_t desc_result;
478 uint16_t count = 1;
479 esp_gatt_status_t descr_status = esp_ble_gattc_get_descr_by_char_handle(
480 this->gattc_if_, this->conn_id_, param->reg_for_notify.handle, NOTIFY_DESC_UUID, &desc_result, &count);
481 if (descr_status != ESP_GATT_OK) {
482 this->log_gattc_warning_("esp_ble_gattc_get_descr_by_char_handle", descr_status);
483 break;
484 }
485 esp_gattc_char_elem_t char_result;
486 esp_gatt_status_t char_status =
487 esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, param->reg_for_notify.handle,
488 param->reg_for_notify.handle, &char_result, &count, 0);
489 if (char_status != ESP_GATT_OK) {
490 this->log_gattc_warning_("esp_ble_gattc_get_all_char", char_status);
491 break;
492 }
493
494 /*
495 1 = notify
496 2 = indicate
497 */
498 uint16_t notify_en = char_result.properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY ? 1 : 2;
499 esp_err_t status =
500 esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, desc_result.handle, sizeof(notify_en),
501 (uint8_t *) &notify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
502 ESP_LOGV(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties);
503 if (status) {
504 this->log_gattc_warning_("esp_ble_gattc_write_char_descr", status);
505 }
506 break;
507 }
508
509 case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
510 this->log_gattc_data_event_("UNREG_FOR_NOTIFY");
511 break;
512 }
513
514 default:
515 // Unknown events logged at VERBOSE to avoid UART delays during time-sensitive operations
516 ESP_LOGV(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_, event);
517 break;
518 }
519 return true;
520}
521
522// clients can't call defer() directly since it's protected.
523void BLEClientBase::run_later(std::function<void()> &&f) { // NOLINT
524 this->defer(std::move(f));
525}
526
527void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
528 switch (event) {
529 // This event is sent by the server when it requests security
530 case ESP_GAP_BLE_SEC_REQ_EVT:
531 if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr))
532 return;
533 ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_, event);
534 esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
535 break;
536 // This event is sent once authentication has completed
537 case ESP_GAP_BLE_AUTH_CMPL_EVT:
538 if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr))
539 return;
540 char addr_str[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
541 format_mac_addr_upper(param->ble_security.auth_cmpl.bd_addr, addr_str);
542 ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_, addr_str);
543 if (!param->ble_security.auth_cmpl.success) {
544 this->log_error_("auth fail reason", param->ble_security.auth_cmpl.fail_reason);
545 } else {
546 this->paired_ = true;
547 ESP_LOGD(TAG, "[%d] [%s] auth success type = %d mode = %d", this->connection_index_, this->address_str_,
548 param->ble_security.auth_cmpl.addr_type, param->ble_security.auth_cmpl.auth_mode);
549 }
550 break;
551
552 // There are other events we'll want to implement at some point to support things like pass key
553 // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md
554 default:
555 break;
556 }
557}
558
559// Parse GATT values into a float for a sensor.
560// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
561float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) {
562 // A length of one means a single octet value.
563 if (length == 0)
564 return 0;
565 if (length == 1)
566 return (float) ((uint8_t) value[0]);
567
568 switch (value[0]) {
569 case 0x1: // boolean.
570 case 0x2: // 2bit.
571 case 0x3: // nibble.
572 case 0x4: // uint8.
573 return (float) ((uint8_t) value[1]);
574 case 0x5: // uint12.
575 case 0x6: // uint16.
576 if (length > 2) {
577 return (float) encode_uint16(value[1], value[2]);
578 }
579 [[fallthrough]];
580 case 0x7: // uint24.
581 if (length > 3) {
582 return (float) encode_uint24(value[1], value[2], value[3]);
583 }
584 [[fallthrough]];
585 case 0x8: // uint32.
586 if (length > 4) {
587 return (float) encode_uint32(value[1], value[2], value[3], value[4]);
588 }
589 [[fallthrough]];
590 case 0xC: // int8.
591 return (float) ((int8_t) value[1]);
592 case 0xD: // int12.
593 case 0xE: // int16.
594 if (length > 2) {
595 return (float) ((int16_t) (value[1] << 8) + (int16_t) value[2]);
596 }
597 [[fallthrough]];
598 case 0xF: // int24.
599 if (length > 3) {
600 return (float) ((int32_t) (value[1] << 16) + (int32_t) (value[2] << 8) + (int32_t) (value[3]));
601 }
602 [[fallthrough]];
603 case 0x10: // int32.
604 if (length > 4) {
605 return (float) ((int32_t) (value[1] << 24) + (int32_t) (value[2] << 16) + (int32_t) (value[3] << 8) +
606 (int32_t) (value[4]));
607 }
608 }
609 ESP_LOGW(TAG, "[%d] [%s] Cannot parse characteristic value of type 0x%x length %d", this->connection_index_,
610 this->address_str_, value[0], length);
611 return NAN;
612}
613
614#ifdef USE_ESP32_BLE_DEVICE
616 for (auto *svc : this->services_) {
617 if (svc->uuid == uuid)
618 return svc;
619 }
620 return nullptr;
621}
622
623BLEService *BLEClientBase::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); }
624
626 auto *svc = this->get_service(service);
627 if (svc == nullptr)
628 return nullptr;
629 return svc->get_characteristic(chr);
630}
631
632BLECharacteristic *BLEClientBase::get_characteristic(uint16_t service, uint16_t chr) {
633 return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr));
634}
635
637 for (auto *svc : this->services_) {
638 if (!svc->parsed)
639 svc->parse_characteristics();
640 for (auto *chr : svc->characteristics) {
641 if (chr->handle == handle)
642 return chr;
643 }
644 }
645 return nullptr;
646}
647
649 auto *chr = this->get_characteristic(handle);
650 if (chr != nullptr) {
651 if (!chr->parsed)
652 chr->parse_descriptors();
653 for (auto &desc : chr->descriptors) {
654 if (desc->uuid.get_uuid().uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG)
655 return desc;
656 }
657 }
658 return nullptr;
659}
660
662 auto *svc = this->get_service(service);
663 if (svc == nullptr)
664 return nullptr;
665 auto *ch = svc->get_characteristic(chr);
666 if (ch == nullptr)
667 return nullptr;
668 return ch->get_descriptor(descr);
669}
670
671BLEDescriptor *BLEClientBase::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) {
672 return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr),
673 espbt::ESPBTUUID::from_uint16(descr));
674}
675
677 for (auto *svc : this->services_) {
678 if (!svc->parsed)
679 svc->parse_characteristics();
680 for (auto *chr : svc->characteristics) {
681 if (!chr->parsed)
682 chr->parse_descriptors();
683 for (auto *desc : chr->descriptors) {
684 if (desc->handle == handle)
685 return desc;
686 }
687 }
688 }
689 return nullptr;
690}
691#endif // USE_ESP32_BLE_DEVICE
692
693} // namespace esphome::esp32_ble_client
694
695#endif // USE_ESP32
uint8_t status
Definition bl0942.h:8
virtual 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.
Definition component.h:479
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
Definition component.h:373
std::vector< BLEService * > services_
char address_str_[MAC_ADDRESS_PRETTY_BUFFER_SIZE]
void log_gattc_warning_(const char *operation, esp_gatt_status_t status)
void log_connection_params_(const char *param_type)
BLEDescriptor * get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr)
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
void update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout, const char *param_type)
BLECharacteristic * get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr)
virtual void set_address(uint64_t address)
void run_later(std::function< void()> &&f)
BLEService * get_service(espbt::ESPBTUUID uuid)
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override
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 print_bt_device_info(const ESPBTDevice &device)
void set_state_internal_(ClientState st)
Set state without IDLE handling - use for direct state transitions.
esp_ble_addr_type_t get_address_type() const
const char * message
Definition component.cpp:38
ESP32BLETracker * global_esp32_ble_tracker
const char * client_state_to_string(ClientState state)
ESP32BLE * global_ble
Definition ble.cpp:671
const float AFTER_BLUETOOTH
Definition component.cpp:87
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.
Definition helpers.h:532
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.
Definition helpers.h:536
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition helpers.h:528
char * format_mac_addr_upper(const uint8_t *mac, char *output)
Format MAC address as XX:XX:XX:XX:XX:XX (uppercase, colon separators)
Definition helpers.h:1045
uint16_t length
Definition tt21100.cpp:0