ESPHome 2026.8.0b4
Loading...
Searching...
No Matches
ble_device.cpp
Go to the documentation of this file.
1// ble_device.cpp
2//
3// Platform-neutral implementation of the shared BLE advertisement types.
4// Parses raw BLE advertisement data into ESPBTDevice.
5
6#include "ble_device.h"
7
8#include "ble_aes_ccm.h"
9
11#include "esphome/core/hal.h"
13#include "esphome/core/log.h"
14
15#include <cstring>
16
18
19static const char *const TAG = "ble_device_base";
20
21// Longest advertisement payload worth hex-dumping at VERY_VERBOSE
22// (legacy advertising: 31-byte adv + 31-byte scan response).
23static constexpr size_t BLE_ADV_MAX_LOG_BYTES = 62;
24
25// ---------------------------------------------------------------------------
26// ESPBTUUID
27// ---------------------------------------------------------------------------
28
32 ret.uuid_.uuid16 = uuid;
33 return ret;
34}
35
39 ret.uuid_.uuid32 = uuid;
40 return ret;
41}
42
43ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) {
46 memcpy(ret.uuid_.uuid128, data, 16);
47 return ret;
48}
49
53 for (int i = 0; i < 16; i++)
54 ret.uuid_.uuid128[i] = data[15 - i];
55 return ret;
56}
57
58ESPBTUUID ESPBTUUID::from_raw(const char *data, size_t length) {
59 // Same text-parsing semantics as the historical esp32_ble::ESPBTUUID::from_raw.
61 if (length == 4) {
62 // 16-bit UUID as 4-character hex string
63 auto parsed = parse_hex<uint16_t>(data, length);
64 if (parsed.has_value()) {
65 ret.type_ = Type::UUID16;
66 ret.uuid_.uuid16 = parsed.value();
67 }
68 } else if (length == 8) {
69 // 32-bit UUID as 8-character hex string
70 auto parsed = parse_hex<uint32_t>(data, length);
71 if (parsed.has_value()) {
72 ret.type_ = Type::UUID32;
73 ret.uuid_.uuid32 = parsed.value();
74 }
75 } else if (length == 16) {
76 // 16 raw bytes (little-endian 128-bit UUID)
77 ret.type_ = Type::UUID128;
78 memcpy(ret.uuid_.uuid128, reinterpret_cast<const uint8_t *>(data), 16);
79 } else if (length == 36) {
80 // Dashed text form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
81 ret.type_ = Type::UUID128;
82 int n = 0;
83 for (size_t i = 0; i < length; i += 2) {
84 if (data[i] == '-')
85 i++;
86 uint8_t msb = data[i];
87 uint8_t lsb = data[i + 1];
88 if (msb > '9')
89 msb -= 7;
90 if (lsb > '9')
91 lsb -= 7;
92 ret.uuid_.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F);
93 }
94 } else {
95 ESP_LOGE(TAG, "ERROR: UUID value not 4, 8, 16 or 36 bytes - %s", data);
96 }
97 return ret;
98}
99
100#ifdef USE_ESP32
101ESPBTUUID ESPBTUUID::from_uuid(esp_bt_uuid_t uuid) {
102 if (uuid.len == 0) // the unset sentinel get_uuid() emits
103 return {};
104 if (uuid.len == ESP_UUID_LEN_16)
105 return ESPBTUUID::from_uint16(uuid.uuid.uuid16);
106 if (uuid.len == ESP_UUID_LEN_32)
107 return ESPBTUUID::from_uint32(uuid.uuid.uuid32);
108 return ESPBTUUID::from_raw(uuid.uuid.uuid128);
109}
110
111esp_bt_uuid_t ESPBTUUID::get_uuid() const {
112 esp_bt_uuid_t ret;
113 switch (this->type_) {
114 case Type::UNSET:
115 ret.len = 0;
116 memset(&ret.uuid, 0, sizeof(ret.uuid));
117 break;
118 case Type::UUID16:
119 ret.len = ESP_UUID_LEN_16;
120 ret.uuid.uuid16 = this->uuid_.uuid16;
121 break;
122 case Type::UUID32:
123 ret.len = ESP_UUID_LEN_32;
124 ret.uuid.uuid32 = this->uuid_.uuid32;
125 break;
126 default:
127 case Type::UUID128:
128 ret.len = ESP_UUID_LEN_128;
129 memcpy(ret.uuid.uuid128, this->uuid_.uuid128, ESP_UUID_LEN_128);
130 break;
131 }
132 return ret;
133}
134
135void ESPBTDevice::parse_scan_rst(const esp32_ble::BLEScanResult &scan_result) {
136 this->scan_result_ = &scan_result;
137 // BLEScanResult's bda is most-significant octet first; the neutral ingest
138 // takes the BLE controller (LSB-first) order, so reverse — address_uint64()/
139 // address_str_to() then produce exactly the historical esp32 values.
140 uint8_t mac_lsb_first[MAC_ADDRESS_SIZE];
141 for (uint8_t i = 0; i < 6; i++)
142 mac_lsb_first[i] = scan_result.bda[5 - i];
143 this->from_scan_result(mac_lsb_first, scan_result.rssi, scan_result.ble_addr_type, scan_result.ble_adv,
144 scan_result.adv_data_len + scan_result.scan_rsp_len);
145}
146#endif // USE_ESP32
147
149 // Widening an unset UUID stays unset; expanding it would produce a set 0x0000 base UUID.
150 if (this->type_ == Type::UNSET || this->type_ == Type::UUID128)
151 return *this;
152 uint8_t data[16];
153 this->to_128bit_(data);
154 return ESPBTUUID::from_raw(data);
155}
156
157bool ESPBTUUID::contains(uint8_t data1, uint8_t data2) const {
158 // Adjacent byte-pair search — identical semantics to esp32_ble::ESPBTUUID::contains.
159 switch (this->type_) {
160 case Type::UNSET:
161 return false;
162 case Type::UUID16:
163 return (this->uuid_.uuid16 >> 8) == data2 && (this->uuid_.uuid16 & 0xFF) == data1;
164 case Type::UUID32:
165 for (uint8_t i = 0; i < 3; i++) {
166 bool a = ((this->uuid_.uuid32 >> i * 8) & 0xFF) == data1;
167 bool b = ((this->uuid_.uuid32 >> (i + 1) * 8) & 0xFF) == data2;
168 if (a && b)
169 return true;
170 }
171 return false;
172 case Type::UUID128:
173 for (uint8_t i = 0; i < 15; i++) {
174 if (this->uuid_.uuid128[i] == data1 && this->uuid_.uuid128[i + 1] == data2)
175 return true;
176 }
177 return false;
178 }
179 return false;
180}
181
182const char *ESPBTUUID::to_str(char *buf) const {
183 // Identical output format to esp32_ble::ESPBTUUID::to_str.
184 char *pos = buf;
185 switch (this->type_) {
186 case Type::UNSET:
187 memcpy(buf, "None", 5);
188 return buf;
189 case Type::UUID16:
190 *pos++ = '0';
191 *pos++ = 'x';
192 *pos++ = format_hex_pretty_char(this->uuid_.uuid16 >> 12);
193 *pos++ = format_hex_pretty_char((this->uuid_.uuid16 >> 8) & 0x0F);
194 *pos++ = format_hex_pretty_char((this->uuid_.uuid16 >> 4) & 0x0F);
195 *pos++ = format_hex_pretty_char(this->uuid_.uuid16 & 0x0F);
196 *pos = 0; // NUL-terminate
197 return buf;
198 case Type::UUID32:
199 *pos++ = '0';
200 *pos++ = 'x';
201 for (int shift = 28; shift >= 0; shift -= 4)
202 *pos++ = format_hex_pretty_char((this->uuid_.uuid32 >> shift) & 0x0F);
203 *pos = 0; // NUL-terminate
204 return buf;
205 default:
206 case Type::UUID128:
207 // Format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
208 for (int8_t i = 15; i >= 0; i--) {
209 uint8_t byte = this->uuid_.uuid128[i];
210 *pos++ = format_hex_pretty_char(byte >> 4);
211 *pos++ = format_hex_pretty_char(byte & 0x0F);
212 if (i == 12 || i == 10 || i == 8 || i == 6)
213 *pos++ = '-';
214 }
215 *pos = 0; // NUL-terminate
216 return buf;
217 }
218}
219
220void ESPBTUUID::to_128bit_(uint8_t out[16]) const {
221 // Bluetooth Base UUID 00000000-0000-1000-8000-00805F9B34FB (LSB-first), with the 16/32-bit
222 // value placed at bytes 12..; identical expansion to esp32_ble::ESPBTUUID::as_128bit().
223 // Callers screen out UNSET first (operator==, as_128bit); it would expand like 0x0000.
224 static const uint8_t BASE[16] = {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,
225 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
226 if (this->type_ == Type::UUID128) {
227 memcpy(out, this->uuid_.uuid128, 16);
228 return;
229 }
230 memcpy(out, BASE, 16);
231 const uint32_t value = (this->type_ == Type::UUID32) ? this->uuid_.uuid32 : this->uuid_.uuid16;
232 const size_t len = (this->type_ == Type::UUID32) ? 4 : 2;
233 for (size_t i = 0; i < len; i++)
234 out[12 + i] = (value >> (i * 8)) & 0xFF;
235}
236
237bool ESPBTUUID::operator==(const ESPBTUUID &other) const {
238 if (this->type_ == other.type_) {
239 switch (this->type_) {
240 case Type::UNSET:
241 return true;
242 case Type::UUID16:
243 return this->uuid_.uuid16 == other.uuid_.uuid16;
244 case Type::UUID32:
245 return this->uuid_.uuid32 == other.uuid_.uuid32;
246 case Type::UUID128:
247 return memcmp(this->uuid_.uuid128, other.uuid_.uuid128, 16) == 0;
248 }
249 return false;
250 }
251 // Unset never equals a set UUID; 0x0000 is a valid value, distinct from "not configured".
252 if (this->type_ == Type::UNSET || other.type_ == Type::UNSET)
253 return false;
254 // Different widths: expand both to the 128-bit Bluetooth Base UUID form and compare, so a
255 // configured 16/32-bit UUID matches the equivalent 128-bit advertisement (esp32 parity).
256 uint8_t a[16];
257 uint8_t b[16];
258 this->to_128bit_(a);
259 other.to_128bit_(b);
260 return memcmp(a, b, 16) == 0;
261}
262
263// ---------------------------------------------------------------------------
264// ESPBLEiBeacon
265// ---------------------------------------------------------------------------
266
267ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(this->beacon_data_)); }
268
269optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData &data, bool *prefix_rejected) {
270 // iBeacon manufacturer specific data (after company-ID bytes have been stripped):
271 // [0x02][0x15][16-byte UUID][2-byte major][2-byte minor][1-byte power] = exactly 23 bytes
272 if (!data.uuid.contains(0x4C, 0x00)) // Apple company ID 0x004C
273 return {};
274 if (data.data.size() != 23)
275 return {};
276 // Require the iBeacon sub-type/length prefix — stricter than the legacy
277 // esp32 parser, which accepted any 23-byte Apple payload and surfaced
278 // non-iBeacon frames as garbage beacons.
279 if (data.data[0] != 0x02 || data.data[1] != 0x15) {
280 if (prefix_rejected != nullptr)
281 *prefix_rejected = true;
282 return {};
283 }
284 return ESPBLEiBeacon(data.data.data());
285}
286
287// ---------------------------------------------------------------------------
288// ESPBTDevice
289// ---------------------------------------------------------------------------
290
291optional<ESPBLEiBeacon> ESPBTDevice::get_ibeacon() const {
292 bool prefix_rejected = false;
293 uint8_t rejected_sub_type = 0;
294 uint8_t rejected_len = 0;
295 for (const auto &it : this->manufacturer_datas_) {
296 bool rejected = false;
297 auto res = ESPBLEiBeacon::from_manufacturer_data(it, &rejected);
298 if (res.has_value())
299 return res;
300 if (rejected && !prefix_rejected) {
301 prefix_rejected = true;
302 rejected_sub_type = it.data[0];
303 rejected_len = it.data[1];
304 }
305 }
306 if (prefix_rejected) {
307 // Only when no beacon was found at all: these frames were accepted before
308 // the prefix check, so their disappearance must be observable at the
309 // default log level. Throttled so a chatty non-iBeacon Apple advertiser
310 // cannot flood the log; a different address may bypass the shared window
311 // so that advertiser cannot mask the device that actually regressed — but
312 // with a 1 s floor, or two alternating advertisers log every frame.
313 static uint32_t last_log = 0;
314 static uint64_t last_addr = 0;
315 const uint32_t now = millis();
316 const uint64_t addr = this->address_uint64();
317 const uint32_t since = now - last_log;
318 if (last_log == 0 || since > 60000 || (addr != last_addr && since > 1000)) {
319 last_log = now;
320 last_addr = addr;
321 char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
322 ESP_LOGD(TAG, "%s: 23-byte Apple frame without iBeacon prefix ignored (sub-type 0x%02X len 0x%02X)",
323 this->address_str_to(addr_buf), rejected_sub_type, rejected_len);
324 }
325 }
326 return {};
327}
328
329const char *ESPBTDevice::address_type_str() const {
330 switch (this->address_type_) {
331 case BLE_ADDR_TYPE_PUBLIC:
332 return "PUBLIC";
333 case BLE_ADDR_TYPE_RANDOM:
334 return "RANDOM";
335 case BLE_ADDR_TYPE_RPA_PUBLIC:
336 return "RPA_PUBLIC";
337 case BLE_ADDR_TYPE_RPA_RANDOM:
338 return "RPA_RANDOM";
339 default:
340 return "UNKNOWN";
341 }
342}
343
344void ESPBTDevice::from_scan_result(const uint8_t *mac, int rssi, uint8_t addr_type, const uint8_t *data,
345 uint16_t data_len) {
346 // Ingest is BLE controller order (LSB-first); store in printable (MSB-first)
347 // order so the raw address() accessor matches the historical esp32 layout.
348 for (uint8_t i = 0; i < 6; i++)
349 this->address_[i] = mac[5 - i];
350 this->address_type_ = addr_type;
351 this->rssi_ = rssi;
352 this->name_len_ = 0;
353 this->name_[0] = '\0';
354 this->service_uuids_.clear();
355 this->manufacturer_datas_.clear();
356 this->service_datas_.clear();
357 this->tx_powers_.clear();
358 this->appearance_.reset();
359 this->ad_flag_.reset();
360 this->parse_adv_(data, data_len);
361
362#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
363 char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
364 ESP_LOGVV(TAG,
365 "Parse Result:\n"
366 " Address: %s (%s)\n"
367 " RSSI: %d\n"
368 " Name: '%s'",
369 this->address_str_to(addr_buf), this->address_type_str(), this->rssi_, this->name_);
370 for (auto &it : this->tx_powers_) {
371 ESP_LOGVV(TAG, " TX Power: %d", it);
372 }
373 if (this->appearance_.has_value()) {
374 ESP_LOGVV(TAG, " Appearance: %u", *this->appearance_);
375 }
376 if (this->ad_flag_.has_value()) {
377 ESP_LOGVV(TAG, " Ad Flag: %u", *this->ad_flag_);
378 }
379 char uuid_buf[UUID_STR_LEN];
380 for (auto &uuid : this->service_uuids_) {
381 ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_str(uuid_buf));
382 }
383 char hex_buf[format_hex_pretty_size(BLE_ADV_MAX_LOG_BYTES)];
384 for (auto &mfg_data : this->manufacturer_datas_) {
385 auto ibeacon = ESPBLEiBeacon::from_manufacturer_data(mfg_data);
386 if (ibeacon.has_value()) {
387 ESP_LOGVV(TAG,
388 " Manufacturer iBeacon:\n"
389 " UUID: %s\n"
390 " Major: %u\n"
391 " Minor: %u\n"
392 " TXPower: %d",
393 ibeacon.value().get_uuid().to_str(uuid_buf), ibeacon.value().get_major(), ibeacon.value().get_minor(),
394 ibeacon.value().get_signal_power());
395 } else {
396 ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", mfg_data.uuid.to_str(uuid_buf),
397 format_hex_pretty_to(hex_buf, mfg_data.data.data(), mfg_data.data.size()));
398 }
399 }
400 for (auto &svc_data : this->service_datas_) {
401 ESP_LOGVV(TAG,
402 " Service data:\n"
403 " UUID: %s\n"
404 " Data: %s",
405 svc_data.uuid.to_str(uuid_buf),
406 format_hex_pretty_to(hex_buf, svc_data.data.data(), svc_data.data.size()));
407 }
408 ESP_LOGVV(TAG, " Adv data: %s", format_hex_pretty_to(hex_buf, data, data_len));
409#endif // ESPHOME_LOG_HAS_VERY_VERBOSE
410}
411
412// Remove before 2027.2.0
413std::string ESPBTDevice::address_str() const {
415 return std::string(this->address_str_to(buf));
416}
417
418const char *ESPBTDevice::address_str_to(char *buf) const {
419 // address_ is stored in printable (MSB-first) order.
421 return buf;
422}
423
425 // address_ is MSB-first; byte 0 of the result is the LSB (esp32 semantics).
426 uint64_t addr = 0;
427 for (int i = 0; i < 6; i++)
428 addr |= static_cast<uint64_t>(this->address_[i]) << ((5 - i) * 8);
429 return addr;
430}
431
432bool ESPBTDevice::resolve_irk(const uint8_t *irk) const {
433#ifdef USE_BLE_DEVICE_IRK
434 // Bluetooth Core 5.x "ah" function: hash = e(IRK, padding | prand)[low 24 bits].
435 // The resolvable private address is prand (top 3 bytes) | hash (bottom 3 bytes).
436 // Uses the portable software AES-128 shared with the CCM decryptor, so IRK
437 // matching behaves identically on every platform (volume is one block per
438 // advertisement from a matching RPA device — software AES is not a cost).
439 uint8_t ecb_plaintext[16] = {0};
440 uint8_t ecb_ciphertext[16];
441 const uint64_t addr64 = this->address_uint64();
442 ecb_plaintext[13] = (addr64 >> 40) & 0xff;
443 ecb_plaintext[14] = (addr64 >> 32) & 0xff;
444 ecb_plaintext[15] = (addr64 >> 24) & 0xff;
445 aes128_encrypt_block(irk, ecb_plaintext, ecb_ciphertext);
446 return ecb_ciphertext[15] == (addr64 & 0xff) && ecb_ciphertext[14] == ((addr64 >> 8) & 0xff) &&
447 ecb_ciphertext[13] == ((addr64 >> 16) & 0xff);
448#else
449 // No sensor configured an irk: in this build; the AES core is compiled out.
450 (void) irk;
451 return false;
452#endif
453}
454
455void ESPBTDevice::parse_adv_(const uint8_t *payload, uint16_t len) {
456 // BLE AD structure TLV: [length][type][value...]
457 // length includes the type byte.
458 uint16_t offset = 0;
459 while (offset < len) {
460 uint8_t ad_len = payload[offset++];
461 if (ad_len == 0)
462 continue; // possible zero-padded advertisement data (esp32_ble_tracker skips these too)
463 if (offset + ad_len > len)
464 break;
465 uint8_t ad_type = payload[offset];
466 const uint8_t *ad_data = &payload[offset + 1];
467 uint8_t ad_data_len = ad_len - 1;
468 offset += ad_len;
469
470 switch (ad_type) {
471 case 0x01: // Flags
472 if (ad_data_len >= 1)
473 this->ad_flag_ = ad_data[0];
474 break;
475
476 case 0x08: // Shortened Local Name
477 case 0x09: // Complete Local Name
478 // Keep the longest name seen — a merged adv + scan-response frame may carry both the
479 // shortened and the complete name, and the shortened form must never replace the
480 // complete one (same rule as esp32_ble_tracker's parse_adv_).
481 if (ad_data_len > this->name_len_) {
482 uint8_t name_len = ad_data_len > MAX_ADV_NAME_LEN ? MAX_ADV_NAME_LEN : static_cast<uint8_t>(ad_data_len);
483 memcpy(this->name_, ad_data, name_len);
484 this->name_[name_len] = '\0';
485 this->name_len_ = name_len;
486 }
487 break;
488
489 case 0x0A: // TX Power Level
490 if (ad_data_len >= 1)
491 this->tx_powers_.push_back(static_cast<int8_t>(ad_data[0]));
492 break;
493
494 case 0x19: // Appearance
495 if (ad_data_len >= 2)
496 this->appearance_ = static_cast<uint16_t>(ad_data[0]) | (static_cast<uint16_t>(ad_data[1]) << 8);
497 break;
498
499 case 0x02: // Incomplete List of 16-bit Service UUIDs
500 case 0x03: // Complete List of 16-bit Service UUIDs
501 for (uint8_t i = 0; (i + 1) < ad_data_len; i += 2) {
502 uint16_t uuid = (static_cast<uint16_t>(ad_data[i + 1]) << 8) | ad_data[i];
503 this->service_uuids_.push_back(ESPBTUUID::from_uint16(uuid));
504 }
505 break;
506
507 case 0x04: // Incomplete List of 32-bit Service UUIDs
508 case 0x05: // Complete List of 32-bit Service UUIDs
509 for (uint8_t i = 0; (i + 3) < ad_data_len; i += 4) {
510 uint32_t uuid = (static_cast<uint32_t>(ad_data[i + 3]) << 24) |
511 (static_cast<uint32_t>(ad_data[i + 2]) << 16) | (static_cast<uint32_t>(ad_data[i + 1]) << 8) |
512 ad_data[i];
513 this->service_uuids_.push_back(ESPBTUUID::from_uint32(uuid));
514 }
515 break;
516
517 case 0x06: // Incomplete List of 128-bit Service UUIDs
518 case 0x07: // Complete List of 128-bit Service UUIDs
519 for (uint8_t i = 0; (i + 15) < ad_data_len; i += 16)
520 this->service_uuids_.push_back(ESPBTUUID::from_raw(&ad_data[i]));
521 break;
522
523 case 0xFF: // Manufacturer Specific Data
524 if (ad_data_len >= 2) {
525 uint16_t company_id = (static_cast<uint16_t>(ad_data[1]) << 8) | ad_data[0];
526 ServiceData sd;
527 sd.uuid = ESPBTUUID::from_uint16(company_id);
528 sd.data.assign(ad_data + 2, ad_data + ad_data_len);
529 this->manufacturer_datas_.push_back(std::move(sd));
530 }
531 break;
532
533 case 0x16: // Service Data — 16-bit UUID
534 if (ad_data_len >= 2) {
535 uint16_t uuid = (static_cast<uint16_t>(ad_data[1]) << 8) | ad_data[0];
536 ServiceData sd;
537 sd.uuid = ESPBTUUID::from_uint16(uuid);
538 sd.data.assign(ad_data + 2, ad_data + ad_data_len);
539 this->service_datas_.push_back(std::move(sd));
540 }
541 break;
542
543 case 0x20: // Service Data — 32-bit UUID
544 if (ad_data_len >= 4) {
545 uint32_t uuid = (static_cast<uint32_t>(ad_data[3]) << 24) | (static_cast<uint32_t>(ad_data[2]) << 16) |
546 (static_cast<uint32_t>(ad_data[1]) << 8) | ad_data[0];
547 ServiceData sd;
548 sd.uuid = ESPBTUUID::from_uint32(uuid);
549 sd.data.assign(ad_data + 4, ad_data + ad_data_len);
550 this->service_datas_.push_back(std::move(sd));
551 }
552 break;
553
554 case 0x21: // Service Data — 128-bit UUID
555 if (ad_data_len >= 16) {
556 ServiceData sd;
557 sd.uuid = ESPBTUUID::from_raw(ad_data);
558 sd.data.assign(ad_data + 16, ad_data + ad_data_len);
559 this->service_datas_.push_back(std::move(sd));
560 }
561 break;
562
563 default:
564 break;
565 }
566 }
567}
568
569// ---------------------------------------------------------------------------
570// DiscoveredDeviceLog
571// ---------------------------------------------------------------------------
572
573void DiscoveredDeviceLog::log_device(const char *tag, const ESPBTDevice &device) {
574#ifdef ESPHOME_LOG_HAS_DEBUG
575 // Everything here feeds ESP_LOGD: below DEBUG the whole body (including the
576 // dedup vector growth) would be pure overhead, so compile it out entirely.
577 const uint64_t address = device.address_uint64();
578 for (auto &disc : this->already_discovered_) {
579 if (disc == address)
580 return;
581 }
582 this->already_discovered_.push_back(address);
583
585 ESP_LOGD(tag,
586 "Found device %s RSSI=%d\n"
587 " Address Type: %s",
588 device.address_str_to(addr_buf), device.get_rssi(), device.address_type_str());
589 if (!device.get_name().empty()) {
590 ESP_LOGD(tag, " Name: '%s'", device.get_name().c_str());
591 }
592 for (auto &tx_power : device.get_tx_powers()) {
593 ESP_LOGD(tag, " TX Power: %d", tx_power);
594 }
595#endif // ESPHOME_LOG_HAS_DEBUG
596}
597
598} // namespace esphome::ble_device_base
uint8_t address
Definition bl0906.h:4
constexpr const char * c_str() const
Definition string_ref.h:73
constexpr bool empty() const
Definition string_ref.h:76
void log_device(const char *tag, const ESPBTDevice &device)
Log the device at DEBUG the first time its MAC is seen this scan period.
static optional< ESPBLEiBeacon > from_manufacturer_data(const ServiceData &data, bool *prefix_rejected=nullptr)
prefix_rejected: caller must initialise to false; set to true ONLY when a 23-byte Apple frame was ref...
struct PACKED esphome::ble_device_base::ESPBLEiBeacon::BeaconData beacon_data_
ESPDEPRECATED("Use address_str_to() instead. Removed in 2027.2.0.", "2026.8.0") std const char * address_str_to(char *buf) const
Return MAC as "XX:XX:XX:XX:XX:XX" string.
void parse_scan_rst(const esp32_ble::BLEScanResult &scan_result)
Historical esp32 ingest (esp32 builds only): parse an ESP-IDF scan result.
std::vector< ESPBTUUID > service_uuids_
Definition ble_device.h:251
const std::vector< int8_t > & get_tx_powers() const
Definition ble_device.h:226
void from_scan_result(const uint8_t *mac, int rssi, uint8_t addr_type, const uint8_t *data, uint16_t data_len)
Populate from a raw scan result delivered by a BLE tracker backend.
static constexpr size_t MAC_ADDRESS_PRETTY_BUFFER_SIZE
Definition ble_device.h:185
char name_[MAX_ADV_NAME_LEN+1]
Definition ble_device.h:249
void parse_adv_(const uint8_t *payload, uint16_t len)
std::vector< ServiceData > service_datas_
Definition ble_device.h:253
const esp32_ble::BLEScanResult * scan_result_
Definition ble_device.h:255
std::vector< ServiceData > manufacturer_datas_
Definition ble_device.h:252
optional< ESPBLEiBeacon > get_ibeacon() const
uint64_t address_uint64() const
Return MAC as packed uint64 (byte 0 in LSB — matches esp32's address_uint64).
const char * address_type_str() const
Human-readable address type ("PUBLIC", "RANDOM", "RPA_PUBLIC", "RPA_RANDOM" or "UNKNOWN"),...
uint8_t address_[MAC_ADDRESS_SIZE]
Definition ble_device.h:244
static constexpr uint8_t MAX_ADV_NAME_LEN
Definition ble_device.h:242
StringRef get_name() const
Advertised name as a view into the fixed buffer (always NUL-terminated, so c_str() is safe); converts...
Definition ble_device.h:221
bool resolve_irk(const uint8_t *irk) const
Resolve a Resolvable Private Address against a 16-byte IRK (Bluetooth "ah" function,...
static ESPBTUUID from_uuid(esp_bt_uuid_t uuid)
Source compatibility with the historical esp32_ble API (esp32 builds only).
static ESPBTUUID from_uint16(uint16_t uuid)
static ESPBTUUID from_raw(const uint8_t *data)
Construct from raw 16-byte little-endian UUID.
bool contains(uint8_t data1, uint8_t data2) const
True if the UUID value contains the adjacent byte pair (data1, data2).
void to_128bit_(uint8_t out[16]) const
static ESPBTUUID from_raw_reversed(const uint8_t *data)
Construct from raw 16-byte big-endian UUID (reversed on store).
union esphome::ble_device_base::ESPBTUUID::@21 uuid_
static ESPBTUUID from_uint32(uint32_t uuid)
ESPBTUUID as_128bit() const
Expand to the 128-bit Bluetooth Base UUID form.
bool operator==(const ESPBTUUID &other) const
const char * to_str(char *buf) const
Write "0xABCD" / "0xABCDEF01" / the dashed 128-bit form, or "None" for an unset UUID,...
int ret
void aes128_encrypt_block(const uint8_t key[16], const uint8_t in[16], uint8_t out[16])
AES-128 single-block encrypt (the same software cipher CCM uses).
const char * tag
Definition log.h:74
ESPHOME_ALWAYS_INLINE char format_hex_pretty_char(uint8_t v)
Convert a nibble (0-15) to uppercase hex char (used for pretty printing)
Definition helpers.h:1279
const void size_t len
Definition hal.h:64
size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count)
Parse bytes from a hex-encoded string into a byte array.
Definition helpers.cpp:274
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:406
size_t size_t pos
Definition helpers.h:1062
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:1426
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
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:1493
static void uint32_t
uint16_t length
Definition tt21100.cpp:0