mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 13:27:14 +00:00
[wifi] Use queue abstraction for LibreTiny WiFi events (#15343)
This commit is contained in:
@@ -443,6 +443,13 @@ async def component_to_code(config):
|
|||||||
# 4-8KB flash). Even if linked, it would use locks, so explicit FreeRTOS
|
# 4-8KB flash). Even if linked, it would use locks, so explicit FreeRTOS
|
||||||
# mutexes are simpler and equivalent.
|
# mutexes are simpler and equivalent.
|
||||||
cg.add_define(ThreadModel.MULTI_NO_ATOMICS)
|
cg.add_define(ThreadModel.MULTI_NO_ATOMICS)
|
||||||
|
# Enable FreeRTOS static allocation so FreeRTOSQueue can use
|
||||||
|
# xQueueCreateStatic (queue storage in BSS, no heap allocation).
|
||||||
|
# Also moves FreeRTOS internal structures (timer command queue) to BSS.
|
||||||
|
# BK72xx's FreeRTOSConfig.h doesn't define this, defaulting to 0.
|
||||||
|
# The -D wins over the #ifndef default in FreeRTOS.h.
|
||||||
|
# Not enabled on RTL87xx/LN882x — costs more heap than it saves there.
|
||||||
|
cg.add_build_flag("-DconfigSUPPORT_STATIC_ALLOCATION=1")
|
||||||
|
|
||||||
# RTL8710B needs FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake
|
# RTL8710B needs FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake
|
||||||
# required by AsyncTCP 3.4.3+ (https://github.com/esphome/esphome/issues/10220)
|
# required by AsyncTCP 3.4.3+ (https://github.com/esphome/esphome/issues/10220)
|
||||||
|
|||||||
52
esphome/components/libretiny/freertos_static_alloc.c
Normal file
52
esphome/components/libretiny/freertos_static_alloc.c
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* FreeRTOS static allocation callbacks for LibreTiny platforms.
|
||||||
|
*
|
||||||
|
* Required when configSUPPORT_STATIC_ALLOCATION is enabled. These callbacks
|
||||||
|
* provide memory for the idle and timer tasks. Following ESP-IDF's approach,
|
||||||
|
* we allocate from the FreeRTOS heap (pvPortMalloc) rather than using truly
|
||||||
|
* static buffers, to avoid assumptions about memory layout.
|
||||||
|
*
|
||||||
|
* This enables xQueueCreateStatic, xTaskCreateStatic, etc. throughout ESPHome,
|
||||||
|
* allowing queue storage to live in BSS with zero runtime heap allocation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef USE_BK72XX
|
||||||
|
|
||||||
|
#include <FreeRTOS.h>
|
||||||
|
#include <task.h>
|
||||||
|
|
||||||
|
#if (configSUPPORT_STATIC_ALLOCATION == 1)
|
||||||
|
|
||||||
|
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer,
|
||||||
|
uint32_t *pulIdleTaskStackSize) {
|
||||||
|
/* Stack grows down on ARM — allocate stack first, then TCB,
|
||||||
|
* so the stack does not grow into the TCB. */
|
||||||
|
StackType_t *stack = (StackType_t *) pvPortMalloc(configMINIMAL_STACK_SIZE * sizeof(StackType_t));
|
||||||
|
StaticTask_t *tcb = (StaticTask_t *) pvPortMalloc(sizeof(StaticTask_t));
|
||||||
|
configASSERT(stack != NULL);
|
||||||
|
configASSERT(tcb != NULL);
|
||||||
|
|
||||||
|
*ppxIdleTaskTCBBuffer = tcb;
|
||||||
|
*ppxIdleTaskStackBuffer = stack;
|
||||||
|
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (configUSE_TIMERS == 1)
|
||||||
|
|
||||||
|
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer,
|
||||||
|
uint32_t *pulTimerTaskStackSize) {
|
||||||
|
StackType_t *stack = (StackType_t *) pvPortMalloc(configTIMER_TASK_STACK_DEPTH * sizeof(StackType_t));
|
||||||
|
StaticTask_t *tcb = (StaticTask_t *) pvPortMalloc(sizeof(StaticTask_t));
|
||||||
|
configASSERT(stack != NULL);
|
||||||
|
configASSERT(tcb != NULL);
|
||||||
|
|
||||||
|
*ppxTimerTaskTCBBuffer = tcb;
|
||||||
|
*ppxTimerTaskStackBuffer = stack;
|
||||||
|
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* configUSE_TIMERS */
|
||||||
|
|
||||||
|
#endif /* configSUPPORT_STATIC_ALLOCATION */
|
||||||
|
|
||||||
|
#endif /* USE_BK72XX */
|
||||||
@@ -732,9 +732,16 @@ void WiFiComponent::restart_adapter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WiFiComponent::loop() {
|
void WiFiComponent::loop() {
|
||||||
this->wifi_loop_();
|
bool events_processed = this->wifi_loop_();
|
||||||
const uint32_t now = App.get_loop_component_start_time();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
this->update_connected_state_();
|
// Connection state can only change when events are processed (ESP-IDF/LibreTiny)
|
||||||
|
// or polled (ESP8266/Pico W). Skip the expensive wifi_sta_connect_status_() call
|
||||||
|
// when no events arrived and we're already in steady state.
|
||||||
|
// Must also run when connected_ is false — after state transitions to STA_CONNECTED,
|
||||||
|
// connected_ won't be set until update_connected_state_() runs.
|
||||||
|
if (events_processed || !this->connected_) {
|
||||||
|
this->update_connected_state_();
|
||||||
|
}
|
||||||
|
|
||||||
if (this->has_sta()) {
|
if (this->has_sta()) {
|
||||||
#if defined(USE_WIFI_CONNECT_TRIGGER) || defined(USE_WIFI_DISCONNECT_TRIGGER)
|
#if defined(USE_WIFI_CONNECT_TRIGGER) || defined(USE_WIFI_DISCONNECT_TRIGGER)
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#include "esphome/core/lock_free_queue.h"
|
#include "esphome/core/lock_free_queue.h"
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(USE_LIBRETINY) && defined(ESPHOME_THREAD_MULTI_ATOMICS)
|
||||||
|
#include "esphome/core/lock_free_queue.h"
|
||||||
|
#elif defined(USE_LIBRETINY) && defined(ESPHOME_THREAD_MULTI_NO_ATOMICS)
|
||||||
|
#include "esphome/core/freertos_queue.h"
|
||||||
|
#endif
|
||||||
#include "esphome/core/string_ref.h"
|
#include "esphome/core/string_ref.h"
|
||||||
|
|
||||||
#include <span>
|
#include <span>
|
||||||
@@ -657,7 +662,7 @@ class WiFiComponent final : public Component {
|
|||||||
|
|
||||||
void connect_soon_();
|
void connect_soon_();
|
||||||
|
|
||||||
void wifi_loop_();
|
bool wifi_loop_();
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
void process_pending_callbacks_();
|
void process_pending_callbacks_();
|
||||||
#endif
|
#endif
|
||||||
@@ -882,6 +887,19 @@ class WiFiComponent final : public Component {
|
|||||||
LockFreeQueue<IDFWiFiEvent, 17> event_queue_;
|
LockFreeQueue<IDFWiFiEvent, 17> event_queue_;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_LIBRETINY
|
||||||
|
// Thread-safe queue for WiFi events from LibreTiny callback thread.
|
||||||
|
// LockFreeQueue on platforms with hardware atomics (RTL87xx, LN882x),
|
||||||
|
// FreeRTOSQueue on platforms without (BK72xx).
|
||||||
|
static constexpr uint8_t LT_EVENT_QUEUE_SIZE = 16;
|
||||||
|
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
||||||
|
// Ring buffer reserves one slot, so +1 for 16 usable slots
|
||||||
|
LockFreeQueue<LTWiFiEvent, LT_EVENT_QUEUE_SIZE + 1> event_queue_;
|
||||||
|
#else
|
||||||
|
FreeRTOSQueue<LTWiFiEvent, LT_EVENT_QUEUE_SIZE> event_queue_;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Stores a pointer to a string literal (static storage duration).
|
// Stores a pointer to a string literal (static storage duration).
|
||||||
// ONLY set from Python-generated code with string literals - never dynamic strings.
|
// ONLY set from Python-generated code with string literals - never dynamic strings.
|
||||||
|
|||||||
@@ -938,7 +938,10 @@ network::IPAddress WiFiComponent::wifi_gateway_ip_() {
|
|||||||
return network::IPAddress(&ip.gw);
|
return network::IPAddress(&ip.gw);
|
||||||
}
|
}
|
||||||
network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return network::IPAddress(dns_getserver(num)); }
|
network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return network::IPAddress(dns_getserver(num)); }
|
||||||
void WiFiComponent::wifi_loop_() { this->process_pending_callbacks_(); }
|
bool WiFiComponent::wifi_loop_() {
|
||||||
|
this->process_pending_callbacks_();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void WiFiComponent::process_pending_callbacks_() {
|
void WiFiComponent::process_pending_callbacks_() {
|
||||||
// Process callbacks deferred from ESP8266 SDK system context (~2KB stack)
|
// Process callbacks deferred from ESP8266 SDK system context (~2KB stack)
|
||||||
|
|||||||
@@ -715,17 +715,25 @@ const char *get_disconnect_reason_str(uint8_t reason) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiFiComponent::wifi_loop_() {
|
bool WiFiComponent::wifi_loop_() {
|
||||||
|
// Use pop() directly instead of empty() — pop() costs 1 memw (acquire on tail_),
|
||||||
|
// while empty() costs 2 memw (acquire on both head_ and tail_) on Xtensa.
|
||||||
|
IDFWiFiEvent *data = this->event_queue_.pop();
|
||||||
|
if (data == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
wifi_process_event_(data);
|
||||||
|
delete data; // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
|
} while ((data = this->event_queue_.pop()) != nullptr);
|
||||||
|
|
||||||
|
// Drops only occur when the queue is full, and only this loop drains it,
|
||||||
|
// so if pop() returned nullptr above we can skip this check.
|
||||||
uint16_t dropped = this->event_queue_.get_and_reset_dropped_count();
|
uint16_t dropped = this->event_queue_.get_and_reset_dropped_count();
|
||||||
if (dropped > 0) {
|
if (dropped > 0) {
|
||||||
ESP_LOGW(TAG, "Dropped %u WiFi events due to buffer overflow", dropped);
|
ESP_LOGW(TAG, "Dropped %u WiFi events due to buffer overflow", dropped);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
IDFWiFiEvent *data;
|
|
||||||
while ((data = this->event_queue_.pop()) != nullptr) {
|
|
||||||
wifi_process_event_(data);
|
|
||||||
delete data; // NOLINT(cppcoreguidelines-owning-memory)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Events are processed from queue in main loop context, but listener notifications
|
// Events are processed from queue in main loop context, but listener notifications
|
||||||
// must be deferred until after the state machine transitions (in check_connecting_finished)
|
// must be deferred until after the state machine transitions (in check_connecting_finished)
|
||||||
|
|||||||
@@ -10,9 +10,6 @@
|
|||||||
#include "lwip/err.h"
|
#include "lwip/err.h"
|
||||||
#include "lwip/dns.h"
|
#include "lwip/dns.h"
|
||||||
|
|
||||||
#include <FreeRTOS.h>
|
|
||||||
#include <queue.h>
|
|
||||||
|
|
||||||
#ifdef USE_BK72XX
|
#ifdef USE_BK72XX
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <wlan_ui_pub.h>
|
#include <wlan_ui_pub.h>
|
||||||
@@ -43,16 +40,13 @@ static const char *const TAG = "wifi_lt";
|
|||||||
// (like connection status flags) from the callback causes race conditions:
|
// (like connection status flags) from the callback causes race conditions:
|
||||||
// - The main loop may never see state changes (values cached in registers)
|
// - The main loop may never see state changes (values cached in registers)
|
||||||
// - State changes may be visible in inconsistent order
|
// - State changes may be visible in inconsistent order
|
||||||
// - LibreTiny targets (BK7231, RTL8720) lack atomic instructions (no LDREX/STREX)
|
|
||||||
//
|
//
|
||||||
// Solution: Queue events in the callback and process them in the main loop.
|
// Solution: Queue events in the callback and process them in the main loop.
|
||||||
// This is the same approach used by ESP32 IDF's wifi_process_event_().
|
// This is the same approach used by ESP32 IDF's wifi_process_event_().
|
||||||
// All state modifications happen in the main loop context, eliminating races.
|
// All state modifications happen in the main loop context, eliminating races.
|
||||||
|
//
|
||||||
static constexpr size_t EVENT_QUEUE_SIZE = 16; // Max pending WiFi events before overflow
|
// On platforms with hardware atomics (RTL87xx, LN882x): LockFreeQueue (SPSC ring buffer)
|
||||||
static QueueHandle_t s_event_queue = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
// On platforms without (BK72xx): FreeRTOSQueue (xQueue wrapper with critical sections)
|
||||||
static volatile uint32_t s_event_queue_overflow_count =
|
|
||||||
0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
|
||||||
|
|
||||||
// Event structure for queued WiFi events - contains a copy of event data
|
// Event structure for queued WiFi events - contains a copy of event data
|
||||||
// to avoid lifetime issues with the original event data from the callback
|
// to avoid lifetime issues with the original event data from the callback
|
||||||
@@ -352,10 +346,6 @@ using esphome_wifi_event_info_t = arduino_event_info_t;
|
|||||||
// Event callback - runs in WiFi driver thread context
|
// Event callback - runs in WiFi driver thread context
|
||||||
// Only queues events for processing in main loop, no logging or state changes here
|
// Only queues events for processing in main loop, no logging or state changes here
|
||||||
void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_wifi_event_info_t info) {
|
void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_wifi_event_info_t info) {
|
||||||
if (s_event_queue == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate on heap and fill directly to avoid extra memcpy
|
// Allocate on heap and fill directly to avoid extra memcpy
|
||||||
auto *to_send = new LTWiFiEvent{}; // NOLINT(cppcoreguidelines-owning-memory)
|
auto *to_send = new LTWiFiEvent{}; // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
to_send->event_id = event;
|
to_send->event_id = event;
|
||||||
@@ -428,9 +418,8 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Queue event (don't block if queue is full)
|
// Queue event (don't block if queue is full)
|
||||||
if (xQueueSend(s_event_queue, &to_send, 0) != pdPASS) {
|
if (!this->event_queue_.push(to_send)) {
|
||||||
delete to_send; // NOLINT(cppcoreguidelines-owning-memory)
|
delete to_send; // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
s_event_queue_overflow_count++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,14 +609,6 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void WiFiComponent::wifi_pre_setup_() {
|
void WiFiComponent::wifi_pre_setup_() {
|
||||||
// Create event queue for thread-safe event handling
|
|
||||||
// Events are pushed from WiFi callback thread and processed in main loop
|
|
||||||
s_event_queue = xQueueCreate(EVENT_QUEUE_SIZE, sizeof(LTWiFiEvent *));
|
|
||||||
if (s_event_queue == nullptr) {
|
|
||||||
ESP_LOGE(TAG, "Failed to create event queue");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WiFi.onEvent(
|
WiFi.onEvent(
|
||||||
[this](arduino_event_id_t event, arduino_event_info_t info) { this->wifi_event_callback_(event, info); });
|
[this](arduino_event_id_t event, arduino_event_info_t info) { this->wifi_event_callback_(event, info); });
|
||||||
// Make sure WiFi is in clean state before anything starts
|
// Make sure WiFi is in clean state before anything starts
|
||||||
@@ -796,28 +777,26 @@ int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); }
|
|||||||
network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; }
|
network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; }
|
||||||
network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; }
|
network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; }
|
||||||
network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; }
|
network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; }
|
||||||
void WiFiComponent::wifi_loop_() {
|
bool WiFiComponent::wifi_loop_() {
|
||||||
// Process all pending events from the queue
|
// Use pop() directly instead of empty() — avoids redundant synchronization.
|
||||||
if (s_event_queue == nullptr) {
|
// LockFreeQueue: pop() costs 1 memw vs empty()'s 2 memw on Xtensa.
|
||||||
return;
|
// FreeRTOSQueue: pop() is 1 critical section vs empty() + pop() = 2.
|
||||||
}
|
LTWiFiEvent *event = this->event_queue_.pop();
|
||||||
|
if (event == nullptr)
|
||||||
// Check for dropped events due to queue overflow
|
return false;
|
||||||
if (s_event_queue_overflow_count > 0) {
|
|
||||||
ESP_LOGW(TAG, "Event queue overflow, %" PRIu32 " events dropped", s_event_queue_overflow_count);
|
|
||||||
s_event_queue_overflow_count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
LTWiFiEvent *event;
|
|
||||||
if (xQueueReceive(s_event_queue, &event, 0) != pdTRUE) {
|
|
||||||
// No more events
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
do {
|
||||||
wifi_process_event_(event);
|
wifi_process_event_(event);
|
||||||
delete event; // NOLINT(cppcoreguidelines-owning-memory)
|
delete event; // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
|
} while ((event = this->event_queue_.pop()) != nullptr);
|
||||||
|
|
||||||
|
// Drops only occur when the queue is full, and only this loop drains it,
|
||||||
|
// so if pop() returned nullptr above we can skip this check.
|
||||||
|
uint16_t dropped = this->event_queue_.get_and_reset_dropped_count();
|
||||||
|
if (dropped > 0) {
|
||||||
|
ESP_LOGW(TAG, "Dropped %" PRIu16 " WiFi events due to buffer overflow", dropped);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::wifi
|
} // namespace esphome::wifi
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ network::IPAddress WiFiComponent::wifi_dns_ip_(int num) {
|
|||||||
// Connect state listener notifications are deferred until after the state machine
|
// Connect state listener notifications are deferred until after the state machine
|
||||||
// transitions (in check_connecting_finished) so that conditions like wifi.connected
|
// transitions (in check_connecting_finished) so that conditions like wifi.connected
|
||||||
// return correct values in automations.
|
// return correct values in automations.
|
||||||
void WiFiComponent::wifi_loop_() {
|
bool WiFiComponent::wifi_loop_() {
|
||||||
// Handle scan completion
|
// Handle scan completion
|
||||||
if (this->state_ == WIFI_COMPONENT_STATE_STA_SCANNING && !cyw43_wifi_scan_active(&cyw43_state)) {
|
if (this->state_ == WIFI_COMPONENT_STATE_STA_SCANNING && !cyw43_wifi_scan_active(&cyw43_state)) {
|
||||||
this->scan_done_ = true;
|
this->scan_done_ = true;
|
||||||
@@ -365,6 +365,7 @@ void WiFiComponent::wifi_loop_() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiFiComponent::wifi_pre_setup_() {}
|
void WiFiComponent::wifi_pre_setup_() {}
|
||||||
|
|||||||
99
esphome/core/freertos_queue.h
Normal file
99
esphome/core/freertos_queue.h
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
|
#ifdef ESPHOME_THREAD_MULTI_NO_ATOMICS
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include <FreeRTOS.h>
|
||||||
|
#include <queue.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FreeRTOS queue wrapper for single-producer single-consumer scenarios on
|
||||||
|
* platforms without hardware atomic support (e.g. BK72xx ARM968E-S).
|
||||||
|
*
|
||||||
|
* Provides the same API as LockFreeQueue (push, pop, get_and_reset_dropped_count,
|
||||||
|
* empty, full, size) but uses xQueue internally, which synchronizes via
|
||||||
|
* FreeRTOS critical sections. Uses xQueueCreateStatic so the queue storage
|
||||||
|
* lives in BSS with zero runtime heap allocation.
|
||||||
|
*
|
||||||
|
* @tparam T The type of elements stored in the queue (stored as pointers)
|
||||||
|
* @tparam SIZE The maximum number of elements
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
|
||||||
|
template<class T, uint8_t SIZE> class FreeRTOSQueue {
|
||||||
|
public:
|
||||||
|
FreeRTOSQueue() : dropped_count_(0) {
|
||||||
|
this->handle_ = xQueueCreateStatic(SIZE, sizeof(T *), this->storage_, &this->queue_buf_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No destructor — ESPHome components are never destroyed. Intentionally
|
||||||
|
// omitted to avoid pulling in vQueueDelete code on resource-constrained targets.
|
||||||
|
|
||||||
|
// Non-copyable, non-movable — queue handle is not transferable
|
||||||
|
FreeRTOSQueue(const FreeRTOSQueue &) = delete;
|
||||||
|
FreeRTOSQueue &operator=(const FreeRTOSQueue &) = delete;
|
||||||
|
FreeRTOSQueue(FreeRTOSQueue &&) = delete;
|
||||||
|
FreeRTOSQueue &operator=(FreeRTOSQueue &&) = delete;
|
||||||
|
|
||||||
|
bool push(T *element) {
|
||||||
|
if (element == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (xQueueSend(this->handle_, &element, 0) != pdPASS) {
|
||||||
|
this->increment_dropped_count();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
T *pop() {
|
||||||
|
T *element;
|
||||||
|
if (xQueueReceive(this->handle_, &element, 0) != pdTRUE) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t get_and_reset_dropped_count() {
|
||||||
|
// Fast path: plain read of aligned uint16_t is a single ARM load instruction.
|
||||||
|
// Worst case is reading a stale zero and reporting drops one iteration later.
|
||||||
|
// Avoids critical section overhead on every loop() call since drops are rare.
|
||||||
|
if (this->dropped_count_ == 0)
|
||||||
|
return 0;
|
||||||
|
// Declare outside critical section — BK72xx portENTER_CRITICAL may introduce a scope
|
||||||
|
uint16_t count;
|
||||||
|
portENTER_CRITICAL();
|
||||||
|
count = this->dropped_count_;
|
||||||
|
this->dropped_count_ = 0;
|
||||||
|
portEXIT_CRITICAL();
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void increment_dropped_count() {
|
||||||
|
portENTER_CRITICAL();
|
||||||
|
this->dropped_count_++;
|
||||||
|
portEXIT_CRITICAL();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const { return uxQueueMessagesWaiting(this->handle_) == 0; }
|
||||||
|
|
||||||
|
bool full() const { return uxQueueSpacesAvailable(this->handle_) == 0; }
|
||||||
|
|
||||||
|
size_t size() const { return uxQueueMessagesWaiting(this->handle_); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Static storage for the queue — lives in BSS, no heap allocation
|
||||||
|
uint8_t storage_[SIZE * sizeof(T *)];
|
||||||
|
StaticQueue_t queue_buf_;
|
||||||
|
QueueHandle_t handle_;
|
||||||
|
uint16_t dropped_count_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // ESPHOME_THREAD_MULTI_NO_ATOMICS
|
||||||
Reference in New Issue
Block a user