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