esphome 2025.6.0b3__py3-none-any.whl → 2025.6.2__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.
Files changed (31) hide show
  1. esphome/components/api/api_pb2.cpp +2 -0
  2. esphome/components/api/api_pb2.h +1 -0
  3. esphome/components/audio/audio_reader.cpp +41 -21
  4. esphome/components/esp32/__init__.py +2 -2
  5. esphome/components/esp32_ble/ble.cpp +108 -46
  6. esphome/components/esp32_ble/ble.h +2 -0
  7. esphome/components/esp32_ble/ble_event.h +242 -75
  8. esphome/components/esp32_ble/ble_event_pool.h +72 -0
  9. esphome/components/esp32_ble/queue.h +14 -11
  10. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +1 -0
  11. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +4 -0
  12. esphome/components/i2c/__init__.py +33 -0
  13. esphome/components/lvgl/widgets/qrcode.py +4 -6
  14. esphome/components/mcp23xxx_base/mcp23xxx_base.cpp +5 -1
  15. esphome/components/nextion/nextion.cpp +2 -10
  16. esphome/components/openthread/__init__.py +5 -5
  17. esphome/components/openthread/openthread.cpp +3 -3
  18. esphome/components/openthread/tlv.py +7 -0
  19. esphome/components/speaker/media_player/audio_pipeline.cpp +12 -13
  20. esphome/components/voice_assistant/__init__.py +12 -1
  21. esphome/components/voice_assistant/voice_assistant.cpp +36 -2
  22. esphome/components/voice_assistant/voice_assistant.h +4 -0
  23. esphome/config_validation.py +44 -1
  24. esphome/const.py +1 -1
  25. esphome/yaml_util.py +2 -1
  26. {esphome-2025.6.0b3.dist-info → esphome-2025.6.2.dist-info}/METADATA +1 -1
  27. {esphome-2025.6.0b3.dist-info → esphome-2025.6.2.dist-info}/RECORD +31 -30
  28. {esphome-2025.6.0b3.dist-info → esphome-2025.6.2.dist-info}/WHEEL +0 -0
  29. {esphome-2025.6.0b3.dist-info → esphome-2025.6.2.dist-info}/entry_points.txt +0 -0
  30. {esphome-2025.6.0b3.dist-info → esphome-2025.6.2.dist-info}/licenses/LICENSE +0 -0
  31. {esphome-2025.6.0b3.dist-info → esphome-2025.6.2.dist-info}/top_level.txt +0 -0
