esphome 2025.6.0b3__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.
@@ -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
  }
@@ -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
- if (gap_event == ESP_GAP_BLE_SCAN_RESULT_EVT) {
330
- // Use the new scan event handler - no memcpy!
331
- for (auto *scan_handler : this->gap_scan_event_handlers_) {
332
- scan_handler->gap_scan_event_handler(ble_event->scan_result());
333
- }
334
- } else if (gap_event == ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT ||
335
- gap_event == ESP_GAP_BLE_SCAN_START_COMPLETE_EVT ||
336
- gap_event == ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT) {
337
- // All three scan complete events have the same structure with just status
338
- // The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe
339
- // This is verified at compile-time by static_assert checks in ble_event.h
340
- // The struct already contains our copy of the status (copied in BLEEvent constructor)
341
- ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
342
- for (auto *gap_handler : this->gap_event_handlers_) {
343
- gap_handler->gap_event_handler(
344
- gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.scan_complete));
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
- // Destructor will clean up external allocations for GATTC/GATTS
353
- ble_event->~BLEEvent();
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
- size_t dropped = this->ble_events_.get_and_reset_dropped_count();
405
+ uint16_t dropped = this->ble_events_.get_and_reset_dropped_count();
363
406
  if (dropped > 0) {
364
- ESP_LOGW(TAG, "Dropped %zu BLE events due to buffer overflow", 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
- // Check if queue is full before allocating
370
- if (global_ble->ble_events_.full()) {
371
- // Queue is full, drop the event
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
- BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
377
- if (new_event == nullptr) {
378
- // Memory too fragmented to allocate new event. Can only drop it until memory comes back
379
- global_ble->ble_events_.increment_dropped_count();
380
- return;
381
- }
382
- new (new_event) BLEEvent(args...);
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
- // Only queue the 4 GAP events we actually handle
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
- // 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;
@@ -33,6 +33,7 @@ bool Nextion::send_command_(const std::string &command) {
33
33
 
34
34
  #ifdef USE_NEXTION_COMMAND_SPACING
35
35
  if (!this->ignore_is_setup_ && !this->command_pacer_.can_send()) {
36
+ ESP_LOGN(TAG, "Command spacing: delaying command '%s'", command.c_str());
36
37
  return false;
37
38
  }
38
39
  #endif // USE_NEXTION_COMMAND_SPACING
@@ -43,10 +44,6 @@ bool Nextion::send_command_(const std::string &command) {
43
44
  const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF};
44
45
  this->write_array(to_send, sizeof(to_send));
45
46
 
46
- #ifdef USE_NEXTION_COMMAND_SPACING
47
- this->command_pacer_.mark_sent();
48
- #endif // USE_NEXTION_COMMAND_SPACING
49
-
50
47
  return true;
51
48
  }
52
49
 
@@ -377,12 +374,6 @@ void Nextion::process_nextion_commands_() {
377
374
  size_t commands_processed = 0;
378
375
  #endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
379
376
 
380
- #ifdef USE_NEXTION_COMMAND_SPACING
381
- if (!this->command_pacer_.can_send()) {
382
- return; // Will try again in next loop iteration
383
- }
384
- #endif
385
-
386
377
  size_t to_process_length = 0;
387
378
  std::string to_process;
388
379
 
@@ -430,6 +421,7 @@ void Nextion::process_nextion_commands_() {
430
421
  }
431
422
  #ifdef USE_NEXTION_COMMAND_SPACING
432
423
  this->command_pacer_.mark_sent(); // Here is where we should mark the command as sent
424
+ ESP_LOGN(TAG, "Command spacing: marked command sent at %u ms", millis());
433
425
  #endif
434
426
  break;
435
427
  case 0x02: // invalid Component ID or name was used
@@ -46,7 +46,7 @@ def set_sdkconfig_options(config):
46
46
  add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PANID", config[CONF_PAN_ID])
47
47
  add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_CHANNEL", config[CONF_CHANNEL])
48
48
  add_idf_sdkconfig_option(
49
- "CONFIG_OPENTHREAD_NETWORK_MASTERKEY", f"{config[CONF_NETWORK_KEY]:X}"
49
+ "CONFIG_OPENTHREAD_NETWORK_MASTERKEY", f"{config[CONF_NETWORK_KEY]:X}".lower()
50
50
  )
51
51
 
52
52
  if network_name := config.get(CONF_NETWORK_NAME):
@@ -54,14 +54,14 @@ def set_sdkconfig_options(config):
54
54
 
55
55
  if (ext_pan_id := config.get(CONF_EXT_PAN_ID)) is not None:
56
56
  add_idf_sdkconfig_option(
57
- "CONFIG_OPENTHREAD_NETWORK_EXTPANID", f"{ext_pan_id:X}"
57
+ "CONFIG_OPENTHREAD_NETWORK_EXTPANID", f"{ext_pan_id:X}".lower()
58
58
  )
59
59
  if (mesh_local_prefix := config.get(CONF_MESH_LOCAL_PREFIX)) is not None:
60
60
  add_idf_sdkconfig_option(
61
- "CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX", f"{mesh_local_prefix:X}"
61
+ "CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX", f"{mesh_local_prefix}".lower()
62
62
  )
63
63
  if (pskc := config.get(CONF_PSKC)) is not None:
64
- add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PSKC", f"{pskc:X}")
64
+ add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PSKC", f"{pskc:X}".lower())
65
65
 
66
66
  if CONF_FORCE_DATASET in config:
67
67
  if config[CONF_FORCE_DATASET]:
@@ -98,7 +98,7 @@ _CONNECTION_SCHEMA = cv.Schema(
98
98
  cv.Optional(CONF_EXT_PAN_ID): cv.hex_int,
99
99
  cv.Optional(CONF_NETWORK_NAME): cv.string_strict,
100
100
  cv.Optional(CONF_PSKC): cv.hex_int,
101
- cv.Optional(CONF_MESH_LOCAL_PREFIX): cv.hex_int,
101
+ cv.Optional(CONF_MESH_LOCAL_PREFIX): cv.ipv6network,
102
102
  }
103
103
  )
104
104
 
@@ -137,7 +137,7 @@ void OpenThreadSrpComponent::setup() {
137
137
  // Copy the mdns services to our local instance so that the c_str pointers remain valid for the lifetime of this
138
138
  // component
139
139
  this->mdns_services_ = this->mdns_->get_services();
140
- ESP_LOGW(TAG, "Setting up SRP services. count = %d\n", this->mdns_services_.size());
140
+ ESP_LOGD(TAG, "Setting up SRP services. count = %d\n", this->mdns_services_.size());
141
141
  for (const auto &service : this->mdns_services_) {
142
142
  otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance);
143
143
  if (!entry) {
@@ -185,11 +185,11 @@ void OpenThreadSrpComponent::setup() {
185
185
  if (error != OT_ERROR_NONE) {
186
186
  ESP_LOGW(TAG, "Failed to add service: %s", otThreadErrorToString(error));
187
187
  }
188
- ESP_LOGW(TAG, "Added service: %s", full_service.c_str());
188
+ ESP_LOGD(TAG, "Added service: %s", full_service.c_str());
189
189
  }
190
190
 
191
191
  otSrpClientEnableAutoStartMode(instance, srp_start_callback, nullptr);
192
- ESP_LOGW(TAG, "Finished SRP setup");
192
+ ESP_LOGD(TAG, "Finished SRP setup");
193
193
  }
194
194
 
195
195
  void *OpenThreadSrpComponent::pool_alloc_(size_t size) {
@@ -1,5 +1,6 @@
1
1
  # Sourced from https://gist.github.com/agners/0338576e0003318b63ec1ea75adc90f9
2
2
  import binascii
3
+ import ipaddress
3
4
 
4
5
  from esphome.const import CONF_CHANNEL
5
6
 
@@ -37,6 +38,12 @@ def parse_tlv(tlv) -> dict:
37
38
  if tag in TLV_TYPES:
38
39
  if tag == 3:
39
40
  output[TLV_TYPES[tag]] = val.decode("utf-8")
41
+ elif tag == 7:
42
+ mesh_local_prefix = binascii.hexlify(val).decode("utf-8")
43
+ mesh_local_prefix_str = f"{mesh_local_prefix}0000000000000000"
44
+ ipv6_bytes = bytes.fromhex(mesh_local_prefix_str)
45
+ ipv6_address = ipaddress.IPv6Address(ipv6_bytes)
46
+ output[TLV_TYPES[tag]] = f"{ipv6_address}/64"
40
47
  else:
41
48
  output[TLV_TYPES[tag]] = int.from_bytes(val)
42
49
  return output
@@ -3,7 +3,15 @@
3
3
  from contextlib import contextmanager
4
4
  from dataclasses import dataclass
5
5
  from datetime import datetime
6
- from ipaddress import AddressValueError, IPv4Address, ip_address
6
+ from ipaddress import (
7
+ AddressValueError,
8
+ IPv4Address,
9
+ IPv4Network,
10
+ IPv6Address,
11
+ IPv6Network,
12
+ ip_address,
13
+ ip_network,
14
+ )
7
15
  import logging
8
16
  import os
9
17
  import re
@@ -1176,6 +1184,14 @@ def ipv4address(value):
1176
1184
  return address
1177
1185
 
1178
1186
 
1187
+ def ipv6address(value):
1188
+ try:
1189
+ address = IPv6Address(value)
1190
+ except AddressValueError as exc:
1191
+ raise Invalid(f"{value} is not a valid IPv6 address") from exc
1192
+ return address
1193
+
1194
+
1179
1195
  def ipv4address_multi_broadcast(value):
1180
1196
  address = ipv4address(value)
1181
1197
  if not (address.is_multicast or (address == IPv4Address("255.255.255.255"))):
@@ -1193,6 +1209,33 @@ def ipaddress(value):
1193
1209
  return address
1194
1210
 
1195
1211
 
1212
+ def ipv4network(value):
1213
+ """Validate that the value is a valid IPv4 network."""
1214
+ try:
1215
+ network = IPv4Network(value, strict=False)
1216
+ except ValueError as exc:
1217
+ raise Invalid(f"{value} is not a valid IPv4 network") from exc
1218
+ return network
1219
+
1220
+
1221
+ def ipv6network(value):
1222
+ """Validate that the value is a valid IPv6 network."""
1223
+ try:
1224
+ network = IPv6Network(value, strict=False)
1225
+ except ValueError as exc:
1226
+ raise Invalid(f"{value} is not a valid IPv6 network") from exc
1227
+ return network
1228
+
1229
+
1230
+ def ipnetwork(value):
1231
+ """Validate that the value is a valid IP network."""
1232
+ try:
1233
+ network = ip_network(value, strict=False)
1234
+ except ValueError as exc:
1235
+ raise Invalid(f"{value} is not a valid IP network") from exc
1236
+ return network
1237
+
1238
+
1196
1239
  def _valid_topic(value):
1197
1240
  """Validate that this is a valid topic name/filter."""
1198
1241
  if value is None: # Used to disable publishing and subscribing
esphome/const.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Constants used by esphome."""
2
2
 
3
- __version__ = "2025.6.0b3"
3
+ __version__ = "2025.6.1"
4
4
 
5
5
  ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
6
6
  VALID_SUBSTITUTIONS_CHARACTERS = (
esphome/yaml_util.py CHANGED
@@ -5,7 +5,7 @@ import fnmatch
5
5
  import functools
6
6
  import inspect
7
7
  from io import BytesIO, TextIOBase, TextIOWrapper
8
- from ipaddress import _BaseAddress
8
+ from ipaddress import _BaseAddress, _BaseNetwork
9
9
  import logging
10
10
  import math
11
11
  import os
@@ -621,6 +621,7 @@ ESPHomeDumper.add_multi_representer(str, ESPHomeDumper.represent_stringify)
621
621
  ESPHomeDumper.add_multi_representer(int, ESPHomeDumper.represent_int)
622
622
  ESPHomeDumper.add_multi_representer(float, ESPHomeDumper.represent_float)
623
623
  ESPHomeDumper.add_multi_representer(_BaseAddress, ESPHomeDumper.represent_stringify)
624
+ ESPHomeDumper.add_multi_representer(_BaseNetwork, ESPHomeDumper.represent_stringify)
624
625
  ESPHomeDumper.add_multi_representer(MACAddress, ESPHomeDumper.represent_stringify)
625
626
  ESPHomeDumper.add_multi_representer(TimePeriod, ESPHomeDumper.represent_stringify)
626
627
  ESPHomeDumper.add_multi_representer(Lambda, ESPHomeDumper.represent_lambda)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esphome
3
- Version: 2025.6.0b3
3
+ Version: 2025.6.1
4
4
  Summary: ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems.
5
5
  Author-email: The ESPHome Authors <esphome@openhomefoundation.org>
6
6
  License: MIT
@@ -4,8 +4,8 @@ esphome/automation.py,sha256=9xmW3AmWDd2oKB7zF-UITYIiSci8ys8qiylK-rcU7Rg,15689
4
4
  esphome/codegen.py,sha256=GePHUM7xdXb_Pil59SHVsXg2F4VBPgkH-Fz2PDX8Z54,1873
5
5
  esphome/config.py,sha256=GsKqzNb4OBxA92eltdGYtP7e9fgtk80QsquhnUesb50,39948
6
6
  esphome/config_helpers.py,sha256=MKf_wzO35nn41FvigXE0iYKDslPgL2ruf8R-EPtTT2I,3256
7
- esphome/config_validation.py,sha256=N3za01KZnbbj4GqUAB1y0nW9qHRQUazsPzIVv1ZoxBk,62366
8
- esphome/const.py,sha256=v5xXgheq5lPDdWVYJQ2tGTYCMt8JYJklFjTYH1FHoU4,41817
7
+ esphome/config_validation.py,sha256=Nt8rQegT0VRAcWumtI0rJ92kW0milKMv3b443ZUzcfY,63437
8
+ esphome/const.py,sha256=QgR3DaFz_YxZhrNTslTHOqnNYmKGGGhf1OEEUyyynR4,41815
9
9
  esphome/coroutine.py,sha256=HNBqqhaTbpvsOI19bTXltxJCMVtoeqZPe4qTf4CKkAc,9309
10
10
  esphome/cpp_generator.py,sha256=2MbyMVt9hg7cFD0X8885IiJfFNjdqkqjis3P-0oa5L0,31346
11
11
  esphome/cpp_helpers.py,sha256=6C2vNbOIhZKi43xRVlk5hp9GfshfBn-rc5D_ZFUEYaE,4801
@@ -28,7 +28,7 @@ esphome/voluptuous_schema.py,sha256=tQUOLvVec6v4pxfWpa8CMgXqBqomuqUUYEJqCJOPhNs,
28
28
  esphome/vscode.py,sha256=pKBx_9jmQlRJB1xiqjWq2-pFhXae8VNSFGYoqxRBMkw,4279
29
29
  esphome/wizard.py,sha256=mru5jCpYTNrgaure7bP6fTkZ-1OfoUmqsowYuFBWRdU,15377
30
30
  esphome/writer.py,sha256=qxT5CJOIXEJlqJy490layWc-47QLPFBWUpKOsy8FQvQ,11040
31
- esphome/yaml_util.py,sha256=PewbmiODTAuJ5pTlawtGi-vdTdOHhzgproUhed5Bbk8,23140
31
+ esphome/yaml_util.py,sha256=k1zp-z1lpjIpRFbnzk6I3mwJouU2_9EtjNpgJdMA2eo,23239
32
32
  esphome/zeroconf.py,sha256=dy3aWh1Lf4Sh5e7Izlq30FkdzAKWA6IGvZkXuxYrxFE,6511
33
33
  esphome/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  esphome/components/a01nyub/__init__.py,sha256=d_xNluCS0T63CxdXJGuw7raK7G9vAEwXNJpnA9GthzA,34
@@ -187,8 +187,8 @@ esphome/components/api/api_connection.h,sha256=oZhcUrl6RByGhtLENp41-5b6CC0NXgbTu
187
187
  esphome/components/api/api_frame_helper.cpp,sha256=KCHNw9HQ_Id3TirJytskB_1AO4tZz5NtZVZ9DCl3zPM,38965
188
188
  esphome/components/api/api_frame_helper.h,sha256=gytvsuTbh4beQ2lGgGy-Crf-gQSCDU2z3plEa6EbgX8,9944
189
189
  esphome/components/api/api_noise_context.h,sha256=y_3hWMKXtKxyCwZ8cKQjn3gQqenaAX5DhcCFQ6kBiPc,606
190
- esphome/components/api/api_pb2.cpp,sha256=nBWCdtEy754ZsoF6uAXwAD28xEdOrOEbpiThyV6Mk1I,319294
191
- esphome/components/api/api_pb2.h,sha256=3QNzjjz7AYbGYcZhOayD7GWEQMLMAQeP_naEx17IK4U,106863
190
+ esphome/components/api/api_pb2.cpp,sha256=Es5qQLWZnKYurq9d9Gxp6eakh4Cne7cV2_lrE1j5sUM,319391
191
+ esphome/components/api/api_pb2.h,sha256=RIJPU96qJ7uT4enMyXJKnA931Scr73vrZKMlACJiiwg,106904
192
192
  esphome/components/api/api_pb2_service.cpp,sha256=6K191Ols2tcmccr0leWTGgyJljPyfzOAJdF4Ep9TIrY,32073
193
193
  esphome/components/api/api_pb2_service.h,sha256=CiUQWqbEIWSTN_Ku8RgA1UoVHUT2EWKjOUw9g28rYDc,16444
194
194
  esphome/components/api/api_pb2_size.h,sha256=i9wFf675YrfJy9Em_vZLPJ9osRHRNkOb7DMd1LpK3YM,13430
@@ -839,15 +839,16 @@ esphome/components/esp32/post_build.py.script,sha256=ZBsPNunx2BH4ZiRyXnjTP7D7eN2
839
839
  esphome/components/esp32/preferences.cpp,sha256=WpEG6PenHwrAIonuoV_chdDJriJXF5dcpIbuYfmaUCQ,6229
840
840
  esphome/components/esp32/preferences.h,sha256=9HIy-BOgjOXJiEgOizZ_Qb8-l6K4eb3VSPW8Y8ffuWM,165
841
841
  esphome/components/esp32_ble/__init__.py,sha256=aShI9hnF7NU0M8GrIYgOFqF4N2QL96Iuhzrh8SV1pus,9598
842
- esphome/components/esp32_ble/ble.cpp,sha256=UxTFLB0HG6z8ypHN2UcW1fiXt_LYngOtDz5qTkL8NZc,15451
843
- esphome/components/esp32_ble/ble.h,sha256=XiX3hvjhqQRc01xrLVus8q8J4iZo36m5Z8xbYeaDd1I,5798
842
+ esphome/components/esp32_ble/ble.cpp,sha256=Trg51D750u2AsKTCpktiGMF26BHOQSvzxzKW-etuJBU,17932
843
+ esphome/components/esp32_ble/ble.h,sha256=eRr_nTLqWzkKn_Po4i4wuA_AQsiHduUu_8uBQjtRF80,5878
844
844
  esphome/components/esp32_ble/ble_advertising.cpp,sha256=LFrrx7xMjXAf7pt7Q43XzlwwcE1YKKulE3Zbuqow6JU,6104
845
845
  esphome/components/esp32_ble/ble_advertising.h,sha256=tbN2CN1CqlosOvnG0NlMHC_0JPMZ2ugTzIIOSmQl30s,1576
846
- esphome/components/esp32_ble/ble_event.h,sha256=YuYhDR-ELOXWQkzD9xLZ6C9NioVInOraUlROwOnlp4c,9190
846
+ esphome/components/esp32_ble/ble_event.h,sha256=3Zuq6mkPwBLg6AR4Q_7aebxLh7EWAlb4rLDmOL3DWIc,18301
847
+ esphome/components/esp32_ble/ble_event_pool.h,sha256=HWGuDfX34P-CgMtB5yzie2Y53dWtX-Cq-Scp0cXGMlo,1758
847
848
  esphome/components/esp32_ble/ble_scan_result.h,sha256=3hpnLDvy9Fr7sMuophP_db4GcF76qWSZKOn_ZljYoME,546
848
849
  esphome/components/esp32_ble/ble_uuid.cpp,sha256=2go_q78xgFlQRH_ZpM4tdsDJqyqe_3Ssn2PJFkpjlfI,6167
849
850
  esphome/components/esp32_ble/ble_uuid.h,sha256=6OL7GrNP-pxIqANnLlsd4IWCRnVzRD3Zx20pD4uGymA,916
850
- esphome/components/esp32_ble/queue.h,sha256=Tu2gzHSkErzIkGFWDNrkEVaNjHuDb8BiWhfncZJfTD4,2308
851
+ esphome/components/esp32_ble/queue.h,sha256=11OyhJYHxuY5UTuJZawxWCoeHsGkig9uiZ3oVhN1j2M,2625
851
852
  esphome/components/esp32_ble_beacon/__init__.py,sha256=ts8d3ZFLBH_QE6cW748AZUXaK_yCqg3rzlJcObTGkAQ,3190
852
853
  esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp,sha256=Y1xsFMHU306rZ8ZU7Y0UmhU22gXYe_28o7jg1t5e3Ms,4202
853
854
  esphome/components/esp32_ble_beacon/esp32_ble_beacon.h,sha256=tDM9fy5VLIrCp45bqgsqHAm6FTsn7ixoOZkuTugZqqw,1868
@@ -874,8 +875,8 @@ esphome/components/esp32_ble_server/ble_service.cpp,sha256=cLJpq-eEFNXV4tlkSHXQW
874
875
  esphome/components/esp32_ble_server/ble_service.h,sha256=BvKpr2fsUlNnviH9gdokI7IcuTBu0C7bNFT0vvIuN1Y,2306
875
876
  esphome/components/esp32_ble_tracker/__init__.py,sha256=cWHciy2tEUDNBC_bUqE-1hPSSGecbat3-8HUBitjOqQ,15170
876
877
  esphome/components/esp32_ble_tracker/automation.h,sha256=0pDA6EX__f14sT0KJwcnqg7UOsueKjjegHPznQj9biw,3795
877
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp,sha256=-ZbJGP_aANwaqqL9F30zMJHfHmbDn00M6Wi4n3NR3iI,33168
878
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.h,sha256=SDSrBSFfdL3owk2lwBcyfG5JvgxBQyOVSx6CqgbIwEQ,10678
878
+ esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp,sha256=VLZ9egEuses6lxLIQJQ43YDg2VYYlQVNYjgQSRrecxk,33205
879
+ esphome/components/esp32_ble_tracker/esp32_ble_tracker.h,sha256=bhbPLtxVvW0rwQWUXBzcby0SwyvER0KLmEucXawfuSQ,10849
879
880
  esphome/components/esp32_camera/__init__.py,sha256=3M3EhzV9jPpWMiAHPaVWbYUW3D_uEM_6Je4Gd2EGhNg,12631
880
881
  esphome/components/esp32_camera/esp32_camera.cpp,sha256=Q7QjA1DBd_dnQkB6xos7STKZPhSDM0C09m1YvO7E1U4,16685
881
882
  esphome/components/esp32_camera/esp32_camera.h,sha256=bCDwl44XrornN6_KHe8htnrUJOoFlE-KMipggt3NWa8,7590
@@ -2015,7 +2016,7 @@ esphome/components/nextion/__init__.py,sha256=1f1kBHOUMvvwSjeIgY6cWSS_cAuRILmgs7
2015
2016
  esphome/components/nextion/automation.h,sha256=baaMGf6qTWI2OILZvkIkiKiW_pM21YZqTNbeggVXeXw,4555
2016
2017
  esphome/components/nextion/base_component.py,sha256=4O2S8GLPQerIbYlIYX7O3hEkHN7unn7u9pcRCkcBlIo,4290
2017
2018
  esphome/components/nextion/display.py,sha256=GszSWhlTFnGYlw-fj8oH3vxtaTdpMHK1JEltSs1dCO8,7977
2018
- esphome/components/nextion/nextion.cpp,sha256=vvyTE1FsBYBUlIYhJkTcptVWy6EqaN2YnzoUT3mRVOg,42308
2019
+ esphome/components/nextion/nextion.cpp,sha256=814O1mFUe9SozcaVsIxzmxsSLsNKxCvV43JYMbGaW1g,42214
2019
2020
  esphome/components/nextion/nextion.h,sha256=SQvZnSQx1gMH9b76sIOEh-IK5mjAxPffBrQNWi0uA5Y,51539
2020
2021
  esphome/components/nextion/nextion_base.h,sha256=3v9nuHpvpFxMKutV4bzuu1OB_uj26PRDaDc5gTv_2rc,2416
2021
2022
  esphome/components/nextion/nextion_commands.cpp,sha256=XjTHKSw7UHf0fujti5u_keUFGtYIAlrrTKNACSxnE60,17716
@@ -2120,12 +2121,12 @@ esphome/components/opentherm/sensor/__init__.py,sha256=uNb3f8CaWk7HE97GEKb_Jw61j
2120
2121
  esphome/components/opentherm/switch/__init__.py,sha256=rgirLZntBuvFQ_7T0WEfhC6pPE6dXdIXV37-DZq3yIE,1136
2121
2122
  esphome/components/opentherm/switch/switch.cpp,sha256=UurvOy-U1IEmNxWMCCpkPCueR-POh-FBI8fBjWWc64o,820
2122
2123
  esphome/components/opentherm/switch/switch.h,sha256=JhJFaXrJi3orbavFDc4llzmmHwU0yN2v_VuwJx3r7FU,410
2123
- esphome/components/openthread/__init__.py,sha256=nSK4UsZCkhOVRCgtET-BCRES_Quw1J6MVkLTTl_5JTo,5074
2124
+ esphome/components/openthread/__init__.py,sha256=lZ5TETobR61V1-jAEm0gEOnNCzRqci9Ni-4dKy-001w,5108
2124
2125
  esphome/components/openthread/const.py,sha256=jJvW1yrA2naAIuhZ4aGH9WEFm8MNvsbTcUJqrJR8CfM,288
2125
- esphome/components/openthread/openthread.cpp,sha256=5SNdOVTU8qFQ_ypWf7isD_nYJlAf4VgrC8NcIBlVZ1o,7121
2126
+ esphome/components/openthread/openthread.cpp,sha256=SrMt0cNvPMpHk0eZDf9DPE8GGIuDpTfevjItX_87qGQ,7121
2126
2127
  esphome/components/openthread/openthread.h,sha256=0FpY2swy0Dpvk51doFcX1IbKfKPO7CaFWFmS1hTeQD8,1855
2127
2128
  esphome/components/openthread/openthread_esp.cpp,sha256=I8bbPhwJQDfnd8RqhpZJqezJXayZztv1ZnnlLIB_KqU,4714
2128
- esphome/components/openthread/tlv.py,sha256=XFDhzIPzcdgextuZtGTNSDdMZrYCLaSnp13BMMslRgI,1230
2129
+ esphome/components/openthread/tlv.py,sha256=hYi_WM0Ja2Eo2gFR_8xIzcMY6En4g12JpolLBIb12LQ,1620
2129
2130
  esphome/components/openthread_info/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2130
2131
  esphome/components/openthread_info/openthread_info_text_sensor.cpp,sha256=M2kQAgRn2uRsjsJF96lEHtD39HTAhCp5bn2gLDARR34,1113
2131
2132
  esphome/components/openthread_info/openthread_info_text_sensor.h,sha256=nLnWZBLkq3Dvnr1Rg8Lnj7bO3EhqSfYfRiQkYOQSmPg,6932
@@ -3595,9 +3596,9 @@ esphome/dashboard/util/itertools.py,sha256=8eLrWEWmICLtXNxkKdYPQV0c_N4GEz8m9Npnb
3595
3596
  esphome/dashboard/util/password.py,sha256=cQz3b9B-ijTe7zS6BeCW0hc3pWv6JjC78jmnycYYAh8,321
3596
3597
  esphome/dashboard/util/subprocess.py,sha256=T8EW6dbU4LPd2DG1dRrdh8li71tt6J1isn411poMhkk,1022
3597
3598
  esphome/dashboard/util/text.py,sha256=ENDnfN4O0NdA3CKVJjQYabFbwbrsIhVKrAMQe53qYu4,534
3598
- esphome-2025.6.0b3.dist-info/licenses/LICENSE,sha256=HzEjkBInJe44L4WvAOPfhPJJDNj6YbnqFyvGWRzArGM,36664
3599
- esphome-2025.6.0b3.dist-info/METADATA,sha256=IJwYU8F4CJZgXzX5hXwV5hzXCS3Lki22NTXL31DllxY,3680
3600
- esphome-2025.6.0b3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
3601
- esphome-2025.6.0b3.dist-info/entry_points.txt,sha256=mIxVNuWtbYzeEcaWCl-AQ-97aBOWbnYBAK8nbF6P4M0,50
3602
- esphome-2025.6.0b3.dist-info/top_level.txt,sha256=0GSXEW3cnITpgG3qnsSMz0qoqJHAFyfw7Y8MVtEf1Yk,8
3603
- esphome-2025.6.0b3.dist-info/RECORD,,
3599
+ esphome-2025.6.1.dist-info/licenses/LICENSE,sha256=HzEjkBInJe44L4WvAOPfhPJJDNj6YbnqFyvGWRzArGM,36664
3600
+ esphome-2025.6.1.dist-info/METADATA,sha256=YHEkqz_qYmFQhk5PdKCeT3WQyl3ukyRrK4sFTarKYWg,3678
3601
+ esphome-2025.6.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
3602
+ esphome-2025.6.1.dist-info/entry_points.txt,sha256=mIxVNuWtbYzeEcaWCl-AQ-97aBOWbnYBAK8nbF6P4M0,50
3603
+ esphome-2025.6.1.dist-info/top_level.txt,sha256=0GSXEW3cnITpgG3qnsSMz0qoqJHAFyfw7Y8MVtEf1Yk,8
3604
+ esphome-2025.6.1.dist-info/RECORD,,