esphome 2025.6.0b2__py3-none-any.whl → 2025.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- esphome/components/api/api_pb2.cpp +2 -0
- esphome/components/api/api_pb2.h +1 -0
- esphome/components/esp32_ble/ble.cpp +108 -46
- esphome/components/esp32_ble/ble.h +2 -0
- esphome/components/esp32_ble/ble_event.h +242 -75
- esphome/components/esp32_ble/ble_event_pool.h +72 -0
- esphome/components/esp32_ble/queue.h +14 -11
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +1 -0
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +4 -0
- esphome/components/i2s_audio/i2s_audio.cpp +1 -1
- esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +37 -22
- esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +2 -0
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +28 -10
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +1 -0
- esphome/components/light/light_state.h +15 -15
- esphome/components/nextion/nextion.cpp +2 -10
- esphome/components/openthread/__init__.py +5 -5
- esphome/components/openthread/openthread.cpp +3 -3
- esphome/components/openthread/tlv.py +7 -0
- esphome/components/spi/spi_arduino.cpp +22 -9
- esphome/components/switch/switch.h +13 -7
- esphome/config_validation.py +44 -1
- esphome/const.py +1 -1
- esphome/yaml_util.py +2 -1
- {esphome-2025.6.0b2.dist-info → esphome-2025.6.1.dist-info}/METADATA +1 -1
- {esphome-2025.6.0b2.dist-info → esphome-2025.6.1.dist-info}/RECORD +30 -29
- {esphome-2025.6.0b2.dist-info → esphome-2025.6.1.dist-info}/WHEEL +0 -0
- {esphome-2025.6.0b2.dist-info → esphome-2025.6.1.dist-info}/entry_points.txt +0 -0
- {esphome-2025.6.0b2.dist-info → esphome-2025.6.1.dist-info}/licenses/LICENSE +0 -0
- {esphome-2025.6.0b2.dist-info → esphome-2025.6.1.dist-info}/top_level.txt +0 -0
@@ -516,6 +516,8 @@ template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::V
|
|
516
516
|
return "VOICE_ASSISTANT_TTS_STREAM_START";
|
517
517
|
case enums::VOICE_ASSISTANT_TTS_STREAM_END:
|
518
518
|
return "VOICE_ASSISTANT_TTS_STREAM_END";
|
519
|
+
case enums::VOICE_ASSISTANT_INTENT_PROGRESS:
|
520
|
+
return "VOICE_ASSISTANT_INTENT_PROGRESS";
|
519
521
|
default:
|
520
522
|
return "UNKNOWN";
|
521
523
|
}
|
esphome/components/api/api_pb2.h
CHANGED
@@ -208,6 +208,7 @@ enum VoiceAssistantEvent : uint32_t {
|
|
208
208
|
VOICE_ASSISTANT_STT_VAD_END = 12,
|
209
209
|
VOICE_ASSISTANT_TTS_STREAM_START = 98,
|
210
210
|
VOICE_ASSISTANT_TTS_STREAM_END = 99,
|
211
|
+
VOICE_ASSISTANT_INTENT_PROGRESS = 100,
|
211
212
|
};
|
212
213
|
enum VoiceAssistantTimerEvent : uint32_t {
|
213
214
|
VOICE_ASSISTANT_TIMER_STARTED = 0,
|
@@ -1,6 +1,7 @@
|
|
1
1
|
#ifdef USE_ESP32
|
2
2
|
|
3
3
|
#include "ble.h"
|
4
|
+
#include "ble_event_pool.h"
|
4
5
|
|
5
6
|
#include "esphome/core/application.h"
|
6
7
|
#include "esphome/core/log.h"
|
@@ -23,9 +24,6 @@ namespace esp32_ble {
|
|
23
24
|
|
24
25
|
static const char *const TAG = "esp32_ble";
|
25
26
|
|
26
|
-
static RAMAllocator<BLEEvent> EVENT_ALLOCATOR( // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
27
|
-
RAMAllocator<BLEEvent>::ALLOW_FAILURE | RAMAllocator<BLEEvent>::ALLOC_INTERNAL);
|
28
|
-
|
29
27
|
void ESP32BLE::setup() {
|
30
28
|
global_ble = this;
|
31
29
|
ESP_LOGCONFIG(TAG, "Running setup");
|
@@ -326,32 +324,77 @@ void ESP32BLE::loop() {
|
|
326
324
|
}
|
327
325
|
case BLEEvent::GAP: {
|
328
326
|
esp_gap_ble_cb_event_t gap_event = ble_event->event_.gap.gap_event;
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
scan_handler->
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
//
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
327
|
+
switch (gap_event) {
|
328
|
+
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
329
|
+
// Use the new scan event handler - no memcpy!
|
330
|
+
for (auto *scan_handler : this->gap_scan_event_handlers_) {
|
331
|
+
scan_handler->gap_scan_event_handler(ble_event->scan_result());
|
332
|
+
}
|
333
|
+
break;
|
334
|
+
|
335
|
+
// Scan complete events
|
336
|
+
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
337
|
+
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
338
|
+
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
339
|
+
// All three scan complete events have the same structure with just status
|
340
|
+
// The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe
|
341
|
+
// This is verified at compile-time by static_assert checks in ble_event.h
|
342
|
+
// The struct already contains our copy of the status (copied in BLEEvent constructor)
|
343
|
+
ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
|
344
|
+
for (auto *gap_handler : this->gap_event_handlers_) {
|
345
|
+
gap_handler->gap_event_handler(
|
346
|
+
gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.scan_complete));
|
347
|
+
}
|
348
|
+
break;
|
349
|
+
|
350
|
+
// Advertising complete events
|
351
|
+
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
|
352
|
+
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
|
353
|
+
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
|
354
|
+
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
355
|
+
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
356
|
+
// All advertising complete events have the same structure with just status
|
357
|
+
ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
|
358
|
+
for (auto *gap_handler : this->gap_event_handlers_) {
|
359
|
+
gap_handler->gap_event_handler(
|
360
|
+
gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.adv_complete));
|
361
|
+
}
|
362
|
+
break;
|
363
|
+
|
364
|
+
// RSSI complete event
|
365
|
+
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
|
366
|
+
ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
|
367
|
+
for (auto *gap_handler : this->gap_event_handlers_) {
|
368
|
+
gap_handler->gap_event_handler(
|
369
|
+
gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.read_rssi_complete));
|
370
|
+
}
|
371
|
+
break;
|
372
|
+
|
373
|
+
// Security events
|
374
|
+
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
375
|
+
case ESP_GAP_BLE_SEC_REQ_EVT:
|
376
|
+
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT:
|
377
|
+
case ESP_GAP_BLE_PASSKEY_REQ_EVT:
|
378
|
+
case ESP_GAP_BLE_NC_REQ_EVT:
|
379
|
+
ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
|
380
|
+
for (auto *gap_handler : this->gap_event_handlers_) {
|
381
|
+
gap_handler->gap_event_handler(
|
382
|
+
gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.security));
|
383
|
+
}
|
384
|
+
break;
|
385
|
+
|
386
|
+
default:
|
387
|
+
// Unknown/unhandled event
|
388
|
+
ESP_LOGW(TAG, "Unhandled GAP event type in loop: %d", gap_event);
|
389
|
+
break;
|
346
390
|
}
|
347
391
|
break;
|
348
392
|
}
|
349
393
|
default:
|
350
394
|
break;
|
351
395
|
}
|
352
|
-
//
|
353
|
-
ble_event
|
354
|
-
EVENT_ALLOCATOR.deallocate(ble_event, 1);
|
396
|
+
// Return the event to the pool
|
397
|
+
this->ble_event_pool_.release(ble_event);
|
355
398
|
ble_event = this->ble_events_.pop();
|
356
399
|
}
|
357
400
|
if (this->advertising_ != nullptr) {
|
@@ -359,37 +402,41 @@ void ESP32BLE::loop() {
|
|
359
402
|
}
|
360
403
|
|
361
404
|
// Log dropped events periodically
|
362
|
-
|
405
|
+
uint16_t dropped = this->ble_events_.get_and_reset_dropped_count();
|
363
406
|
if (dropped > 0) {
|
364
|
-
ESP_LOGW(TAG, "Dropped %
|
407
|
+
ESP_LOGW(TAG, "Dropped %u BLE events due to buffer overflow", dropped);
|
365
408
|
}
|
366
409
|
}
|
367
410
|
|
411
|
+
// Helper function to load new event data based on type
|
412
|
+
void load_ble_event(BLEEvent *event, esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
413
|
+
event->load_gap_event(e, p);
|
414
|
+
}
|
415
|
+
|
416
|
+
void load_ble_event(BLEEvent *event, esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
417
|
+
event->load_gattc_event(e, i, p);
|
418
|
+
}
|
419
|
+
|
420
|
+
void load_ble_event(BLEEvent *event, esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
421
|
+
event->load_gatts_event(e, i, p);
|
422
|
+
}
|
423
|
+
|
368
424
|
template<typename... Args> void enqueue_ble_event(Args... args) {
|
369
|
-
//
|
370
|
-
|
371
|
-
|
425
|
+
// Allocate an event from the pool
|
426
|
+
BLEEvent *event = global_ble->ble_event_pool_.allocate();
|
427
|
+
if (event == nullptr) {
|
428
|
+
// No events available - queue is full or we're out of memory
|
372
429
|
global_ble->ble_events_.increment_dropped_count();
|
373
430
|
return;
|
374
431
|
}
|
375
432
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
// Push the event - since we're the only producer and we checked full() above,
|
385
|
-
// this should always succeed unless we have a bug
|
386
|
-
if (!global_ble->ble_events_.push(new_event)) {
|
387
|
-
// This should not happen in SPSC queue with single producer
|
388
|
-
ESP_LOGE(TAG, "BLE queue push failed unexpectedly");
|
389
|
-
new_event->~BLEEvent();
|
390
|
-
EVENT_ALLOCATOR.deallocate(new_event, 1);
|
391
|
-
}
|
392
|
-
} // NOLINT(clang-analyzer-unix.Malloc)
|
433
|
+
// Load new event data (replaces previous event)
|
434
|
+
load_ble_event(event, args...);
|
435
|
+
|
436
|
+
// Push the event to the queue
|
437
|
+
global_ble->ble_events_.push(event);
|
438
|
+
// Push always succeeds because we're the only producer and the pool ensures we never exceed queue size
|
439
|
+
}
|
393
440
|
|
394
441
|
// Explicit template instantiations for the friend function
|
395
442
|
template void enqueue_ble_event(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *);
|
@@ -398,11 +445,26 @@ template void enqueue_ble_event(esp_gattc_cb_event_t, esp_gatt_if_t, esp_ble_gat
|
|
398
445
|
|
399
446
|
void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
400
447
|
switch (event) {
|
401
|
-
//
|
448
|
+
// Queue GAP events that components need to handle
|
449
|
+
// Scanning events - used by esp32_ble_tracker
|
402
450
|
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
403
451
|
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
404
452
|
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
405
453
|
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
454
|
+
// Advertising events - used by esp32_ble_beacon and esp32_ble server
|
455
|
+
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
|
456
|
+
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
|
457
|
+
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
|
458
|
+
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
459
|
+
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
460
|
+
// Connection events - used by ble_client
|
461
|
+
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
|
462
|
+
// Security events - used by ble_client and bluetooth_proxy
|
463
|
+
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
464
|
+
case ESP_GAP_BLE_SEC_REQ_EVT:
|
465
|
+
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT:
|
466
|
+
case ESP_GAP_BLE_PASSKEY_REQ_EVT:
|
467
|
+
case ESP_GAP_BLE_NC_REQ_EVT:
|
406
468
|
enqueue_ble_event(event, param);
|
407
469
|
return;
|
408
470
|
|
@@ -12,6 +12,7 @@
|
|
12
12
|
#include "esphome/core/helpers.h"
|
13
13
|
|
14
14
|
#include "ble_event.h"
|
15
|
+
#include "ble_event_pool.h"
|
15
16
|
#include "queue.h"
|
16
17
|
|
17
18
|
#ifdef USE_ESP32
|
@@ -148,6 +149,7 @@ class ESP32BLE : public Component {
|
|
148
149
|
BLEComponentState state_{BLE_COMPONENT_STATE_OFF};
|
149
150
|
|
150
151
|
LockFreeQueue<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_events_;
|
152
|
+
BLEEventPool<MAX_BLE_QUEUE_SIZE> ble_event_pool_;
|
151
153
|
BLEAdvertising *advertising_{};
|
152
154
|
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
|
153
155
|
uint32_t advertising_cycle_time_{};
|
@@ -24,16 +24,45 @@ static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param) == si
|
|
24
24
|
"ESP-IDF scan_stop_cmpl structure has unexpected size");
|
25
25
|
|
26
26
|
// Verify the status field is at offset 0 (first member)
|
27
|
-
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_param_cmpl.status) ==
|
28
|
-
offsetof(esp_ble_gap_cb_param_t, scan_param_cmpl),
|
27
|
+
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_param_cmpl.status) == 0,
|
29
28
|
"status must be first member of scan_param_cmpl");
|
30
|
-
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_start_cmpl.status) ==
|
31
|
-
offsetof(esp_ble_gap_cb_param_t, scan_start_cmpl),
|
29
|
+
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_start_cmpl.status) == 0,
|
32
30
|
"status must be first member of scan_start_cmpl");
|
33
|
-
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl.status) ==
|
34
|
-
offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl),
|
31
|
+
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl.status) == 0,
|
35
32
|
"status must be first member of scan_stop_cmpl");
|
36
33
|
|
34
|
+
// Compile-time verification for advertising complete events
|
35
|
+
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_data_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
36
|
+
"ESP-IDF adv_data_cmpl structure has unexpected size");
|
37
|
+
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_rsp_data_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
38
|
+
"ESP-IDF scan_rsp_data_cmpl structure has unexpected size");
|
39
|
+
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_data_raw_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
40
|
+
"ESP-IDF adv_data_raw_cmpl structure has unexpected size");
|
41
|
+
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_start_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
42
|
+
"ESP-IDF adv_start_cmpl structure has unexpected size");
|
43
|
+
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_stop_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
44
|
+
"ESP-IDF adv_stop_cmpl structure has unexpected size");
|
45
|
+
|
46
|
+
// Verify the status field is at offset 0 for advertising events
|
47
|
+
static_assert(offsetof(esp_ble_gap_cb_param_t, adv_data_cmpl.status) == 0,
|
48
|
+
"status must be first member of adv_data_cmpl");
|
49
|
+
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_rsp_data_cmpl.status) == 0,
|
50
|
+
"status must be first member of scan_rsp_data_cmpl");
|
51
|
+
static_assert(offsetof(esp_ble_gap_cb_param_t, adv_data_raw_cmpl.status) == 0,
|
52
|
+
"status must be first member of adv_data_raw_cmpl");
|
53
|
+
static_assert(offsetof(esp_ble_gap_cb_param_t, adv_start_cmpl.status) == 0,
|
54
|
+
"status must be first member of adv_start_cmpl");
|
55
|
+
static_assert(offsetof(esp_ble_gap_cb_param_t, adv_stop_cmpl.status) == 0,
|
56
|
+
"status must be first member of adv_stop_cmpl");
|
57
|
+
|
58
|
+
// Compile-time verification for RSSI complete event structure
|
59
|
+
static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.status) == 0,
|
60
|
+
"status must be first member of read_rssi_cmpl");
|
61
|
+
static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.rssi) == sizeof(esp_bt_status_t),
|
62
|
+
"rssi must immediately follow status in read_rssi_cmpl");
|
63
|
+
static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.remote_addr) == sizeof(esp_bt_status_t) + sizeof(int8_t),
|
64
|
+
"remote_addr must follow rssi in read_rssi_cmpl");
|
65
|
+
|
37
66
|
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
38
67
|
// This class stores each event with minimal memory usage.
|
39
68
|
// GAP events (99% of traffic) don't have the vector overhead.
|
@@ -51,6 +80,13 @@ static_assert(offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl.status) ==
|
|
51
80
|
// - GATTC/GATTS events: We heap-allocate and copy the entire param struct, ensuring
|
52
81
|
// the data remains valid even after the BLE callback returns. The original
|
53
82
|
// param pointer from ESP-IDF is only valid during the callback.
|
83
|
+
//
|
84
|
+
// CRITICAL DESIGN NOTE:
|
85
|
+
// The heap allocations for GATTC/GATTS events are REQUIRED for memory safety.
|
86
|
+
// DO NOT attempt to optimize by removing these allocations or storing pointers
|
87
|
+
// to the original ESP-IDF data. The ESP-IDF callback data has a different lifetime
|
88
|
+
// than our event processing, and accessing it after the callback returns would
|
89
|
+
// result in use-after-free bugs and crashes.
|
54
90
|
class BLEEvent {
|
55
91
|
public:
|
56
92
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
@@ -60,19 +96,155 @@ class BLEEvent {
|
|
60
96
|
GATTS,
|
61
97
|
};
|
62
98
|
|
99
|
+
// Type definitions for cleaner method signatures
|
100
|
+
struct StatusOnlyData {
|
101
|
+
esp_bt_status_t status;
|
102
|
+
};
|
103
|
+
|
104
|
+
struct RSSICompleteData {
|
105
|
+
esp_bt_status_t status;
|
106
|
+
int8_t rssi;
|
107
|
+
esp_bd_addr_t remote_addr;
|
108
|
+
};
|
109
|
+
|
63
110
|
// Constructor for GAP events - no external allocations needed
|
64
111
|
BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
65
112
|
this->type_ = GAP;
|
113
|
+
this->init_gap_data_(e, p);
|
114
|
+
}
|
115
|
+
|
116
|
+
// Constructor for GATTC events - uses heap allocation
|
117
|
+
// IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization.
|
118
|
+
// The param pointer from ESP-IDF is only valid during the callback execution.
|
119
|
+
// Since BLE events are processed asynchronously in the main loop, we must create
|
120
|
+
// our own copy to ensure the data remains valid until the event is processed.
|
121
|
+
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
122
|
+
this->type_ = GATTC;
|
123
|
+
this->init_gattc_data_(e, i, p);
|
124
|
+
}
|
125
|
+
|
126
|
+
// Constructor for GATTS events - uses heap allocation
|
127
|
+
// IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization.
|
128
|
+
// The param pointer from ESP-IDF is only valid during the callback execution.
|
129
|
+
// Since BLE events are processed asynchronously in the main loop, we must create
|
130
|
+
// our own copy to ensure the data remains valid until the event is processed.
|
131
|
+
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
132
|
+
this->type_ = GATTS;
|
133
|
+
this->init_gatts_data_(e, i, p);
|
134
|
+
}
|
135
|
+
|
136
|
+
// Destructor to clean up heap allocations
|
137
|
+
~BLEEvent() { this->cleanup_heap_data(); }
|
138
|
+
|
139
|
+
// Default constructor for pre-allocation in pool
|
140
|
+
BLEEvent() : type_(GAP) {}
|
141
|
+
|
142
|
+
// Clean up any heap-allocated data
|
143
|
+
void cleanup_heap_data() {
|
144
|
+
if (this->type_ == GAP) {
|
145
|
+
return;
|
146
|
+
}
|
147
|
+
if (this->type_ == GATTC) {
|
148
|
+
delete this->event_.gattc.gattc_param;
|
149
|
+
delete this->event_.gattc.data;
|
150
|
+
this->event_.gattc.gattc_param = nullptr;
|
151
|
+
this->event_.gattc.data = nullptr;
|
152
|
+
return;
|
153
|
+
}
|
154
|
+
if (this->type_ == GATTS) {
|
155
|
+
delete this->event_.gatts.gatts_param;
|
156
|
+
delete this->event_.gatts.data;
|
157
|
+
this->event_.gatts.gatts_param = nullptr;
|
158
|
+
this->event_.gatts.data = nullptr;
|
159
|
+
}
|
160
|
+
}
|
161
|
+
|
162
|
+
// Load new event data for reuse (replaces previous event data)
|
163
|
+
void load_gap_event(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
164
|
+
this->cleanup_heap_data();
|
165
|
+
this->type_ = GAP;
|
166
|
+
this->init_gap_data_(e, p);
|
167
|
+
}
|
168
|
+
|
169
|
+
void load_gattc_event(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
170
|
+
this->cleanup_heap_data();
|
171
|
+
this->type_ = GATTC;
|
172
|
+
this->init_gattc_data_(e, i, p);
|
173
|
+
}
|
174
|
+
|
175
|
+
void load_gatts_event(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
176
|
+
this->cleanup_heap_data();
|
177
|
+
this->type_ = GATTS;
|
178
|
+
this->init_gatts_data_(e, i, p);
|
179
|
+
}
|
180
|
+
|
181
|
+
// Disable copy to prevent double-delete
|
182
|
+
BLEEvent(const BLEEvent &) = delete;
|
183
|
+
BLEEvent &operator=(const BLEEvent &) = delete;
|
184
|
+
|
185
|
+
union {
|
186
|
+
// NOLINTNEXTLINE(readability-identifier-naming)
|
187
|
+
struct gap_event {
|
188
|
+
esp_gap_ble_cb_event_t gap_event;
|
189
|
+
union {
|
190
|
+
BLEScanResult scan_result; // 73 bytes - Used by: esp32_ble_tracker
|
191
|
+
// This matches ESP-IDF's scan complete event structures
|
192
|
+
// All three (scan_param_cmpl, scan_start_cmpl, scan_stop_cmpl) have identical layout
|
193
|
+
// Used by: esp32_ble_tracker
|
194
|
+
StatusOnlyData scan_complete; // 1 byte
|
195
|
+
// Advertising complete events all have same structure
|
196
|
+
// Used by: esp32_ble_beacon, esp32_ble server components
|
197
|
+
// ADV_DATA_SET, SCAN_RSP_DATA_SET, ADV_DATA_RAW_SET, ADV_START, ADV_STOP
|
198
|
+
StatusOnlyData adv_complete; // 1 byte
|
199
|
+
// RSSI complete event
|
200
|
+
// Used by: ble_client (ble_rssi_sensor component)
|
201
|
+
RSSICompleteData read_rssi_complete; // 8 bytes
|
202
|
+
// Security events - we store the full security union
|
203
|
+
// Used by: ble_client (automation), bluetooth_proxy, esp32_ble_client
|
204
|
+
esp_ble_sec_t security; // Variable size, but fits within scan_result size
|
205
|
+
};
|
206
|
+
} gap; // 80 bytes total
|
207
|
+
|
208
|
+
// NOLINTNEXTLINE(readability-identifier-naming)
|
209
|
+
struct gattc_event {
|
210
|
+
esp_gattc_cb_event_t gattc_event;
|
211
|
+
esp_gatt_if_t gattc_if;
|
212
|
+
esp_ble_gattc_cb_param_t *gattc_param; // Heap-allocated
|
213
|
+
std::vector<uint8_t> *data; // Heap-allocated
|
214
|
+
} gattc; // 16 bytes (pointers only)
|
215
|
+
|
216
|
+
// NOLINTNEXTLINE(readability-identifier-naming)
|
217
|
+
struct gatts_event {
|
218
|
+
esp_gatts_cb_event_t gatts_event;
|
219
|
+
esp_gatt_if_t gatts_if;
|
220
|
+
esp_ble_gatts_cb_param_t *gatts_param; // Heap-allocated
|
221
|
+
std::vector<uint8_t> *data; // Heap-allocated
|
222
|
+
} gatts; // 16 bytes (pointers only)
|
223
|
+
} event_; // 80 bytes
|
224
|
+
|
225
|
+
ble_event_t type_;
|
226
|
+
|
227
|
+
// Helper methods to access event data
|
228
|
+
ble_event_t type() const { return type_; }
|
229
|
+
esp_gap_ble_cb_event_t gap_event_type() const { return event_.gap.gap_event; }
|
230
|
+
const BLEScanResult &scan_result() const { return event_.gap.scan_result; }
|
231
|
+
esp_bt_status_t scan_complete_status() const { return event_.gap.scan_complete.status; }
|
232
|
+
esp_bt_status_t adv_complete_status() const { return event_.gap.adv_complete.status; }
|
233
|
+
const RSSICompleteData &read_rssi_complete() const { return event_.gap.read_rssi_complete; }
|
234
|
+
const esp_ble_sec_t &security() const { return event_.gap.security; }
|
235
|
+
|
236
|
+
private:
|
237
|
+
// Initialize GAP event data
|
238
|
+
void init_gap_data_(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
66
239
|
this->event_.gap.gap_event = e;
|
67
240
|
|
68
241
|
if (p == nullptr) {
|
69
242
|
return; // Invalid event, but we can't log in header file
|
70
243
|
}
|
71
244
|
|
72
|
-
//
|
245
|
+
// Copy data based on event type
|
73
246
|
switch (e) {
|
74
247
|
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
75
|
-
// Copy only the fields we use from scan results
|
76
248
|
memcpy(this->event_.gap.scan_result.bda, p->scan_rst.bda, sizeof(esp_bd_addr_t));
|
77
249
|
this->event_.gap.scan_result.ble_addr_type = p->scan_rst.ble_addr_type;
|
78
250
|
this->event_.gap.scan_result.rssi = p->scan_rst.rssi;
|
@@ -95,16 +267,53 @@ class BLEEvent {
|
|
95
267
|
this->event_.gap.scan_complete.status = p->scan_stop_cmpl.status;
|
96
268
|
break;
|
97
269
|
|
270
|
+
// Advertising complete events - all have same structure with just status
|
271
|
+
// Used by: esp32_ble_beacon, esp32_ble server components
|
272
|
+
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
|
273
|
+
this->event_.gap.adv_complete.status = p->adv_data_cmpl.status;
|
274
|
+
break;
|
275
|
+
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
|
276
|
+
this->event_.gap.adv_complete.status = p->scan_rsp_data_cmpl.status;
|
277
|
+
break;
|
278
|
+
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: // Used by: esp32_ble_beacon
|
279
|
+
this->event_.gap.adv_complete.status = p->adv_data_raw_cmpl.status;
|
280
|
+
break;
|
281
|
+
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: // Used by: esp32_ble_beacon
|
282
|
+
this->event_.gap.adv_complete.status = p->adv_start_cmpl.status;
|
283
|
+
break;
|
284
|
+
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: // Used by: esp32_ble_beacon
|
285
|
+
this->event_.gap.adv_complete.status = p->adv_stop_cmpl.status;
|
286
|
+
break;
|
287
|
+
|
288
|
+
// RSSI complete event
|
289
|
+
// Used by: ble_client (ble_rssi_sensor)
|
290
|
+
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
|
291
|
+
this->event_.gap.read_rssi_complete.status = p->read_rssi_cmpl.status;
|
292
|
+
this->event_.gap.read_rssi_complete.rssi = p->read_rssi_cmpl.rssi;
|
293
|
+
memcpy(this->event_.gap.read_rssi_complete.remote_addr, p->read_rssi_cmpl.remote_addr, sizeof(esp_bd_addr_t));
|
294
|
+
break;
|
295
|
+
|
296
|
+
// Security events - copy the entire security union
|
297
|
+
// Used by: ble_client, bluetooth_proxy, esp32_ble_client
|
298
|
+
case ESP_GAP_BLE_AUTH_CMPL_EVT: // Used by: bluetooth_proxy, esp32_ble_client
|
299
|
+
case ESP_GAP_BLE_SEC_REQ_EVT: // Used by: esp32_ble_client
|
300
|
+
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: // Used by: ble_client automation
|
301
|
+
case ESP_GAP_BLE_PASSKEY_REQ_EVT: // Used by: ble_client automation
|
302
|
+
case ESP_GAP_BLE_NC_REQ_EVT: // Used by: ble_client automation
|
303
|
+
memcpy(&this->event_.gap.security, &p->ble_security, sizeof(esp_ble_sec_t));
|
304
|
+
break;
|
305
|
+
|
98
306
|
default:
|
99
|
-
// We only
|
307
|
+
// We only store data for GAP events that components currently use
|
308
|
+
// Unknown events still get queued and logged in ble.cpp:375 as
|
309
|
+
// "Unhandled GAP event type in loop" - this helps identify new events
|
310
|
+
// that components might need in the future
|
100
311
|
break;
|
101
312
|
}
|
102
313
|
}
|
103
314
|
|
104
|
-
//
|
105
|
-
|
106
|
-
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
107
|
-
this->type_ = GATTC;
|
315
|
+
// Initialize GATTC event data
|
316
|
+
void init_gattc_data_(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
108
317
|
this->event_.gattc.gattc_event = e;
|
109
318
|
this->event_.gattc.gattc_if = i;
|
110
319
|
|
@@ -117,9 +326,15 @@ class BLEEvent {
|
|
117
326
|
// Heap-allocate param and data
|
118
327
|
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
|
119
328
|
// while GAP events (99%) are stored inline to minimize memory usage
|
329
|
+
// IMPORTANT: This heap allocation provides clear ownership semantics:
|
330
|
+
// - The BLEEvent owns the allocated memory for its lifetime
|
331
|
+
// - The data remains valid from the BLE callback context until processed in the main loop
|
332
|
+
// - Without this copy, we'd have use-after-free bugs as ESP-IDF reuses the callback memory
|
120
333
|
this->event_.gattc.gattc_param = new esp_ble_gattc_cb_param_t(*p);
|
121
334
|
|
122
335
|
// Copy data for events that need it
|
336
|
+
// The param struct contains pointers (e.g., notify.value) that point to temporary buffers.
|
337
|
+
// We must copy this data to ensure it remains valid when the event is processed later.
|
123
338
|
switch (e) {
|
124
339
|
case ESP_GATTC_NOTIFY_EVT:
|
125
340
|
this->event_.gattc.data = new std::vector<uint8_t>(p->notify.value, p->notify.value + p->notify.value_len);
|
@@ -136,10 +351,8 @@ class BLEEvent {
|
|
136
351
|
}
|
137
352
|
}
|
138
353
|
|
139
|
-
//
|
140
|
-
|
141
|
-
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
142
|
-
this->type_ = GATTS;
|
354
|
+
// Initialize GATTS event data
|
355
|
+
void init_gatts_data_(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
143
356
|
this->event_.gatts.gatts_event = e;
|
144
357
|
this->event_.gatts.gatts_if = i;
|
145
358
|
|
@@ -152,9 +365,15 @@ class BLEEvent {
|
|
152
365
|
// Heap-allocate param and data
|
153
366
|
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
|
154
367
|
// while GAP events (99%) are stored inline to minimize memory usage
|
368
|
+
// IMPORTANT: This heap allocation provides clear ownership semantics:
|
369
|
+
// - The BLEEvent owns the allocated memory for its lifetime
|
370
|
+
// - The data remains valid from the BLE callback context until processed in the main loop
|
371
|
+
// - Without this copy, we'd have use-after-free bugs as ESP-IDF reuses the callback memory
|
155
372
|
this->event_.gatts.gatts_param = new esp_ble_gatts_cb_param_t(*p);
|
156
373
|
|
157
374
|
// Copy data for events that need it
|
375
|
+
// The param struct contains pointers (e.g., write.value) that point to temporary buffers.
|
376
|
+
// We must copy this data to ensure it remains valid when the event is processed later.
|
158
377
|
switch (e) {
|
159
378
|
case ESP_GATTS_WRITE_EVT:
|
160
379
|
this->event_.gatts.data = new std::vector<uint8_t>(p->write.value, p->write.value + p->write.len);
|
@@ -165,66 +384,14 @@ class BLEEvent {
|
|
165
384
|
break;
|
166
385
|
}
|
167
386
|
}
|
387
|
+
};
|
168
388
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
case GATTC:
|
173
|
-
delete this->event_.gattc.gattc_param;
|
174
|
-
delete this->event_.gattc.data;
|
175
|
-
break;
|
176
|
-
case GATTS:
|
177
|
-
delete this->event_.gatts.gatts_param;
|
178
|
-
delete this->event_.gatts.data;
|
179
|
-
break;
|
180
|
-
default:
|
181
|
-
break;
|
182
|
-
}
|
183
|
-
}
|
184
|
-
|
185
|
-
// Disable copy to prevent double-delete
|
186
|
-
BLEEvent(const BLEEvent &) = delete;
|
187
|
-
BLEEvent &operator=(const BLEEvent &) = delete;
|
188
|
-
|
189
|
-
union {
|
190
|
-
// NOLINTNEXTLINE(readability-identifier-naming)
|
191
|
-
struct gap_event {
|
192
|
-
esp_gap_ble_cb_event_t gap_event;
|
193
|
-
union {
|
194
|
-
BLEScanResult scan_result; // 73 bytes
|
195
|
-
// This matches ESP-IDF's scan complete event structures
|
196
|
-
// All three (scan_param_cmpl, scan_start_cmpl, scan_stop_cmpl) have identical layout
|
197
|
-
struct {
|
198
|
-
esp_bt_status_t status;
|
199
|
-
} scan_complete; // 1 byte
|
200
|
-
};
|
201
|
-
} gap; // 80 bytes total
|
202
|
-
|
203
|
-
// NOLINTNEXTLINE(readability-identifier-naming)
|
204
|
-
struct gattc_event {
|
205
|
-
esp_gattc_cb_event_t gattc_event;
|
206
|
-
esp_gatt_if_t gattc_if;
|
207
|
-
esp_ble_gattc_cb_param_t *gattc_param; // Heap-allocated
|
208
|
-
std::vector<uint8_t> *data; // Heap-allocated
|
209
|
-
} gattc; // 16 bytes (pointers only)
|
210
|
-
|
211
|
-
// NOLINTNEXTLINE(readability-identifier-naming)
|
212
|
-
struct gatts_event {
|
213
|
-
esp_gatts_cb_event_t gatts_event;
|
214
|
-
esp_gatt_if_t gatts_if;
|
215
|
-
esp_ble_gatts_cb_param_t *gatts_param; // Heap-allocated
|
216
|
-
std::vector<uint8_t> *data; // Heap-allocated
|
217
|
-
} gatts; // 16 bytes (pointers only)
|
218
|
-
} event_; // 80 bytes
|
219
|
-
|
220
|
-
ble_event_t type_;
|
389
|
+
// Verify the gap_event struct hasn't grown beyond expected size
|
390
|
+
// The gap member in the union should be 80 bytes (including the gap_event enum)
|
391
|
+
static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)) <= 80, "gap_event struct has grown beyond 80 bytes");
|
221
392
|
|
222
|
-
|
223
|
-
|
224
|
-
esp_gap_ble_cb_event_t gap_event_type() const { return event_.gap.gap_event; }
|
225
|
-
const BLEScanResult &scan_result() const { return event_.gap.scan_result; }
|
226
|
-
esp_bt_status_t scan_complete_status() const { return event_.gap.scan_complete.status; }
|
227
|
-
};
|
393
|
+
// Verify esp_ble_sec_t fits within our union
|
394
|
+
static_assert(sizeof(esp_ble_sec_t) <= 73, "esp_ble_sec_t is larger than BLEScanResult");
|
228
395
|
|
229
396
|
// BLEEvent total size: 84 bytes (80 byte union + 1 byte type + 3 bytes padding)
|
230
397
|
|