mirror of
https://github.com/esphome/esphome.git
synced 2025-09-04 20:32:21 +01:00
Fix unbound BLE event queue growth and reduce memory usage (#9052)
This commit is contained in:
@@ -23,6 +23,9 @@ namespace esp32_ble {
|
||||
|
||||
static const char *const TAG = "esp32_ble";
|
||||
|
||||
// Maximum size of the BLE event queue
|
||||
static constexpr size_t MAX_BLE_QUEUE_SIZE = SCAN_RESULT_BUFFER_SIZE * 2;
|
||||
|
||||
static RAMAllocator<BLEEvent> EVENT_ALLOCATOR( // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
RAMAllocator<BLEEvent>::ALLOW_FAILURE | RAMAllocator<BLEEvent>::ALLOC_INTERNAL);
|
||||
|
||||
@@ -304,20 +307,52 @@ void ESP32BLE::loop() {
|
||||
BLEEvent *ble_event = this->ble_events_.pop();
|
||||
while (ble_event != nullptr) {
|
||||
switch (ble_event->type_) {
|
||||
case BLEEvent::GATTS:
|
||||
this->real_gatts_event_handler_(ble_event->event_.gatts.gatts_event, ble_event->event_.gatts.gatts_if,
|
||||
&ble_event->event_.gatts.gatts_param);
|
||||
case BLEEvent::GATTS: {
|
||||
esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event;
|
||||
esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if;
|
||||
esp_ble_gatts_cb_param_t *param = ble_event->event_.gatts.gatts_param;
|
||||
ESP_LOGV(TAG, "gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
|
||||
for (auto *gatts_handler : this->gatts_event_handlers_) {
|
||||
gatts_handler->gatts_event_handler(event, gatts_if, param);
|
||||
}
|
||||
break;
|
||||
case BLEEvent::GATTC:
|
||||
this->real_gattc_event_handler_(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if,
|
||||
&ble_event->event_.gattc.gattc_param);
|
||||
}
|
||||
case BLEEvent::GATTC: {
|
||||
esp_gattc_cb_event_t event = ble_event->event_.gattc.gattc_event;
|
||||
esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if;
|
||||
esp_ble_gattc_cb_param_t *param = ble_event->event_.gattc.gattc_param;
|
||||
ESP_LOGV(TAG, "gattc_event [esp_gatt_if: %d] - %d", gattc_if, event);
|
||||
for (auto *gattc_handler : this->gattc_event_handlers_) {
|
||||
gattc_handler->gattc_event_handler(event, gattc_if, param);
|
||||
}
|
||||
break;
|
||||
case BLEEvent::GAP:
|
||||
this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param);
|
||||
}
|
||||
case BLEEvent::GAP: {
|
||||
esp_gap_ble_cb_event_t gap_event = ble_event->event_.gap.gap_event;
|
||||
if (gap_event == ESP_GAP_BLE_SCAN_RESULT_EVT) {
|
||||
// Use the new scan event handler - no memcpy!
|
||||
for (auto *scan_handler : this->gap_scan_event_handlers_) {
|
||||
scan_handler->gap_scan_event_handler(ble_event->scan_result());
|
||||
}
|
||||
} else if (gap_event == ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT ||
|
||||
gap_event == ESP_GAP_BLE_SCAN_START_COMPLETE_EVT ||
|
||||
gap_event == ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT) {
|
||||
// All three scan complete events have the same structure with just status
|
||||
// The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe
|
||||
// This is verified at compile-time by static_assert checks in ble_event.h
|
||||
// The struct already contains our copy of the status (copied in BLEEvent constructor)
|
||||
ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
|
||||
for (auto *gap_handler : this->gap_event_handlers_) {
|
||||
gap_handler->gap_event_handler(
|
||||
gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.scan_complete));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Destructor will clean up external allocations for GATTC/GATTS
|
||||
ble_event->~BLEEvent();
|
||||
EVENT_ALLOCATOR.deallocate(ble_event, 1);
|
||||
ble_event = this->ble_events_.pop();
|
||||
@@ -327,59 +362,55 @@ void ESP32BLE::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
template<typename... Args> void enqueue_ble_event(Args... args) {
|
||||
if (global_ble->ble_events_.size() >= MAX_BLE_QUEUE_SIZE) {
|
||||
ESP_LOGD(TAG, "Event queue full (%zu), dropping event", MAX_BLE_QUEUE_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
|
||||
if (new_event == nullptr) {
|
||||
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
|
||||
return;
|
||||
}
|
||||
new (new_event) BLEEvent(event, param);
|
||||
new (new_event) BLEEvent(args...);
|
||||
global_ble->ble_events_.push(new_event);
|
||||
} // NOLINT(clang-analyzer-unix.Malloc)
|
||||
|
||||
void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event);
|
||||
for (auto *gap_handler : this->gap_event_handlers_) {
|
||||
gap_handler->gap_event_handler(event, param);
|
||||
// Explicit template instantiations for the friend function
|
||||
template void enqueue_ble_event(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *);
|
||||
template void enqueue_ble_event(esp_gatts_cb_event_t, esp_gatt_if_t, esp_ble_gatts_cb_param_t *);
|
||||
template void enqueue_ble_event(esp_gattc_cb_event_t, esp_gatt_if_t, esp_ble_gattc_cb_param_t *);
|
||||
|
||||
void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
switch (event) {
|
||||
// Only queue the 4 GAP events we actually handle
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
||||
enqueue_ble_event(event, param);
|
||||
return;
|
||||
|
||||
// Ignore these GAP events as they are not relevant for our use case
|
||||
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
|
||||
case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT:
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ESP_LOGW(TAG, "Ignoring unexpected GAP event type: %d", event);
|
||||
}
|
||||
|
||||
void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param) {
|
||||
BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
|
||||
if (new_event == nullptr) {
|
||||
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
|
||||
return;
|
||||
}
|
||||
new (new_event) BLEEvent(event, gatts_if, param);
|
||||
global_ble->ble_events_.push(new_event);
|
||||
} // NOLINT(clang-analyzer-unix.Malloc)
|
||||
|
||||
void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param) {
|
||||
ESP_LOGV(TAG, "(BLE) gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
|
||||
for (auto *gatts_handler : this->gatts_event_handlers_) {
|
||||
gatts_handler->gatts_event_handler(event, gatts_if, param);
|
||||
}
|
||||
enqueue_ble_event(event, gatts_if, param);
|
||||
}
|
||||
|
||||
void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
|
||||
if (new_event == nullptr) {
|
||||
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
|
||||
return;
|
||||
}
|
||||
new (new_event) BLEEvent(event, gattc_if, param);
|
||||
global_ble->ble_events_.push(new_event);
|
||||
} // NOLINT(clang-analyzer-unix.Malloc)
|
||||
|
||||
void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
ESP_LOGV(TAG, "(BLE) gattc_event [esp_gatt_if: %d] - %d", gattc_if, event);
|
||||
for (auto *gattc_handler : this->gattc_event_handlers_) {
|
||||
gattc_handler->gattc_event_handler(event, gattc_if, param);
|
||||
}
|
||||
enqueue_ble_event(event, gattc_if, param);
|
||||
}
|
||||
|
||||
float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; }
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "ble_advertising.h"
|
||||
#include "ble_uuid.h"
|
||||
#include "ble_scan_result.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
@@ -22,6 +23,13 @@
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
|
||||
// Maximum number of BLE scan results to buffer
|
||||
#ifdef USE_PSRAM
|
||||
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 32;
|
||||
#else
|
||||
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 20;
|
||||
#endif
|
||||
|
||||
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address);
|
||||
|
||||
// NOLINTNEXTLINE(modernize-use-using)
|
||||
@@ -57,6 +65,11 @@ class GAPEventHandler {
|
||||
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
|
||||
};
|
||||
|
||||
class GAPScanEventHandler {
|
||||
public:
|
||||
virtual void gap_scan_event_handler(const BLEScanResult &scan_result) = 0;
|
||||
};
|
||||
|
||||
class GATTcEventHandler {
|
||||
public:
|
||||
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
@@ -101,6 +114,9 @@ class ESP32BLE : public Component {
|
||||
void advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback);
|
||||
|
||||
void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); }
|
||||
void register_gap_scan_event_handler(GAPScanEventHandler *handler) {
|
||||
this->gap_scan_event_handlers_.push_back(handler);
|
||||
}
|
||||
void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); }
|
||||
void register_gatts_event_handler(GATTsEventHandler *handler) { this->gatts_event_handlers_.push_back(handler); }
|
||||
void register_ble_status_event_handler(BLEStatusEventHandler *handler) {
|
||||
@@ -113,16 +129,16 @@ class ESP32BLE : public Component {
|
||||
static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
|
||||
void real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
void real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
|
||||
bool ble_setup_();
|
||||
bool ble_dismantle_();
|
||||
bool ble_pre_setup_();
|
||||
void advertising_init_();
|
||||
|
||||
private:
|
||||
template<typename... Args> friend void enqueue_ble_event(Args... args);
|
||||
|
||||
std::vector<GAPEventHandler *> gap_event_handlers_;
|
||||
std::vector<GAPScanEventHandler *> gap_scan_event_handlers_;
|
||||
std::vector<GATTcEventHandler *> gattc_event_handlers_;
|
||||
std::vector<GATTsEventHandler *> gatts_event_handlers_;
|
||||
std::vector<BLEStatusEventHandler *> ble_status_event_handlers_;
|
||||
|
@@ -2,92 +2,232 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <cstddef> // for offsetof
|
||||
#include <vector>
|
||||
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <esp_gatts_api.h>
|
||||
|
||||
#include "ble_scan_result.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
|
||||
// Compile-time verification that ESP-IDF scan complete events only contain a status field
|
||||
// This ensures our reinterpret_cast in ble.cpp is safe
|
||||
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
||||
"ESP-IDF scan_param_cmpl structure has unexpected size");
|
||||
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
||||
"ESP-IDF scan_start_cmpl structure has unexpected size");
|
||||
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
||||
"ESP-IDF scan_stop_cmpl structure has unexpected size");
|
||||
|
||||
// Verify the status field is at offset 0 (first member)
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_param_cmpl.status) ==
|
||||
offsetof(esp_ble_gap_cb_param_t, scan_param_cmpl),
|
||||
"status must be first member of scan_param_cmpl");
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_start_cmpl.status) ==
|
||||
offsetof(esp_ble_gap_cb_param_t, scan_start_cmpl),
|
||||
"status must be first member of scan_start_cmpl");
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl.status) ==
|
||||
offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl),
|
||||
"status must be first member of scan_stop_cmpl");
|
||||
|
||||
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
||||
// This class stores each event in a single type.
|
||||
// This class stores each event with minimal memory usage.
|
||||
// GAP events (99% of traffic) don't have the vector overhead.
|
||||
// GATTC/GATTS events use heap allocation for their param and data.
|
||||
//
|
||||
// Event flow:
|
||||
// 1. ESP-IDF BLE stack calls our static handlers in the BLE task context
|
||||
// 2. The handlers create a BLEEvent instance, copying only the data we need
|
||||
// 3. The event is pushed to a thread-safe queue
|
||||
// 4. In the main loop(), events are popped from the queue and processed
|
||||
// 5. The event destructor cleans up any external allocations
|
||||
//
|
||||
// Thread safety:
|
||||
// - GAP events: We copy only the fields we need directly into the union
|
||||
// - GATTC/GATTS events: We heap-allocate and copy the entire param struct, ensuring
|
||||
// the data remains valid even after the BLE callback returns. The original
|
||||
// param pointer from ESP-IDF is only valid during the callback.
|
||||
class BLEEvent {
|
||||
public:
|
||||
BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||
this->event_.gap.gap_event = e;
|
||||
memcpy(&this->event_.gap.gap_param, p, sizeof(esp_ble_gap_cb_param_t));
|
||||
this->type_ = GAP;
|
||||
};
|
||||
|
||||
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||
this->event_.gattc.gattc_event = e;
|
||||
this->event_.gattc.gattc_if = i;
|
||||
memcpy(&this->event_.gattc.gattc_param, p, sizeof(esp_ble_gattc_cb_param_t));
|
||||
// Need to also make a copy of relevant event data.
|
||||
switch (e) {
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
this->data.assign(p->notify.value, p->notify.value + p->notify.value_len);
|
||||
this->event_.gattc.gattc_param.notify.value = this->data.data();
|
||||
break;
|
||||
case ESP_GATTC_READ_CHAR_EVT:
|
||||
case ESP_GATTC_READ_DESCR_EVT:
|
||||
this->data.assign(p->read.value, p->read.value + p->read.value_len);
|
||||
this->event_.gattc.gattc_param.read.value = this->data.data();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->type_ = GATTC;
|
||||
};
|
||||
|
||||
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||
this->event_.gatts.gatts_event = e;
|
||||
this->event_.gatts.gatts_if = i;
|
||||
memcpy(&this->event_.gatts.gatts_param, p, sizeof(esp_ble_gatts_cb_param_t));
|
||||
// Need to also make a copy of relevant event data.
|
||||
switch (e) {
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
this->data.assign(p->write.value, p->write.value + p->write.len);
|
||||
this->event_.gatts.gatts_param.write.value = this->data.data();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->type_ = GATTS;
|
||||
};
|
||||
|
||||
union {
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gap_event {
|
||||
esp_gap_ble_cb_event_t gap_event;
|
||||
esp_ble_gap_cb_param_t gap_param;
|
||||
} gap;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gattc_event {
|
||||
esp_gattc_cb_event_t gattc_event;
|
||||
esp_gatt_if_t gattc_if;
|
||||
esp_ble_gattc_cb_param_t gattc_param;
|
||||
} gattc;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gatts_event {
|
||||
esp_gatts_cb_event_t gatts_event;
|
||||
esp_gatt_if_t gatts_if;
|
||||
esp_ble_gatts_cb_param_t gatts_param;
|
||||
} gatts;
|
||||
} event_;
|
||||
|
||||
std::vector<uint8_t> data{};
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
enum ble_event_t : uint8_t {
|
||||
GAP,
|
||||
GATTC,
|
||||
GATTS,
|
||||
} type_;
|
||||
};
|
||||
|
||||
// Constructor for GAP events - no external allocations needed
|
||||
BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||
this->type_ = GAP;
|
||||
this->event_.gap.gap_event = e;
|
||||
|
||||
if (p == nullptr) {
|
||||
return; // Invalid event, but we can't log in header file
|
||||
}
|
||||
|
||||
// Only copy the data we actually use for each GAP event type
|
||||
switch (e) {
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
||||
// Copy only the fields we use from scan results
|
||||
memcpy(this->event_.gap.scan_result.bda, p->scan_rst.bda, sizeof(esp_bd_addr_t));
|
||||
this->event_.gap.scan_result.ble_addr_type = p->scan_rst.ble_addr_type;
|
||||
this->event_.gap.scan_result.rssi = p->scan_rst.rssi;
|
||||
this->event_.gap.scan_result.adv_data_len = p->scan_rst.adv_data_len;
|
||||
this->event_.gap.scan_result.scan_rsp_len = p->scan_rst.scan_rsp_len;
|
||||
this->event_.gap.scan_result.search_evt = p->scan_rst.search_evt;
|
||||
memcpy(this->event_.gap.scan_result.ble_adv, p->scan_rst.ble_adv,
|
||||
ESP_BLE_ADV_DATA_LEN_MAX + ESP_BLE_SCAN_RSP_DATA_LEN_MAX);
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
||||
this->event_.gap.scan_complete.status = p->scan_param_cmpl.status;
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||
this->event_.gap.scan_complete.status = p->scan_start_cmpl.status;
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
||||
this->event_.gap.scan_complete.status = p->scan_stop_cmpl.status;
|
||||
break;
|
||||
|
||||
default:
|
||||
// We only handle 4 GAP event types, others are dropped
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor for GATTC events - uses heap allocation
|
||||
// Creates a copy of the param struct since the original is only valid during the callback
|
||||
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||
this->type_ = GATTC;
|
||||
this->event_.gattc.gattc_event = e;
|
||||
this->event_.gattc.gattc_if = i;
|
||||
|
||||
if (p == nullptr) {
|
||||
this->event_.gattc.gattc_param = nullptr;
|
||||
this->event_.gattc.data = nullptr;
|
||||
return; // Invalid event, but we can't log in header file
|
||||
}
|
||||
|
||||
// Heap-allocate param and data
|
||||
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
|
||||
// while GAP events (99%) are stored inline to minimize memory usage
|
||||
this->event_.gattc.gattc_param = new esp_ble_gattc_cb_param_t(*p);
|
||||
|
||||
// Copy data for events that need it
|
||||
switch (e) {
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
this->event_.gattc.data = new std::vector<uint8_t>(p->notify.value, p->notify.value + p->notify.value_len);
|
||||
this->event_.gattc.gattc_param->notify.value = this->event_.gattc.data->data();
|
||||
break;
|
||||
case ESP_GATTC_READ_CHAR_EVT:
|
||||
case ESP_GATTC_READ_DESCR_EVT:
|
||||
this->event_.gattc.data = new std::vector<uint8_t>(p->read.value, p->read.value + p->read.value_len);
|
||||
this->event_.gattc.gattc_param->read.value = this->event_.gattc.data->data();
|
||||
break;
|
||||
default:
|
||||
this->event_.gattc.data = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor for GATTS events - uses heap allocation
|
||||
// Creates a copy of the param struct since the original is only valid during the callback
|
||||
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||
this->type_ = GATTS;
|
||||
this->event_.gatts.gatts_event = e;
|
||||
this->event_.gatts.gatts_if = i;
|
||||
|
||||
if (p == nullptr) {
|
||||
this->event_.gatts.gatts_param = nullptr;
|
||||
this->event_.gatts.data = nullptr;
|
||||
return; // Invalid event, but we can't log in header file
|
||||
}
|
||||
|
||||
// Heap-allocate param and data
|
||||
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
|
||||
// while GAP events (99%) are stored inline to minimize memory usage
|
||||
this->event_.gatts.gatts_param = new esp_ble_gatts_cb_param_t(*p);
|
||||
|
||||
// Copy data for events that need it
|
||||
switch (e) {
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
this->event_.gatts.data = new std::vector<uint8_t>(p->write.value, p->write.value + p->write.len);
|
||||
this->event_.gatts.gatts_param->write.value = this->event_.gatts.data->data();
|
||||
break;
|
||||
default:
|
||||
this->event_.gatts.data = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Destructor to clean up heap allocations
|
||||
~BLEEvent() {
|
||||
switch (this->type_) {
|
||||
case GATTC:
|
||||
delete this->event_.gattc.gattc_param;
|
||||
delete this->event_.gattc.data;
|
||||
break;
|
||||
case GATTS:
|
||||
delete this->event_.gatts.gatts_param;
|
||||
delete this->event_.gatts.data;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Disable copy to prevent double-delete
|
||||
BLEEvent(const BLEEvent &) = delete;
|
||||
BLEEvent &operator=(const BLEEvent &) = delete;
|
||||
|
||||
union {
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gap_event {
|
||||
esp_gap_ble_cb_event_t gap_event;
|
||||
union {
|
||||
BLEScanResult scan_result; // 73 bytes
|
||||
// This matches ESP-IDF's scan complete event structures
|
||||
// All three (scan_param_cmpl, scan_start_cmpl, scan_stop_cmpl) have identical layout
|
||||
struct {
|
||||
esp_bt_status_t status;
|
||||
} scan_complete; // 1 byte
|
||||
};
|
||||
} gap; // 80 bytes total
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gattc_event {
|
||||
esp_gattc_cb_event_t gattc_event;
|
||||
esp_gatt_if_t gattc_if;
|
||||
esp_ble_gattc_cb_param_t *gattc_param; // Heap-allocated
|
||||
std::vector<uint8_t> *data; // Heap-allocated
|
||||
} gattc; // 16 bytes (pointers only)
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gatts_event {
|
||||
esp_gatts_cb_event_t gatts_event;
|
||||
esp_gatt_if_t gatts_if;
|
||||
esp_ble_gatts_cb_param_t *gatts_param; // Heap-allocated
|
||||
std::vector<uint8_t> *data; // Heap-allocated
|
||||
} gatts; // 16 bytes (pointers only)
|
||||
} event_; // 80 bytes
|
||||
|
||||
ble_event_t type_;
|
||||
|
||||
// Helper methods to access event data
|
||||
ble_event_t type() const { return type_; }
|
||||
esp_gap_ble_cb_event_t gap_event_type() const { return event_.gap.gap_event; }
|
||||
const BLEScanResult &scan_result() const { return event_.gap.scan_result; }
|
||||
esp_bt_status_t scan_complete_status() const { return event_.gap.scan_complete.status; }
|
||||
};
|
||||
|
||||
// BLEEvent total size: 84 bytes (80 byte union + 1 byte type + 3 bytes padding)
|
||||
|
||||
} // namespace esp32_ble
|
||||
} // namespace esphome
|
||||
|
||||
|
24
esphome/components/esp32_ble/ble_scan_result.h
Normal file
24
esphome/components/esp32_ble/ble_scan_result.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gap_ble_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
|
||||
// Structure for BLE scan results - only fields we actually use
|
||||
struct __attribute__((packed)) BLEScanResult {
|
||||
esp_bd_addr_t bda;
|
||||
uint8_t ble_addr_type;
|
||||
int8_t rssi;
|
||||
uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX + ESP_BLE_SCAN_RSP_DATA_LEN_MAX];
|
||||
uint8_t adv_data_len;
|
||||
uint8_t scan_rsp_len;
|
||||
uint8_t search_evt;
|
||||
}; // ~73 bytes vs ~400 bytes for full esp_ble_gap_cb_param_t
|
||||
|
||||
} // namespace esp32_ble
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -45,6 +45,17 @@ template<class T> class Queue {
|
||||
return element;
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
// Lock-free size check. While std::queue::size() is not thread-safe, we intentionally
|
||||
// avoid locking here to prevent blocking the BLE callback thread. The size is only
|
||||
// used to decide whether to drop incoming events when the queue is near capacity.
|
||||
// With a queue limit of 40-64 events and normal processing, dropping events should
|
||||
// be extremely rare. When it does approach capacity, being off by 1-2 events is
|
||||
// acceptable to avoid blocking the BLE stack's time-sensitive callbacks.
|
||||
// Trade-off: We prefer occasional dropped events over potential BLE stack delays.
|
||||
return q_.size();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::queue<T *> q_;
|
||||
SemaphoreHandle_t m_;
|
||||
|
Reference in New Issue
Block a user