mirror of
https://github.com/esphome/esphome.git
synced 2026-11-15 01:25:57 +00:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b310c985e0 | ||
|
|
7bc87b9b00 | ||
|
|
a5c1b4f532 | ||
|
|
8fc6fcf724 | ||
|
|
22a2efd8fd | ||
|
|
e0354ae794 | ||
|
|
f9299c1c58 | ||
|
|
09c40438e2 | ||
|
|
63df68acc4 | ||
|
|
8936e4f54b | ||
|
|
1c1cf21b16 | ||
|
|
71a0b9cfca | ||
|
|
ebc4dd2cc9 | ||
|
|
8f705e7cdb | ||
|
|
c2067f4452 | ||
|
|
e1b38f5171 | ||
|
|
ef10ea99b9 |
No files matched your search
@@ -37,6 +37,7 @@ from esphome.types import ConfigType
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DOMAIN = "logger"
|
||||
logger_ns = cg.esphome_ns.namespace("logger")
|
||||
LOG_LEVELS = {
|
||||
"NONE": cg.global_ns.ESPHOME_LOG_LEVEL_NONE,
|
||||
@@ -93,6 +94,7 @@ UART_SELECTION_RP2040 = [USB_CDC, UART0, UART1]
|
||||
|
||||
UART_SELECTION_NRF52 = [USB_CDC, UART0]
|
||||
|
||||
# Read by device-builder
|
||||
HARDWARE_UART_TO_UART_SELECTION = {
|
||||
UART0: logger_ns.UART_SELECTION_UART0,
|
||||
UART0_SWAP: logger_ns.UART_SELECTION_UART0_SWAP,
|
||||
@@ -108,8 +110,6 @@ HARDWARE_UART_TO_SERIAL = {
|
||||
UART0: cg.global_ns.Serial,
|
||||
UART0_SWAP: cg.global_ns.Serial,
|
||||
UART1: cg.global_ns.Serial1,
|
||||
UART2: cg.global_ns.Serial2,
|
||||
DEFAULT: cg.global_ns.Serial,
|
||||
},
|
||||
PLATFORM_RP2: {
|
||||
UART0: cg.global_ns.Serial1,
|
||||
@@ -503,13 +503,10 @@ async def to_code(config: ConfigType) -> None:
|
||||
CORE.data.setdefault(DOMAIN, {})[CONF_LEVEL] = level
|
||||
tx_buffer_size = config[CONF_TX_BUFFER_SIZE]
|
||||
cg.add_define("ESPHOME_LOGGER_TX_BUFFER_SIZE", tx_buffer_size)
|
||||
# Determine task log buffer size. The buffer is a direct member of Logger
|
||||
# (no separate heap allocation).
|
||||
task_log_buffer_size = 0
|
||||
if CORE.is_esp32 or CORE.is_libretiny or CORE.is_nrf52:
|
||||
task_log_buffer_size = config[CONF_TASK_LOG_BUFFER_SIZE]
|
||||
elif CORE.is_host:
|
||||
task_log_buffer_size = 64 # Fixed 64 slots for host
|
||||
# The host has a fixed 64 slots
|
||||
task_log_buffer_size = (
|
||||
64 if CORE.is_host else config.get(CONF_TASK_LOG_BUFFER_SIZE, 0)
|
||||
)
|
||||
if task_log_buffer_size > 0:
|
||||
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
|
||||
cg.add_define("ESPHOME_TASK_LOG_BUFFER_SIZE", task_log_buffer_size)
|
||||
@@ -542,17 +539,17 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(log.set_boot_log_level(tag, LOG_LEVELS[log_level]))
|
||||
if CORE.is_esp32 or CORE.is_host:
|
||||
cg.add(log.create_pthread_key())
|
||||
# set_uart_selection() must be called before pre_setup() because
|
||||
# pre_setup() switches on uart_ to decide which hardware to initialize
|
||||
# (e.g. UART0 vs USB_SERIAL_JTAG). Without this, uart_ is still the
|
||||
# default UART_SELECTION_UART0 and the wrong hardware gets initialized.
|
||||
# uart_ is UART0 in C++ except on LibreTiny where it is DEFAULT; skip the
|
||||
# setter when the config matches it.
|
||||
cpp_default_uart = DEFAULT if CORE.is_libretiny else UART0
|
||||
if (
|
||||
hardware_uart := config.get(CONF_HARDWARE_UART)
|
||||
) is not None and hardware_uart != cpp_default_uart:
|
||||
cg.add(log.set_uart_selection(HARDWARE_UART_TO_UART_SELECTION[hardware_uart]))
|
||||
if (hardware_uart := config.get(CONF_HARDWARE_UART)) is not None:
|
||||
cg.add_define(f"USE_LOGGER_UART_SELECTION_{hardware_uart}")
|
||||
cg.add_define(
|
||||
"ESPHOME_LOGGER_UART_SELECTION",
|
||||
cg.RawExpression(f"logger::UART_SELECTION_{hardware_uart}"),
|
||||
)
|
||||
# ESP8266 without serial logging never opens a port
|
||||
cg.add_define(
|
||||
"ESPHOME_LOGGER_UART_NAME",
|
||||
"NONE" if CORE.is_esp8266 and baud_rate == 0 else hardware_uart,
|
||||
)
|
||||
# pre_setup() sets global_logger and must run before any other code
|
||||
# that may call ESP_LOG* (e.g. setup_preferences contains ESP_LOGVV).
|
||||
cg.add(log.pre_setup())
|
||||
@@ -647,10 +644,8 @@ async def _late_logger_init(config: ConfigType) -> None:
|
||||
|
||||
if config[CONF_HARDWARE_UART] == USB_CDC:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
|
||||
cg.add_define("USE_LOGGER_UART_SELECTION_USB_CDC")
|
||||
elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True)
|
||||
cg.add_define("USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG")
|
||||
# Log V2 formats centrally in esp_log() (~4KB flash, ~180B RAM). 6.1
|
||||
# adds CONSTRAINED_ENV_SAFE=n, keeping early/DRAM logs on
|
||||
# esp_rom_printf instead of pulling ~1.2KB esp_rom_vprintf into IRAM.
|
||||
@@ -667,8 +662,13 @@ async def _late_logger_init(config: ConfigType) -> None:
|
||||
cg.add_define("USE_ESP32_LOG_V2")
|
||||
# --wrap: a strong definition collides; see logger_esp32.cpp
|
||||
cg.add_build_flag("-Wl,--wrap=esp_log_format")
|
||||
if CORE.is_rp2 and config[CONF_HARDWARE_UART] == USB_CDC:
|
||||
cg.add_define("USE_LOGGER_UART_SELECTION_USB_CDC")
|
||||
# Zephyr polls the USB port only when it logs there
|
||||
if (
|
||||
CORE.using_zephyr
|
||||
and has_serial_logging
|
||||
and config[CONF_HARDWARE_UART] == USB_CDC
|
||||
):
|
||||
cg.add_define("USE_LOGGER_ZEPHYR_CDC_LOOP")
|
||||
# Define platform support flags for components that need auto-detection
|
||||
try:
|
||||
uart_selection(USB_SERIAL_JTAG)
|
||||
@@ -718,7 +718,6 @@ async def _late_logger_init(config: ConfigType) -> None:
|
||||
if config[CONF_HARDWARE_UART] == UART1:
|
||||
zephyr_add_overlay("""&uart1 { status = "okay";};""")
|
||||
if config[CONF_HARDWARE_UART] == USB_CDC:
|
||||
cg.add_define("USE_LOGGER_UART_SELECTION_USB_CDC")
|
||||
zephyr_add_prj_conf("UART_LINE_CTRL", True)
|
||||
|
||||
# Register at end for safe mode
|
||||
@@ -739,8 +738,6 @@ async def _late_logger_init(config: ConfigType) -> None:
|
||||
conf,
|
||||
)
|
||||
|
||||
CORE.add_job(final_step)
|
||||
|
||||
|
||||
def validate_printf(value: ConfigType) -> ConfigType:
|
||||
# https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python
|
||||
@@ -875,11 +872,6 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
}
|
||||
)
|
||||
|
||||
# Keys for CORE.data storage
|
||||
DOMAIN = "logger"
|
||||
KEY_LEVEL_LISTENERS = "level_listeners"
|
||||
KEY_LOG_LISTENERS = "log_listeners"
|
||||
|
||||
|
||||
def request_logger_level_listeners() -> None:
|
||||
"""Request that logger level listeners be compiled in.
|
||||
@@ -888,29 +880,18 @@ def request_logger_level_listeners() -> None:
|
||||
function during their code generation. This enables the add_level_listener()
|
||||
method and compiles in the listener vector.
|
||||
"""
|
||||
CORE.data.setdefault(DOMAIN, {})[KEY_LEVEL_LISTENERS] = True
|
||||
cg.add_define("USE_LOGGER_LEVEL_LISTENERS")
|
||||
|
||||
|
||||
_request_log_listener_slot = cg.slot_counter("ESPHOME_LOG_MAX_LISTENERS")
|
||||
|
||||
|
||||
def request_console() -> None:
|
||||
"""Compile in Logger::console_read() and console_write(); call from to_code."""
|
||||
cg.add_define("USE_LOGGER_CONSOLE")
|
||||
|
||||
|
||||
def request_log_listener() -> None:
|
||||
"""Request a log listener slot.
|
||||
|
||||
Components that need to receive log messages should call this function
|
||||
during their code generation. This increments the listener count used
|
||||
to size the StaticVector.
|
||||
"""
|
||||
data = CORE.data.setdefault(DOMAIN, {})
|
||||
data[KEY_LOG_LISTENERS] = data.get(KEY_LOG_LISTENERS, 0) + 1
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
async def final_step() -> None:
|
||||
"""Final code generation step to configure optional logger features."""
|
||||
domain_data = CORE.data.get(DOMAIN, {})
|
||||
if domain_data.get(KEY_LEVEL_LISTENERS, False):
|
||||
cg.add_define("USE_LOGGER_LEVEL_LISTENERS")
|
||||
|
||||
# Only generate log listener code if any component needs it
|
||||
log_listener_count = domain_data.get(KEY_LOG_LISTENERS, 0)
|
||||
if log_listener_count > 0:
|
||||
cg.add_define("USE_LOG_LISTENERS")
|
||||
cg.add_define("ESPHOME_LOG_MAX_LISTENERS", log_listener_count)
|
||||
"""Reserve a log listener slot; call once per listener from to_code."""
|
||||
cg.add_define("USE_LOG_LISTENERS")
|
||||
_request_log_listener_slot()
|
||||
@@ -32,7 +32,7 @@ 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)
|
||||
#elif defined(USE_ZEPHYR)
|
||||
k_tid_t current_task = k_current_get();
|
||||
const bool is_main_task = (current_task == this->main_task_);
|
||||
#else // USE_HOST
|
||||
@@ -57,10 +57,10 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
||||
// (we already have the handle from the main task check above).
|
||||
// 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);
|
||||
const char *thread_name = this->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);
|
||||
const char *thread_name = this->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);
|
||||
@@ -103,10 +103,10 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li
|
||||
{
|
||||
#ifdef USE_HOST
|
||||
// Host always has console output - no baud_rate check needed
|
||||
static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 512;
|
||||
static constexpr size_t MAX_CONSOLE_LOG_MSG_SIZE = 512;
|
||||
#else
|
||||
// Maximum size for console log messages (includes null terminator)
|
||||
static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144;
|
||||
static constexpr size_t MAX_CONSOLE_LOG_MSG_SIZE = 144;
|
||||
#endif
|
||||
char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety
|
||||
LogBuffer buf{console_buffer, MAX_CONSOLE_LOG_MSG_SIZE};
|
||||
@@ -118,13 +118,11 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li
|
||||
}
|
||||
#else
|
||||
// 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).
|
||||
// Logging calls are NOT thread-safe: the recursion guard is a plain bool and tx_buffer_ has no locking.
|
||||
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_)
|
||||
if (level > this->level_for(tag) || this->main_task_recursion_guard_)
|
||||
return;
|
||||
// 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);
|
||||
this->log_message_to_buffer_and_send_(this->main_task_recursion_guard_, level, tag, line, format, args, nullptr);
|
||||
}
|
||||
#endif // USE_ESP32 || USE_HOST || USE_LIBRETINY || USE_ZEPHYR
|
||||
|
||||
@@ -136,10 +134,10 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
||||
//
|
||||
void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
|
||||
va_list args) { // NOLINT
|
||||
if (level > this->level_for(tag) || global_recursion_guard_)
|
||||
if (level > this->level_for(tag) || this->main_task_recursion_guard_)
|
||||
return;
|
||||
|
||||
this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args, nullptr);
|
||||
this->log_message_to_buffer_and_send_(this->main_task_recursion_guard_, level, tag, line, format, args, nullptr);
|
||||
}
|
||||
#endif // USE_ESP8266
|
||||
|
||||
@@ -184,10 +182,10 @@ Logger::Logger(uint32_t baud_rate) : baud_rate_(baud_rate) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || defined(USE_LOGGER_ZEPHYR_CDC_LOOP)
|
||||
void Logger::loop() {
|
||||
this->process_messages_();
|
||||
#if defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC)
|
||||
#ifdef USE_LOGGER_ZEPHYR_CDC_LOOP
|
||||
this->cdc_loop_();
|
||||
#endif
|
||||
}
|
||||
@@ -213,11 +211,11 @@ void Logger::process_messages_() {
|
||||
this->write_log_buffer_to_console_(buf);
|
||||
}
|
||||
}
|
||||
#if !(defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||
// Zephyr with USB CDC keeps the loop running for cdc_loop_()
|
||||
#ifndef USE_LOGGER_ZEPHYR_CDC_LOOP
|
||||
else {
|
||||
// No messages to process, disable loop if appropriate
|
||||
// This reduces overhead when there's no async logging activity
|
||||
this->disable_loop_when_buffer_empty_();
|
||||
// A concurrent enable_loop_soon_any_context() still wakes the next iteration
|
||||
this->disable_loop();
|
||||
}
|
||||
#endif
|
||||
#endif // USE_ESPHOME_TASK_LOG_BUFFER
|
||||
@@ -265,7 +263,7 @@ void Logger::boot_log_capture_(uint8_t level, const char *tag, const LogBuffer &
|
||||
|
||||
void Logger::replay_boot_logs() {
|
||||
// The replay shares tx_buffer_ with normal logging, so it is dropped like any other re-entrant log
|
||||
bool &recursion_guard = this->main_loop_recursion_guard_();
|
||||
bool &recursion_guard = this->main_task_recursion_guard_;
|
||||
if (recursion_guard)
|
||||
return;
|
||||
this->boot_log_done_ = true; // Stops capture first, so the replay output is not buffered again
|
||||
@@ -307,6 +305,25 @@ static const LogString *get_log_level_str(uint8_t level) {
|
||||
return LogLevelStrings::get_log_str(level, LogLevelStrings::LAST_INDEX);
|
||||
}
|
||||
|
||||
#ifndef USE_HOST
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
#ifdef USE_LOGGER_UART_SELECTION_DEFAULT
|
||||
// LibreTiny resolves DEFAULT to the board's port only when serial logging is on
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->get_uart()) {
|
||||
case UART_SELECTION_UART0:
|
||||
return LOG_STR("UART0");
|
||||
case UART_SELECTION_UART1:
|
||||
return LOG_STR("UART1");
|
||||
default:
|
||||
return LOG_STR("UART2");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return LOG_STR(ESPHOME_LOGGER_UART_NAME);
|
||||
}
|
||||
#endif
|
||||
|
||||
void Logger::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Logger:\n"
|
||||
@@ -342,7 +359,7 @@ void Logger::dump_config() {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ZEPHYR
|
||||
dump_crash_();
|
||||
this->dump_crash_();
|
||||
#ifdef USE_BOOTLOADER_ADAFRUIT
|
||||
this->dump_bootloader_();
|
||||
#endif
|
||||
|
||||
@@ -42,9 +42,6 @@ struct device;
|
||||
namespace esphome::logger {
|
||||
|
||||
/** Lightweight callback for receiving log messages without virtual dispatch overhead.
|
||||
*
|
||||
* Replaces the former LogListener virtual interface to eliminate per-implementer
|
||||
* vtable sub-tables and thunk code (~39 bytes saved per class that used LogListener).
|
||||
*
|
||||
* Usage:
|
||||
* // In your component's setup():
|
||||
@@ -73,7 +70,7 @@ struct LogCallback {
|
||||
* public:
|
||||
* void setup() override {
|
||||
* if (logger::global_logger != nullptr)
|
||||
* logger::global_logger->add_logger_level_listener(this);
|
||||
* logger::global_logger->add_level_listener(this);
|
||||
* }
|
||||
* void on_log_level_change(uint8_t level) override {
|
||||
* // Handle log level change
|
||||
@@ -127,6 +124,9 @@ enum UARTSelection : uint8_t {
|
||||
UART_SELECTION_UART0_SWAP,
|
||||
#endif // USE_ESP8266
|
||||
};
|
||||
|
||||
// The port chosen by hardware_uart, fixed at build time
|
||||
static constexpr UARTSelection CONFIGURED_UART_SELECTION = ESPHOME_LOGGER_UART_SELECTION;
|
||||
#endif // USE_ESP32 || USE_ESP8266 || USE_RP2 || USE_LIBRETINY || USE_ZEPHYR
|
||||
|
||||
/**
|
||||
@@ -138,7 +138,7 @@ enum UARTSelection : uint8_t {
|
||||
* - On ESP32: Uses task-specific recursion guards
|
||||
* * Main task: Uses a dedicated boolean member variable for efficiency
|
||||
* * Other tasks: Uses pthread TLS with a dynamically allocated key for task-specific state
|
||||
* - On other platforms: Uses a simple global recursion guard
|
||||
* - On single-task platforms: Uses the main task guard alone
|
||||
*
|
||||
* We use pthread TLS via pthread_key_create to create a unique key for storing
|
||||
* task-specific recursion state, which:
|
||||
@@ -149,7 +149,7 @@ enum UARTSelection : uint8_t {
|
||||
class Logger final : public Component {
|
||||
public:
|
||||
explicit Logger(uint32_t baud_rate);
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || defined(USE_LOGGER_ZEPHYR_CDC_LOOP)
|
||||
void loop() override;
|
||||
#endif
|
||||
#if defined(USE_RP2_CRASH_HANDLER) && defined(USE_LOGGER_UART_SELECTION_USB_CDC)
|
||||
@@ -157,21 +157,52 @@ class Logger final : public Component {
|
||||
#endif
|
||||
/// Manually set the baud rate for serial, set to 0 to disable.
|
||||
void set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
|
||||
uint32_t get_baud_rate() const { return baud_rate_; }
|
||||
uint32_t get_baud_rate() const { return this->baud_rate_; }
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
|
||||
Stream *get_hw_serial() const { return hw_serial_; }
|
||||
Stream *get_hw_serial() const { return this->hw_serial_; }
|
||||
#endif
|
||||
#ifdef USE_ESP32
|
||||
uart_port_t get_uart_num() const { return uart_num_; }
|
||||
void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); }
|
||||
/// UART_NUM_0 when logging to USB
|
||||
constexpr uart_port_t get_uart_num() const {
|
||||
#if defined(USE_LOGGER_UART_SELECTION_USB_CDC) || defined(USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG)
|
||||
return UART_NUM_0;
|
||||
#else
|
||||
return static_cast<uart_port_t>(CONFIGURED_UART_SELECTION);
|
||||
#endif
|
||||
#ifdef USE_HOST
|
||||
void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); }
|
||||
}
|
||||
#endif
|
||||
#if defined(USE_ESP32) || defined(USE_HOST)
|
||||
void create_pthread_key() { pthread_key_create(&this->log_recursion_key_, nullptr); }
|
||||
#endif
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
||||
void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
|
||||
/// Get the UART used by the logger.
|
||||
UARTSelection get_uart() const { return this->uart_; }
|
||||
#ifdef USE_LIBRETINY
|
||||
// DEFAULT resolves to the board's default port
|
||||
constexpr UARTSelection get_uart() const {
|
||||
return CONFIGURED_UART_SELECTION == UART_SELECTION_DEFAULT ? static_cast<UARTSelection>(LT_UART_DEFAULT_SERIAL + 1)
|
||||
: CONFIGURED_UART_SELECTION;
|
||||
}
|
||||
#else
|
||||
constexpr UARTSelection get_uart() const { return CONFIGURED_UART_SELECTION; }
|
||||
#endif
|
||||
#endif
|
||||
#ifdef USE_LOGGER_CONSOLE
|
||||
// Raw bytes on the console port, without log framing; request with logger.request_console()
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
|
||||
size_t console_write(const uint8_t *data, size_t len) { return this->hw_serial_->write(data, len); }
|
||||
bool console_read(uint8_t &byte) {
|
||||
int c = this->hw_serial_->read();
|
||||
if (c < 0)
|
||||
return false;
|
||||
byte = c;
|
||||
return true;
|
||||
}
|
||||
#elif defined(USE_ESP32)
|
||||
/// Blocking write; returns the bytes written.
|
||||
size_t console_write(const uint8_t *data, size_t len);
|
||||
/// Non-blocking read of one byte; false if none is waiting.
|
||||
bool console_read(uint8_t &byte);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/// Set the default log level for this logger.
|
||||
@@ -242,14 +273,17 @@ class Logger final : public Component {
|
||||
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_UART_SELECTION_USB_CDC)
|
||||
#ifdef USE_LOGGER_ZEPHYR_CDC_LOOP
|
||||
void cdc_loop_();
|
||||
#endif
|
||||
void process_messages_();
|
||||
#if defined(USE_HOST) || defined(USE_ZEPHYR)
|
||||
void write_msg_(const char *msg, uint16_t len);
|
||||
// msg already ends in a newline
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
|
||||
inline void HOT write_msg_(const char *msg, uint16_t len) { this->hw_serial_->write(msg, len); }
|
||||
#elif defined(USE_ESP32)
|
||||
inline void write_msg_(const char *msg, uint16_t len); // Defined in logger_esp32.h
|
||||
#else
|
||||
inline void write_msg_(const char *msg, uint16_t len); // Defined in platform-specific logger_*.h
|
||||
void write_msg_(const char *msg, uint16_t len);
|
||||
#endif
|
||||
|
||||
// Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator
|
||||
@@ -317,7 +351,6 @@ class Logger final : public Component {
|
||||
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
// Helper to format a pre-formatted message from the task log buffer and notify listeners
|
||||
// Used by process_messages_ to avoid code duplication between ESP32 and host platforms
|
||||
inline void HOT format_buffered_message_and_notify_(uint8_t level, const char *tag, uint16_t line,
|
||||
const char *thread_name, const char *text, uint16_t text_length,
|
||||
LogBuffer &buf) {
|
||||
@@ -359,15 +392,8 @@ class Logger final : public Component {
|
||||
#ifdef USE_HOST
|
||||
pthread_t main_thread_{}; // Main thread for pthread_equal() comparison
|
||||
#endif
|
||||
#ifdef USE_ESP32
|
||||
// Task-specific recursion guards:
|
||||
// - Main task uses a dedicated member variable for efficiency
|
||||
// - Other tasks use pthread TLS with a dynamically created key via pthread_key_create
|
||||
pthread_key_t log_recursion_key_; // 4 bytes
|
||||
uart_port_t uart_num_; // 4 bytes (enum defaults to int size)
|
||||
#endif
|
||||
#ifdef USE_HOST
|
||||
// Thread-specific recursion guards using pthread TLS
|
||||
#if defined(USE_ESP32) || defined(USE_HOST)
|
||||
// Non-main task recursion guard (pthread TLS)
|
||||
pthread_key_t log_recursion_key_;
|
||||
#endif
|
||||
|
||||
@@ -392,23 +418,10 @@ class Logger final : public Component {
|
||||
bool boot_log_done_{false}; // Set on first replay; stops further capture
|
||||
#endif
|
||||
uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2) || defined(USE_ZEPHYR)
|
||||
UARTSelection uart_{UART_SELECTION_UART0}; // Must match cpp_default_uart in __init__.py
|
||||
#endif
|
||||
#ifdef USE_LIBRETINY
|
||||
UARTSelection uart_{UART_SELECTION_DEFAULT}; // Must match cpp_default_uart in __init__.py
|
||||
#endif
|
||||
#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
||||
bool main_task_recursion_guard_{false};
|
||||
bool main_task_recursion_guard_{false}; // Guards tx_buffer_ on the main loop
|
||||
#ifdef USE_LIBRETINY
|
||||
bool non_main_task_recursion_guard_{false}; // Shared guard for all non-main tasks on LibreTiny
|
||||
#endif
|
||||
// The guard that protects tx_buffer_ on the main loop, for callers that run on any platform
|
||||
bool &main_loop_recursion_guard_() { return this->main_task_recursion_guard_; }
|
||||
#else
|
||||
bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
|
||||
bool &main_loop_recursion_guard_() { return this->global_recursion_guard_; }
|
||||
#endif
|
||||
|
||||
// Large buffers placed last to keep frequently-accessed member offsets small
|
||||
char tx_buffer_[ESPHOME_LOGGER_TX_BUFFER_SIZE + 1]; // +1 for null terminator
|
||||
@@ -477,9 +490,9 @@ class Logger final : public Component {
|
||||
class NonMainTaskRecursionGuard {
|
||||
public:
|
||||
explicit NonMainTaskRecursionGuard(pthread_key_t key) : key_(key) {
|
||||
pthread_setspecific(key_, reinterpret_cast<void *>(1));
|
||||
pthread_setspecific(this->key_, reinterpret_cast<void *>(1));
|
||||
}
|
||||
~NonMainTaskRecursionGuard() { pthread_setspecific(key_, nullptr); }
|
||||
~NonMainTaskRecursionGuard() { pthread_setspecific(this->key_, nullptr); }
|
||||
NonMainTaskRecursionGuard(const NonMainTaskRecursionGuard &) = delete;
|
||||
NonMainTaskRecursionGuard &operator=(const NonMainTaskRecursionGuard &) = delete;
|
||||
NonMainTaskRecursionGuard(NonMainTaskRecursionGuard &&) = delete;
|
||||
@@ -490,40 +503,24 @@ class Logger final : public Component {
|
||||
};
|
||||
|
||||
// Check if non-main task is already in recursion (via TLS)
|
||||
inline bool HOT is_non_main_task_recursive_() const { return pthread_getspecific(log_recursion_key_) != nullptr; }
|
||||
inline bool HOT is_non_main_task_recursive_() const {
|
||||
return pthread_getspecific(this->log_recursion_key_) != nullptr;
|
||||
}
|
||||
|
||||
// Create RAII guard for non-main task recursion
|
||||
inline NonMainTaskRecursionGuard make_non_main_task_guard_() { return NonMainTaskRecursionGuard(log_recursion_key_); }
|
||||
|
||||
#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
|
||||
// This is safe because:
|
||||
// - 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_; }
|
||||
|
||||
// Create RAII guard for non-main task recursion (uses shared boolean for all non-main tasks)
|
||||
inline RecursionGuard make_non_main_task_guard_() { return RecursionGuard(non_main_task_recursion_guard_); }
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) && !(defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||
// Disable loop when task buffer is empty
|
||||
// Zephyr with USB CDC needs loop active to poll port readiness via cdc_loop_()
|
||||
inline void disable_loop_when_buffer_empty_() {
|
||||
// Thread safety note: This is safe even if another task calls enable_loop_soon_any_context()
|
||||
// concurrently. If that happens between our check and disable_loop(), the enable request
|
||||
// will be processed on the next main loop iteration since:
|
||||
// - disable_loop() takes effect immediately
|
||||
// - enable_loop_soon_any_context() sets a pending flag that's checked at loop start
|
||||
this->disable_loop();
|
||||
inline NonMainTaskRecursionGuard make_non_main_task_guard_() {
|
||||
return NonMainTaskRecursionGuard(this->log_recursion_key_);
|
||||
}
|
||||
|
||||
#elif defined(USE_LIBRETINY)
|
||||
// No FreeRTOS TLS on LibreTiny: all non-main tasks share one guard
|
||||
inline bool HOT is_non_main_task_recursive_() const { return this->non_main_task_recursion_guard_; }
|
||||
inline RecursionGuard make_non_main_task_guard_() { return RecursionGuard(this->non_main_task_recursion_guard_); }
|
||||
|
||||
#elif defined(USE_ZEPHYR)
|
||||
// Thread-local guard (__thread)
|
||||
inline bool HOT is_non_main_task_recursive_() const { return non_main_task_recursion_guard; }
|
||||
inline RecursionGuard make_non_main_task_guard_() { return RecursionGuard(non_main_task_recursion_guard); }
|
||||
#endif
|
||||
};
|
||||
extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
@@ -563,14 +560,7 @@ void log_crash_to_client();
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
// Platform-specific inline implementations of write_msg_()
|
||||
// Must be included after the Logger class definition is complete
|
||||
#if defined(USE_ESP32)
|
||||
// ESP32 inline write_msg_(), included after the Logger class definition is complete
|
||||
#ifdef USE_ESP32
|
||||
#include "logger_esp32.h"
|
||||
#elif defined(USE_ESP8266)
|
||||
#include "logger_esp8266.h"
|
||||
#elif defined(USE_RP2)
|
||||
#include "logger_rp2.h"
|
||||
#elif defined(USE_LIBRETINY)
|
||||
#include "logger_libretiny.h"
|
||||
#endif
|
||||
@@ -17,6 +17,9 @@
|
||||
#include <driver/usb_serial_jtag_vfs.h>
|
||||
#endif
|
||||
#endif
|
||||
#if defined(USE_LOGGER_CONSOLE) && defined(USE_LOGGER_UART_SELECTION_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
|
||||
#include <esp_private/usb_console.h>
|
||||
#endif
|
||||
#if defined(CONFIG_PM_ENABLE) && defined(CONFIG_FREERTOS_USE_TICKLESS_IDLE) && \
|
||||
(ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0))
|
||||
#include "esp_sleep.h"
|
||||
@@ -73,7 +76,8 @@ static void init_usb_serial_jtag() {
|
||||
}
|
||||
#endif
|
||||
|
||||
void init_uart(uart_port_t uart_num, uint32_t baud_rate, int tx_buffer_size) {
|
||||
#if !defined(USE_LOGGER_UART_SELECTION_USB_CDC) && !defined(USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG)
|
||||
static void init_uart(uart_port_t uart_num, uint32_t baud_rate, int tx_buffer_size) {
|
||||
uart_config_t uart_config{};
|
||||
uart_config.baud_rate = (int) baud_rate;
|
||||
uart_config.data_bits = UART_DATA_8_BITS;
|
||||
@@ -97,37 +101,22 @@ void init_uart(uart_port_t uart_num, uint32_t baud_rate, int tx_buffer_size) {
|
||||
esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_ALWAYS_FLUSH_UART);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// get_uart_num() and pre_setup() cast the selection straight to a port
|
||||
static_assert(static_cast<int>(UART_SELECTION_UART0) == static_cast<int>(UART_NUM_0) &&
|
||||
static_cast<int>(UART_SELECTION_UART1) == static_cast<int>(UART_NUM_1));
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
static_assert(static_cast<int>(UART_SELECTION_UART2) == static_cast<int>(UART_NUM_2));
|
||||
#endif
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
this->uart_num_ = UART_NUM_0;
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
this->uart_num_ = UART_NUM_0;
|
||||
init_uart(this->uart_num_, baud_rate_, ESPHOME_LOGGER_TX_BUFFER_SIZE);
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->uart_num_ = UART_NUM_1;
|
||||
init_uart(this->uart_num_, baud_rate_, ESPHOME_LOGGER_TX_BUFFER_SIZE);
|
||||
break;
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
case UART_SELECTION_UART2:
|
||||
this->uart_num_ = UART_NUM_2;
|
||||
init_uart(this->uart_num_, baud_rate_, ESPHOME_LOGGER_TX_BUFFER_SIZE);
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
case UART_SELECTION_USB_SERIAL_JTAG:
|
||||
#ifdef USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG
|
||||
init_usb_serial_jtag();
|
||||
init_usb_serial_jtag();
|
||||
#elif !defined(USE_LOGGER_UART_SELECTION_USB_CDC)
|
||||
init_uart(this->get_uart_num(), this->baud_rate_, ESPHOME_LOGGER_TX_BUFFER_SIZE);
|
||||
#endif
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
global_logger = this;
|
||||
@@ -139,29 +128,36 @@ void Logger::pre_setup() {
|
||||
#endif
|
||||
}
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
return LOG_STR("UART0");
|
||||
case UART_SELECTION_UART1:
|
||||
return LOG_STR("UART1");
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
case UART_SELECTION_UART2:
|
||||
return LOG_STR("UART2");
|
||||
#ifdef USE_LOGGER_CONSOLE
|
||||
size_t Logger::console_write(const uint8_t *data, size_t len) {
|
||||
int written = 0;
|
||||
#if defined(USE_LOGGER_UART_SELECTION_USB_CDC)
|
||||
#ifdef CONFIG_ESP_CONSOLE_USB_CDC
|
||||
written = esp_usb_console_write_buf(reinterpret_cast<const char *>(data), len);
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
return LOG_STR("USB_CDC");
|
||||
#elif defined(USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG)
|
||||
written = usb_serial_jtag_write_bytes(data, len, 20 / portTICK_PERIOD_MS);
|
||||
#else
|
||||
written = uart_write_bytes(this->get_uart_num(), data, len);
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
case UART_SELECTION_USB_SERIAL_JTAG:
|
||||
return LOG_STR("USB_SERIAL_JTAG");
|
||||
#endif
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
return written > 0 ? written : 0;
|
||||
}
|
||||
|
||||
bool Logger::console_read(uint8_t &byte) {
|
||||
#if defined(USE_LOGGER_UART_SELECTION_USB_CDC)
|
||||
#ifdef CONFIG_ESP_CONSOLE_USB_CDC
|
||||
return esp_usb_console_available_for_read() && esp_usb_console_read_buf(reinterpret_cast<char *>(&byte), 1) == 1;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
#elif defined(USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG)
|
||||
return usb_serial_jtag_read_bytes(&byte, 1, 0) > 0;
|
||||
#else
|
||||
return uart_read_bytes(this->get_uart_num(), &byte, 1, 0) > 0;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CRASH_HANDLER
|
||||
bool crash_log_pending() { return esp32::crash_handler_has_data(); }
|
||||
void log_crash_to_client() {
|
||||
|
||||
@@ -19,7 +19,7 @@ inline void HOT Logger::write_msg_(const char *msg, uint16_t len) {
|
||||
fwrite(msg, 1, len, stdout);
|
||||
#else
|
||||
// Regular UART - single write including newline (already in buffer)
|
||||
uart_write_bytes(this->uart_num_, msg, len);
|
||||
uart_write_bytes(this->get_uart_num(), msg, len);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ void Logger::pre_setup() {
|
||||
#if defined(USE_ESP8266_LOGGER_SERIAL)
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
||||
Serial.swap();
|
||||
}
|
||||
#ifdef USE_LOGGER_UART_SELECTION_UART0_SWAP
|
||||
Serial.swap();
|
||||
#endif
|
||||
Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
#elif defined(USE_ESP8266_LOGGER_SERIAL1)
|
||||
this->hw_serial_ = &Serial1;
|
||||
@@ -35,19 +35,6 @@ void Logger::pre_setup() {
|
||||
#endif
|
||||
}
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
#if defined(USE_ESP8266_LOGGER_SERIAL)
|
||||
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
||||
return LOG_STR("UART0_SWAP");
|
||||
}
|
||||
return LOG_STR("UART0");
|
||||
#elif defined(USE_ESP8266_LOGGER_SERIAL1)
|
||||
return LOG_STR("UART1");
|
||||
#else
|
||||
return LOG_STR("NONE");
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_CRASH_HANDLER
|
||||
bool crash_log_pending() { return esp8266::crash_handler_has_data(); }
|
||||
void log_crash_to_client() { esp8266::crash_handler_log(); }
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
// Single write with newline already in buffer (added by caller)
|
||||
inline void HOT Logger::write_msg_(const char *msg, uint16_t len) { this->hw_serial_->write(msg, len); }
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
#endif
|
||||
@@ -12,45 +12,28 @@ ESPHOME_LOG_TAG(TAG, "logger");
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
#if LT_HW_UART0
|
||||
case UART_SELECTION_UART0:
|
||||
this->hw_serial_ = &Serial0;
|
||||
libretiny::ensure_serial_handle(0);
|
||||
Serial0.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if LT_HW_UART1
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial1;
|
||||
libretiny::ensure_serial_handle(1);
|
||||
Serial1.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if LT_HW_UART2
|
||||
case UART_SELECTION_UART2:
|
||||
this->hw_serial_ = &Serial2;
|
||||
libretiny::ensure_serial_handle(2);
|
||||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
if (this->uart_ != UART_SELECTION_DEFAULT) {
|
||||
ESP_LOGW(TAG, " The chosen logger UART port is not available on this board."
|
||||
"The default port was used instead.");
|
||||
}
|
||||
break;
|
||||
#if defined(USE_LOGGER_UART_SELECTION_UART0) && LT_HW_UART0
|
||||
this->hw_serial_ = &Serial0;
|
||||
libretiny::ensure_serial_handle(0);
|
||||
Serial0.begin(this->baud_rate_);
|
||||
#elif defined(USE_LOGGER_UART_SELECTION_UART1) && LT_HW_UART1
|
||||
this->hw_serial_ = &Serial1;
|
||||
libretiny::ensure_serial_handle(1);
|
||||
Serial1.begin(this->baud_rate_);
|
||||
#elif defined(USE_LOGGER_UART_SELECTION_UART2) && LT_HW_UART2
|
||||
this->hw_serial_ = &Serial2;
|
||||
libretiny::ensure_serial_handle(2);
|
||||
Serial2.begin(this->baud_rate_);
|
||||
#else
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
if (CONFIGURED_UART_SELECTION != UART_SELECTION_DEFAULT) {
|
||||
ESP_LOGW(TAG, " The chosen logger UART port is not available on this board."
|
||||
"The default port was used instead.");
|
||||
}
|
||||
|
||||
#endif
|
||||
// change lt_log() port to match default Serial
|
||||
if (this->uart_ == UART_SELECTION_DEFAULT) {
|
||||
this->uart_ = (UARTSelection) (LT_UART_DEFAULT_SERIAL + 1);
|
||||
lt_log_set_port(LT_UART_DEFAULT_SERIAL);
|
||||
} else {
|
||||
lt_log_set_port(this->uart_ - 1);
|
||||
}
|
||||
lt_log_set_port(this->get_uart() - 1);
|
||||
}
|
||||
|
||||
global_logger = this;
|
||||
@@ -60,20 +43,6 @@ void Logger::pre_setup() {
|
||||
#endif
|
||||
}
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_DEFAULT:
|
||||
return LOG_STR("DEFAULT");
|
||||
case UART_SELECTION_UART0:
|
||||
return LOG_STR("UART0");
|
||||
case UART_SELECTION_UART1:
|
||||
return LOG_STR("UART1");
|
||||
case UART_SELECTION_UART2:
|
||||
default:
|
||||
return LOG_STR("UART2");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_LIBRETINY_CRASH_HANDLER
|
||||
bool crash_log_pending() { return libretiny::crash_handler_has_data(); }
|
||||
void log_crash_to_client() { libretiny::crash_handler_log(); }
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
// Single write with newline already in buffer (added by caller)
|
||||
inline void HOT Logger::write_msg_(const char *msg, uint16_t len) { this->hw_serial_->write(msg, len); }
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
#endif
|
||||
@@ -26,20 +26,16 @@ void Logger::setup() {
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
break;
|
||||
}
|
||||
#if defined(USE_LOGGER_UART_SELECTION_USB_CDC)
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
#elif defined(USE_LOGGER_UART_SELECTION_UART1)
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
#else
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
#endif
|
||||
}
|
||||
global_logger = this;
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
@@ -49,21 +45,6 @@ void Logger::pre_setup() {
|
||||
#endif
|
||||
}
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
return LOG_STR("UART0");
|
||||
case UART_SELECTION_UART1:
|
||||
return LOG_STR("UART1");
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
return LOG_STR("USB_CDC");
|
||||
#endif
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_CRASH_HANDLER
|
||||
bool crash_log_pending() { return rp2::crash_handler_has_data(); }
|
||||
void log_crash_to_client() { rp2::crash_handler_log(); }
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_RP2
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
// Single write with newline already in buffer (added by caller)
|
||||
inline void HOT Logger::write_msg_(const char *msg, uint16_t len) { this->hw_serial_->write(msg, len); }
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
#endif
|
||||
@@ -42,9 +42,9 @@ __attribute__((section(".noinit"))) struct {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "logger");
|
||||
|
||||
#ifdef USE_LOGGER_UART_SELECTION_USB_CDC
|
||||
#ifdef USE_LOGGER_ZEPHYR_CDC_LOOP
|
||||
void Logger::cdc_loop_() {
|
||||
if (this->uart_ != UART_SELECTION_USB_CDC || this->uart_dev_ == nullptr) {
|
||||
if (this->uart_dev_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
static bool opened = false;
|
||||
@@ -74,26 +74,20 @@ void Logger::cdc_loop_() {
|
||||
void Logger::pre_setup() {
|
||||
#ifdef CONFIG_SERIAL
|
||||
if (this->baud_rate_ > 0) {
|
||||
static const struct device *uart_dev = nullptr;
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0: // NOLINT(bugprone-branch-clone)
|
||||
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(ESPHOME_LOGGER_UART0_NODE));
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(uart1));
|
||||
break;
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
const struct device *uart_dev = nullptr;
|
||||
#if defined(USE_LOGGER_UART_SELECTION_USB_CDC)
|
||||
#if defined(CONFIG_USB_DEVICE_STACK) || defined(CONFIG_USB_DEVICE_STACK_NEXT)
|
||||
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(cdc_acm_uart0));
|
||||
if (device_is_ready(uart_dev)) {
|
||||
// Nothing can log this early, and the logger has no output without USB anyway
|
||||
zephyr::usb_device_enable();
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
#endif
|
||||
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(cdc_acm_uart0));
|
||||
if (device_is_ready(uart_dev)) {
|
||||
// Nothing can log this early, and the logger has no output without USB anyway
|
||||
zephyr::usb_device_enable();
|
||||
}
|
||||
#endif
|
||||
#elif defined(USE_LOGGER_UART_SELECTION_UART1)
|
||||
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(uart1));
|
||||
#else
|
||||
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(ESPHOME_LOGGER_UART0_NODE));
|
||||
#endif
|
||||
if (device_is_ready(uart_dev)) {
|
||||
this->uart_dev_ = uart_dev;
|
||||
#if defined(USE_LOGGER_WAIT_FOR_CDC) && defined(USE_LOGGER_UART_SELECTION_USB_CDC)
|
||||
@@ -114,14 +108,10 @@ void Logger::pre_setup() {
|
||||
char reason_buffer[zephyr::RESET_REASON_BUFFER_SIZE];
|
||||
const char *reset_reason = zephyr::get_reset_reason(std::span<char, zephyr::RESET_REASON_BUFFER_SIZE>(reason_buffer));
|
||||
ESP_LOGI(TAG, "Reset reason: %s", reset_reason);
|
||||
dump_crash_();
|
||||
this->dump_crash_();
|
||||
#endif
|
||||
#ifdef USE_CRASH_HANDLER
|
||||
#ifdef USE_LOGGER_UART_SELECTION_USB_CDC
|
||||
// Nobody reads a USB port this early; cdc_loop_() reports the dump once the port is opened
|
||||
if (this->uart_ == UART_SELECTION_USB_CDC)
|
||||
return;
|
||||
#endif
|
||||
#if defined(USE_CRASH_HANDLER) && !defined(USE_LOGGER_ZEPHYR_CDC_LOOP)
|
||||
// Without a UART (baud_rate: 0, or one that did not come up) nothing reaches the console
|
||||
if (this->uart_dev_ != nullptr)
|
||||
log_crash_to_client();
|
||||
@@ -143,22 +133,7 @@ void HOT Logger::write_msg_(const char *msg, uint16_t len) {
|
||||
}
|
||||
}
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
return LOG_STR("UART0");
|
||||
case UART_SELECTION_UART1:
|
||||
return LOG_STR("UART1");
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
return LOG_STR("USB_CDC");
|
||||
#endif
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
static const uint8_t REASON_BUF_SIZE = 32;
|
||||
static constexpr uint8_t REASON_BUF_SIZE = 32;
|
||||
|
||||
static const char *reason_to_str(unsigned int reason, char *buf) {
|
||||
switch (reason) {
|
||||
|
||||
@@ -44,19 +44,15 @@ void TaskLogBuffer::release_message_main_loop() {
|
||||
|
||||
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)
|
||||
// Measure on a copy; the caller's va_list formats the text later
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
int ret = vsnprintf(nullptr, 0, format, args_copy);
|
||||
int ret = task_log_cap_length(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<size_t>(ret) > MAX_TEXT_SIZE) ? MAX_TEXT_SIZE : ret;
|
||||
uint16_t text_length = ret;
|
||||
|
||||
// Calculate total size needed (header + text length + null terminator)
|
||||
size_t total_size = sizeof(LogMessage) + text_length + 1;
|
||||
@@ -69,24 +65,10 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
|
||||
return false; // Failed to acquire memory
|
||||
}
|
||||
|
||||
// Set up the message header in the acquired memory
|
||||
LogMessage *msg = static_cast<LogMessage *>(acquired_memory);
|
||||
msg->level = level;
|
||||
msg->tag = tag;
|
||||
msg->line = line;
|
||||
|
||||
// Store the thread name now instead of waiting until main loop processing
|
||||
// This avoids crashes if the task completes or is deleted between when this message
|
||||
// is enqueued and when it's processed by the main loop
|
||||
if (thread_name != nullptr) {
|
||||
strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1);
|
||||
msg->thread_name[sizeof(msg->thread_name) - 1] = '\0'; // Ensure null termination
|
||||
} else {
|
||||
msg->thread_name[0] = '\0'; // Empty string if no thread name
|
||||
}
|
||||
fill_task_log_header(msg, level, tag, line, thread_name);
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -96,12 +78,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove trailing newlines
|
||||
while (text_length > 0 && text_area[text_length - 1] == '\n') {
|
||||
text_length--;
|
||||
}
|
||||
|
||||
msg->text_length = text_length;
|
||||
msg->text_length = trim_trailing_newlines(text_area, text_length);
|
||||
// Complete the send operation with the acquired memory
|
||||
result = xRingbufferSendComplete(this->handle_(), acquired_memory);
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/ringbuf.h>
|
||||
|
||||
#include "task_log_message.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
/**
|
||||
@@ -32,19 +34,7 @@ namespace esphome::logger {
|
||||
*/
|
||||
class TaskLogBuffer {
|
||||
public:
|
||||
// Structure for a log message header (text data follows immediately after)
|
||||
struct LogMessage {
|
||||
const char *tag; // We store the pointer, assuming tags are static
|
||||
char thread_name[16]; // Store thread name directly (only used for non-main threads)
|
||||
uint16_t text_length; // Length of the message text (up to ~64KB)
|
||||
uint16_t line; // Source code line number
|
||||
uint8_t level; // Log level (0-7)
|
||||
|
||||
// Methods for accessing message contents
|
||||
inline char *text_data() { return reinterpret_cast<char *>(this) + sizeof(LogMessage); }
|
||||
|
||||
inline const char *text_data() const { return reinterpret_cast<const char *>(this) + sizeof(LogMessage); }
|
||||
};
|
||||
using LogMessage = TaskLogMessage;
|
||||
|
||||
TaskLogBuffer();
|
||||
// No destructor: Logger is never destroyed
|
||||
|
||||
@@ -71,18 +71,8 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
|
||||
|
||||
LogMessage &msg = this->slots_[slot_index];
|
||||
|
||||
// Fill in the message header
|
||||
msg.level = level;
|
||||
msg.tag = tag;
|
||||
msg.line = line;
|
||||
|
||||
// Store the thread name now to avoid crashes if thread exits before processing
|
||||
if (thread_name != nullptr) {
|
||||
strncpy(msg.thread_name, thread_name, sizeof(msg.thread_name) - 1);
|
||||
msg.thread_name[sizeof(msg.thread_name) - 1] = '\0';
|
||||
} else {
|
||||
msg.thread_name[0] = '\0';
|
||||
}
|
||||
// Copied now; the thread may exit before processing
|
||||
fill_task_log_header(&msg, level, tag, line, thread_name);
|
||||
|
||||
// Format the message text
|
||||
int ret = vsnprintf(msg.text, sizeof(msg.text), format, args);
|
||||
@@ -94,10 +84,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
|
||||
msg.text_length = static_cast<uint16_t>(std::min(static_cast<size_t>(ret), sizeof(msg.text) - 1));
|
||||
}
|
||||
|
||||
// Remove trailing newlines
|
||||
while (msg.text_length > 0 && msg.text[msg.text_length - 1] == '\n') {
|
||||
msg.text_length--;
|
||||
}
|
||||
msg.text_length = trim_trailing_newlines(msg.text, msg.text_length);
|
||||
msg.text[msg.text_length] = '\0';
|
||||
|
||||
// Commit the slot
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <cstring>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "task_log_message.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,13 +13,6 @@ TaskLogBuffer::TaskLogBuffer() {
|
||||
this->mutex_ = xSemaphoreCreateMutex();
|
||||
}
|
||||
|
||||
TaskLogBuffer::~TaskLogBuffer() {
|
||||
if (this->mutex_ != nullptr) {
|
||||
vSemaphoreDelete(this->mutex_);
|
||||
this->mutex_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
size_t TaskLogBuffer::available_contiguous_space_() const {
|
||||
if (this->head_ >= this->tail_) {
|
||||
// head is ahead of or equal to tail
|
||||
@@ -85,19 +78,15 @@ void TaskLogBuffer::release_message_main_loop() {
|
||||
|
||||
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)
|
||||
// Measure on a copy; the caller's va_list formats the text later
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
int ret = vsnprintf(nullptr, 0, format, args_copy);
|
||||
int ret = task_log_cap_length(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<size_t>(ret) > MAX_TEXT_SIZE) ? MAX_TEXT_SIZE : ret;
|
||||
uint16_t text_length = ret;
|
||||
|
||||
// Calculate total size needed (header + text length + null terminator)
|
||||
size_t total_size = message_total_size(text_length);
|
||||
@@ -128,17 +117,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
|
||||
|
||||
// Write message header
|
||||
LogMessage *msg = reinterpret_cast<LogMessage *>(this->storage_ + write_pos);
|
||||
msg->level = level;
|
||||
msg->tag = tag;
|
||||
msg->line = line;
|
||||
|
||||
// Store the thread name now to avoid crashes if task is deleted before processing
|
||||
if (thread_name != nullptr) {
|
||||
strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1);
|
||||
msg->thread_name[sizeof(msg->thread_name) - 1] = '\0';
|
||||
} else {
|
||||
msg->thread_name[0] = '\0';
|
||||
}
|
||||
fill_task_log_header(msg, level, tag, line, thread_name);
|
||||
|
||||
// Format the message text directly into the buffer
|
||||
char *text_area = msg->text_data();
|
||||
@@ -150,9 +129,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
|
||||
}
|
||||
|
||||
// Trim before storing so the next message reuses the freed bytes
|
||||
while (text_length > 0 && text_area[text_length - 1] == '\n') {
|
||||
text_length--;
|
||||
}
|
||||
text_length = trim_trailing_newlines(text_area, text_length);
|
||||
text_area[text_length] = '\0';
|
||||
msg->text_length = text_length;
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <semphr.h>
|
||||
#include <task.h>
|
||||
|
||||
#include "task_log_message.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
/**
|
||||
@@ -42,25 +44,14 @@ namespace esphome::logger {
|
||||
*/
|
||||
class TaskLogBuffer {
|
||||
public:
|
||||
// Structure for a log message header (text data follows immediately after)
|
||||
struct LogMessage {
|
||||
const char *tag; // We store the pointer, assuming tags are static
|
||||
char thread_name[16]; // Store thread name directly (only used for non-main threads)
|
||||
uint16_t text_length; // Length of the message text (up to ~64KB)
|
||||
uint16_t line; // Source code line number
|
||||
uint8_t level; // Log level (0-7)
|
||||
|
||||
// Methods for accessing message contents
|
||||
inline char *text_data() { return reinterpret_cast<char *>(this) + sizeof(LogMessage); }
|
||||
inline const char *text_data() const { return reinterpret_cast<const char *>(this) + sizeof(LogMessage); }
|
||||
};
|
||||
using LogMessage = TaskLogMessage;
|
||||
|
||||
// Padding marker level to indicate wrap-around point (stored in LogMessage.level field)
|
||||
// Valid log levels are 0-7, so 0xFF cannot be a real message
|
||||
static constexpr uint8_t PADDING_MARKER_LEVEL = 0xFF;
|
||||
|
||||
TaskLogBuffer();
|
||||
~TaskLogBuffer();
|
||||
// No destructor: Logger is never destroyed
|
||||
|
||||
// NOT thread-safe - borrow a message from the buffer, only call from main loop
|
||||
bool borrow_message_main_loop(LogMessage *&message, uint16_t &text_length);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
__thread bool non_main_task_recursion_guard_; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
__thread bool non_main_task_recursion_guard; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
|
||||
@@ -29,29 +29,21 @@ TaskLogBuffer::TaskLogBuffer() {
|
||||
|
||||
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)
|
||||
// Measure on a copy; the caller's va_list formats the text later
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
int ret = vsnprintf(nullptr, 0, format, args_copy);
|
||||
int ret = task_log_cap_length(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<size_t>(ret) > MAX_TEXT_SIZE) ? MAX_TEXT_SIZE : ret;
|
||||
uint16_t text_length = ret;
|
||||
size_t total_size = total_size_in_32bit_words(text_length);
|
||||
auto *msg = reinterpret_cast<LogMessage *>(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
|
||||
fill_task_log_header(msg, level, tag, line, thread_name);
|
||||
|
||||
// Format the message text directly into the acquired memory
|
||||
// We add 1 to text_length to ensure space for null terminator during formatting
|
||||
@@ -90,11 +82,7 @@ bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &tex
|
||||
// we claimed buffer already, const_cast is safe here
|
||||
message = const_cast<LogMessage *>(reinterpret_cast<const LogMessage *>(this->current_token_));
|
||||
|
||||
text_length = message->text_length;
|
||||
// Remove trailing newlines
|
||||
while (text_length > 0 && message->text_data()[text_length - 1] == '\n') {
|
||||
text_length--;
|
||||
}
|
||||
text_length = trim_trailing_newlines(message->text_data(), message->text_length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <zephyr/sys/mpsc_pbuf.h>
|
||||
|
||||
#include "task_log_message.h"
|
||||
|
||||
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)
|
||||
extern __thread bool non_main_task_recursion_guard; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
|
||||
@@ -34,7 +36,6 @@ class TaskLogBuffer {
|
||||
inline char *text_data() { return reinterpret_cast<char *>(this) + sizeof(LogMessage); }
|
||||
};
|
||||
TaskLogBuffer();
|
||||
~TaskLogBuffer() = default;
|
||||
|
||||
// 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_); }
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
// Header of a variable-size task log message; the text follows it
|
||||
struct TaskLogMessage {
|
||||
const char *tag; // We store the pointer, assuming tags are static
|
||||
char thread_name[16]; // Copied, the task may be gone before the main loop reads it
|
||||
uint16_t text_length;
|
||||
uint16_t line;
|
||||
uint8_t level;
|
||||
|
||||
inline char *text_data() { return reinterpret_cast<char *>(this) + sizeof(TaskLogMessage); }
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
||||
static constexpr int TASK_LOG_MAX_TEXT_SIZE = 255;
|
||||
|
||||
inline int task_log_cap_length(int ret) { return ret > TASK_LOG_MAX_TEXT_SIZE ? TASK_LOG_MAX_TEXT_SIZE : ret; }
|
||||
#endif
|
||||
|
||||
template<typename Message>
|
||||
inline void fill_task_log_header(Message *msg, uint8_t level, const char *tag, uint16_t line, const char *thread_name) {
|
||||
msg->level = level;
|
||||
msg->tag = tag;
|
||||
msg->line = line;
|
||||
if (thread_name != nullptr) {
|
||||
strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1);
|
||||
msg->thread_name[sizeof(msg->thread_name) - 1] = '\0';
|
||||
} else {
|
||||
msg->thread_name[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
inline uint16_t trim_trailing_newlines(const char *text, uint16_t length) {
|
||||
while (length > 0 && text[length - 1] == '\n')
|
||||
length--;
|
||||
return length;
|
||||
}
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
#endif // USE_ESPHOME_TASK_LOG_BUFFER
|
||||
+25
-3
@@ -151,6 +151,7 @@
|
||||
#define USE_LINE_READER
|
||||
#define USE_LOCK
|
||||
#define USE_LOGGER
|
||||
#define USE_LOGGER_CONSOLE
|
||||
#define USE_LOGGER_LEVEL_LISTENERS
|
||||
#define USE_LOGGER_RUNTIME_TAG_LEVELS
|
||||
#define USE_LVGL
|
||||
@@ -758,16 +759,26 @@
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2)
|
||||
#define USE_LOGGER_USB_CDC
|
||||
#define USE_LOGGER_UART_SELECTION_USB_CDC
|
||||
#define ESPHOME_LOGGER_UART_NAME "USB_CDC"
|
||||
#define ESPHOME_LOGGER_UART_SELECTION logger::UART_SELECTION_USB_CDC
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32H21)
|
||||
#define USE_LOGGER_USB_SERIAL_JTAG
|
||||
#define USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG
|
||||
#define ESPHOME_LOGGER_UART_NAME "USB_SERIAL_JTAG"
|
||||
#define ESPHOME_LOGGER_UART_SELECTION logger::UART_SELECTION_USB_SERIAL_JTAG
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32H21) || defined(USE_ESP32_VARIANT_ESP32H4) || defined(USE_ESP32_VARIANT_ESP32P4) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32S31)
|
||||
defined(USE_ESP32_VARIANT_ESP32H4) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S31)
|
||||
#define USE_LOGGER_USB_CDC
|
||||
#define USE_LOGGER_UART_SELECTION_USB_CDC
|
||||
#define USE_LOGGER_USB_SERIAL_JTAG
|
||||
#define USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG
|
||||
#define ESPHOME_LOGGER_UART_NAME "USB_SERIAL_JTAG"
|
||||
#define ESPHOME_LOGGER_UART_SELECTION logger::UART_SELECTION_USB_SERIAL_JTAG
|
||||
#else
|
||||
#define USE_LOGGER_UART_SELECTION_UART0
|
||||
#define ESPHOME_LOGGER_UART_NAME "UART0"
|
||||
#define ESPHOME_LOGGER_UART_SELECTION logger::UART_SELECTION_UART0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -781,6 +792,9 @@
|
||||
#define USE_WIFI_SCAN_RESULTS_LOCK
|
||||
#define USE_ESP8266_LOGGER_SERIAL
|
||||
#define USE_ESP8266_LOGGER_SERIAL1
|
||||
#define USE_LOGGER_UART_SELECTION_UART0
|
||||
#define ESPHOME_LOGGER_UART_NAME "UART0"
|
||||
#define ESPHOME_LOGGER_UART_SELECTION logger::UART_SELECTION_UART0
|
||||
#define USE_ESP8266_PREFERENCES_FLASH
|
||||
#define ESP8266_PREFERENCES_FLASH_WORDS 128
|
||||
#define ESP8266_PREFERENCES_RTC_EBOOT_WORDS 32
|
||||
@@ -826,6 +840,8 @@
|
||||
#define USE_I2C
|
||||
#define USE_LOGGER_USB_CDC
|
||||
#define USE_LOGGER_UART_SELECTION_USB_CDC
|
||||
#define ESPHOME_LOGGER_UART_NAME "USB_CDC"
|
||||
#define ESPHOME_LOGGER_UART_SELECTION logger::UART_SELECTION_USB_CDC
|
||||
#define USE_SOCKET_IMPL_LWIP_TCP
|
||||
#define USE_RP2040_BLE
|
||||
#define USE_RP2_BLE_TRACKER
|
||||
@@ -856,6 +872,9 @@
|
||||
#if defined(USE_BK72XX) || defined(USE_RTL87XX) || defined(USE_LN882X)
|
||||
#define USE_LIBRETINY_CRASH_HANDLER
|
||||
#endif
|
||||
#define USE_LOGGER_UART_SELECTION_DEFAULT
|
||||
#define ESPHOME_LOGGER_UART_NAME "DEFAULT"
|
||||
#define ESPHOME_LOGGER_UART_SELECTION logger::UART_SELECTION_DEFAULT
|
||||
#ifdef USE_BK72XX
|
||||
#define USE_REMOTE_RECEIVER_TIMER_SAMPLING
|
||||
#endif
|
||||
@@ -910,8 +929,11 @@
|
||||
#define ESPHOME_TASK_LOG_BUFFER_SIZE 768
|
||||
#define USE_LOGGER_EARLY_MESSAGE
|
||||
#define USE_LOGGER_UART_SELECTION_USB_CDC
|
||||
#define ESPHOME_LOGGER_UART_NAME "USB_CDC"
|
||||
#define ESPHOME_LOGGER_UART_SELECTION logger::UART_SELECTION_USB_CDC
|
||||
#define USE_LOGGER_USB_CDC
|
||||
#define USE_LOGGER_WAIT_FOR_CDC
|
||||
#define USE_LOGGER_ZEPHYR_CDC_LOOP
|
||||
#define USE_NRF52_DFU
|
||||
#define USE_NRF52_REG0_VOUT 5
|
||||
#define USE_NRF52_UICR_ERASE
|
||||
|
||||
@@ -68,38 +68,6 @@ def test_logger_pre_setup_before_other_components(generate_main):
|
||||
)
|
||||
|
||||
|
||||
def test_default_uart_selection_is_not_emitted(generate_main):
|
||||
"""UART0 is the C++ initializer on ESP8266, so the setter is skipped."""
|
||||
main_cpp = generate_main("tests/component_tests/logger/test_logger.yaml")
|
||||
|
||||
assert "set_uart_selection(" not in main_cpp
|
||||
|
||||
|
||||
def test_custom_uart_selection_is_emitted(generate_main):
|
||||
"""A non default UART still reaches the setter before pre_setup()."""
|
||||
main_cpp = generate_main("tests/component_tests/logger/test_logger_uart1.yaml")
|
||||
|
||||
assert "set_uart_selection(logger::UART_SELECTION_UART1);" in main_cpp
|
||||
|
||||
|
||||
def test_libretiny_default_uart_selection_is_not_emitted(generate_main):
|
||||
"""DEFAULT is the C++ initializer on LibreTiny, so the setter is skipped."""
|
||||
main_cpp = generate_main(
|
||||
"tests/component_tests/logger/test_logger_libretiny_default.yaml"
|
||||
)
|
||||
|
||||
assert "set_uart_selection(" not in main_cpp
|
||||
|
||||
|
||||
def test_libretiny_uart0_is_emitted(generate_main):
|
||||
"""UART0 is not the LibreTiny initializer, so it must still be set."""
|
||||
main_cpp = generate_main(
|
||||
"tests/component_tests/logger/test_logger_libretiny_uart0.yaml"
|
||||
)
|
||||
|
||||
assert "set_uart_selection(logger::UART_SELECTION_UART0);" in main_cpp
|
||||
|
||||
|
||||
def test_ram_log_strings_is_deprecated(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
@@ -168,23 +136,108 @@ def test_log_v2_only_on_native_toolchain(
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config", "usb_cdc"),
|
||||
("config", "port", "name", "zephyr_cdc_loop"),
|
||||
[
|
||||
pytest.param("test_logger_rp2_usb_cdc.yaml", True, id="usb_cdc"),
|
||||
pytest.param("test_logger_rp2_uart0.yaml", False, id="uart0"),
|
||||
pytest.param("test_logger.yaml", "UART0", "UART0", False, id="esp8266_default"),
|
||||
pytest.param(
|
||||
"test_logger_uart1.yaml", "UART1", "UART1", False, id="esp8266_uart1"
|
||||
),
|
||||
pytest.param(
|
||||
"test_logger_uart0_swap.yaml",
|
||||
"UART0_SWAP",
|
||||
"UART0_SWAP",
|
||||
False,
|
||||
id="esp8266_uart0_swap",
|
||||
),
|
||||
# ESP8266 without serial logging never opens a port
|
||||
pytest.param(
|
||||
"test_logger_no_serial.yaml", "UART0", "NONE", False, id="esp8266_no_serial"
|
||||
),
|
||||
pytest.param(
|
||||
"test_logger_libretiny_default.yaml",
|
||||
"DEFAULT",
|
||||
"DEFAULT",
|
||||
False,
|
||||
id="libretiny_default",
|
||||
),
|
||||
pytest.param(
|
||||
"test_logger_libretiny_uart0.yaml",
|
||||
"UART0",
|
||||
"UART0",
|
||||
False,
|
||||
id="libretiny_uart0",
|
||||
),
|
||||
pytest.param(
|
||||
"test_logger_rp2_usb_cdc.yaml",
|
||||
"USB_CDC",
|
||||
"USB_CDC",
|
||||
False,
|
||||
id="rp2_usb_cdc",
|
||||
),
|
||||
pytest.param(
|
||||
"test_logger_rp2_uart0.yaml", "UART0", "UART0", False, id="rp2_uart0"
|
||||
),
|
||||
pytest.param(
|
||||
"test_logger_esp32_usb_cdc.yaml",
|
||||
"USB_CDC",
|
||||
"USB_CDC",
|
||||
False,
|
||||
id="esp32_usb_cdc",
|
||||
),
|
||||
pytest.param(
|
||||
"test_logger_esp32_usb_serial_jtag.yaml",
|
||||
"USB_SERIAL_JTAG",
|
||||
"USB_SERIAL_JTAG",
|
||||
False,
|
||||
id="esp32_usb_serial_jtag",
|
||||
),
|
||||
pytest.param(
|
||||
"test_logger_nrf52_usb_cdc.yaml",
|
||||
"USB_CDC",
|
||||
"USB_CDC",
|
||||
True,
|
||||
id="nrf52_usb_cdc",
|
||||
),
|
||||
# Zephyr polls the USB port only when it logs there
|
||||
pytest.param(
|
||||
"test_logger_nrf52_usb_cdc_no_serial.yaml",
|
||||
"USB_CDC",
|
||||
"USB_CDC",
|
||||
False,
|
||||
id="nrf52_no_serial",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_rp2_usb_cdc_selection_define(
|
||||
def test_uart_selection_defines(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
config: str,
|
||||
usb_cdc: bool,
|
||||
port: str,
|
||||
name: str,
|
||||
zephyr_cdc_loop: bool,
|
||||
) -> None:
|
||||
"""The USB CDC crash replay only compiles in when the logger uses USB CDC."""
|
||||
"""The chosen port is fixed at build time: one flag, its enum value and its dump_config name."""
|
||||
generate_main(f"tests/component_tests/logger/{config}")
|
||||
|
||||
defines = {d.name: d.value for d in CORE.defines}
|
||||
prefix = "USE_LOGGER_UART_SELECTION_"
|
||||
assert {d.removeprefix(prefix) for d in defines if d.startswith(prefix)} == {port}
|
||||
assert (
|
||||
"USE_LOGGER_UART_SELECTION_USB_CDC" in {d.name for d in CORE.defines}
|
||||
) is usb_cdc
|
||||
str(defines["ESPHOME_LOGGER_UART_SELECTION"])
|
||||
== f"logger::UART_SELECTION_{port}"
|
||||
)
|
||||
assert str(defines["ESPHOME_LOGGER_UART_NAME"]) == f'"{name}"'
|
||||
assert ("USE_LOGGER_ZEPHYR_CDC_LOOP" in defines) is zephyr_cdc_loop
|
||||
|
||||
|
||||
def test_log_listener_slots_count_every_listener(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
) -> None:
|
||||
"""Two on_message triggers and the API each take a slot."""
|
||||
generate_main("tests/component_tests/logger/test_logger_listeners.yaml")
|
||||
|
||||
assert (
|
||||
str({d.name: d.value for d in CORE.defines}["ESPHOME_LOG_MAX_LISTENERS"]) == "3"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
variant: esp32s3
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
logger:
|
||||
hardware_uart: USB_CDC
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
variant: esp32c3
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
logger:
|
||||
hardware_uart: USB_SERIAL_JTAG
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
variant: esp32
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
wifi:
|
||||
ssid: test
|
||||
password: testtesttest
|
||||
|
||||
api:
|
||||
|
||||
logger:
|
||||
on_message:
|
||||
- level: ERROR
|
||||
then:
|
||||
- lambda: "(void) message;"
|
||||
- level: WARN
|
||||
then:
|
||||
- lambda: "(void) message;"
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: d1_mini_lite
|
||||
|
||||
logger:
|
||||
baud_rate: 0
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
nrf52:
|
||||
board: adafruit_itsybitsy_nrf52840
|
||||
bootloader: adafruit_nrf52_sd140_v6
|
||||
|
||||
logger:
|
||||
hardware_uart: USB_CDC
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
nrf52:
|
||||
board: adafruit_itsybitsy_nrf52840
|
||||
bootloader: adafruit_nrf52_sd140_v6
|
||||
|
||||
logger:
|
||||
hardware_uart: USB_CDC
|
||||
baud_rate: 0
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: d1_mini_lite
|
||||
|
||||
logger:
|
||||
hardware_uart: UART0_SWAP
|
||||
@@ -0,0 +1,54 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
// The cap and message header are compiled only for platforms with a task log buffer
|
||||
#define USE_LIBRETINY
|
||||
#ifndef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
#define USE_ESPHOME_TASK_LOG_BUFFER
|
||||
#endif
|
||||
#include "esphome/components/logger/task_log_message.h"
|
||||
#undef USE_LIBRETINY
|
||||
|
||||
namespace esphome::logger::testing {
|
||||
|
||||
TEST(TaskLogMessageTest, CapLengthStopsAt255) {
|
||||
EXPECT_EQ(task_log_cap_length(0), 0);
|
||||
EXPECT_EQ(task_log_cap_length(255), 255);
|
||||
EXPECT_EQ(task_log_cap_length(256), 255);
|
||||
EXPECT_EQ(task_log_cap_length(1000), 255);
|
||||
}
|
||||
|
||||
TEST(TaskLogMessageTest, HeaderCopiesFieldsAndThreadName) {
|
||||
TaskLogMessage msg{};
|
||||
fill_task_log_header(&msg, 3, "tag", 42, "worker");
|
||||
EXPECT_EQ(msg.level, 3);
|
||||
EXPECT_STREQ(msg.tag, "tag");
|
||||
EXPECT_EQ(msg.line, 42);
|
||||
EXPECT_STREQ(msg.thread_name, "worker");
|
||||
}
|
||||
|
||||
TEST(TaskLogMessageTest, HeaderNullThreadNameIsEmpty) {
|
||||
TaskLogMessage msg{};
|
||||
std::memset(msg.thread_name, 'x', sizeof(msg.thread_name));
|
||||
fill_task_log_header(&msg, 1, "tag", 1, nullptr);
|
||||
EXPECT_STREQ(msg.thread_name, "");
|
||||
}
|
||||
|
||||
TEST(TaskLogMessageTest, HeaderTruncatesLongThreadName) {
|
||||
TaskLogMessage msg{};
|
||||
fill_task_log_header(&msg, 1, "tag", 1, "a_thread_name_longer_than_sixteen");
|
||||
EXPECT_EQ(std::strlen(msg.thread_name), sizeof(msg.thread_name) - 1);
|
||||
EXPECT_EQ(std::strncmp(msg.thread_name, "a_thread_name_l", sizeof(msg.thread_name) - 1), 0);
|
||||
}
|
||||
|
||||
TEST(TaskLogMessageTest, TrimTrailingNewlines) {
|
||||
EXPECT_EQ(trim_trailing_newlines("", 0), 0);
|
||||
EXPECT_EQ(trim_trailing_newlines("abc", 3), 3);
|
||||
EXPECT_EQ(trim_trailing_newlines("abc\n\n", 5), 3);
|
||||
EXPECT_EQ(trim_trailing_newlines("\n\n\n", 3), 0);
|
||||
EXPECT_EQ(trim_trailing_newlines("a\nb", 3), 3);
|
||||
}
|
||||
|
||||
} // namespace esphome::logger::testing
|
||||
@@ -17,7 +17,6 @@ void setup() {
|
||||
App.pre_setup(name, sizeof(name) - 1, friendly_name, sizeof(friendly_name) - 1);
|
||||
auto *log = new logger::Logger(115200); // NOLINT
|
||||
log->pre_setup();
|
||||
log->set_uart_selection(logger::UART_SELECTION_UART0);
|
||||
App.register_component_(log);
|
||||
|
||||
auto *wifi = new wifi::WiFiComponent(); // NOLINT
|
||||
|
||||
Reference in new issue
Block a user