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.
- esphome/components/api/api_pb2.cpp +2 -0
- esphome/components/api/api_pb2.h +1 -0
- esphome/components/audio/audio_reader.cpp +41 -21
- esphome/components/esp32/__init__.py +2 -2
- 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/i2c/__init__.py +33 -0
- esphome/components/lvgl/widgets/qrcode.py +4 -6
- esphome/components/mcp23xxx_base/mcp23xxx_base.cpp +5 -1
- 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/speaker/media_player/audio_pipeline.cpp +12 -13
- esphome/components/voice_assistant/__init__.py +12 -1
- esphome/components/voice_assistant/voice_assistant.cpp +36 -2
- esphome/components/voice_assistant/voice_assistant.h +4 -0
- esphome/config_validation.py +44 -1
- esphome/const.py +1 -1
- esphome/yaml_util.py +2 -1
- {esphome-2025.6.0b3.dist-info → esphome-2025.6.2.dist-info}/METADATA +1 -1
- {esphome-2025.6.0b3.dist-info → esphome-2025.6.2.dist-info}/RECORD +31 -30
- {esphome-2025.6.0b3.dist-info → esphome-2025.6.2.dist-info}/WHEEL +0 -0
- {esphome-2025.6.0b3.dist-info → esphome-2025.6.2.dist-info}/entry_points.txt +0 -0
- {esphome-2025.6.0b3.dist-info → esphome-2025.6.2.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
//
|
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
|
|
@@ -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,
|
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
|
-
|
30
|
-
|
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
|
-
|
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
|
-
|
57
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
75
|
-
std::atomic<
|
76
|
-
|
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
|
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
|
-
|
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() {
|
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_); }
|