ESPHome  2025.2.0
ble_client_base.cpp
Go to the documentation of this file.
1 #include "ble_client_base.h"
2 
3 #include "esphome/core/helpers.h"
4 #include "esphome/core/log.h"
5 
6 #ifdef USE_ESP32
7 
8 namespace esphome {
9 namespace esp32_ble_client {
10 
11 static const char *const TAG = "esp32_ble_client";
12 static const esp_bt_uuid_t NOTIFY_DESC_UUID = {
13  .len = ESP_UUID_LEN_16,
14  .uuid =
15  {
16  .uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,
17  },
18 };
19 
21  static uint8_t connection_index = 0;
22  this->connection_index_ = connection_index++;
23 }
24 
26  if (!esp32_ble::global_ble->is_active()) {
28  return;
29  }
30  if (this->state_ == espbt::ClientState::INIT) {
31  auto ret = esp_ble_gattc_app_register(this->app_id);
32  if (ret) {
33  ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
34  this->mark_failed();
35  }
37  }
38  // READY_TO_CONNECT means we have discovered the device
39  // and the scanner has been stopped by the tracker.
40  if (this->state_ == espbt::ClientState::READY_TO_CONNECT) {
41  this->connect();
42  }
43 }
44 
46 
48  ESP_LOGCONFIG(TAG, " Address: %s", this->address_str().c_str());
49  ESP_LOGCONFIG(TAG, " Auto-Connect: %s", TRUEFALSE(this->auto_connect_));
50  std::string state_name;
51  switch (this->state()) {
53  state_name = "INIT";
54  break;
55  case espbt::ClientState::DISCONNECTING:
56  state_name = "DISCONNECTING";
57  break;
59  state_name = "IDLE";
60  break;
61  case espbt::ClientState::SEARCHING:
62  state_name = "SEARCHING";
63  break;
64  case espbt::ClientState::DISCOVERED:
65  state_name = "DISCOVERED";
66  break;
67  case espbt::ClientState::READY_TO_CONNECT:
68  state_name = "READY_TO_CONNECT";
69  break;
70  case espbt::ClientState::CONNECTING:
71  state_name = "CONNECTING";
72  break;
74  state_name = "CONNECTED";
75  break;
76  case espbt::ClientState::ESTABLISHED:
77  state_name = "ESTABLISHED";
78  break;
79  default:
80  state_name = "UNKNOWN_STATE";
81  break;
82  }
83  ESP_LOGCONFIG(TAG, " State: %s", state_name.c_str());
84  if (this->status_ == ESP_GATT_NO_RESOURCES) {
85  ESP_LOGE(TAG, " Failed due to no resources. Try to reduce number of BLE clients in config.");
86  } else if (this->status_ != ESP_GATT_OK) {
87  ESP_LOGW(TAG, " Failed due to error code %d", this->status_);
88  }
89 }
90 
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 && this->state_ != espbt::ClientState::SEARCHING)
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 
110  ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(),
111  this->remote_addr_type_);
112  this->paired_ = false;
113  auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
114  if (ret) {
115  ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(),
116  ret);
118  } else {
119  this->set_state(espbt::ClientState::CONNECTING);
120  }
121 }
122 
123 esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
124 
126  if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING)
127  return;
128  ESP_LOGI(TAG, "[%d] [%s] Disconnecting.", this->connection_index_, this->address_str_.c_str());
129  auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
130  if (err != ESP_OK) {
131  ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_close error, err=%d", this->connection_index_, this->address_str_.c_str(),
132  err);
133  }
134 
135  if (this->state_ == espbt::ClientState::SEARCHING || this->state_ == espbt::ClientState::READY_TO_CONNECT ||
136  this->state_ == espbt::ClientState::DISCOVERED) {
137  this->set_address(0);
139  } else {
140  this->set_state(espbt::ClientState::DISCONNECTING);
141  }
142 }
143 
145  for (auto &svc : this->services_)
146  delete svc; // NOLINT(cppcoreguidelines-owning-memory)
147  this->services_.clear();
148 #ifndef CONFIG_BT_GATTC_CACHE_NVS_FLASH
149  esp_ble_gattc_cache_clean(this->remote_bda_);
150 #endif
151 }
152 
153 void BLEClientBase::log_event_(const char *name) {
154  ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), name);
155 }
156 
157 bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
158  esp_ble_gattc_cb_param_t *param) {
159  if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
160  return false;
161  if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_)
162  return false;
163 
164  ESP_LOGV(TAG, "[%d] [%s] gattc_event_handler: event=%d gattc_if=%d", this->connection_index_,
165  this->address_str_.c_str(), event, esp_gattc_if);
166 
167  switch (event) {
168  case ESP_GATTC_REG_EVT: {
169  if (param->reg.status == ESP_GATT_OK) {
170  ESP_LOGV(TAG, "[%d] [%s] gattc registered app id %d", this->connection_index_, this->address_str_.c_str(),
171  this->app_id);
172  this->gattc_if_ = esp_gattc_if;
173  } else {
174  ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_,
175  this->address_str_.c_str(), param->reg.app_id, param->reg.status);
176  this->status_ = param->reg.status;
177  this->mark_failed();
178  }
179  break;
180  }
181  case ESP_GATTC_OPEN_EVT: {
182  if (!this->check_addr(param->open.remote_bda))
183  return false;
184  this->log_event_("ESP_GATTC_OPEN_EVT");
185  this->conn_id_ = param->open.conn_id;
186  this->service_count_ = 0;
187  if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
188  ESP_LOGW(TAG, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(),
189  param->open.status);
191  break;
192  }
193  auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id);
194  if (ret) {
195  ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_,
196  this->address_str_.c_str(), ret);
197  }
199  if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
200  ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str());
201  // only set our state, subclients might have more stuff to do yet.
202  this->state_ = espbt::ClientState::ESTABLISHED;
203  break;
204  }
205  esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
206  break;
207  }
208  case ESP_GATTC_CONNECT_EVT: {
209  if (!this->check_addr(param->connect.remote_bda))
210  return false;
211  this->log_event_("ESP_GATTC_CONNECT_EVT");
212  break;
213  }
214  case ESP_GATTC_DISCONNECT_EVT: {
215  if (!this->check_addr(param->disconnect.remote_bda))
216  return false;
217  ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_,
218  this->address_str_.c_str(), param->disconnect.reason);
219  this->release_services();
221  break;
222  }
223 
224  case ESP_GATTC_CFG_MTU_EVT: {
225  if (this->conn_id_ != param->cfg_mtu.conn_id)
226  return false;
227  if (param->cfg_mtu.status != ESP_GATT_OK) {
228  ESP_LOGW(TAG, "[%d] [%s] cfg_mtu failed, mtu %d, status %d", this->connection_index_,
229  this->address_str_.c_str(), param->cfg_mtu.mtu, param->cfg_mtu.status);
230  // No state change required here - disconnect event will follow if needed.
231  break;
232  }
233  ESP_LOGD(TAG, "[%d] [%s] cfg_mtu status %d, mtu %d", this->connection_index_, this->address_str_.c_str(),
234  param->cfg_mtu.status, param->cfg_mtu.mtu);
235  this->mtu_ = param->cfg_mtu.mtu;
236  break;
237  }
238  case ESP_GATTC_CLOSE_EVT: {
239  if (this->conn_id_ != param->close.conn_id)
240  return false;
241  this->log_event_("ESP_GATTC_CLOSE_EVT");
242  this->release_services();
244  break;
245  }
246  case ESP_GATTC_SEARCH_RES_EVT: {
247  if (this->conn_id_ != param->search_res.conn_id)
248  return false;
249  this->service_count_++;
250  if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
251  // V3 clients don't need services initialized since
252  // they only request by handle after receiving the services.
253  break;
254  }
255  BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory)
256  ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
257  ble_service->start_handle = param->search_res.start_handle;
258  ble_service->end_handle = param->search_res.end_handle;
259  ble_service->client = this;
260  this->services_.push_back(ble_service);
261  break;
262  }
263  case ESP_GATTC_SEARCH_CMPL_EVT: {
264  if (this->conn_id_ != param->search_cmpl.conn_id)
265  return false;
266  this->log_event_("ESP_GATTC_SEARCH_CMPL_EVT");
267  for (auto &svc : this->services_) {
268  ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
269  svc->uuid.to_string().c_str());
270  ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_,
271  this->address_str_.c_str(), svc->start_handle, svc->end_handle);
272  }
273  ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str());
274  this->state_ = espbt::ClientState::ESTABLISHED;
275  break;
276  }
277  case ESP_GATTC_READ_DESCR_EVT: {
278  if (this->conn_id_ != param->write.conn_id)
279  return false;
280  this->log_event_("ESP_GATTC_READ_DESCR_EVT");
281  break;
282  }
283  case ESP_GATTC_WRITE_DESCR_EVT: {
284  if (this->conn_id_ != param->write.conn_id)
285  return false;
286  this->log_event_("ESP_GATTC_WRITE_DESCR_EVT");
287  break;
288  }
289  case ESP_GATTC_WRITE_CHAR_EVT: {
290  if (this->conn_id_ != param->write.conn_id)
291  return false;
292  this->log_event_("ESP_GATTC_WRITE_CHAR_EVT");
293  break;
294  }
295  case ESP_GATTC_READ_CHAR_EVT: {
296  if (this->conn_id_ != param->read.conn_id)
297  return false;
298  this->log_event_("ESP_GATTC_READ_CHAR_EVT");
299  break;
300  }
301  case ESP_GATTC_NOTIFY_EVT: {
302  if (this->conn_id_ != param->notify.conn_id)
303  return false;
304  this->log_event_("ESP_GATTC_NOTIFY_EVT");
305  break;
306  }
307  case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
308  this->log_event_("ESP_GATTC_REG_FOR_NOTIFY_EVT");
309  if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
310  this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
311  // Client is responsible for flipping the descriptor value
312  // when using the cache
313  break;
314  }
315  esp_gattc_descr_elem_t desc_result;
316  uint16_t count = 1;
317  esp_gatt_status_t descr_status = esp_ble_gattc_get_descr_by_char_handle(
318  this->gattc_if_, this->conn_id_, param->reg_for_notify.handle, NOTIFY_DESC_UUID, &desc_result, &count);
319  if (descr_status != ESP_GATT_OK) {
320  ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_descr_by_char_handle error, status=%d", this->connection_index_,
321  this->address_str_.c_str(), descr_status);
322  break;
323  }
324  esp_gattc_char_elem_t char_result;
325  esp_gatt_status_t char_status =
326  esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, param->reg_for_notify.handle,
327  param->reg_for_notify.handle, &char_result, &count, 0);
328  if (char_status != ESP_GATT_OK) {
329  ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_,
330  this->address_str_.c_str(), char_status);
331  break;
332  }
333 
334  /*
335  1 = notify
336  2 = indicate
337  */
338  uint16_t notify_en = char_result.properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY ? 1 : 2;
339  esp_err_t status =
340  esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, desc_result.handle, sizeof(notify_en),
341  (uint8_t *) &notify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
342  ESP_LOGD(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties);
343  if (status) {
344  ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, status=%d", this->connection_index_,
345  this->address_str_.c_str(), status);
346  }
347  break;
348  }
349 
350  default:
351  // ideally would check all other events for matching conn_id
352  ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_.c_str(), event);
353  break;
354  }
355  return true;
356 }
357 
358 // clients can't call defer() directly since it's protected.
359 void BLEClientBase::run_later(std::function<void()> &&f) { // NOLINT
360  this->defer(std::move(f));
361 }
362 
363 void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
364  switch (event) {
365  // This event is sent by the server when it requests security
366  case ESP_GAP_BLE_SEC_REQ_EVT:
367  if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr))
368  return;
369  ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_.c_str(), event);
370  esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
371  break;
372  // This event is sent once authentication has completed
373  case ESP_GAP_BLE_AUTH_CMPL_EVT:
374  if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr))
375  return;
376  esp_bd_addr_t bd_addr;
377  memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
378  ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(),
379  format_hex(bd_addr, 6).c_str());
380  if (!param->ble_security.auth_cmpl.success) {
381  ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(),
382  param->ble_security.auth_cmpl.fail_reason);
383  } else {
384  this->paired_ = true;
385  ESP_LOGD(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_,
386  this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type,
387  param->ble_security.auth_cmpl.auth_mode);
388  }
389  break;
390 
391  // There are other events we'll want to implement at some point to support things like pass key
392  // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md
393  default:
394  break;
395  }
396 }
397 
398 // Parse GATT values into a float for a sensor.
399 // Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
400 float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) {
401  // A length of one means a single octet value.
402  if (length == 0)
403  return 0;
404  if (length == 1)
405  return (float) ((uint8_t) value[0]);
406 
407  switch (value[0]) {
408  case 0x1: // boolean.
409  case 0x2: // 2bit.
410  case 0x3: // nibble.
411  case 0x4: // uint8.
412  return (float) ((uint8_t) value[1]);
413  case 0x5: // uint12.
414  case 0x6: // uint16.
415  if (length > 2) {
416  return (float) encode_uint16(value[1], value[2]);
417  }
418  // fall through
419  case 0x7: // uint24.
420  if (length > 3) {
421  return (float) encode_uint24(value[1], value[2], value[3]);
422  }
423  // fall through
424  case 0x8: // uint32.
425  if (length > 4) {
426  return (float) encode_uint32(value[1], value[2], value[3], value[4]);
427  }
428  // fall through
429  case 0xC: // int8.
430  return (float) ((int8_t) value[1]);
431  case 0xD: // int12.
432  case 0xE: // int16.
433  if (length > 2) {
434  return (float) ((int16_t) (value[1] << 8) + (int16_t) value[2]);
435  }
436  // fall through
437  case 0xF: // int24.
438  if (length > 3) {
439  return (float) ((int32_t) (value[1] << 16) + (int32_t) (value[2] << 8) + (int32_t) (value[3]));
440  }
441  // fall through
442  case 0x10: // int32.
443  if (length > 4) {
444  return (float) ((int32_t) (value[1] << 24) + (int32_t) (value[2] << 16) + (int32_t) (value[3] << 8) +
445  (int32_t) (value[4]));
446  }
447  }
448  ESP_LOGW(TAG, "[%d] [%s] Cannot parse characteristic value of type 0x%x length %d", this->connection_index_,
449  this->address_str_.c_str(), value[0], length);
450  return NAN;
451 }
452 
454  for (auto *svc : this->services_) {
455  if (svc->uuid == uuid)
456  return svc;
457  }
458  return nullptr;
459 }
460 
462 
464  auto *svc = this->get_service(service);
465  if (svc == nullptr)
466  return nullptr;
467  return svc->get_characteristic(chr);
468 }
469 
470 BLECharacteristic *BLEClientBase::get_characteristic(uint16_t service, uint16_t chr) {
472 }
473 
475  for (auto *svc : this->services_) {
476  if (!svc->parsed)
477  svc->parse_characteristics();
478  for (auto *chr : svc->characteristics) {
479  if (chr->handle == handle)
480  return chr;
481  }
482  }
483  return nullptr;
484 }
485 
487  auto *chr = this->get_characteristic(handle);
488  if (chr != nullptr) {
489  if (!chr->parsed)
490  chr->parse_descriptors();
491  for (auto &desc : chr->descriptors) {
492  if (desc->uuid.get_uuid().uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG)
493  return desc;
494  }
495  }
496  return nullptr;
497 }
498 
500  auto *svc = this->get_service(service);
501  if (svc == nullptr)
502  return nullptr;
503  auto *ch = svc->get_characteristic(chr);
504  if (ch == nullptr)
505  return nullptr;
506  return ch->get_descriptor(descr);
507 }
508 
509 BLEDescriptor *BLEClientBase::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) {
512 }
513 
515  for (auto *svc : this->services_) {
516  if (!svc->parsed)
517  svc->parse_characteristics();
518  for (auto *chr : svc->characteristics) {
519  if (!chr->parsed)
520  chr->parse_descriptors();
521  for (auto *desc : chr->descriptors) {
522  if (desc->handle == handle)
523  return desc;
524  }
525  }
526  }
527  return nullptr;
528 }
529 
530 } // namespace esp32_ble_client
531 } // namespace esphome
532 
533 #endif // USE_ESP32
BLEDescriptor * get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr)
const char * name
Definition: stm32flash.h:78
BLEService * get_service(espbt::ESPBTUUID uuid)
ESP32BLE * global_ble
Definition: ble.cpp:438
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:361
std::vector< BLEService * > services_
void defer(const std::string &name, std::function< void()> &&f)
Defer a callback to the next loop() call.
Definition: component.cpp:130
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:195
bool check_addr(esp_bd_addr_t &addr)
void run_later(std::function< void()> &&f)
const float AFTER_BLUETOOTH
Definition: component.cpp:22
BLEDescriptor * get_config_descriptor(uint16_t handle)
static ESPBTUUID from_uuid(esp_bt_uuid_t uuid)
Definition: ble_uuid.cpp:97
ESP32BLETracker * global_esp32_ble_tracker
virtual void set_state(ClientState st)
static ESPBTUUID from_uint16(uint16_t uuid)
Definition: ble_uuid.cpp:16
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:200
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:191
esp_ble_addr_type_t get_address_type() const
uint8_t status
Definition: bl0942.h:74
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override
BLECharacteristic * get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr)
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
uint16_t length
Definition: tt21100.cpp:12
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
bool parse_device(const espbt::ESPBTDevice &device) override
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override
void print_bt_device_info(const ESPBTDevice &device)
float parse_char_value(uint8_t *value, uint16_t length)