@@ -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
- // Only copy the data we actually use for each GAP event type
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 handle 4 GAP event types, others are dropped
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
- // Constructor for GATTC events - uses heap allocation
105
- // Creates a copy of the param struct since the original is only valid during the callback
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
- // Constructor for GATTS events - uses heap allocation
140
- // Creates a copy of the param struct since the original is only valid during the callback
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
- // Destructor to clean up heap allocations
170
- ~BLEEvent() {
171
- switch (this->type_) {
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
- // Helper methods to access event data
223
- ble_event_t type() const { return type_; }
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
 
@@ -0,0 +1,72 @@
1
+ #pragma once
2
+
3
+ #ifdef USE_ESP32
4
+
5
+ #include <atomic>
6
+ #include <cstddef>
7
+ #include "ble_event.h"
8
+ #include "queue.h"
9
+ #include "esphome/core/helpers.h"
10
+
11
+ namespace esphome {
12
+ namespace esp32_ble {
13
+
14
+ // BLE Event Pool - On-demand pool of BLEEvent objects to avoid heap fragmentation
15
+ // Events are allocated on first use and reused thereafter, growing to peak usage
16
+ template<uint8_t SIZE> class BLEEventPool {
17
+ public:
18
+ BLEEventPool() : total_created_(0) {}
19
+
20
+ ~BLEEventPool() {
21
+ // Clean up any remaining events in the free list
22
+ BLEEvent *event;
23
+ while ((event = this->free_list_.pop()) != nullptr) {
24
+ delete event;
25
+ }
26
+ }
27
+
28
+ // Allocate an event from the pool
29
+ // Returns nullptr if pool is full
30
+ BLEEvent *allocate() {
31
+ // Try to get from free list first
32
+ BLEEvent *event = this->free_list_.pop();
33
+ if (event != nullptr)
34
+ return event;
35
+
36
+ // Need to create a new event
37
+ if (this->total_created_ >= SIZE) {
38
+ // Pool is at capacity
39
+ return nullptr;
40
+ }
41
+
42
+ // Use internal RAM for better performance
43
+ RAMAllocator<BLEEvent> allocator(RAMAllocator<BLEEvent>::ALLOC_INTERNAL);
44
+ event = allocator.allocate(1);
45
+
46
+ if (event == nullptr) {
47
+ // Memory allocation failed
48
+ return nullptr;
49
+ }
50
+
51
+ // Placement new to construct the object
52
+ new (event) BLEEvent();
53
+ this->total_created_++;
54
+ return event;
55
+ }
56
+
57
+ // Return an event to the pool for reuse
58
+ void release(BLEEvent *event) {
59
+ if (event != nullptr) {
60
+ this->free_list_.push(event);
61
+ }
62
+ }
63
+
64
+ private:
65
+ LockFreeQueue<BLEEvent, SIZE> free_list_; // Free events ready for reuse
66
+ uint8_t total_created_; // Total events created (high water mark)
67
+ };
68
+
69
+ } // namespace esp32_ble
70
+ } // namespace esphome
71
+
72
+ #endif
@@ -18,7 +18,7 @@
18
18
  namespace esphome {
19
19
  namespace esp32_ble {
20
20
 
21
- template<class T, size_t SIZE> class LockFreeQueue {
21
+ template<class T, uint8_t SIZE> class LockFreeQueue {
22
22
  public:
23
23
  LockFreeQueue() : head_(0), tail_(0), dropped_count_(0) {}
24
24
 
@@ -26,8 +26,8 @@ template<class T, size_t SIZE> class LockFreeQueue {
26
26
  if (element == nullptr)
27
27
  return false;
28
28
 
29
- size_t current_tail = tail_.load(std::memory_order_relaxed);
30
- size_t next_tail = (current_tail + 1) % SIZE;
29
+ uint8_t current_tail = tail_.load(std::memory_order_relaxed);
30
+ uint8_t next_tail = (current_tail + 1) % SIZE;
31
31
 
32
32
  if (next_tail == head_.load(std::memory_order_acquire)) {
33
33
  // Buffer full
@@ -41,7 +41,7 @@ template<class T, size_t SIZE> class LockFreeQueue {
41
41
  }
42
42
 
43
43
  T *pop() {
44
- size_t current_head = head_.load(std::memory_order_relaxed);
44
+ uint8_t current_head = head_.load(std::memory_order_relaxed);
45
45
 
46
46
  if (current_head == tail_.load(std::memory_order_acquire)) {
47
47
  return nullptr; // Empty
@@ -53,27 +53,30 @@ template<class T, size_t SIZE> class LockFreeQueue {
53
53
  }
54
54
 
55
55
  size_t size() const {
56
- size_t tail = tail_.load(std::memory_order_acquire);
57
- size_t head = head_.load(std::memory_order_acquire);
56
+ uint8_t tail = tail_.load(std::memory_order_acquire);
57
+ uint8_t head = head_.load(std::memory_order_acquire);
58
58
  return (tail - head + SIZE) % SIZE;
59
59
  }
60
60
 
61
- size_t get_and_reset_dropped_count() { return dropped_count_.exchange(0, std::memory_order_relaxed); }
61
+ uint16_t get_and_reset_dropped_count() { return dropped_count_.exchange(0, std::memory_order_relaxed); }
62
62
 
63
63
  void increment_dropped_count() { dropped_count_.fetch_add(1, std::memory_order_relaxed); }
64
64
 
65
65
  bool empty() const { return head_.load(std::memory_order_acquire) == tail_.load(std::memory_order_acquire); }
66
66
 
67
67
  bool full() const {
68
- size_t next_tail = (tail_.load(std::memory_order_relaxed) + 1) % SIZE;
68
+ uint8_t next_tail = (tail_.load(std::memory_order_relaxed) + 1) % SIZE;
69
69
  return next_tail == head_.load(std::memory_order_acquire);
70
70
  }
71
71
 
72
72
  protected:
73
73
  T *buffer_[SIZE];
74
- std::atomic<size_t> head_;
75
- std::atomic<size_t> tail_;
76
- std::atomic<size_t> dropped_count_;
74
+ // Atomic: written by producer (push/increment), read+reset by consumer (get_and_reset)
75
+ std::atomic<uint16_t> dropped_count_; // 65535 max - more than enough for drop tracking
76
+ // Atomic: written by consumer (pop), read by producer (push) to check if full
77
+ std::atomic<uint8_t> head_;
78
+ // Atomic: written by producer (push), read by consumer (pop) to check if empty
79
+ std::atomic<uint8_t> tail_;
77
80
  };
78
81
 
79
82
  } // namespace esp32_ble
@@ -522,6 +522,7 @@ optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData
522
522
  }
523
523
 
524
524
  void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) {
525
+ this->scan_result_ = &scan_result;
525
526
  for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++)
526
527
  this->address_[i] = scan_result.bda[i];
527
528
  this->address_type_ = static_cast<esp_ble_addr_type_t>(scan_result.ble_addr_type);
@@ -85,6 +85,9 @@ class ESPBTDevice {
85
85
 
86
86
  const std::vector<ServiceData> &get_service_datas() const { return service_datas_; }
87
87
 
88
+ // Exposed through a function for use in lambdas
89
+ const BLEScanResult &get_scan_result() const { return *scan_result_; }
90
+
88
91
  bool resolve_irk(const uint8_t *irk) const;
89
92
 
90
93
  optional<ESPBLEiBeacon> get_ibeacon() const {
@@ -111,6 +114,7 @@ class ESPBTDevice {
111
114
  std::vector<ESPBTUUID> service_uuids_{};
112
115
  std::vector<ServiceData> manufacturer_datas_{};
113
116
  std::vector<ServiceData> service_datas_{};
117
+ const BLEScanResult *scan_result_{nullptr};
114
118
  };
115
119
 
116
120
  class ESP32BLETracker;
@@ -1,5 +1,8 @@
1
+ import logging
2
+
1
3
  from esphome import pins
2
4
  import esphome.codegen as cg
5
+ from esphome.components import esp32
3
6
  import esphome.config_validation as cv
4
7
  from esphome.const import (
5
8
  CONF_ADDRESS,
@@ -12,6 +15,8 @@ from esphome.const import (
12
15
  CONF_SCL,
13
16
  CONF_SDA,
14
17
  CONF_TIMEOUT,
18
+ KEY_CORE,
19
+ KEY_FRAMEWORK_VERSION,
15
20
  PLATFORM_ESP32,
16
21
  PLATFORM_ESP8266,
17
22
  PLATFORM_RP2040,
@@ -19,6 +24,7 @@ from esphome.const import (
19
24
  from esphome.core import CORE, coroutine_with_priority
20
25
  import esphome.final_validate as fv
21
26
 
27
+ LOGGER = logging.getLogger(__name__)
22
28
  CODEOWNERS = ["@esphome/core"]
23
29
  i2c_ns = cg.esphome_ns.namespace("i2c")
24
30
  I2CBus = i2c_ns.class_("I2CBus")
@@ -40,6 +46,32 @@ def _bus_declare_type(value):
40
46
  raise NotImplementedError
41
47
 
42
48
 
49
+ def validate_config(config):
50
+ if (
51
+ config[CONF_SCAN]
52
+ and CORE.is_esp32
53
+ and CORE.using_esp_idf
54
+ and esp32.get_esp32_variant()
55
+ in [
56
+ esp32.const.VARIANT_ESP32C5,
57
+ esp32.const.VARIANT_ESP32C6,
58
+ esp32.const.VARIANT_ESP32P4,
59
+ ]
60
+ ):
61
+ version: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
62
+ if version.major == 5 and (
63
+ (version.minor == 3 and version.patch <= 3)
64
+ or (version.minor == 4 and version.patch <= 1)
65
+ ):
66
+ LOGGER.warning(
67
+ "There is a bug in esp-idf version %s that breaks I2C scan, I2C scan "
68
+ "has been disabled, see https://github.com/esphome/issues/issues/7128",
69
+ str(version),
70
+ )
71
+ config[CONF_SCAN] = False
72
+ return config
73
+
74
+
43
75
  pin_with_input_and_output_support = pins.internal_gpio_pin_number(
44
76
  {CONF_OUTPUT: True, CONF_INPUT: True}
45
77
  )
@@ -65,6 +97,7 @@ CONFIG_SCHEMA = cv.All(
65
97
  }
66
98
  ).extend(cv.COMPONENT_SCHEMA),
67
99
  cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]),
100
+ validate_config,
68
101
  )
69
102
 
70
103
 
@@ -3,7 +3,7 @@ import esphome.config_validation as cv
3
3
  from esphome.const import CONF_SIZE, CONF_TEXT
4
4
  from esphome.cpp_generator import MockObjClass
5
5
 
6
- from ..defines import CONF_MAIN, literal
6
+ from ..defines import CONF_MAIN
7
7
  from ..lv_validation import color, color_retmapper, lv_text
8
8
  from ..lvcode import LocalVariable, lv, lv_expr
9
9
  from ..schemas import TEXT_SCHEMA
@@ -34,7 +34,7 @@ class QrCodeType(WidgetType):
34
34
  )
35
35
 
36
36
  def get_uses(self):
37
- return ("canvas", "img")
37
+ return ("canvas", "img", "label")
38
38
 
39
39
  def obj_creator(self, parent: MockObjClass, config: dict):
40
40
  dark_color = color_retmapper(config[CONF_DARK_COLOR])
@@ -45,10 +45,8 @@ class QrCodeType(WidgetType):
45
45
  async def to_code(self, w: Widget, config):
46
46
  if (value := config.get(CONF_TEXT)) is not None:
47
47
  value = await lv_text.process(value)
48
- with LocalVariable(
49
- "qr_text", cg.const_char_ptr, value, modifier=""
50
- ) as str_obj:
51
- lv.qrcode_update(w.obj, str_obj, literal(f"strlen({str_obj})"))
48
+ with LocalVariable("qr_text", cg.std_string, value, modifier="") as str_obj:
49
+ lv.qrcode_update(w.obj, str_obj.c_str(), str_obj.size())
52
50
 
53
51
 
54
52
  qr_code_spec = QrCodeType()
@@ -6,7 +6,11 @@ namespace mcp23xxx_base {
6
6
 
7
7
  float MCP23XXXBase::get_setup_priority() const { return setup_priority::IO; }
8
8
 
9
- void MCP23XXXGPIOPin::setup() { pin_mode(flags_); }
9
+ void MCP23XXXGPIOPin::setup() {
10
+ pin_mode(flags_);
11
+ this->parent_->pin_interrupt_mode(this->pin_, this->interrupt_mode_);
12
+ }
13
+
10
14
  void MCP23XXXGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
11
15
  bool MCP23XXXGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
12
16
  void MCP23XXXGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }