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