From 9bdae5183ca87c99d44381d01cb4c20b4f7ecdab Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Wed, 11 Feb 2026 16:43:55 +0100 Subject: [PATCH] [nrf52,logger] add support for task_log_buffer_size (#13862) Co-authored-by: J. Nick Koston --- esphome/components/logger/__init__.py | 14 +- esphome/components/logger/log_buffer.h | 190 +++++++++++++++ esphome/components/logger/logger.cpp | 91 +++----- esphome/components/logger/logger.h | 219 ++---------------- esphome/components/logger/logger_zephyr.cpp | 2 +- .../logger/task_log_buffer_esp32.cpp | 17 +- .../components/logger/task_log_buffer_esp32.h | 5 +- .../logger/task_log_buffer_host.cpp | 23 +- .../components/logger/task_log_buffer_host.h | 13 +- .../logger/task_log_buffer_libretiny.cpp | 22 +- .../logger/task_log_buffer_libretiny.h | 8 +- .../logger/task_log_buffer_zephyr.cpp | 116 ++++++++++ .../logger/task_log_buffer_zephyr.h | 66 ++++++ esphome/core/defines.h | 1 + .../logger/test.nrf52-adafruit.yaml | 1 + 15 files changed, 479 insertions(+), 309 deletions(-) create mode 100644 esphome/components/logger/log_buffer.h create mode 100644 esphome/components/logger/task_log_buffer_zephyr.cpp create mode 100644 esphome/components/logger/task_log_buffer_zephyr.h diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 40ceaec7dc..b2952d7995 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -231,9 +231,16 @@ CONFIG_SCHEMA = cv.All( bk72xx=768, ln882x=768, rtl87xx=768, + nrf52=768, ): cv.All( cv.only_on( - [PLATFORM_ESP32, PLATFORM_BK72XX, PLATFORM_LN882X, PLATFORM_RTL87XX] + [ + PLATFORM_ESP32, + PLATFORM_BK72XX, + PLATFORM_LN882X, + PLATFORM_RTL87XX, + PLATFORM_NRF52, + ] ), cv.validate_bytes, cv.Any( @@ -313,11 +320,13 @@ async def to_code(config): ) if CORE.is_esp32: cg.add(log.create_pthread_key()) - if CORE.is_esp32 or CORE.is_libretiny: + if CORE.is_esp32 or CORE.is_libretiny or CORE.is_nrf52: task_log_buffer_size = config[CONF_TASK_LOG_BUFFER_SIZE] if task_log_buffer_size > 0: cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER") cg.add(log.init_log_buffer(task_log_buffer_size)) + if CORE.using_zephyr: + zephyr_add_prj_conf("MPSC_PBUF", True) elif CORE.is_host: cg.add(log.create_pthread_key()) cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER") @@ -417,6 +426,7 @@ async def to_code(config): pass if CORE.is_nrf52: + zephyr_add_prj_conf("THREAD_LOCAL_STORAGE", True) if config[CONF_HARDWARE_UART] == UART0: zephyr_add_overlay("""&uart0 { status = "okay";};""") if config[CONF_HARDWARE_UART] == UART1: diff --git a/esphome/components/logger/log_buffer.h b/esphome/components/logger/log_buffer.h new file mode 100644 index 0000000000..3d87278248 --- /dev/null +++ b/esphome/components/logger/log_buffer.h @@ -0,0 +1,190 @@ +#pragma once + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome::logger { + +// Maximum header size: 35 bytes fixed + 32 bytes tag + 16 bytes thread name = 83 bytes (45 byte safety margin) +static constexpr uint16_t MAX_HEADER_SIZE = 128; + +// ANSI color code last digit (30-38 range, store only last digit to save RAM) +static constexpr char LOG_LEVEL_COLOR_DIGIT[] = { + '\0', // NONE + '1', // ERROR (31 = red) + '3', // WARNING (33 = yellow) + '2', // INFO (32 = green) + '5', // CONFIG (35 = magenta) + '6', // DEBUG (36 = cyan) + '7', // VERBOSE (37 = gray) + '8', // VERY_VERBOSE (38 = white) +}; + +static constexpr char LOG_LEVEL_LETTER_CHARS[] = { + '\0', // NONE + 'E', // ERROR + 'W', // WARNING + 'I', // INFO + 'C', // CONFIG + 'D', // DEBUG + 'V', // VERBOSE (VERY_VERBOSE uses two 'V's) +}; + +// Buffer wrapper for log formatting functions +struct LogBuffer { + char *data; + uint16_t size; + uint16_t pos{0}; + // Replaces the null terminator with a newline for console output. + // Must be called after notify_listeners_() since listeners need null-terminated strings. + // Console output uses length-based writes (buf.pos), so null terminator is not needed. + void terminate_with_newline() { + if (this->pos < this->size) { + this->data[this->pos++] = '\n'; + } else if (this->size > 0) { + // Buffer was full - replace last char with newline to ensure it's visible + this->data[this->size - 1] = '\n'; + this->pos = this->size; + } + } + void HOT write_header(uint8_t level, const char *tag, int line, const char *thread_name) { + // Early return if insufficient space - intentionally don't update pos to prevent partial writes + if (this->pos + MAX_HEADER_SIZE > this->size) + return; + + char *p = this->current_(); + + // Write ANSI color + this->write_ansi_color_(p, level); + + // Construct: [LEVEL][tag:line] + *p++ = '['; + if (level != 0) { + if (level >= 7) { + *p++ = 'V'; // VERY_VERBOSE = "VV" + *p++ = 'V'; + } else { + *p++ = LOG_LEVEL_LETTER_CHARS[level]; + } + } + *p++ = ']'; + *p++ = '['; + + // Copy tag + this->copy_string_(p, tag); + + *p++ = ':'; + + // Format line number without modulo operations + if (line > 999) [[unlikely]] { + int thousands = line / 1000; + *p++ = '0' + thousands; + line -= thousands * 1000; + } + int hundreds = line / 100; + int remainder = line - hundreds * 100; + int tens = remainder / 10; + *p++ = '0' + hundreds; + *p++ = '0' + tens; + *p++ = '0' + (remainder - tens * 10); + *p++ = ']'; + +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) || defined(USE_HOST) + // Write thread name with bold red color + if (thread_name != nullptr) { + this->write_ansi_color_(p, 1); // Bold red for thread name + *p++ = '['; + this->copy_string_(p, thread_name); + *p++ = ']'; + this->write_ansi_color_(p, level); // Restore original color + } +#endif + + *p++ = ':'; + *p++ = ' '; + + this->pos = p - this->data; + } + void HOT format_body(const char *format, va_list args) { + this->format_vsnprintf_(format, args); + this->finalize_(); + } +#ifdef USE_STORE_LOG_STR_IN_FLASH + void HOT format_body_P(PGM_P format, va_list args) { + this->format_vsnprintf_P_(format, args); + this->finalize_(); + } +#endif + void write_body(const char *text, uint16_t text_length) { + this->write_(text, text_length); + this->finalize_(); + } + + private: + bool full_() const { return this->pos >= this->size; } + uint16_t remaining_() const { return this->size - this->pos; } + char *current_() { return this->data + this->pos; } + void write_(const char *value, uint16_t length) { + const uint16_t available = this->remaining_(); + const uint16_t copy_len = (length < available) ? length : available; + if (copy_len > 0) { + memcpy(this->current_(), value, copy_len); + this->pos += copy_len; + } + } + void finalize_() { + // Write color reset sequence + static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1; + this->write_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN); + // Null terminate + this->data[this->full_() ? this->size - 1 : this->pos] = '\0'; + } + void strip_trailing_newlines_() { + while (this->pos > 0 && this->data[this->pos - 1] == '\n') + this->pos--; + } + void process_vsnprintf_result_(int ret) { + if (ret < 0) + return; + const uint16_t rem = this->remaining_(); + this->pos += (ret >= rem) ? (rem - 1) : static_cast(ret); + this->strip_trailing_newlines_(); + } + void format_vsnprintf_(const char *format, va_list args) { + if (this->full_()) + return; + this->process_vsnprintf_result_(vsnprintf(this->current_(), this->remaining_(), format, args)); + } +#ifdef USE_STORE_LOG_STR_IN_FLASH + void format_vsnprintf_P_(PGM_P format, va_list args) { + if (this->full_()) + return; + this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args)); + } +#endif + // Write ANSI color escape sequence to buffer, updates pointer in place + // Caller is responsible for ensuring buffer has sufficient space + void write_ansi_color_(char *&p, uint8_t level) { + if (level == 0) + return; + // Direct buffer fill: "\033[{bold};3{color}m" (7 bytes) + *p++ = '\033'; + *p++ = '['; + *p++ = (level == 1) ? '1' : '0'; // Only ERROR is bold + *p++ = ';'; + *p++ = '3'; + *p++ = LOG_LEVEL_COLOR_DIGIT[level]; + *p++ = 'm'; + } + // Copy string without null terminator, updates pointer in place + // Caller is responsible for ensuring buffer has sufficient space + void copy_string_(char *&p, const char *str) { + const size_t len = strlen(str); + // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - intentionally no null terminator, building string piece by + // piece + memcpy(p, str, len); + p += len; + } +}; + +} // namespace esphome::logger diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index bb20c403e5..e1b49bcb61 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -10,9 +10,9 @@ namespace esphome::logger { static const char *const TAG = "logger"; -#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) -// Implementation for multi-threaded platforms (ESP32 with FreeRTOS, Host with pthreads, LibreTiny with FreeRTOS) -// Main thread/task always uses direct buffer access for console output and callbacks +#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) +// Implementation for multi-threaded platforms (ESP32 with FreeRTOS, Host with pthreads, LibreTiny with FreeRTOS, +// Zephyr) Main thread/task always uses direct buffer access for console output and callbacks // // For non-main threads/tasks: // - WITH task log buffer: Prefer sending to ring buffer for async processing @@ -31,6 +31,9 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch // Get task handle once - used for both main task check and passing to non-main thread handler TaskHandle_t current_task = xTaskGetCurrentTaskHandle(); const bool is_main_task = (current_task == this->main_task_); +#elif (USE_ZEPHYR) + k_tid_t current_task = k_current_get(); + const bool is_main_task = (current_task == this->main_task_); #else // USE_HOST const bool is_main_task = pthread_equal(pthread_self(), this->main_thread_); #endif @@ -54,6 +57,9 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch // Host: pass a stack buffer for pthread_getname_np to write into. #if defined(USE_ESP32) || defined(USE_LIBRETINY) const char *thread_name = get_thread_name_(current_task); +#elif defined(USE_ZEPHYR) + char thread_name_buf[MAX_POINTER_REPRESENTATION]; + const char *thread_name = get_thread_name_(thread_name_buf, current_task); #else // USE_HOST char thread_name_buf[THREAD_NAME_BUF_SIZE]; const char *thread_name = this->get_thread_name_(thread_name_buf); @@ -83,18 +89,21 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li // This is safe to call from any context including ISRs this->enable_loop_soon_any_context(); } -#endif // USE_ESPHOME_TASK_LOG_BUFFER - +#endif // Emergency console logging for non-main threads when ring buffer is full or disabled // This is a fallback mechanism to ensure critical log messages are visible // Note: This may cause interleaved/corrupted console output if multiple threads // log simultaneously, but it's better than losing important messages entirely #ifdef USE_HOST - if (!message_sent) { + if (!message_sent) +#else + if (!message_sent && this->baud_rate_ > 0) // If logging is enabled, write to console +#endif + { +#ifdef USE_HOST // Host always has console output - no baud_rate check needed static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 512; #else - if (!message_sent && this->baud_rate_ > 0) { // If logging is enabled, write to console // Maximum size for console log messages (includes null terminator) static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144; #endif @@ -107,22 +116,16 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li // RAII guard automatically resets on return } #else -// Implementation for single-task platforms (ESP8266, RP2040, Zephyr) -// TODO: Zephyr may have multiple threads (work queues, etc.) but uses this single-task path. +// Implementation for single-task platforms (ESP8266, RP2040) // Logging calls are NOT thread-safe: global_recursion_guard_ is a plain bool and tx_buffer_ has no locking. // Not a problem in practice yet since Zephyr has no API support (logs are console-only). void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT if (level > this->level_for(tag) || global_recursion_guard_) return; -#ifdef USE_ZEPHYR - char tmp[MAX_POINTER_REPRESENTATION]; - this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args, - this->get_thread_name_(tmp)); -#else // Other single-task platforms don't have thread names, so pass nullptr + // Other single-task platforms don't have thread names, so pass nullptr this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args, nullptr); -#endif } -#endif // USE_ESP32 / USE_HOST / USE_LIBRETINY +#endif // USE_ESP32 || USE_HOST || USE_LIBRETINY || USE_ZEPHYR #ifdef USE_STORE_LOG_STR_IN_FLASH // Implementation for ESP8266 with flash string support. @@ -163,19 +166,12 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate } #ifdef USE_ESPHOME_TASK_LOG_BUFFER void Logger::init_log_buffer(size_t total_buffer_size) { -#ifdef USE_HOST // Host uses slot count instead of byte size - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - allocated once, never freed - this->log_buffer_ = new logger::TaskLogBufferHost(total_buffer_size); -#elif defined(USE_ESP32) // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - allocated once, never freed this->log_buffer_ = new logger::TaskLogBuffer(total_buffer_size); -#elif defined(USE_LIBRETINY) - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - allocated once, never freed - this->log_buffer_ = new logger::TaskLogBufferLibreTiny(total_buffer_size); -#endif -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +// Zephyr needs loop working to check when CDC port is open +#if !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) // Start with loop disabled when using task buffer (unless using USB CDC on ESP32) // The loop will be enabled automatically when messages arrive this->disable_loop_when_buffer_empty_(); @@ -183,52 +179,33 @@ void Logger::init_log_buffer(size_t total_buffer_size) { } #endif -#ifdef USE_ESPHOME_TASK_LOG_BUFFER -void Logger::loop() { this->process_messages_(); } +#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_USB_CDC)) +void Logger::loop() { + this->process_messages_(); +#if defined(USE_ZEPHYR) && defined(USE_LOGGER_USB_CDC) + this->cdc_loop_(); +#endif +} #endif void Logger::process_messages_() { #ifdef USE_ESPHOME_TASK_LOG_BUFFER // Process any buffered messages when available if (this->log_buffer_->has_messages()) { -#ifdef USE_HOST - logger::TaskLogBufferHost::LogMessage *message; - while (this->log_buffer_->get_message_main_loop(&message)) { - const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; - LogBuffer buf{this->tx_buffer_, this->tx_buffer_size_}; - this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, message->text, - message->text_length, buf); - this->log_buffer_->release_message_main_loop(); - this->write_log_buffer_to_console_(buf); - } -#elif defined(USE_ESP32) logger::TaskLogBuffer::LogMessage *message; - const char *text; - void *received_token; - while (this->log_buffer_->borrow_message_main_loop(&message, &text, &received_token)) { + uint16_t text_length; + while (this->log_buffer_->borrow_message_main_loop(message, text_length)) { const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; LogBuffer buf{this->tx_buffer_, this->tx_buffer_size_}; - this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, text, - message->text_length, buf); - // Release the message to allow other tasks to use it as soon as possible - this->log_buffer_->release_message_main_loop(received_token); - this->write_log_buffer_to_console_(buf); - } -#elif defined(USE_LIBRETINY) - logger::TaskLogBufferLibreTiny::LogMessage *message; - const char *text; - while (this->log_buffer_->borrow_message_main_loop(&message, &text)) { - const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; - LogBuffer buf{this->tx_buffer_, this->tx_buffer_size_}; - this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, text, - message->text_length, buf); + this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, + message->text_data(), text_length, buf); // Release the message to allow other tasks to use it as soon as possible this->log_buffer_->release_message_main_loop(); this->write_log_buffer_to_console_(buf); } -#endif } -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +// Zephyr needs loop working to check when CDC port is open +#if !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) else { // No messages to process, disable loop if appropriate // This reduces overhead when there's no async logging activity diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index ecf032ee0e..835542dd8f 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -13,15 +13,11 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#ifdef USE_ESPHOME_TASK_LOG_BUFFER -#ifdef USE_HOST +#include "log_buffer.h" #include "task_log_buffer_host.h" -#elif defined(USE_ESP32) #include "task_log_buffer_esp32.h" -#elif defined(USE_LIBRETINY) #include "task_log_buffer_libretiny.h" -#endif -#endif +#include "task_log_buffer_zephyr.h" #ifdef USE_ARDUINO #if defined(USE_ESP8266) @@ -97,195 +93,10 @@ struct CStrCompare { }; #endif -// ANSI color code last digit (30-38 range, store only last digit to save RAM) -static constexpr char LOG_LEVEL_COLOR_DIGIT[] = { - '\0', // NONE - '1', // ERROR (31 = red) - '3', // WARNING (33 = yellow) - '2', // INFO (32 = green) - '5', // CONFIG (35 = magenta) - '6', // DEBUG (36 = cyan) - '7', // VERBOSE (37 = gray) - '8', // VERY_VERBOSE (38 = white) -}; - -static constexpr char LOG_LEVEL_LETTER_CHARS[] = { - '\0', // NONE - 'E', // ERROR - 'W', // WARNING - 'I', // INFO - 'C', // CONFIG - 'D', // DEBUG - 'V', // VERBOSE (VERY_VERBOSE uses two 'V's) -}; - -// Maximum header size: 35 bytes fixed + 32 bytes tag + 16 bytes thread name = 83 bytes (45 byte safety margin) -static constexpr uint16_t MAX_HEADER_SIZE = 128; - -// "0x" + 2 hex digits per byte + '\0' -static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; - // Stack buffer size for retrieving thread/task names from the OS // macOS allows up to 64 bytes, Linux up to 16 static constexpr size_t THREAD_NAME_BUF_SIZE = 64; -// Buffer wrapper for log formatting functions -struct LogBuffer { - char *data; - uint16_t size; - uint16_t pos{0}; - // Replaces the null terminator with a newline for console output. - // Must be called after notify_listeners_() since listeners need null-terminated strings. - // Console output uses length-based writes (buf.pos), so null terminator is not needed. - void terminate_with_newline() { - if (this->pos < this->size) { - this->data[this->pos++] = '\n'; - } else if (this->size > 0) { - // Buffer was full - replace last char with newline to ensure it's visible - this->data[this->size - 1] = '\n'; - this->pos = this->size; - } - } - void HOT write_header(uint8_t level, const char *tag, int line, const char *thread_name) { - // Early return if insufficient space - intentionally don't update pos to prevent partial writes - if (this->pos + MAX_HEADER_SIZE > this->size) - return; - - char *p = this->current_(); - - // Write ANSI color - this->write_ansi_color_(p, level); - - // Construct: [LEVEL][tag:line] - *p++ = '['; - if (level != 0) { - if (level >= 7) { - *p++ = 'V'; // VERY_VERBOSE = "VV" - *p++ = 'V'; - } else { - *p++ = LOG_LEVEL_LETTER_CHARS[level]; - } - } - *p++ = ']'; - *p++ = '['; - - // Copy tag - this->copy_string_(p, tag); - - *p++ = ':'; - - // Format line number without modulo operations - if (line > 999) [[unlikely]] { - int thousands = line / 1000; - *p++ = '0' + thousands; - line -= thousands * 1000; - } - int hundreds = line / 100; - int remainder = line - hundreds * 100; - int tens = remainder / 10; - *p++ = '0' + hundreds; - *p++ = '0' + tens; - *p++ = '0' + (remainder - tens * 10); - *p++ = ']'; - -#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) || defined(USE_HOST) - // Write thread name with bold red color - if (thread_name != nullptr) { - this->write_ansi_color_(p, 1); // Bold red for thread name - *p++ = '['; - this->copy_string_(p, thread_name); - *p++ = ']'; - this->write_ansi_color_(p, level); // Restore original color - } -#endif - - *p++ = ':'; - *p++ = ' '; - - this->pos = p - this->data; - } - void HOT format_body(const char *format, va_list args) { - this->format_vsnprintf_(format, args); - this->finalize_(); - } -#ifdef USE_STORE_LOG_STR_IN_FLASH - void HOT format_body_P(PGM_P format, va_list args) { - this->format_vsnprintf_P_(format, args); - this->finalize_(); - } -#endif - void write_body(const char *text, uint16_t text_length) { - this->write_(text, text_length); - this->finalize_(); - } - - private: - bool full_() const { return this->pos >= this->size; } - uint16_t remaining_() const { return this->size - this->pos; } - char *current_() { return this->data + this->pos; } - void write_(const char *value, uint16_t length) { - const uint16_t available = this->remaining_(); - const uint16_t copy_len = (length < available) ? length : available; - if (copy_len > 0) { - memcpy(this->current_(), value, copy_len); - this->pos += copy_len; - } - } - void finalize_() { - // Write color reset sequence - static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1; - this->write_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN); - // Null terminate - this->data[this->full_() ? this->size - 1 : this->pos] = '\0'; - } - void strip_trailing_newlines_() { - while (this->pos > 0 && this->data[this->pos - 1] == '\n') - this->pos--; - } - void process_vsnprintf_result_(int ret) { - if (ret < 0) - return; - const uint16_t rem = this->remaining_(); - this->pos += (ret >= rem) ? (rem - 1) : static_cast(ret); - this->strip_trailing_newlines_(); - } - void format_vsnprintf_(const char *format, va_list args) { - if (this->full_()) - return; - this->process_vsnprintf_result_(vsnprintf(this->current_(), this->remaining_(), format, args)); - } -#ifdef USE_STORE_LOG_STR_IN_FLASH - void format_vsnprintf_P_(PGM_P format, va_list args) { - if (this->full_()) - return; - this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args)); - } -#endif - // Write ANSI color escape sequence to buffer, updates pointer in place - // Caller is responsible for ensuring buffer has sufficient space - void write_ansi_color_(char *&p, uint8_t level) { - if (level == 0) - return; - // Direct buffer fill: "\033[{bold};3{color}m" (7 bytes) - *p++ = '\033'; - *p++ = '['; - *p++ = (level == 1) ? '1' : '0'; // Only ERROR is bold - *p++ = ';'; - *p++ = '3'; - *p++ = LOG_LEVEL_COLOR_DIGIT[level]; - *p++ = 'm'; - } - // Copy string without null terminator, updates pointer in place - // Caller is responsible for ensuring buffer has sufficient space - void copy_string_(char *&p, const char *str) { - const size_t len = strlen(str); - // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - intentionally no null terminator, building string piece by - // piece - memcpy(p, str, len); - p += len; - } -}; - #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) /** Enum for logging UART selection * @@ -411,11 +222,14 @@ class Logger : public Component { bool &flag_; }; -#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) // Handles non-main thread logging only (~0.1% of calls) // thread_name is resolved by the caller from the task handle, avoiding redundant lookups void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args, const char *thread_name); +#endif +#if defined(USE_ZEPHYR) && defined(USE_LOGGER_USB_CDC) + void cdc_loop_(); #endif void process_messages_(); void write_msg_(const char *msg, uint16_t len); @@ -534,13 +348,7 @@ class Logger : public Component { std::vector level_listeners_; // Log level change listeners #endif #ifdef USE_ESPHOME_TASK_LOG_BUFFER -#ifdef USE_HOST - logger::TaskLogBufferHost *log_buffer_{nullptr}; // Allocated once, never freed -#elif defined(USE_ESP32) logger::TaskLogBuffer *log_buffer_{nullptr}; // Allocated once, never freed -#elif defined(USE_LIBRETINY) - logger::TaskLogBufferLibreTiny *log_buffer_{nullptr}; // Allocated once, never freed -#endif #endif // Group smaller types together at the end @@ -552,7 +360,7 @@ class Logger : public Component { #ifdef USE_LIBRETINY UARTSelection uart_{UART_SELECTION_DEFAULT}; #endif -#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) bool main_task_recursion_guard_{false}; #ifdef USE_LIBRETINY bool non_main_task_recursion_guard_{false}; // Shared guard for all non-main tasks on LibreTiny @@ -595,8 +403,10 @@ class Logger : public Component { } #elif defined(USE_ZEPHYR) - const char *HOT get_thread_name_(std::span buff) { - k_tid_t current_task = k_current_get(); + const char *HOT get_thread_name_(std::span buff, k_tid_t current_task = nullptr) { + if (current_task == nullptr) { + current_task = k_current_get(); + } if (current_task == main_task_) { return nullptr; // Main task } @@ -635,7 +445,7 @@ class Logger : public Component { // Create RAII guard for non-main task recursion inline NonMainTaskRecursionGuard make_non_main_task_guard_() { return NonMainTaskRecursionGuard(log_recursion_key_); } -#elif defined(USE_LIBRETINY) +#elif defined(USE_LIBRETINY) || defined(USE_ZEPHYR) // LibreTiny doesn't have FreeRTOS TLS, so use a simple approach: // - Main task uses dedicated boolean (same as ESP32) // - Non-main tasks share a single recursion guard @@ -643,6 +453,8 @@ class Logger : public Component { // - Recursion from logging within logging is the main concern // - Cross-task "recursion" is prevented by the buffer mutex anyway // - Missing a recursive call from another task is acceptable (falls back to direct output) + // + // Zephyr use __thread as TLS // Check if non-main task is already in recursion inline bool HOT is_non_main_task_recursive_() const { return non_main_task_recursion_guard_; } @@ -651,7 +463,8 @@ class Logger : public Component { inline RecursionGuard make_non_main_task_guard_() { return RecursionGuard(non_main_task_recursion_guard_); } #endif -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +// Zephyr needs loop working to check when CDC port is open +#if defined(USE_ESPHOME_TASK_LOG_BUFFER) && !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) // Disable loop when task buffer is empty (with USB CDC check on ESP32) inline void disable_loop_when_buffer_empty_() { // Thread safety note: This is safe even if another task calls enable_loop_soon_any_context() diff --git a/esphome/components/logger/logger_zephyr.cpp b/esphome/components/logger/logger_zephyr.cpp index 1fc0acd573..f565c5760c 100644 --- a/esphome/components/logger/logger_zephyr.cpp +++ b/esphome/components/logger/logger_zephyr.cpp @@ -14,7 +14,7 @@ namespace esphome::logger { static const char *const TAG = "logger"; #ifdef USE_LOGGER_USB_CDC -void Logger::loop() { +void Logger::cdc_loop_() { if (this->uart_ != UART_SELECTION_USB_CDC || this->uart_dev_ == nullptr) { return; } diff --git a/esphome/components/logger/task_log_buffer_esp32.cpp b/esphome/components/logger/task_log_buffer_esp32.cpp index 56c0a4ae2d..e747ddc4d8 100644 --- a/esphome/components/logger/task_log_buffer_esp32.cpp +++ b/esphome/components/logger/task_log_buffer_esp32.cpp @@ -31,8 +31,8 @@ TaskLogBuffer::~TaskLogBuffer() { } } -bool TaskLogBuffer::borrow_message_main_loop(LogMessage **message, const char **text, void **received_token) { - if (message == nullptr || text == nullptr || received_token == nullptr) { +bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &text_length) { + if (this->current_token_) { return false; } @@ -43,18 +43,19 @@ bool TaskLogBuffer::borrow_message_main_loop(LogMessage **message, const char ** } LogMessage *msg = static_cast(received_item); - *message = msg; - *text = msg->text_data(); - *received_token = received_item; + message = msg; + text_length = msg->text_length; + this->current_token_ = received_item; return true; } -void TaskLogBuffer::release_message_main_loop(void *token) { - if (token == nullptr) { +void TaskLogBuffer::release_message_main_loop() { + if (this->current_token_ == nullptr) { return; } - vRingbufferReturnItem(ring_buffer_, token); + vRingbufferReturnItem(ring_buffer_, 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); } diff --git a/esphome/components/logger/task_log_buffer_esp32.h b/esphome/components/logger/task_log_buffer_esp32.h index 6c1bafaeba..88d72eacfc 100644 --- a/esphome/components/logger/task_log_buffer_esp32.h +++ b/esphome/components/logger/task_log_buffer_esp32.h @@ -52,10 +52,10 @@ class TaskLogBuffer { ~TaskLogBuffer(); // NOT thread-safe - borrow a message from the ring buffer, only call from main loop - bool borrow_message_main_loop(LogMessage **message, const char **text, void **received_token); + bool borrow_message_main_loop(LogMessage *&message, uint16_t &text_length); // NOT thread-safe - release a message buffer and update the counter, only call from main loop - void release_message_main_loop(void *token); + void release_message_main_loop(); // Thread-safe - send a message to the ring buffer from any thread bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name, @@ -78,6 +78,7 @@ class TaskLogBuffer { // Atomic counter for message tracking (only differences matter) std::atomic message_counter_{0}; // Incremented when messages are committed mutable uint16_t last_processed_counter_{0}; // Tracks last processed message + void *current_token_{nullptr}; }; } // namespace esphome::logger diff --git a/esphome/components/logger/task_log_buffer_host.cpp b/esphome/components/logger/task_log_buffer_host.cpp index 676686304a..c2ab009db4 100644 --- a/esphome/components/logger/task_log_buffer_host.cpp +++ b/esphome/components/logger/task_log_buffer_host.cpp @@ -10,16 +10,16 @@ namespace esphome::logger { -TaskLogBufferHost::TaskLogBufferHost(size_t slot_count) : slot_count_(slot_count) { +TaskLogBuffer::TaskLogBuffer(size_t slot_count) : slot_count_(slot_count) { // Allocate message slots this->slots_ = std::make_unique(slot_count); } -TaskLogBufferHost::~TaskLogBufferHost() { +TaskLogBuffer::~TaskLogBuffer() { // unique_ptr handles cleanup automatically } -int TaskLogBufferHost::acquire_write_slot_() { +int TaskLogBuffer::acquire_write_slot_() { // Try to reserve a slot using compare-and-swap size_t current_reserve = this->reserve_index_.load(std::memory_order_relaxed); @@ -43,7 +43,7 @@ int TaskLogBufferHost::acquire_write_slot_() { } } -void TaskLogBufferHost::commit_write_slot_(int slot_index) { +void TaskLogBuffer::commit_write_slot_(int slot_index) { // Mark the slot as ready for reading this->slots_[slot_index].ready.store(true, std::memory_order_release); @@ -70,8 +70,8 @@ void TaskLogBufferHost::commit_write_slot_(int slot_index) { } } -bool TaskLogBufferHost::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name, - const char *format, va_list args) { +bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name, + const char *format, va_list args) { // Acquire a slot int slot_index = this->acquire_write_slot_(); if (slot_index < 0) { @@ -115,11 +115,7 @@ bool TaskLogBufferHost::send_message_thread_safe(uint8_t level, const char *tag, return true; } -bool TaskLogBufferHost::get_message_main_loop(LogMessage **message) { - if (message == nullptr) { - return false; - } - +bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &text_length) { size_t current_read = this->read_index_.load(std::memory_order_relaxed); size_t current_write = this->write_index_.load(std::memory_order_acquire); @@ -134,11 +130,12 @@ bool TaskLogBufferHost::get_message_main_loop(LogMessage **message) { return false; } - *message = &msg; + message = &msg; + text_length = msg.text_length; return true; } -void TaskLogBufferHost::release_message_main_loop() { +void TaskLogBuffer::release_message_main_loop() { size_t current_read = this->read_index_.load(std::memory_order_relaxed); // Clear the ready flag diff --git a/esphome/components/logger/task_log_buffer_host.h b/esphome/components/logger/task_log_buffer_host.h index f8e4ee7bee..1d4d2b0ec1 100644 --- a/esphome/components/logger/task_log_buffer_host.h +++ b/esphome/components/logger/task_log_buffer_host.h @@ -21,12 +21,12 @@ namespace esphome::logger { * * Threading Model: Multi-Producer Single-Consumer (MPSC) * - Multiple threads can safely call send_message_thread_safe() concurrently - * - Only the main loop thread calls get_message_main_loop() and release_message_main_loop() + * - Only the main loop thread calls borrow_message_main_loop() and release_message_main_loop() * * Producers (multiple threads) Consumer (main loop only) * │ │ * ▼ ▼ - * acquire_write_slot_() get_message_main_loop() + * acquire_write_slot_() bool borrow_message_main_loop() * CAS on reserve_index_ read write_index_ * │ check ready flag * ▼ │ @@ -48,7 +48,7 @@ namespace esphome::logger { * - Atomic CAS for slot reservation allows multiple producers without locks * - Single consumer (main loop) processes messages in order */ -class TaskLogBufferHost { +class TaskLogBuffer { public: // Default number of message slots - host has plenty of memory static constexpr size_t DEFAULT_SLOT_COUNT = 64; @@ -71,15 +71,16 @@ class TaskLogBufferHost { thread_name[0] = '\0'; text[0] = '\0'; } + inline char *text_data() { return this->text; } }; /// Constructor that takes the number of message slots - explicit TaskLogBufferHost(size_t slot_count); - ~TaskLogBufferHost(); + explicit TaskLogBuffer(size_t slot_count); + ~TaskLogBuffer(); // NOT thread-safe - get next message from buffer, only call from main loop // Returns true if a message was retrieved, false if buffer is empty - bool get_message_main_loop(LogMessage **message); + bool borrow_message_main_loop(LogMessage *&message, uint16_t &text_length); // NOT thread-safe - release the message after processing, only call from main loop void release_message_main_loop(); diff --git a/esphome/components/logger/task_log_buffer_libretiny.cpp b/esphome/components/logger/task_log_buffer_libretiny.cpp index 5a22857dcb..5969f6fb40 100644 --- a/esphome/components/logger/task_log_buffer_libretiny.cpp +++ b/esphome/components/logger/task_log_buffer_libretiny.cpp @@ -8,7 +8,7 @@ namespace esphome::logger { -TaskLogBufferLibreTiny::TaskLogBufferLibreTiny(size_t total_buffer_size) { +TaskLogBuffer::TaskLogBuffer(size_t total_buffer_size) { this->size_ = total_buffer_size; // Allocate memory for the circular buffer using ESPHome's RAM allocator RAMAllocator allocator; @@ -17,7 +17,7 @@ TaskLogBufferLibreTiny::TaskLogBufferLibreTiny(size_t total_buffer_size) { this->mutex_ = xSemaphoreCreateMutex(); } -TaskLogBufferLibreTiny::~TaskLogBufferLibreTiny() { +TaskLogBuffer::~TaskLogBuffer() { if (this->mutex_ != nullptr) { vSemaphoreDelete(this->mutex_); this->mutex_ = nullptr; @@ -29,7 +29,7 @@ TaskLogBufferLibreTiny::~TaskLogBufferLibreTiny() { } } -size_t TaskLogBufferLibreTiny::available_contiguous_space() const { +size_t TaskLogBuffer::available_contiguous_space() const { if (this->head_ >= this->tail_) { // head is ahead of or equal to tail // Available space is from head to end, plus from start to tail @@ -47,11 +47,7 @@ size_t TaskLogBufferLibreTiny::available_contiguous_space() const { } } -bool TaskLogBufferLibreTiny::borrow_message_main_loop(LogMessage **message, const char **text) { - if (message == nullptr || text == nullptr) { - return false; - } - +bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &text_length) { // Check if buffer was initialized successfully if (this->mutex_ == nullptr || this->storage_ == nullptr) { return false; @@ -77,15 +73,15 @@ bool TaskLogBufferLibreTiny::borrow_message_main_loop(LogMessage **message, cons this->tail_ = 0; msg = reinterpret_cast(this->storage_); } - *message = msg; - *text = msg->text_data(); + message = msg; + text_length = msg->text_length; this->current_message_size_ = message_total_size(msg->text_length); // Keep mutex held until release_message_main_loop() return true; } -void TaskLogBufferLibreTiny::release_message_main_loop() { +void TaskLogBuffer::release_message_main_loop() { // Advance tail past the current message this->tail_ += this->current_message_size_; @@ -100,8 +96,8 @@ void TaskLogBufferLibreTiny::release_message_main_loop() { xSemaphoreGive(this->mutex_); } -bool TaskLogBufferLibreTiny::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, - const char *thread_name, const char *format, va_list args) { +bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name, + const char *format, va_list args) { // First, calculate the exact length needed using a null buffer (no actual writing) va_list args_copy; va_copy(args_copy, args); diff --git a/esphome/components/logger/task_log_buffer_libretiny.h b/esphome/components/logger/task_log_buffer_libretiny.h index bf315e828a..c065065fe7 100644 --- a/esphome/components/logger/task_log_buffer_libretiny.h +++ b/esphome/components/logger/task_log_buffer_libretiny.h @@ -40,7 +40,7 @@ namespace esphome::logger { * - Volatile counter enables fast has_messages() without lock overhead * - If message doesn't fit at end, padding is added and message wraps to start */ -class TaskLogBufferLibreTiny { +class TaskLogBuffer { public: // Structure for a log message header (text data follows immediately after) struct LogMessage { @@ -60,11 +60,11 @@ class TaskLogBufferLibreTiny { static constexpr uint8_t PADDING_MARKER_LEVEL = 0xFF; // Constructor that takes a total buffer size - explicit TaskLogBufferLibreTiny(size_t total_buffer_size); - ~TaskLogBufferLibreTiny(); + explicit TaskLogBuffer(size_t total_buffer_size); + ~TaskLogBuffer(); // NOT thread-safe - borrow a message from the buffer, only call from main loop - bool borrow_message_main_loop(LogMessage **message, const char **text); + bool borrow_message_main_loop(LogMessage *&message, uint16_t &text_length); // NOT thread-safe - release a message buffer, only call from main loop void release_message_main_loop(); diff --git a/esphome/components/logger/task_log_buffer_zephyr.cpp b/esphome/components/logger/task_log_buffer_zephyr.cpp new file mode 100644 index 0000000000..44d12d08a3 --- /dev/null +++ b/esphome/components/logger/task_log_buffer_zephyr.cpp @@ -0,0 +1,116 @@ +#ifdef USE_ZEPHYR + +#include "task_log_buffer_zephyr.h" + +namespace esphome::logger { + +__thread bool non_main_task_recursion_guard_; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + +static inline uint32_t total_size_in_32bit_words(uint16_t text_length) { + // Calculate total size in 32-bit words needed (header + text length + null terminator + 3(4 bytes alignment) + return (sizeof(TaskLogBuffer::LogMessage) + text_length + 1 + 3) / sizeof(uint32_t); +} + +static inline uint32_t get_wlen(const mpsc_pbuf_generic *item) { + return total_size_in_32bit_words(reinterpret_cast(item)->text_length); +} + +TaskLogBuffer::TaskLogBuffer(size_t total_buffer_size) { + // alignment to 4 bytes + total_buffer_size = (total_buffer_size + 3) / sizeof(uint32_t); + this->mpsc_config_.buf = new uint32_t[total_buffer_size]; + this->mpsc_config_.size = total_buffer_size; + this->mpsc_config_.flags = MPSC_PBUF_MODE_OVERWRITE; + this->mpsc_config_.get_wlen = get_wlen, + + mpsc_pbuf_init(&this->log_buffer_, &this->mpsc_config_); +} + +TaskLogBuffer::~TaskLogBuffer() { delete[] this->mpsc_config_.buf; } + +bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name, + const char *format, va_list args) { + // First, calculate the exact length needed using a null buffer (no actual writing) + va_list args_copy; + va_copy(args_copy, args); + int ret = vsnprintf(nullptr, 0, format, args_copy); + va_end(args_copy); + + if (ret <= 0) { + return false; // Formatting error or empty message + } + + // Calculate actual text length (capped to maximum size) + static constexpr size_t MAX_TEXT_SIZE = 255; + size_t text_length = (static_cast(ret) > MAX_TEXT_SIZE) ? MAX_TEXT_SIZE : ret; + size_t total_size = total_size_in_32bit_words(text_length); + auto *msg = reinterpret_cast(mpsc_pbuf_alloc(&this->log_buffer_, total_size, K_NO_WAIT)); + if (msg == nullptr) { + return false; + } + msg->level = level; + msg->tag = tag; + msg->line = line; + strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1); + msg->thread_name[sizeof(msg->thread_name) - 1] = '\0'; // Ensure null termination + + // Format the message text directly into the acquired memory + // We add 1 to text_length to ensure space for null terminator during formatting + char *text_area = msg->text_data(); + ret = vsnprintf(text_area, text_length + 1, format, args); + + // Handle unexpected formatting error (ret < 0 is encoding error; ret == 0 is valid empty output) + if (ret < 0) { + // this should not happen, vsnprintf was called already once + // fill with '\n' to not call mpsc_pbuf_free from producer + // it will be trimmed anyway + for (size_t i = 0; i < text_length; ++i) { + text_area[i] = '\n'; + } + text_area[text_length] = 0; + // do not return false to free the buffer from main thread + } + + msg->text_length = text_length; + + mpsc_pbuf_commit(&this->log_buffer_, reinterpret_cast(msg)); + return true; +} + +bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &text_length) { + if (this->current_token_) { + return false; + } + + this->current_token_ = mpsc_pbuf_claim(&this->log_buffer_); + + if (this->current_token_ == nullptr) { + return false; + } + + // we claimed buffer already, const_cast is safe here + message = const_cast(reinterpret_cast(this->current_token_)); + + text_length = message->text_length; + // Remove trailing newlines + while (text_length > 0 && message->text_data()[text_length - 1] == '\n') { + text_length--; + } + + return true; +} + +void TaskLogBuffer::release_message_main_loop() { + if (this->current_token_ == nullptr) { + return; + } + mpsc_pbuf_free(&this->log_buffer_, this->current_token_); + this->current_token_ = nullptr; +} +#endif // USE_ESPHOME_TASK_LOG_BUFFER + +} // namespace esphome::logger + +#endif // USE_ZEPHYR diff --git a/esphome/components/logger/task_log_buffer_zephyr.h b/esphome/components/logger/task_log_buffer_zephyr.h new file mode 100644 index 0000000000..cc2ed1f687 --- /dev/null +++ b/esphome/components/logger/task_log_buffer_zephyr.h @@ -0,0 +1,66 @@ +#pragma once + +#ifdef USE_ZEPHYR + +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include + +namespace esphome::logger { + +// "0x" + 2 hex digits per byte + '\0' +static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; + +extern __thread bool non_main_task_recursion_guard_; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + +class TaskLogBuffer { + public: + // Structure for a log message header (text data follows immediately after) + struct LogMessage { + MPSC_PBUF_HDR; // this is only 2 bits but no more than 30 bits directly after + uint16_t line; // Source code line number + uint8_t level; // Log level (0-7) +#if defined(CONFIG_THREAD_NAME) + char thread_name[CONFIG_THREAD_MAX_NAME_LEN]; // Store thread name directly (only used for non-main threads) +#else + char thread_name[MAX_POINTER_REPRESENTATION]; // Store thread name directly (only used for non-main threads) +#endif + const char *tag; // We store the pointer, assuming tags are static + uint16_t text_length; // Length of the message text (up to ~64KB) + + // Methods for accessing message contents + inline char *text_data() { return reinterpret_cast(this) + sizeof(LogMessage); } + }; + // Constructor that takes a total buffer size + explicit TaskLogBuffer(size_t total_buffer_size); + ~TaskLogBuffer(); + + // Check if there are messages ready to be processed using an atomic counter for performance + inline bool HOT has_messages() { return mpsc_pbuf_is_pending(&this->log_buffer_); } + + // Get the total buffer size in bytes + inline size_t size() const { return this->mpsc_config_.size * sizeof(uint32_t); } + + // 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); + + // NOT thread-safe - release a message buffer and update the counter, only call from main loop + void release_message_main_loop(); + + // Thread-safe - send a message to the ring buffer from any thread + bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name, + const char *format, va_list args); + + protected: + mpsc_pbuf_buffer_config mpsc_config_{}; + mpsc_pbuf_buffer log_buffer_{}; + const mpsc_pbuf_generic *current_token_{}; +}; + +#endif // USE_ESPHOME_TASK_LOG_BUFFER + +} // namespace esphome::logger + +#endif // USE_ZEPHYR diff --git a/esphome/core/defines.h b/esphome/core/defines.h index ee865a7e65..0c888933bf 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -320,6 +320,7 @@ #endif #ifdef USE_NRF52 +#define USE_ESPHOME_TASK_LOG_BUFFER #define USE_NRF52_DFU #define USE_NRF52_REG0_VOUT 5 #define USE_NRF52_UICR_ERASE diff --git a/tests/components/logger/test.nrf52-adafruit.yaml b/tests/components/logger/test.nrf52-adafruit.yaml index 70b485daac..821a136250 100644 --- a/tests/components/logger/test.nrf52-adafruit.yaml +++ b/tests/components/logger/test.nrf52-adafruit.yaml @@ -5,3 +5,4 @@ esphome: logger: level: DEBUG + task_log_buffer_size: 0