[logger] Use the static ring buffer structure as its handle on ESP32 (#20129)

This commit is contained in:
J. Nick Koston
2026-10-05 19:24:13 -05:00
committed by GitHub
parent 4097f45677
commit 40af90ad25
4 changed files with 49 additions and 16 deletions
+10
View File
@@ -235,6 +235,15 @@ def warn_ram_log_strings(config: ConfigType) -> ConfigType:
return config
def validate_task_log_buffer_alignment(value: int) -> int:
# ESP-IDF rejects a no-split ring buffer whose size is not a multiple of 4
if CORE.is_esp32 and value % 4:
raise cv.Invalid(
f"{CONF_TASK_LOG_BUFFER_SIZE} must be a multiple of 4 on ESP32"
)
return value
def validate_wait_for_cdc(config: ConfigType) -> ConfigType:
if config.get(CONF_WAIT_FOR_CDC) and config.get(CONF_HARDWARE_UART) != USB_CDC:
raise cv.Invalid("wait_for_cdc requires hardware_uart: USB_CDC")
@@ -282,6 +291,7 @@ CONFIG_SCHEMA = cv.All(
max=32768, # Max: Depends on message sizes, typically ~300 messages with default size
),
),
validate_task_log_buffer_alignment,
),
cv.SplitDefault(
CONF_HARDWARE_UART,
@@ -10,15 +10,7 @@ namespace esphome::logger {
TaskLogBuffer::TaskLogBuffer() {
// Create a static ring buffer with RINGBUF_TYPE_NOSPLIT for message integrity
// Storage is a member array (embedded in Logger), no heap allocation needed
this->ring_buffer_ =
xRingbufferCreateStatic(sizeof(this->storage_), RINGBUF_TYPE_NOSPLIT, this->storage_, &this->structure_);
}
TaskLogBuffer::~TaskLogBuffer() {
if (this->ring_buffer_ != nullptr) {
vRingbufferDelete(this->ring_buffer_);
this->ring_buffer_ = nullptr;
}
xRingbufferCreateStatic(sizeof(this->storage_), RINGBUF_TYPE_NOSPLIT, this->storage_, &this->structure_);
}
bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &text_length) {
@@ -27,7 +19,7 @@ bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &tex
}
size_t item_size = 0;
void *received_item = xRingbufferReceive(ring_buffer_, &item_size, 0);
void *received_item = xRingbufferReceive(this->handle_(), &item_size, 0);
if (received_item == nullptr) {
return false;
}
@@ -44,7 +36,7 @@ void TaskLogBuffer::release_message_main_loop() {
if (this->current_token_ == nullptr) {
return;
}
vRingbufferReturnItem(ring_buffer_, this->current_token_);
vRingbufferReturnItem(this->handle_(), this->current_token_);
this->current_token_ = nullptr;
// Update counter to mark all messages as processed
last_processed_counter_ = message_counter_.load(std::memory_order_relaxed);
@@ -71,7 +63,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
// Acquire memory directly from the ring buffer
void *acquired_memory = nullptr;
BaseType_t result = xRingbufferSendAcquire(ring_buffer_, &acquired_memory, total_size, 0);
BaseType_t result = xRingbufferSendAcquire(this->handle_(), &acquired_memory, total_size, 0);
if (result != pdTRUE || acquired_memory == nullptr) {
return false; // Failed to acquire memory
@@ -100,7 +92,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
// Handle unexpected formatting error
if (ret <= 0) {
vRingbufferReturnItem(ring_buffer_, acquired_memory);
vRingbufferReturnItem(this->handle_(), acquired_memory);
return false;
}
@@ -111,7 +103,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
msg->text_length = text_length;
// Complete the send operation with the acquired memory
result = xRingbufferSendComplete(ring_buffer_, acquired_memory);
result = xRingbufferSendComplete(this->handle_(), acquired_memory);
if (result != pdTRUE) {
return false; // Failed to complete the message send
@@ -47,7 +47,7 @@ class TaskLogBuffer {
};
TaskLogBuffer();
~TaskLogBuffer();
// No destructor: Logger is never destroyed
// NOT thread-safe - borrow a message from the ring buffer, only call from main loop
bool borrow_message_main_loop(LogMessage *&message, uint16_t &text_length);
@@ -68,7 +68,11 @@ class TaskLogBuffer {
static constexpr size_t size() { return ESPHOME_TASK_LOG_BUFFER_SIZE; }
private:
RingbufHandle_t ring_buffer_{nullptr}; // FreeRTOS ring buffer handle
// xRingbufferCreateStatic() returns the static structure itself as the handle; it only
// returns NULL for a no-split size that is unaligned or under two item headers
static_assert(ESPHOME_TASK_LOG_BUFFER_SIZE % 4 == 0, "task_log_buffer_size must be a multiple of 4");
RingbufHandle_t handle_() { return &this->structure_; }
StaticRingbuffer_t structure_; // Static structure for the ring buffer
uint8_t storage_[ESPHOME_TASK_LOG_BUFFER_SIZE]; // Embedded in Logger (no separate heap allocation)
@@ -6,7 +6,11 @@ import re
import pytest
from esphome.components.logger import validate_task_log_buffer_alignment
from esphome.config_validation import Invalid
from esphome.const import PlatformFramework
from esphome.core import CORE
from tests.component_tests.types import SetCoreConfigCallable
def test_logger_pre_setup_before_other_components(generate_main):
@@ -111,3 +115,26 @@ def test_flash_log_strings_default_does_not_warn(
generate_main("tests/component_tests/logger/test_logger.yaml")
assert "esp8266_store_log_strings_in_flash" not in caplog.text
@pytest.mark.parametrize("value", [0, 640, 768, 32768])
def test_task_log_buffer_size_aligned_on_esp32(
set_core_config: SetCoreConfigCallable, value: int
) -> None:
set_core_config(PlatformFramework.ESP32_IDF)
assert validate_task_log_buffer_alignment(value) == value
def test_task_log_buffer_size_unaligned_rejected_on_esp32(
set_core_config: SetCoreConfigCallable,
) -> None:
set_core_config(PlatformFramework.ESP32_IDF)
with pytest.raises(Invalid, match="multiple of 4"):
validate_task_log_buffer_alignment(641)
def test_task_log_buffer_size_unaligned_allowed_on_libretiny(
set_core_config: SetCoreConfigCallable,
) -> None:
set_core_config(PlatformFramework.BK72XX_ARDUINO)
assert validate_task_log_buffer_alignment(641) == 641