esphome 2025.6.0b1__py3-none-any.whl → 2025.6.0b3__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_connection.cpp +53 -33
- esphome/components/api/api_connection.h +24 -25
- esphome/components/api/api_pb2.cpp +1 -0
- esphome/components/api/api_pb2.h +65 -226
- esphome/components/api/client.py +1 -3
- esphome/components/api/proto.h +1 -1
- esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +2 -2
- esphome/components/bluetooth_proxy/bluetooth_proxy.h +1 -1
- esphome/components/bme280_base/bme280_base.cpp +2 -3
- esphome/components/datetime/date_entity.cpp +5 -5
- esphome/components/datetime/datetime_base.h +0 -5
- esphome/components/datetime/datetime_entity.cpp +8 -8
- esphome/components/datetime/time_entity.cpp +4 -4
- esphome/components/esp32/__init__.py +30 -3
- esphome/components/esp32_ble/ble.cpp +90 -45
- esphome/components/esp32_ble/ble.h +24 -5
- esphome/components/esp32_ble/ble_event.h +172 -32
- esphome/components/esp32_ble/ble_scan_result.h +24 -0
- esphome/components/esp32_ble/queue.h +53 -27
- esphome/components/esp32_ble_tracker/__init__.py +1 -0
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +95 -66
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +16 -16
- esphome/components/esp32_camera/esp32_camera.cpp +1 -1
- esphome/components/fan/fan.cpp +26 -17
- esphome/components/i2s_audio/i2s_audio.cpp +1 -1
- esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +47 -29
- esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +2 -0
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +28 -10
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +1 -0
- esphome/components/kmeteriso/kmeteriso.cpp +2 -3
- esphome/components/light/light_state.h +15 -15
- esphome/components/logger/logger.cpp +2 -15
- esphome/components/logger/logger.h +1 -2
- esphome/components/mqtt/mqtt_component.cpp +1 -1
- esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp +1 -1
- esphome/components/nextion/nextion_upload_arduino.cpp +12 -9
- esphome/components/nextion/nextion_upload_idf.cpp +11 -9
- esphome/components/nextion/sensor/nextion_sensor.cpp +1 -1
- esphome/components/nextion/text_sensor/nextion_textsensor.cpp +1 -1
- esphome/components/number/number.cpp +1 -1
- esphome/components/number/number.h +0 -4
- esphome/components/prometheus/__init__.py +0 -1
- esphome/components/select/select.cpp +1 -1
- esphome/components/select/select.h +0 -4
- esphome/components/sensor/sensor.cpp +8 -4
- esphome/components/sensor/sensor.h +3 -6
- esphome/components/spi/spi_arduino.cpp +22 -9
- esphome/components/status_led/light/status_led_light.cpp +2 -2
- esphome/components/status_led/light/status_led_light.h +1 -1
- esphome/components/switch/switch.h +13 -7
- esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +14 -9
- esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +1 -0
- esphome/components/text/text.cpp +1 -1
- esphome/components/text/text.h +0 -4
- esphome/components/text_sensor/text_sensor.cpp +8 -4
- esphome/components/text_sensor/text_sensor.h +6 -6
- esphome/components/update/update_entity.cpp +1 -1
- esphome/components/update/update_entity.h +0 -3
- esphome/components/uptime/sensor/uptime_timestamp_sensor.cpp +1 -1
- esphome/components/web_server_idf/__init__.py +0 -2
- esphome/components/web_server_idf/web_server_idf.cpp +6 -2
- esphome/components/web_server_idf/web_server_idf.h +7 -0
- esphome/components/weikai/weikai.cpp +1 -1
- esphome/const.py +1 -1
- esphome/core/application.cpp +14 -8
- esphome/core/application.h +7 -7
- esphome/core/component.cpp +36 -15
- esphome/core/component.h +30 -13
- esphome/core/entity_base.cpp +4 -16
- esphome/core/entity_base.h +27 -13
- esphome/core/helpers.h +1 -1
- esphome/wizard.py +0 -16
- {esphome-2025.6.0b1.dist-info → esphome-2025.6.0b3.dist-info}/METADATA +2 -2
- {esphome-2025.6.0b1.dist-info → esphome-2025.6.0b3.dist-info}/RECORD +78 -77
- {esphome-2025.6.0b1.dist-info → esphome-2025.6.0b3.dist-info}/WHEEL +0 -0
- {esphome-2025.6.0b1.dist-info → esphome-2025.6.0b3.dist-info}/entry_points.txt +0 -0
- {esphome-2025.6.0b1.dist-info → esphome-2025.6.0b3.dist-info}/licenses/LICENSE +0 -0
- {esphome-2025.6.0b1.dist-info → esphome-2025.6.0b3.dist-info}/top_level.txt +0 -0
@@ -2,92 +2,232 @@
|
|
2
2
|
|
3
3
|
#ifdef USE_ESP32
|
4
4
|
|
5
|
+
#include <cstddef> // for offsetof
|
5
6
|
#include <vector>
|
6
7
|
|
7
8
|
#include <esp_gap_ble_api.h>
|
8
9
|
#include <esp_gattc_api.h>
|
9
10
|
#include <esp_gatts_api.h>
|
10
11
|
|
12
|
+
#include "ble_scan_result.h"
|
13
|
+
|
11
14
|
namespace esphome {
|
12
15
|
namespace esp32_ble {
|
16
|
+
|
17
|
+
// Compile-time verification that ESP-IDF scan complete events only contain a status field
|
18
|
+
// This ensures our reinterpret_cast in ble.cpp is safe
|
19
|
+
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
20
|
+
"ESP-IDF scan_param_cmpl structure has unexpected size");
|
21
|
+
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
22
|
+
"ESP-IDF scan_start_cmpl structure has unexpected size");
|
23
|
+
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
24
|
+
"ESP-IDF scan_stop_cmpl structure has unexpected size");
|
25
|
+
|
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),
|
29
|
+
"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),
|
32
|
+
"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),
|
35
|
+
"status must be first member of scan_stop_cmpl");
|
36
|
+
|
13
37
|
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
14
|
-
// This class stores each event
|
38
|
+
// This class stores each event with minimal memory usage.
|
39
|
+
// GAP events (99% of traffic) don't have the vector overhead.
|
40
|
+
// GATTC/GATTS events use heap allocation for their param and data.
|
41
|
+
//
|
42
|
+
// Event flow:
|
43
|
+
// 1. ESP-IDF BLE stack calls our static handlers in the BLE task context
|
44
|
+
// 2. The handlers create a BLEEvent instance, copying only the data we need
|
45
|
+
// 3. The event is pushed to a thread-safe queue
|
46
|
+
// 4. In the main loop(), events are popped from the queue and processed
|
47
|
+
// 5. The event destructor cleans up any external allocations
|
48
|
+
//
|
49
|
+
// Thread safety:
|
50
|
+
// - GAP events: We copy only the fields we need directly into the union
|
51
|
+
// - GATTC/GATTS events: We heap-allocate and copy the entire param struct, ensuring
|
52
|
+
// the data remains valid even after the BLE callback returns. The original
|
53
|
+
// param pointer from ESP-IDF is only valid during the callback.
|
15
54
|
class BLEEvent {
|
16
55
|
public:
|
56
|
+
// NOLINTNEXTLINE(readability-identifier-naming)
|
57
|
+
enum ble_event_t : uint8_t {
|
58
|
+
GAP,
|
59
|
+
GATTC,
|
60
|
+
GATTS,
|
61
|
+
};
|
62
|
+
|
63
|
+
// Constructor for GAP events - no external allocations needed
|
17
64
|
BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
18
|
-
this->event_.gap.gap_event = e;
|
19
|
-
memcpy(&this->event_.gap.gap_param, p, sizeof(esp_ble_gap_cb_param_t));
|
20
65
|
this->type_ = GAP;
|
21
|
-
|
66
|
+
this->event_.gap.gap_event = e;
|
67
|
+
|
68
|
+
if (p == nullptr) {
|
69
|
+
return; // Invalid event, but we can't log in header file
|
70
|
+
}
|
71
|
+
|
72
|
+
// Only copy the data we actually use for each GAP event type
|
73
|
+
switch (e) {
|
74
|
+
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
75
|
+
// Copy only the fields we use from scan results
|
76
|
+
memcpy(this->event_.gap.scan_result.bda, p->scan_rst.bda, sizeof(esp_bd_addr_t));
|
77
|
+
this->event_.gap.scan_result.ble_addr_type = p->scan_rst.ble_addr_type;
|
78
|
+
this->event_.gap.scan_result.rssi = p->scan_rst.rssi;
|
79
|
+
this->event_.gap.scan_result.adv_data_len = p->scan_rst.adv_data_len;
|
80
|
+
this->event_.gap.scan_result.scan_rsp_len = p->scan_rst.scan_rsp_len;
|
81
|
+
this->event_.gap.scan_result.search_evt = p->scan_rst.search_evt;
|
82
|
+
memcpy(this->event_.gap.scan_result.ble_adv, p->scan_rst.ble_adv,
|
83
|
+
ESP_BLE_ADV_DATA_LEN_MAX + ESP_BLE_SCAN_RSP_DATA_LEN_MAX);
|
84
|
+
break;
|
85
|
+
|
86
|
+
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
87
|
+
this->event_.gap.scan_complete.status = p->scan_param_cmpl.status;
|
88
|
+
break;
|
89
|
+
|
90
|
+
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
91
|
+
this->event_.gap.scan_complete.status = p->scan_start_cmpl.status;
|
92
|
+
break;
|
93
|
+
|
94
|
+
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
95
|
+
this->event_.gap.scan_complete.status = p->scan_stop_cmpl.status;
|
96
|
+
break;
|
97
|
+
|
98
|
+
default:
|
99
|
+
// We only handle 4 GAP event types, others are dropped
|
100
|
+
break;
|
101
|
+
}
|
102
|
+
}
|
22
103
|
|
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
|
23
106
|
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
107
|
+
this->type_ = GATTC;
|
24
108
|
this->event_.gattc.gattc_event = e;
|
25
109
|
this->event_.gattc.gattc_if = i;
|
26
|
-
|
27
|
-
|
110
|
+
|
111
|
+
if (p == nullptr) {
|
112
|
+
this->event_.gattc.gattc_param = nullptr;
|
113
|
+
this->event_.gattc.data = nullptr;
|
114
|
+
return; // Invalid event, but we can't log in header file
|
115
|
+
}
|
116
|
+
|
117
|
+
// Heap-allocate param and data
|
118
|
+
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
|
119
|
+
// while GAP events (99%) are stored inline to minimize memory usage
|
120
|
+
this->event_.gattc.gattc_param = new esp_ble_gattc_cb_param_t(*p);
|
121
|
+
|
122
|
+
// Copy data for events that need it
|
28
123
|
switch (e) {
|
29
124
|
case ESP_GATTC_NOTIFY_EVT:
|
30
|
-
this->data
|
31
|
-
this->event_.gattc.gattc_param
|
125
|
+
this->event_.gattc.data = new std::vector<uint8_t>(p->notify.value, p->notify.value + p->notify.value_len);
|
126
|
+
this->event_.gattc.gattc_param->notify.value = this->event_.gattc.data->data();
|
32
127
|
break;
|
33
128
|
case ESP_GATTC_READ_CHAR_EVT:
|
34
129
|
case ESP_GATTC_READ_DESCR_EVT:
|
35
|
-
this->data
|
36
|
-
this->event_.gattc.gattc_param
|
130
|
+
this->event_.gattc.data = new std::vector<uint8_t>(p->read.value, p->read.value + p->read.value_len);
|
131
|
+
this->event_.gattc.gattc_param->read.value = this->event_.gattc.data->data();
|
37
132
|
break;
|
38
133
|
default:
|
134
|
+
this->event_.gattc.data = nullptr;
|
39
135
|
break;
|
40
136
|
}
|
41
|
-
|
42
|
-
};
|
137
|
+
}
|
43
138
|
|
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
|
44
141
|
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
142
|
+
this->type_ = GATTS;
|
45
143
|
this->event_.gatts.gatts_event = e;
|
46
144
|
this->event_.gatts.gatts_if = i;
|
47
|
-
|
48
|
-
|
145
|
+
|
146
|
+
if (p == nullptr) {
|
147
|
+
this->event_.gatts.gatts_param = nullptr;
|
148
|
+
this->event_.gatts.data = nullptr;
|
149
|
+
return; // Invalid event, but we can't log in header file
|
150
|
+
}
|
151
|
+
|
152
|
+
// Heap-allocate param and data
|
153
|
+
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
|
154
|
+
// while GAP events (99%) are stored inline to minimize memory usage
|
155
|
+
this->event_.gatts.gatts_param = new esp_ble_gatts_cb_param_t(*p);
|
156
|
+
|
157
|
+
// Copy data for events that need it
|
49
158
|
switch (e) {
|
50
159
|
case ESP_GATTS_WRITE_EVT:
|
51
|
-
this->data
|
52
|
-
this->event_.gatts.gatts_param
|
160
|
+
this->event_.gatts.data = new std::vector<uint8_t>(p->write.value, p->write.value + p->write.len);
|
161
|
+
this->event_.gatts.gatts_param->write.value = this->event_.gatts.data->data();
|
53
162
|
break;
|
54
163
|
default:
|
164
|
+
this->event_.gatts.data = nullptr;
|
55
165
|
break;
|
56
166
|
}
|
57
|
-
|
58
|
-
|
167
|
+
}
|
168
|
+
|
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;
|
59
188
|
|
60
189
|
union {
|
61
190
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
62
191
|
struct gap_event {
|
63
192
|
esp_gap_ble_cb_event_t gap_event;
|
64
|
-
|
65
|
-
|
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
|
66
202
|
|
67
203
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
68
204
|
struct gattc_event {
|
69
205
|
esp_gattc_cb_event_t gattc_event;
|
70
206
|
esp_gatt_if_t gattc_if;
|
71
|
-
esp_ble_gattc_cb_param_t gattc_param;
|
72
|
-
|
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)
|
73
210
|
|
74
211
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
75
212
|
struct gatts_event {
|
76
213
|
esp_gatts_cb_event_t gatts_event;
|
77
214
|
esp_gatt_if_t gatts_if;
|
78
|
-
esp_ble_gatts_cb_param_t gatts_param;
|
79
|
-
|
80
|
-
|
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
|
81
219
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
220
|
+
ble_event_t type_;
|
221
|
+
|
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; }
|
89
227
|
};
|
90
228
|
|
229
|
+
// BLEEvent total size: 84 bytes (80 byte union + 1 byte type + 3 bytes padding)
|
230
|
+
|
91
231
|
} // namespace esp32_ble
|
92
232
|
} // namespace esphome
|
93
233
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
#ifdef USE_ESP32
|
4
|
+
|
5
|
+
#include <esp_gap_ble_api.h>
|
6
|
+
|
7
|
+
namespace esphome {
|
8
|
+
namespace esp32_ble {
|
9
|
+
|
10
|
+
// Structure for BLE scan results - only fields we actually use
|
11
|
+
struct __attribute__((packed)) BLEScanResult {
|
12
|
+
esp_bd_addr_t bda;
|
13
|
+
uint8_t ble_addr_type;
|
14
|
+
int8_t rssi;
|
15
|
+
uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX + ESP_BLE_SCAN_RSP_DATA_LEN_MAX];
|
16
|
+
uint8_t adv_data_len;
|
17
|
+
uint8_t scan_rsp_len;
|
18
|
+
uint8_t search_evt;
|
19
|
+
}; // ~73 bytes vs ~400 bytes for full esp_ble_gap_cb_param_t
|
20
|
+
|
21
|
+
} // namespace esp32_ble
|
22
|
+
} // namespace esphome
|
23
|
+
|
24
|
+
#endif
|
@@ -2,52 +2,78 @@
|
|
2
2
|
|
3
3
|
#ifdef USE_ESP32
|
4
4
|
|
5
|
-
#include <
|
6
|
-
#include <
|
7
|
-
|
8
|
-
#include <freertos/FreeRTOS.h>
|
9
|
-
#include <freertos/semphr.h>
|
5
|
+
#include <atomic>
|
6
|
+
#include <cstddef>
|
10
7
|
|
11
8
|
/*
|
12
9
|
* BLE events come in from a separate Task (thread) in the ESP32 stack. Rather
|
13
|
-
* than
|
14
|
-
*
|
15
|
-
*
|
16
|
-
*
|
10
|
+
* than using mutex-based locking, this lock-free queue allows the BLE
|
11
|
+
* task to enqueue events without blocking. The main loop() then processes
|
12
|
+
* these events at a safer time.
|
13
|
+
*
|
14
|
+
* This is a Single-Producer Single-Consumer (SPSC) lock-free ring buffer.
|
15
|
+
* The BLE task is the only producer, and the main loop() is the only consumer.
|
17
16
|
*/
|
18
17
|
|
19
18
|
namespace esphome {
|
20
19
|
namespace esp32_ble {
|
21
20
|
|
22
|
-
template<class T> class
|
21
|
+
template<class T, size_t SIZE> class LockFreeQueue {
|
23
22
|
public:
|
24
|
-
|
23
|
+
LockFreeQueue() : head_(0), tail_(0), dropped_count_(0) {}
|
25
24
|
|
26
|
-
|
25
|
+
bool push(T *element) {
|
27
26
|
if (element == nullptr)
|
28
|
-
return;
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
return false;
|
28
|
+
|
29
|
+
size_t current_tail = tail_.load(std::memory_order_relaxed);
|
30
|
+
size_t next_tail = (current_tail + 1) % SIZE;
|
31
|
+
|
32
|
+
if (next_tail == head_.load(std::memory_order_acquire)) {
|
33
|
+
// Buffer full
|
34
|
+
dropped_count_.fetch_add(1, std::memory_order_relaxed);
|
35
|
+
return false;
|
36
|
+
}
|
37
|
+
|
38
|
+
buffer_[current_tail] = element;
|
39
|
+
tail_.store(next_tail, std::memory_order_release);
|
40
|
+
return true;
|
33
41
|
}
|
34
42
|
|
35
43
|
T *pop() {
|
36
|
-
|
37
|
-
|
38
|
-
if (
|
39
|
-
|
40
|
-
element = q_.front();
|
41
|
-
q_.pop();
|
42
|
-
}
|
43
|
-
xSemaphoreGive(m_);
|
44
|
+
size_t current_head = head_.load(std::memory_order_relaxed);
|
45
|
+
|
46
|
+
if (current_head == tail_.load(std::memory_order_acquire)) {
|
47
|
+
return nullptr; // Empty
|
44
48
|
}
|
49
|
+
|
50
|
+
T *element = buffer_[current_head];
|
51
|
+
head_.store((current_head + 1) % SIZE, std::memory_order_release);
|
45
52
|
return element;
|
46
53
|
}
|
47
54
|
|
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);
|
58
|
+
return (tail - head + SIZE) % SIZE;
|
59
|
+
}
|
60
|
+
|
61
|
+
size_t get_and_reset_dropped_count() { return dropped_count_.exchange(0, std::memory_order_relaxed); }
|
62
|
+
|
63
|
+
void increment_dropped_count() { dropped_count_.fetch_add(1, std::memory_order_relaxed); }
|
64
|
+
|
65
|
+
bool empty() const { return head_.load(std::memory_order_acquire) == tail_.load(std::memory_order_acquire); }
|
66
|
+
|
67
|
+
bool full() const {
|
68
|
+
size_t next_tail = (tail_.load(std::memory_order_relaxed) + 1) % SIZE;
|
69
|
+
return next_tail == head_.load(std::memory_order_acquire);
|
70
|
+
}
|
71
|
+
|
48
72
|
protected:
|
49
|
-
|
50
|
-
|
73
|
+
T *buffer_[SIZE];
|
74
|
+
std::atomic<size_t> head_;
|
75
|
+
std::atomic<size_t> tail_;
|
76
|
+
std::atomic<size_t> dropped_count_;
|
51
77
|
};
|
52
78
|
|
53
79
|
} // namespace esp32_ble
|
@@ -268,6 +268,7 @@ async def to_code(config):
|
|
268
268
|
|
269
269
|
parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID])
|
270
270
|
cg.add(parent.register_gap_event_handler(var))
|
271
|
+
cg.add(parent.register_gap_scan_event_handler(var))
|
271
272
|
cg.add(parent.register_gattc_event_handler(var))
|
272
273
|
cg.add(parent.register_ble_status_event_handler(var))
|
273
274
|
cg.add(var.set_parent(parent))
|
@@ -50,17 +50,15 @@ void ESP32BLETracker::setup() {
|
|
50
50
|
ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE");
|
51
51
|
return;
|
52
52
|
}
|
53
|
-
|
54
|
-
|
55
|
-
this->scan_result_buffer_ = allocator.allocate(ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE);
|
53
|
+
RAMAllocator<BLEScanResult> allocator;
|
54
|
+
this->scan_ring_buffer_ = allocator.allocate(SCAN_RESULT_BUFFER_SIZE);
|
56
55
|
|
57
|
-
if (this->
|
58
|
-
ESP_LOGE(TAG, "Could not allocate buffer for BLE Tracker!");
|
56
|
+
if (this->scan_ring_buffer_ == nullptr) {
|
57
|
+
ESP_LOGE(TAG, "Could not allocate ring buffer for BLE Tracker!");
|
59
58
|
this->mark_failed();
|
60
59
|
}
|
61
60
|
|
62
61
|
global_esp32_ble_tracker = this;
|
63
|
-
this->scan_result_lock_ = xSemaphoreCreateMutex();
|
64
62
|
|
65
63
|
#ifdef USE_OTA
|
66
64
|
ota::get_global_ota_callback()->add_on_state_callback(
|
@@ -120,27 +118,31 @@ void ESP32BLETracker::loop() {
|
|
120
118
|
}
|
121
119
|
bool promote_to_connecting = discovered && !searching && !connecting;
|
122
120
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
|
129
|
-
}
|
121
|
+
// Process scan results from lock-free SPSC ring buffer
|
122
|
+
// Consumer side: This runs in the main loop thread
|
123
|
+
if (this->scanner_state_ == ScannerState::RUNNING) {
|
124
|
+
// Load our own index with relaxed ordering (we're the only writer)
|
125
|
+
size_t read_idx = this->ring_read_index_.load(std::memory_order_relaxed);
|
130
126
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
127
|
+
// Load producer's index with acquire to see their latest writes
|
128
|
+
size_t write_idx = this->ring_write_index_.load(std::memory_order_acquire);
|
129
|
+
|
130
|
+
while (read_idx != write_idx) {
|
131
|
+
// Process one result at a time directly from ring buffer
|
132
|
+
BLEScanResult &scan_result = this->scan_ring_buffer_[read_idx];
|
133
|
+
|
134
|
+
if (this->raw_advertisements_) {
|
135
|
+
for (auto *listener : this->listeners_) {
|
136
|
+
listener->parse_devices(&scan_result, 1);
|
137
|
+
}
|
138
|
+
for (auto *client : this->clients_) {
|
139
|
+
client->parse_devices(&scan_result, 1);
|
140
|
+
}
|
137
141
|
}
|
138
|
-
}
|
139
142
|
|
140
|
-
|
141
|
-
for (size_t i = 0; i < index; i++) {
|
143
|
+
if (this->parse_advertisements_) {
|
142
144
|
ESPBTDevice device;
|
143
|
-
device.parse_scan_rst(
|
145
|
+
device.parse_scan_rst(scan_result);
|
144
146
|
|
145
147
|
bool found = false;
|
146
148
|
for (auto *listener : this->listeners_) {
|
@@ -161,9 +163,19 @@ void ESP32BLETracker::loop() {
|
|
161
163
|
this->print_bt_device_info(device);
|
162
164
|
}
|
163
165
|
}
|
166
|
+
|
167
|
+
// Move to next entry in ring buffer
|
168
|
+
read_idx = (read_idx + 1) % SCAN_RESULT_BUFFER_SIZE;
|
169
|
+
|
170
|
+
// Store with release to ensure reads complete before index update
|
171
|
+
this->ring_read_index_.store(read_idx, std::memory_order_release);
|
172
|
+
}
|
173
|
+
|
174
|
+
// Log dropped results periodically
|
175
|
+
size_t dropped = this->scan_results_dropped_.exchange(0, std::memory_order_relaxed);
|
176
|
+
if (dropped > 0) {
|
177
|
+
ESP_LOGW(TAG, "Dropped %zu BLE scan results due to buffer overflow", dropped);
|
164
178
|
}
|
165
|
-
this->scan_result_index_ = 0;
|
166
|
-
xSemaphoreGive(this->scan_result_lock_);
|
167
179
|
}
|
168
180
|
if (this->scanner_state_ == ScannerState::STOPPED) {
|
169
181
|
this->end_of_scan_(); // Change state to IDLE
|
@@ -370,9 +382,6 @@ void ESP32BLETracker::recalculate_advertisement_parser_types() {
|
|
370
382
|
|
371
383
|
void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
372
384
|
switch (event) {
|
373
|
-
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
374
|
-
this->gap_scan_result_(param->scan_rst);
|
375
|
-
break;
|
376
385
|
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
377
386
|
this->gap_scan_set_param_complete_(param->scan_param_cmpl);
|
378
387
|
break;
|
@@ -385,11 +394,57 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga
|
|
385
394
|
default:
|
386
395
|
break;
|
387
396
|
}
|
397
|
+
// Forward all events to clients (scan results are handled separately via gap_scan_event_handler)
|
388
398
|
for (auto *client : this->clients_) {
|
389
399
|
client->gap_event_handler(event, param);
|
390
400
|
}
|
391
401
|
}
|
392
402
|
|
403
|
+
void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) {
|
404
|
+
ESP_LOGV(TAG, "gap_scan_result - event %d", scan_result.search_evt);
|
405
|
+
|
406
|
+
if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
|
407
|
+
// Lock-free SPSC ring buffer write (Producer side)
|
408
|
+
// This runs in the ESP-IDF Bluetooth stack callback thread
|
409
|
+
// IMPORTANT: Only this thread writes to ring_write_index_
|
410
|
+
|
411
|
+
// Load our own index with relaxed ordering (we're the only writer)
|
412
|
+
size_t write_idx = this->ring_write_index_.load(std::memory_order_relaxed);
|
413
|
+
size_t next_write_idx = (write_idx + 1) % SCAN_RESULT_BUFFER_SIZE;
|
414
|
+
|
415
|
+
// Load consumer's index with acquire to see their latest updates
|
416
|
+
size_t read_idx = this->ring_read_index_.load(std::memory_order_acquire);
|
417
|
+
|
418
|
+
// Check if buffer is full
|
419
|
+
if (next_write_idx != read_idx) {
|
420
|
+
// Write to ring buffer
|
421
|
+
this->scan_ring_buffer_[write_idx] = scan_result;
|
422
|
+
|
423
|
+
// Store with release to ensure the write is visible before index update
|
424
|
+
this->ring_write_index_.store(next_write_idx, std::memory_order_release);
|
425
|
+
} else {
|
426
|
+
// Buffer full, track dropped results
|
427
|
+
this->scan_results_dropped_.fetch_add(1, std::memory_order_relaxed);
|
428
|
+
}
|
429
|
+
} else if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
|
430
|
+
// Scan finished on its own
|
431
|
+
if (this->scanner_state_ != ScannerState::RUNNING) {
|
432
|
+
if (this->scanner_state_ == ScannerState::STOPPING) {
|
433
|
+
ESP_LOGE(TAG, "Scan was not running when scan completed.");
|
434
|
+
} else if (this->scanner_state_ == ScannerState::STARTING) {
|
435
|
+
ESP_LOGE(TAG, "Scan was not started when scan completed.");
|
436
|
+
} else if (this->scanner_state_ == ScannerState::FAILED) {
|
437
|
+
ESP_LOGE(TAG, "Scan was in failed state when scan completed.");
|
438
|
+
} else if (this->scanner_state_ == ScannerState::IDLE) {
|
439
|
+
ESP_LOGE(TAG, "Scan was idle when scan completed.");
|
440
|
+
} else if (this->scanner_state_ == ScannerState::STOPPED) {
|
441
|
+
ESP_LOGE(TAG, "Scan was stopped when scan completed.");
|
442
|
+
}
|
443
|
+
}
|
444
|
+
this->set_scanner_state_(ScannerState::STOPPED);
|
445
|
+
}
|
446
|
+
}
|
447
|
+
|
393
448
|
void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) {
|
394
449
|
ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status);
|
395
450
|
if (param.status == ESP_BT_STATUS_DONE) {
|
@@ -444,34 +499,6 @@ void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_
|
|
444
499
|
this->set_scanner_state_(ScannerState::STOPPED);
|
445
500
|
}
|
446
501
|
|
447
|
-
void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
448
|
-
ESP_LOGV(TAG, "gap_scan_result - event %d", param.search_evt);
|
449
|
-
if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
|
450
|
-
if (xSemaphoreTake(this->scan_result_lock_, 0)) {
|
451
|
-
if (this->scan_result_index_ < ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
|
452
|
-
this->scan_result_buffer_[this->scan_result_index_++] = param;
|
453
|
-
}
|
454
|
-
xSemaphoreGive(this->scan_result_lock_);
|
455
|
-
}
|
456
|
-
} else if (param.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
|
457
|
-
// Scan finished on its own
|
458
|
-
if (this->scanner_state_ != ScannerState::RUNNING) {
|
459
|
-
if (this->scanner_state_ == ScannerState::STOPPING) {
|
460
|
-
ESP_LOGE(TAG, "Scan was not running when scan completed.");
|
461
|
-
} else if (this->scanner_state_ == ScannerState::STARTING) {
|
462
|
-
ESP_LOGE(TAG, "Scan was not started when scan completed.");
|
463
|
-
} else if (this->scanner_state_ == ScannerState::FAILED) {
|
464
|
-
ESP_LOGE(TAG, "Scan was in failed state when scan completed.");
|
465
|
-
} else if (this->scanner_state_ == ScannerState::IDLE) {
|
466
|
-
ESP_LOGE(TAG, "Scan was idle when scan completed.");
|
467
|
-
} else if (this->scanner_state_ == ScannerState::STOPPED) {
|
468
|
-
ESP_LOGE(TAG, "Scan was stopped when scan completed.");
|
469
|
-
}
|
470
|
-
}
|
471
|
-
this->set_scanner_state_(ScannerState::STOPPED);
|
472
|
-
}
|
473
|
-
}
|
474
|
-
|
475
502
|
void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
476
503
|
esp_ble_gattc_cb_param_t *param) {
|
477
504
|
for (auto *client : this->clients_) {
|
@@ -494,13 +521,15 @@ optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData
|
|
494
521
|
return ESPBLEiBeacon(data.data.data());
|
495
522
|
}
|
496
523
|
|
497
|
-
void ESPBTDevice::parse_scan_rst(const
|
498
|
-
this->scan_result_ = param;
|
524
|
+
void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) {
|
499
525
|
for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++)
|
500
|
-
this->address_[i] =
|
501
|
-
this->address_type_ =
|
502
|
-
this->rssi_ =
|
503
|
-
|
526
|
+
this->address_[i] = scan_result.bda[i];
|
527
|
+
this->address_type_ = static_cast<esp_ble_addr_type_t>(scan_result.ble_addr_type);
|
528
|
+
this->rssi_ = scan_result.rssi;
|
529
|
+
|
530
|
+
// Parse advertisement data directly
|
531
|
+
uint8_t total_len = scan_result.adv_data_len + scan_result.scan_rsp_len;
|
532
|
+
this->parse_adv_(scan_result.ble_adv, total_len);
|
504
533
|
|
505
534
|
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
506
535
|
ESP_LOGVV(TAG, "Parse Result:");
|
@@ -558,13 +587,13 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
|
|
558
587
|
ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str());
|
559
588
|
}
|
560
589
|
|
561
|
-
ESP_LOGVV(TAG, " Adv data: %s",
|
590
|
+
ESP_LOGVV(TAG, " Adv data: %s",
|
591
|
+
format_hex_pretty(scan_result.ble_adv, scan_result.adv_data_len + scan_result.scan_rsp_len).c_str());
|
562
592
|
#endif
|
563
593
|
}
|
564
|
-
|
594
|
+
|
595
|
+
void ESPBTDevice::parse_adv_(const uint8_t *payload, uint8_t len) {
|
565
596
|
size_t offset = 0;
|
566
|
-
const uint8_t *payload = param.ble_adv;
|
567
|
-
uint8_t len = param.adv_data_len + param.scan_rsp_len;
|
568
597
|
|
569
598
|
while (offset + 2 < len) {
|
570
599
|
const uint8_t field_length = payload[offset++]; // First byte is length of adv record
|