mirror of
https://github.com/esphome/esphome.git
synced 2025-09-14 09:12:19 +01:00
ble pool
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
#include "ble.h"
|
#include "ble.h"
|
||||||
|
#include "ble_event_pool.h"
|
||||||
|
#include "queue_index.h"
|
||||||
|
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
@@ -23,8 +25,7 @@ namespace esp32_ble {
|
|||||||
|
|
||||||
static const char *const TAG = "esp32_ble";
|
static const char *const TAG = "esp32_ble";
|
||||||
|
|
||||||
static RAMAllocator<BLEEvent> EVENT_ALLOCATOR( // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
// No longer need static allocator - using pre-allocated pool instead
|
||||||
RAMAllocator<BLEEvent>::ALLOW_FAILURE | RAMAllocator<BLEEvent>::ALLOC_INTERNAL);
|
|
||||||
|
|
||||||
void ESP32BLE::setup() {
|
void ESP32BLE::setup() {
|
||||||
global_ble = this;
|
global_ble = this;
|
||||||
@@ -301,8 +302,16 @@ void ESP32BLE::loop() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
BLEEvent *ble_event = this->ble_events_.pop();
|
size_t event_idx = this->ble_events_.pop();
|
||||||
while (ble_event != nullptr) {
|
while (event_idx != LockFreeIndexQueue<MAX_BLE_QUEUE_SIZE>::INVALID_INDEX) {
|
||||||
|
BLEEvent *ble_event = this->ble_event_pool_.get(event_idx);
|
||||||
|
if (ble_event == nullptr) {
|
||||||
|
// This should not happen - log error and continue
|
||||||
|
ESP_LOGE(TAG, "Invalid event index: %zu", event_idx);
|
||||||
|
event_idx = this->ble_events_.pop();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
switch (ble_event->type_) {
|
switch (ble_event->type_) {
|
||||||
case BLEEvent::GATTS: {
|
case BLEEvent::GATTS: {
|
||||||
esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event;
|
esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event;
|
||||||
@@ -349,10 +358,9 @@ void ESP32BLE::loop() {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Destructor will clean up external allocations for GATTC/GATTS
|
// Return the event to the pool
|
||||||
ble_event->~BLEEvent();
|
this->ble_event_pool_.deallocate(event_idx);
|
||||||
EVENT_ALLOCATOR.deallocate(ble_event, 1);
|
event_idx = this->ble_events_.pop();
|
||||||
ble_event = this->ble_events_.pop();
|
|
||||||
}
|
}
|
||||||
if (this->advertising_ != nullptr) {
|
if (this->advertising_ != nullptr) {
|
||||||
this->advertising_->loop();
|
this->advertising_->loop();
|
||||||
@@ -363,6 +371,31 @@ void ESP32BLE::loop() {
|
|||||||
if (dropped > 0) {
|
if (dropped > 0) {
|
||||||
ESP_LOGW(TAG, "Dropped %zu BLE events due to buffer overflow", dropped);
|
ESP_LOGW(TAG, "Dropped %zu BLE events due to buffer overflow", dropped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log pool usage periodically (every ~10 seconds)
|
||||||
|
static uint32_t last_pool_log = 0;
|
||||||
|
uint32_t now = millis();
|
||||||
|
if (now - last_pool_log > 10000) {
|
||||||
|
size_t created = this->ble_event_pool_.get_total_created();
|
||||||
|
if (created > 0) {
|
||||||
|
ESP_LOGD(TAG, "BLE event pool: %zu events created (peak usage), %zu currently allocated", created,
|
||||||
|
this->ble_event_pool_.get_allocated_count());
|
||||||
|
}
|
||||||
|
last_pool_log = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to load new event data based on type
|
||||||
|
void load_ble_event(BLEEvent *event, esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||||
|
event->load_gap_event(e, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_ble_event(BLEEvent *event, esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||||
|
event->load_gattc_event(e, i, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_ble_event(BLEEvent *event, esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||||
|
event->load_gatts_event(e, i, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args> void enqueue_ble_event(Args... args) {
|
template<typename... Args> void enqueue_ble_event(Args... args) {
|
||||||
@@ -373,23 +406,35 @@ template<typename... Args> void enqueue_ble_event(Args... args) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
|
// Allocate an event from the pool
|
||||||
if (new_event == nullptr) {
|
size_t event_idx = global_ble->ble_event_pool_.allocate();
|
||||||
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
|
if (event_idx == BLEEventPool<MAX_BLE_QUEUE_SIZE>::INVALID_INDEX) {
|
||||||
|
// Pool is full, drop the event
|
||||||
global_ble->ble_events_.increment_dropped_count();
|
global_ble->ble_events_.increment_dropped_count();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
new (new_event) BLEEvent(args...);
|
|
||||||
|
|
||||||
// Push the event - since we're the only producer and we checked full() above,
|
// Get the event object
|
||||||
// this should always succeed unless we have a bug
|
BLEEvent *event = global_ble->ble_event_pool_.get(event_idx);
|
||||||
if (!global_ble->ble_events_.push(new_event)) {
|
if (event == nullptr) {
|
||||||
|
// This should not happen
|
||||||
|
ESP_LOGE(TAG, "Failed to get event from pool at index %zu", event_idx);
|
||||||
|
global_ble->ble_event_pool_.deallocate(event_idx);
|
||||||
|
global_ble->ble_events_.increment_dropped_count();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load new event data (replaces previous event)
|
||||||
|
load_ble_event(event, args...);
|
||||||
|
|
||||||
|
// Push the event index to the queue
|
||||||
|
if (!global_ble->ble_events_.push(event_idx)) {
|
||||||
// This should not happen in SPSC queue with single producer
|
// This should not happen in SPSC queue with single producer
|
||||||
ESP_LOGE(TAG, "BLE queue push failed unexpectedly");
|
ESP_LOGE(TAG, "BLE queue push failed unexpectedly");
|
||||||
new_event->~BLEEvent();
|
// Return to pool
|
||||||
EVENT_ALLOCATOR.deallocate(new_event, 1);
|
global_ble->ble_event_pool_.deallocate(event_idx);
|
||||||
}
|
}
|
||||||
} // NOLINT(clang-analyzer-unix.Malloc)
|
}
|
||||||
|
|
||||||
// Explicit template instantiations for the friend function
|
// 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_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *);
|
||||||
|
@@ -12,7 +12,9 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
#include "ble_event.h"
|
#include "ble_event.h"
|
||||||
|
#include "ble_event_pool.h"
|
||||||
#include "queue.h"
|
#include "queue.h"
|
||||||
|
#include "queue_index.h"
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
@@ -147,7 +149,8 @@ class ESP32BLE : public Component {
|
|||||||
std::vector<BLEStatusEventHandler *> ble_status_event_handlers_;
|
std::vector<BLEStatusEventHandler *> ble_status_event_handlers_;
|
||||||
BLEComponentState state_{BLE_COMPONENT_STATE_OFF};
|
BLEComponentState state_{BLE_COMPONENT_STATE_OFF};
|
||||||
|
|
||||||
LockFreeQueue<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_events_;
|
LockFreeIndexQueue<MAX_BLE_QUEUE_SIZE> ble_events_;
|
||||||
|
BLEEventPool<MAX_BLE_QUEUE_SIZE> ble_event_pool_;
|
||||||
BLEAdvertising *advertising_{};
|
BLEAdvertising *advertising_{};
|
||||||
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
|
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
|
||||||
uint32_t advertising_cycle_time_{};
|
uint32_t advertising_cycle_time_{};
|
||||||
|
@@ -63,123 +63,66 @@ class BLEEvent {
|
|||||||
// Constructor for GAP events - no external allocations needed
|
// Constructor for GAP events - no external allocations needed
|
||||||
BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||||
this->type_ = GAP;
|
this->type_ = GAP;
|
||||||
this->event_.gap.gap_event = e;
|
this->init_gap_data(e, p);
|
||||||
|
|
||||||
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
|
// Constructor for GATTC events - uses heap allocation
|
||||||
// Creates a copy of the param struct since the original is only valid during the callback
|
// 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) {
|
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||||
this->type_ = GATTC;
|
this->type_ = GATTC;
|
||||||
this->event_.gattc.gattc_event = e;
|
this->init_gattc_data(e, i, p);
|
||||||
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
|
// Constructor for GATTS events - uses heap allocation
|
||||||
// Creates a copy of the param struct since the original is only valid during the callback
|
// 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) {
|
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||||
this->type_ = GATTS;
|
this->type_ = GATTS;
|
||||||
this->event_.gatts.gatts_event = e;
|
this->init_gatts_data(e, i, p);
|
||||||
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
|
// Destructor to clean up heap allocations
|
||||||
~BLEEvent() {
|
~BLEEvent() { this->cleanup_heap_data(); }
|
||||||
switch (this->type_) {
|
|
||||||
case GATTC:
|
// Default constructor for pre-allocation in pool
|
||||||
delete this->event_.gattc.gattc_param;
|
BLEEvent() : type_(GAP) {}
|
||||||
delete this->event_.gattc.data;
|
|
||||||
break;
|
// Clean up any heap-allocated data
|
||||||
case GATTS:
|
void cleanup_heap_data() {
|
||||||
delete this->event_.gatts.gatts_param;
|
if (this->type_ == GAP) {
|
||||||
delete this->event_.gatts.data;
|
return;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
if (this->type_ == GATTC) {
|
||||||
|
delete this->event_.gattc.gattc_param;
|
||||||
|
delete this->event_.gattc.data;
|
||||||
|
this->event_.gattc.gattc_param = nullptr;
|
||||||
|
this->event_.gattc.data = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->type_ == GATTS) {
|
||||||
|
delete this->event_.gatts.gatts_param;
|
||||||
|
delete this->event_.gatts.data;
|
||||||
|
this->event_.gatts.gatts_param = nullptr;
|
||||||
|
this->event_.gatts.data = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load new event data for reuse (replaces previous event data)
|
||||||
|
void load_gap_event(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||||
|
this->cleanup_heap_data();
|
||||||
|
this->type_ = GAP;
|
||||||
|
this->init_gap_data(e, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_gattc_event(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||||
|
this->cleanup_heap_data();
|
||||||
|
this->type_ = GATTC;
|
||||||
|
this->init_gattc_data(e, i, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_gatts_event(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||||
|
this->cleanup_heap_data();
|
||||||
|
this->type_ = GATTS;
|
||||||
|
this->init_gatts_data(e, i, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable copy to prevent double-delete
|
// Disable copy to prevent double-delete
|
||||||
@@ -224,6 +167,102 @@ class BLEEvent {
|
|||||||
esp_gap_ble_cb_event_t gap_event_type() const { return event_.gap.gap_event; }
|
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; }
|
const BLEScanResult &scan_result() const { return event_.gap.scan_result; }
|
||||||
esp_bt_status_t scan_complete_status() const { return event_.gap.scan_complete.status; }
|
esp_bt_status_t scan_complete_status() const { return event_.gap.scan_complete.status; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Initialize GAP event data
|
||||||
|
void init_gap_data(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||||
|
this->event_.gap.gap_event = e;
|
||||||
|
|
||||||
|
if (p == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy data based on event type
|
||||||
|
switch (e) {
|
||||||
|
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
||||||
|
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:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize GATTC event data
|
||||||
|
void init_gattc_data(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;
|
||||||
|
|
||||||
|
if (p == nullptr) {
|
||||||
|
this->event_.gattc.gattc_param = nullptr;
|
||||||
|
this->event_.gattc.data = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heap-allocate param
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize GATTS event data
|
||||||
|
void init_gatts_data(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;
|
||||||
|
|
||||||
|
if (p == nullptr) {
|
||||||
|
this->event_.gatts.gatts_param = nullptr;
|
||||||
|
this->event_.gatts.data = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heap-allocate param
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// BLEEvent total size: 84 bytes (80 byte union + 1 byte type + 3 bytes padding)
|
// BLEEvent total size: 84 bytes (80 byte union + 1 byte type + 3 bytes padding)
|
||||||
|
133
esphome/components/esp32_ble/ble_event_pool.h
Normal file
133
esphome/components/esp32_ble/ble_event_pool.h
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstddef>
|
||||||
|
#include "ble_event.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace esp32_ble {
|
||||||
|
|
||||||
|
// BLE Event Pool - Pre-allocated pool of BLEEvent objects to avoid heap fragmentation
|
||||||
|
// This is a lock-free pool that allows the BLE task to allocate events without malloc
|
||||||
|
template<size_t SIZE> class BLEEventPool {
|
||||||
|
public:
|
||||||
|
BLEEventPool() {
|
||||||
|
// Initialize all slots as unallocated
|
||||||
|
for (size_t i = 0; i < SIZE; i++) {
|
||||||
|
this->events_[i] = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the free list - all indices are initially free
|
||||||
|
for (size_t i = 0; i < SIZE - 1; i++) {
|
||||||
|
this->next_free_[i] = i + 1;
|
||||||
|
}
|
||||||
|
this->next_free_[SIZE - 1] = INVALID_INDEX;
|
||||||
|
|
||||||
|
this->free_head_.store(0, std::memory_order_relaxed);
|
||||||
|
this->allocated_count_.store(0, std::memory_order_relaxed);
|
||||||
|
this->total_created_.store(0, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
~BLEEventPool() {
|
||||||
|
// Delete any events that were created
|
||||||
|
for (size_t i = 0; i < SIZE; i++) {
|
||||||
|
if (this->events_[i] != nullptr) {
|
||||||
|
delete this->events_[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate an event slot and return its index
|
||||||
|
// Returns INVALID_INDEX if pool is full
|
||||||
|
size_t allocate() {
|
||||||
|
while (true) {
|
||||||
|
size_t head = this->free_head_.load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
if (head == INVALID_INDEX) {
|
||||||
|
// Pool is full
|
||||||
|
return INVALID_INDEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t next = this->next_free_[head];
|
||||||
|
|
||||||
|
// Try to update the free list head
|
||||||
|
if (this->free_head_.compare_exchange_weak(head, next, std::memory_order_release, std::memory_order_acquire)) {
|
||||||
|
this->allocated_count_.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
// CAS failed, retry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deallocate an event slot by index
|
||||||
|
void deallocate(size_t index) {
|
||||||
|
if (index >= SIZE) {
|
||||||
|
return; // Invalid index
|
||||||
|
}
|
||||||
|
|
||||||
|
// No destructor call - events are reused
|
||||||
|
// The event's reset methods handle cleanup when switching types
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
size_t head = this->free_head_.load(std::memory_order_acquire);
|
||||||
|
this->next_free_[index] = head;
|
||||||
|
|
||||||
|
// Try to add this index back to the free list
|
||||||
|
if (this->free_head_.compare_exchange_weak(head, index, std::memory_order_release, std::memory_order_acquire)) {
|
||||||
|
this->allocated_count_.fetch_sub(1, std::memory_order_relaxed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// CAS failed, retry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get event by index, creating it if needed
|
||||||
|
BLEEvent *get(size_t index) {
|
||||||
|
if (index >= SIZE) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create event on first access (warm-up)
|
||||||
|
if (this->events_[index] == nullptr) {
|
||||||
|
// Use internal RAM for better performance
|
||||||
|
RAMAllocator<BLEEvent> allocator(RAMAllocator<BLEEvent>::ALLOC_INTERNAL);
|
||||||
|
BLEEvent *event = allocator.allocate(1);
|
||||||
|
|
||||||
|
if (event == nullptr) {
|
||||||
|
// Fall back to regular allocation
|
||||||
|
event = new BLEEvent();
|
||||||
|
} else {
|
||||||
|
// Placement new to construct the object
|
||||||
|
new (event) BLEEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->events_[index] = event;
|
||||||
|
this->total_created_.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this->events_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get number of allocated events
|
||||||
|
size_t get_allocated_count() const { return this->allocated_count_.load(std::memory_order_relaxed); }
|
||||||
|
|
||||||
|
// Get total number of events created (high water mark)
|
||||||
|
size_t get_total_created() const { return this->total_created_.load(std::memory_order_relaxed); }
|
||||||
|
|
||||||
|
static constexpr size_t INVALID_INDEX = SIZE_MAX;
|
||||||
|
|
||||||
|
private:
|
||||||
|
BLEEvent *events_[SIZE]; // Array of pointers, allocated on demand
|
||||||
|
size_t next_free_[SIZE]; // Next free index for each slot
|
||||||
|
std::atomic<size_t> free_head_; // Head of the free list
|
||||||
|
std::atomic<size_t> allocated_count_; // Number of currently allocated events
|
||||||
|
std::atomic<size_t> total_created_; // Total events created (high water mark)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esp32_ble
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
81
esphome/components/esp32_ble/queue_index.h
Normal file
81
esphome/components/esp32_ble/queue_index.h
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace esp32_ble {
|
||||||
|
|
||||||
|
// Lock-free SPSC queue that stores indices instead of pointers
|
||||||
|
// This allows us to use a pre-allocated pool of objects
|
||||||
|
template<size_t SIZE> class LockFreeIndexQueue {
|
||||||
|
public:
|
||||||
|
static constexpr size_t INVALID_INDEX = SIZE_MAX;
|
||||||
|
|
||||||
|
LockFreeIndexQueue() : head_(0), tail_(0), dropped_count_(0) {
|
||||||
|
// Initialize all slots to invalid
|
||||||
|
for (size_t i = 0; i < SIZE; i++) {
|
||||||
|
buffer_[i] = INVALID_INDEX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool push(size_t index) {
|
||||||
|
if (index == INVALID_INDEX)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t current_tail = tail_.load(std::memory_order_relaxed);
|
||||||
|
size_t next_tail = (current_tail + 1) % SIZE;
|
||||||
|
|
||||||
|
if (next_tail == head_.load(std::memory_order_acquire)) {
|
||||||
|
// Buffer full
|
||||||
|
dropped_count_.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_[current_tail] = index;
|
||||||
|
tail_.store(next_tail, std::memory_order_release);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pop() {
|
||||||
|
size_t current_head = head_.load(std::memory_order_relaxed);
|
||||||
|
|
||||||
|
if (current_head == tail_.load(std::memory_order_acquire)) {
|
||||||
|
return INVALID_INDEX; // Empty
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index = buffer_[current_head];
|
||||||
|
head_.store((current_head + 1) % SIZE, std::memory_order_release);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const {
|
||||||
|
size_t tail = tail_.load(std::memory_order_acquire);
|
||||||
|
size_t head = head_.load(std::memory_order_acquire);
|
||||||
|
return (tail - head + SIZE) % SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t get_and_reset_dropped_count() { return dropped_count_.exchange(0, std::memory_order_relaxed); }
|
||||||
|
|
||||||
|
void increment_dropped_count() { dropped_count_.fetch_add(1, std::memory_order_relaxed); }
|
||||||
|
|
||||||
|
bool empty() const { return head_.load(std::memory_order_acquire) == tail_.load(std::memory_order_acquire); }
|
||||||
|
|
||||||
|
bool full() const {
|
||||||
|
size_t next_tail = (tail_.load(std::memory_order_relaxed) + 1) % SIZE;
|
||||||
|
return next_tail == head_.load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
size_t buffer_[SIZE];
|
||||||
|
std::atomic<size_t> head_;
|
||||||
|
std::atomic<size_t> tail_;
|
||||||
|
std::atomic<size_t> dropped_count_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esp32_ble
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
Reference in New Issue
Block a user