mirror of
https://github.com/esphome/esphome.git
synced 2026-11-15 09:36:04 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5829740661 | ||
|
|
fc47adb83f | ||
|
|
ebce86fd9b | ||
|
|
d6a44e1b63 |
No files matched your search
+42
-21
@@ -1,6 +1,7 @@
|
||||
from collections.abc import Awaitable, Callable, Sequence
|
||||
from contextvars import ContextVar
|
||||
from dataclasses import dataclass, field
|
||||
from functools import partial
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
@@ -1151,9 +1152,6 @@ IfAction = cg.esphome_ns.class_("IfAction", Action)
|
||||
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
|
||||
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
|
||||
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
|
||||
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
|
||||
SuspendComponentAction = cg.esphome_ns.class_("SuspendComponentAction", Action)
|
||||
ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action)
|
||||
Automation = cg.esphome_ns.class_("Automation")
|
||||
TriggerForwarder = cg.esphome_ns.class_("TriggerForwarder")
|
||||
TriggerOnTrueForwarder = cg.esphome_ns.class_("TriggerOnTrueForwarder")
|
||||
@@ -1771,25 +1769,44 @@ async def lambda_action_to_code(
|
||||
return new_lambda_pvariable(action_id, lambda_, StatelessLambdaAction, template_arg)
|
||||
|
||||
|
||||
register_simple_action(
|
||||
"component.update",
|
||||
UpdateComponentAction,
|
||||
maybe_simple_id({cv.Required(CONF_ID): cv.use_id(cg.PollingComponent)}),
|
||||
synchronous=True,
|
||||
_POLLING_COMPONENT_SCHEMA = maybe_simple_id(
|
||||
{cv.Required(CONF_ID): cv.use_id(cg.PollingComponent)}
|
||||
)
|
||||
|
||||
|
||||
register_simple_action(
|
||||
"component.suspend",
|
||||
SuspendComponentAction,
|
||||
maybe_simple_id({cv.Required(CONF_ID): cv.use_id(cg.PollingComponent)}),
|
||||
synchronous=True,
|
||||
)
|
||||
async def _polling_component_action(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
calls: list[str],
|
||||
) -> MockObj:
|
||||
"""Run ``calls`` on the component once it is ready, through ``PollingComponent``.
|
||||
|
||||
An if block, not an early return, so actions merged after this one still run.
|
||||
"""
|
||||
parent = await _apply_parent(config)
|
||||
statements = [
|
||||
f"{cg.PollingComponent} &esphome__component = *{parent};",
|
||||
"if (esphome__component.is_ready()) {",
|
||||
*(f" esphome__component.{call};" for call in calls),
|
||||
"}",
|
||||
]
|
||||
return apply_statements_action(action_id, template_arg, args, statements)
|
||||
|
||||
|
||||
for _name, _call in (
|
||||
("component.update", "update()"),
|
||||
("component.suspend", "stop_poller()"),
|
||||
):
|
||||
register_action(_name, ApplyAction, _POLLING_COMPONENT_SCHEMA, synchronous=True)(
|
||||
partial(_polling_component_action, calls=[_call])
|
||||
)
|
||||
|
||||
|
||||
@register_action(
|
||||
"component.resume",
|
||||
ResumeComponentAction,
|
||||
ApplyAction,
|
||||
maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
|
||||
@@ -1806,12 +1823,16 @@ async def component_resume_action_to_code(
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
comp = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, comp)
|
||||
if CONF_UPDATE_INTERVAL in config:
|
||||
template_ = await cg.templatable(config[CONF_UPDATE_INTERVAL], args, cg.uint32)
|
||||
cg.add(var.set_update_interval(template_))
|
||||
return var
|
||||
calls: list[str] = []
|
||||
if (interval := config.get(CONF_UPDATE_INTERVAL)) is not None:
|
||||
if isinstance(interval, Lambda):
|
||||
# Built with the trigger's own argument types and always called, so the body
|
||||
# may modify its by-value arguments
|
||||
lamb = await cg.process_lambda(interval, args, return_type=cg.uint32)
|
||||
interval = CallExpression(lamb, *(MockObj(name) for _, name in args))
|
||||
calls.append(f"set_update_interval({cg.safe_exp(interval)})")
|
||||
calls.append("start_poller()")
|
||||
return await _polling_component_action(config, action_id, template_arg, args, calls)
|
||||
|
||||
|
||||
async def build_action(
|
||||
|
||||
@@ -37,7 +37,6 @@ 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,
|
||||
@@ -94,7 +93,6 @@ 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,
|
||||
@@ -110,6 +108,8 @@ 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,10 +503,13 @@ 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)
|
||||
# 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)
|
||||
)
|
||||
# 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
|
||||
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)
|
||||
@@ -539,17 +542,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())
|
||||
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,
|
||||
)
|
||||
# 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]))
|
||||
# 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())
|
||||
@@ -644,8 +647,10 @@ 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.
|
||||
@@ -662,13 +667,8 @@ 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")
|
||||
# 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")
|
||||
if CORE.is_rp2 and config[CONF_HARDWARE_UART] == USB_CDC:
|
||||
cg.add_define("USE_LOGGER_UART_SELECTION_USB_CDC")
|
||||
# Define platform support flags for components that need auto-detection
|
||||
try:
|
||||
uart_selection(USB_SERIAL_JTAG)
|
||||
@@ -718,6 +718,7 @@ 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
|
||||
@@ -738,6 +739,8 @@ 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
|
||||
@@ -872,6 +875,11 @@ 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.
|
||||
@@ -880,18 +888,29 @@ def request_logger_level_listeners() -> None:
|
||||
function during their code generation. This enables the add_level_listener()
|
||||
method and compiles in the listener vector.
|
||||
"""
|
||||
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")
|
||||
CORE.data.setdefault(DOMAIN, {})[KEY_LEVEL_LISTENERS] = True
|
||||
|
||||
|
||||
def request_log_listener() -> None:
|
||||
"""Reserve a log listener slot; call once per listener from to_code."""
|
||||
cg.add_define("USE_LOG_LISTENERS")
|
||||
_request_log_listener_slot()
|
||||
"""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)
|
||||
@@ -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 defined(USE_ZEPHYR)
|
||||
#elif (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 = this->get_thread_name_(current_task);
|
||||
const char *thread_name = get_thread_name_(current_task);
|
||||
#elif defined(USE_ZEPHYR)
|
||||
char thread_name_buf[MAX_POINTER_REPRESENTATION];
|
||||
const char *thread_name = this->get_thread_name_(thread_name_buf, current_task);
|
||||
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);
|
||||
@@ -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 constexpr size_t MAX_CONSOLE_LOG_MSG_SIZE = 512;
|
||||
static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 512;
|
||||
#else
|
||||
// Maximum size for console log messages (includes null terminator)
|
||||
static constexpr size_t MAX_CONSOLE_LOG_MSG_SIZE = 144;
|
||||
static const 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,11 +118,13 @@ 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: the recursion guard is a plain bool and tx_buffer_ has no locking.
|
||||
// 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) || this->main_task_recursion_guard_)
|
||||
if (level > this->level_for(tag) || global_recursion_guard_)
|
||||
return;
|
||||
this->log_message_to_buffer_and_send_(this->main_task_recursion_guard_, level, tag, line, format, args, 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 // USE_ESP32 || USE_HOST || USE_LIBRETINY || USE_ZEPHYR
|
||||
|
||||
@@ -134,10 +136,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) || this->main_task_recursion_guard_)
|
||||
if (level > this->level_for(tag) || global_recursion_guard_)
|
||||
return;
|
||||
|
||||
this->log_message_to_buffer_and_send_(this->main_task_recursion_guard_, level, tag, line, format, args, nullptr);
|
||||
this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args, nullptr);
|
||||
}
|
||||
#endif // USE_ESP8266
|
||||
|
||||
@@ -182,10 +184,10 @@ Logger::Logger(uint32_t baud_rate) : baud_rate_(baud_rate) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || defined(USE_LOGGER_ZEPHYR_CDC_LOOP)
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||
void Logger::loop() {
|
||||
this->process_messages_();
|
||||
#ifdef USE_LOGGER_ZEPHYR_CDC_LOOP
|
||||
#if defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC)
|
||||
this->cdc_loop_();
|
||||
#endif
|
||||
}
|
||||
@@ -211,11 +213,11 @@ void Logger::process_messages_() {
|
||||
this->write_log_buffer_to_console_(buf);
|
||||
}
|
||||
}
|
||||
// Zephyr with USB CDC keeps the loop running for cdc_loop_()
|
||||
#ifndef USE_LOGGER_ZEPHYR_CDC_LOOP
|
||||
#if !(defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||
else {
|
||||
// A concurrent enable_loop_soon_any_context() still wakes the next iteration
|
||||
this->disable_loop();
|
||||
// No messages to process, disable loop if appropriate
|
||||
// This reduces overhead when there's no async logging activity
|
||||
this->disable_loop_when_buffer_empty_();
|
||||
}
|
||||
#endif
|
||||
#endif // USE_ESPHOME_TASK_LOG_BUFFER
|
||||
@@ -263,7 +265,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_task_recursion_guard_;
|
||||
bool &recursion_guard = this->main_loop_recursion_guard_();
|
||||
if (recursion_guard)
|
||||
return;
|
||||
this->boot_log_done_ = true; // Stops capture first, so the replay output is not buffered again
|
||||
@@ -305,25 +307,6 @@ 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"
|
||||
@@ -359,7 +342,7 @@ void Logger::dump_config() {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ZEPHYR
|
||||
this->dump_crash_();
|
||||
dump_crash_();
|
||||
#ifdef USE_BOOTLOADER_ADAFRUIT
|
||||
this->dump_bootloader_();
|
||||
#endif
|
||||
|
||||
@@ -42,6 +42,9 @@ 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():
|
||||
@@ -70,7 +73,7 @@ struct LogCallback {
|
||||
* public:
|
||||
* void setup() override {
|
||||
* if (logger::global_logger != nullptr)
|
||||
* logger::global_logger->add_level_listener(this);
|
||||
* logger::global_logger->add_logger_level_listener(this);
|
||||
* }
|
||||
* void on_log_level_change(uint8_t level) override {
|
||||
* // Handle log level change
|
||||
@@ -124,9 +127,6 @@ 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 @@ static constexpr UARTSelection CONFIGURED_UART_SELECTION = ESPHOME_LOGGER_UART_S
|
||||
* - 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 single-task platforms: Uses the main task guard alone
|
||||
* - On other platforms: Uses a simple global recursion guard
|
||||
*
|
||||
* We use pthread TLS via pthread_key_create to create a unique key for storing
|
||||
* task-specific recursion state, which:
|
||||
@@ -149,7 +149,7 @@ static constexpr UARTSelection CONFIGURED_UART_SELECTION = ESPHOME_LOGGER_UART_S
|
||||
class Logger final : public Component {
|
||||
public:
|
||||
explicit Logger(uint32_t baud_rate);
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || defined(USE_LOGGER_ZEPHYR_CDC_LOOP)
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||
void loop() override;
|
||||
#endif
|
||||
#if defined(USE_RP2_CRASH_HANDLER) && defined(USE_LOGGER_UART_SELECTION_USB_CDC)
|
||||
@@ -157,52 +157,21 @@ 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 this->baud_rate_; }
|
||||
uint32_t get_baud_rate() const { return baud_rate_; }
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
|
||||
Stream *get_hw_serial() const { return this->hw_serial_; }
|
||||
Stream *get_hw_serial() const { return hw_serial_; }
|
||||
#endif
|
||||
#ifdef USE_ESP32
|
||||
/// 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);
|
||||
uart_port_t get_uart_num() const { return uart_num_; }
|
||||
void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); }
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#if defined(USE_ESP32) || defined(USE_HOST)
|
||||
void create_pthread_key() { pthread_key_create(&this->log_recursion_key_, nullptr); }
|
||||
#ifdef USE_HOST
|
||||
void create_pthread_key() { pthread_key_create(&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.
|
||||
#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
|
||||
UARTSelection get_uart() const { return this->uart_; }
|
||||
#endif
|
||||
|
||||
/// Set the default log level for this logger.
|
||||
@@ -273,17 +242,14 @@ 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
|
||||
#ifdef USE_LOGGER_ZEPHYR_CDC_LOOP
|
||||
#if defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC)
|
||||
void cdc_loop_();
|
||||
#endif
|
||||
void process_messages_();
|
||||
// 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
|
||||
#if defined(USE_HOST) || defined(USE_ZEPHYR)
|
||||
void write_msg_(const char *msg, uint16_t len);
|
||||
#else
|
||||
inline void write_msg_(const char *msg, uint16_t len); // Defined in platform-specific logger_*.h
|
||||
#endif
|
||||
|
||||
// Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator
|
||||
@@ -351,6 +317,7 @@ 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) {
|
||||
@@ -392,8 +359,15 @@ class Logger final : public Component {
|
||||
#ifdef USE_HOST
|
||||
pthread_t main_thread_{}; // Main thread for pthread_equal() comparison
|
||||
#endif
|
||||
#if defined(USE_ESP32) || defined(USE_HOST)
|
||||
// Non-main task recursion guard (pthread TLS)
|
||||
#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
|
||||
pthread_key_t log_recursion_key_;
|
||||
#endif
|
||||
|
||||
@@ -418,10 +392,23 @@ 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};
|
||||
bool main_task_recursion_guard_{false}; // Guards tx_buffer_ on the main loop
|
||||
#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};
|
||||
#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
|
||||
@@ -490,9 +477,9 @@ class Logger final : public Component {
|
||||
class NonMainTaskRecursionGuard {
|
||||
public:
|
||||
explicit NonMainTaskRecursionGuard(pthread_key_t key) : key_(key) {
|
||||
pthread_setspecific(this->key_, reinterpret_cast<void *>(1));
|
||||
pthread_setspecific(key_, reinterpret_cast<void *>(1));
|
||||
}
|
||||
~NonMainTaskRecursionGuard() { pthread_setspecific(this->key_, nullptr); }
|
||||
~NonMainTaskRecursionGuard() { pthread_setspecific(key_, nullptr); }
|
||||
NonMainTaskRecursionGuard(const NonMainTaskRecursionGuard &) = delete;
|
||||
NonMainTaskRecursionGuard &operator=(const NonMainTaskRecursionGuard &) = delete;
|
||||
NonMainTaskRecursionGuard(NonMainTaskRecursionGuard &&) = delete;
|
||||
@@ -503,24 +490,40 @@ 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(this->log_recursion_key_) != nullptr;
|
||||
}
|
||||
inline bool HOT is_non_main_task_recursive_() const { return pthread_getspecific(log_recursion_key_) != nullptr; }
|
||||
|
||||
// Create RAII guard for non-main task recursion
|
||||
inline NonMainTaskRecursionGuard make_non_main_task_guard_() {
|
||||
return NonMainTaskRecursionGuard(this->log_recursion_key_);
|
||||
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();
|
||||
}
|
||||
|
||||
#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)
|
||||
@@ -560,7 +563,14 @@ void log_crash_to_client();
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
// ESP32 inline write_msg_(), included after the Logger class definition is complete
|
||||
#ifdef USE_ESP32
|
||||
// Platform-specific inline implementations of write_msg_()
|
||||
// Must be included after the Logger class definition is complete
|
||||
#if defined(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,9 +17,6 @@
|
||||
#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"
|
||||
@@ -76,8 +73,7 @@ static void init_usb_serial_jtag() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#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) {
|
||||
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;
|
||||
@@ -101,22 +97,37 @@ static void init_uart(uart_port_t uart_num, uint32_t baud_rate, int tx_buffer_si
|
||||
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) {
|
||||
#ifdef USE_LOGGER_UART_SELECTION_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);
|
||||
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();
|
||||
#endif
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
global_logger = this;
|
||||
@@ -128,36 +139,29 @@ void Logger::pre_setup() {
|
||||
#endif
|
||||
}
|
||||
|
||||
#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);
|
||||
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");
|
||||
#endif
|
||||
#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);
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
return LOG_STR("USB_CDC");
|
||||
#endif
|
||||
return written > 0 ? written : 0;
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
case UART_SELECTION_USB_SERIAL_JTAG:
|
||||
return LOG_STR("USB_SERIAL_JTAG");
|
||||
#endif
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
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->get_uart_num(), msg, len);
|
||||
uart_write_bytes(this->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_);
|
||||
#ifdef USE_LOGGER_UART_SELECTION_UART0_SWAP
|
||||
Serial.swap();
|
||||
#endif
|
||||
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
||||
Serial.swap();
|
||||
}
|
||||
Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
#elif defined(USE_ESP8266_LOGGER_SERIAL1)
|
||||
this->hw_serial_ = &Serial1;
|
||||
@@ -35,6 +35,19 @@ 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(); }
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#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,28 +12,45 @@ ESPHOME_LOG_TAG(TAG, "logger");
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
#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.");
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// change lt_log() port to match default Serial
|
||||
lt_log_set_port(this->get_uart() - 1);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
global_logger = this;
|
||||
@@ -43,6 +60,20 @@ 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(); }
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#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,16 +26,20 @@ void Logger::setup() {
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
#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
|
||||
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;
|
||||
}
|
||||
}
|
||||
global_logger = this;
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
@@ -45,6 +49,21 @@ 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(); }
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#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_ZEPHYR_CDC_LOOP
|
||||
#ifdef USE_LOGGER_UART_SELECTION_USB_CDC
|
||||
void Logger::cdc_loop_() {
|
||||
if (this->uart_dev_ == nullptr) {
|
||||
if (this->uart_ != UART_SELECTION_USB_CDC || this->uart_dev_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
static bool opened = false;
|
||||
@@ -74,20 +74,26 @@ void Logger::cdc_loop_() {
|
||||
void Logger::pre_setup() {
|
||||
#ifdef CONFIG_SERIAL
|
||||
if (this->baud_rate_ > 0) {
|
||||
const struct device *uart_dev = nullptr;
|
||||
#if defined(USE_LOGGER_UART_SELECTION_USB_CDC)
|
||||
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:
|
||||
#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();
|
||||
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
|
||||
}
|
||||
#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)
|
||||
@@ -108,10 +114,14 @@ 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);
|
||||
this->dump_crash_();
|
||||
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 defined(USE_CRASH_HANDLER) && !defined(USE_LOGGER_ZEPHYR_CDC_LOOP)
|
||||
if (this->uart_ == UART_SELECTION_USB_CDC)
|
||||
return;
|
||||
#endif
|
||||
// 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();
|
||||
@@ -133,7 +143,22 @@ void HOT Logger::write_msg_(const char *msg, uint16_t len) {
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint8_t REASON_BUF_SIZE = 32;
|
||||
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 const char *reason_to_str(unsigned int reason, char *buf) {
|
||||
switch (reason) {
|
||||
|
||||
@@ -44,15 +44,19 @@ 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) {
|
||||
// Measure on a copy; the caller's va_list formats the text later
|
||||
// First, calculate the exact length needed using a null buffer (no actual writing)
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
int ret = task_log_cap_length(vsnprintf(nullptr, 0, format, args_copy));
|
||||
int ret = vsnprintf(nullptr, 0, format, args_copy);
|
||||
va_end(args_copy);
|
||||
|
||||
if (ret <= 0) {
|
||||
return false; // Formatting error or empty message
|
||||
}
|
||||
uint16_t text_length = ret;
|
||||
|
||||
// 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;
|
||||
|
||||
// Calculate total size needed (header + text length + null terminator)
|
||||
size_t total_size = sizeof(LogMessage) + text_length + 1;
|
||||
@@ -65,10 +69,24 @@ 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);
|
||||
fill_task_log_header(msg, level, tag, line, thread_name);
|
||||
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
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -78,7 +96,12 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
|
||||
return false;
|
||||
}
|
||||
|
||||
msg->text_length = trim_trailing_newlines(text_area, text_length);
|
||||
// Remove trailing newlines
|
||||
while (text_length > 0 && text_area[text_length - 1] == '\n') {
|
||||
text_length--;
|
||||
}
|
||||
|
||||
msg->text_length = text_length;
|
||||
// Complete the send operation with the acquired memory
|
||||
result = xRingbufferSendComplete(this->handle_(), acquired_memory);
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/ringbuf.h>
|
||||
|
||||
#include "task_log_message.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
/**
|
||||
@@ -34,7 +32,19 @@ namespace esphome::logger {
|
||||
*/
|
||||
class TaskLogBuffer {
|
||||
public:
|
||||
using LogMessage = TaskLogMessage;
|
||||
// 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); }
|
||||
};
|
||||
|
||||
TaskLogBuffer();
|
||||
// No destructor: Logger is never destroyed
|
||||
|
||||
@@ -71,8 +71,18 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
|
||||
|
||||
LogMessage &msg = this->slots_[slot_index];
|
||||
|
||||
// Copied now; the thread may exit before processing
|
||||
fill_task_log_header(&msg, level, tag, line, thread_name);
|
||||
// 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';
|
||||
}
|
||||
|
||||
// Format the message text
|
||||
int ret = vsnprintf(msg.text, sizeof(msg.text), format, args);
|
||||
@@ -84,7 +94,10 @@ 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));
|
||||
}
|
||||
|
||||
msg.text_length = trim_trailing_newlines(msg.text, msg.text_length);
|
||||
// Remove trailing newlines
|
||||
while (msg.text_length > 0 && msg.text[msg.text_length - 1] == '\n') {
|
||||
msg.text_length--;
|
||||
}
|
||||
msg.text[msg.text_length] = '\0';
|
||||
|
||||
// Commit the slot
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
#include <cstring>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "task_log_message.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,13 @@ 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
|
||||
@@ -78,15 +85,19 @@ 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) {
|
||||
// Measure on a copy; the caller's va_list formats the text later
|
||||
// First, calculate the exact length needed using a null buffer (no actual writing)
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
int ret = task_log_cap_length(vsnprintf(nullptr, 0, format, args_copy));
|
||||
int ret = vsnprintf(nullptr, 0, format, args_copy);
|
||||
va_end(args_copy);
|
||||
|
||||
if (ret <= 0) {
|
||||
return false; // Formatting error or empty message
|
||||
}
|
||||
uint16_t text_length = ret;
|
||||
|
||||
// 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;
|
||||
|
||||
// Calculate total size needed (header + text length + null terminator)
|
||||
size_t total_size = message_total_size(text_length);
|
||||
@@ -117,7 +128,17 @@ 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);
|
||||
fill_task_log_header(msg, level, tag, line, thread_name);
|
||||
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';
|
||||
}
|
||||
|
||||
// Format the message text directly into the buffer
|
||||
char *text_area = msg->text_data();
|
||||
@@ -129,7 +150,9 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
|
||||
}
|
||||
|
||||
// Trim before storing so the next message reuses the freed bytes
|
||||
text_length = trim_trailing_newlines(text_area, text_length);
|
||||
while (text_length > 0 && text_area[text_length - 1] == '\n') {
|
||||
text_length--;
|
||||
}
|
||||
text_area[text_length] = '\0';
|
||||
msg->text_length = text_length;
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
#include <semphr.h>
|
||||
#include <task.h>
|
||||
|
||||
#include "task_log_message.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
/**
|
||||
@@ -44,14 +42,25 @@ namespace esphome::logger {
|
||||
*/
|
||||
class TaskLogBuffer {
|
||||
public:
|
||||
using LogMessage = TaskLogMessage;
|
||||
// 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); }
|
||||
};
|
||||
|
||||
// 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();
|
||||
// No destructor: Logger is never destroyed
|
||||
~TaskLogBuffer();
|
||||
|
||||
// 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,21 +29,29 @@ 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) {
|
||||
// Measure on a copy; the caller's va_list formats the text later
|
||||
// First, calculate the exact length needed using a null buffer (no actual writing)
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
int ret = task_log_cap_length(vsnprintf(nullptr, 0, format, args_copy));
|
||||
int ret = vsnprintf(nullptr, 0, format, args_copy);
|
||||
va_end(args_copy);
|
||||
|
||||
if (ret <= 0) {
|
||||
return false; // Formatting error or empty message
|
||||
}
|
||||
uint16_t text_length = ret;
|
||||
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
fill_task_log_header(msg, level, tag, line, thread_name);
|
||||
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
|
||||
@@ -82,7 +90,11 @@ 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 = trim_trailing_newlines(message->text_data(), message->text_length);
|
||||
text_length = message->text_length;
|
||||
// Remove trailing newlines
|
||||
while (text_length > 0 && message->text_data()[text_length - 1] == '\n') {
|
||||
text_length--;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,14 +6,12 @@
|
||||
#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
|
||||
|
||||
@@ -36,6 +34,7 @@ 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_); }
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
#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
|
||||
@@ -595,52 +595,4 @@ template<typename... Ts> class WaitUntilAction : public Action<Ts...>, public Co
|
||||
uint8_t stop_generation_{0};
|
||||
};
|
||||
|
||||
template<typename... Ts> class UpdateComponentAction : public Action<Ts...> {
|
||||
public:
|
||||
UpdateComponentAction(PollingComponent *component) : component_(component) {}
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
if (!this->component_->is_ready())
|
||||
return;
|
||||
this->component_->update();
|
||||
}
|
||||
|
||||
protected:
|
||||
PollingComponent *component_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SuspendComponentAction : public Action<Ts...> {
|
||||
public:
|
||||
SuspendComponentAction(PollingComponent *component) : component_(component) {}
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
if (!this->component_->is_ready())
|
||||
return;
|
||||
this->component_->stop_poller();
|
||||
}
|
||||
|
||||
protected:
|
||||
PollingComponent *component_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ResumeComponentAction : public Action<Ts...> {
|
||||
public:
|
||||
ResumeComponentAction(PollingComponent *component) : component_(component) {}
|
||||
TEMPLATABLE_VALUE(uint32_t, update_interval)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
if (!this->component_->is_ready()) {
|
||||
return;
|
||||
}
|
||||
optional<uint32_t> update_interval = this->update_interval_.optional_value(x...);
|
||||
if (update_interval.has_value()) {
|
||||
this->component_->set_update_interval(update_interval.value());
|
||||
}
|
||||
this->component_->start_poller();
|
||||
}
|
||||
|
||||
protected:
|
||||
PollingComponent *component_;
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
+3
-25
@@ -151,7 +151,6 @@
|
||||
#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
|
||||
@@ -759,26 +758,16 @@
|
||||
#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_ESP32H4) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S31)
|
||||
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)
|
||||
#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
|
||||
|
||||
@@ -792,9 +781,6 @@
|
||||
#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
|
||||
@@ -840,8 +826,6 @@
|
||||
#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
|
||||
@@ -872,9 +856,6 @@
|
||||
#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
|
||||
@@ -929,11 +910,8 @@
|
||||
#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,6 +68,38 @@ 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,
|
||||
@@ -136,108 +168,23 @@ def test_log_v2_only_on_native_toolchain(
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config", "port", "name", "zephyr_cdc_loop"),
|
||||
("config", "usb_cdc"),
|
||||
[
|
||||
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",
|
||||
),
|
||||
pytest.param("test_logger_rp2_usb_cdc.yaml", True, id="usb_cdc"),
|
||||
pytest.param("test_logger_rp2_uart0.yaml", False, id="uart0"),
|
||||
],
|
||||
)
|
||||
def test_uart_selection_defines(
|
||||
def test_rp2_usb_cdc_selection_define(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
config: str,
|
||||
port: str,
|
||||
name: str,
|
||||
zephyr_cdc_loop: bool,
|
||||
usb_cdc: bool,
|
||||
) -> None:
|
||||
"""The chosen port is fixed at build time: one flag, its enum value and its dump_config name."""
|
||||
"""The USB CDC crash replay only compiles in when the logger uses USB CDC."""
|
||||
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 (
|
||||
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"
|
||||
)
|
||||
"USE_LOGGER_UART_SELECTION_USB_CDC" in {d.name for d in CORE.defines}
|
||||
) is usb_cdc
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
variant: esp32s3
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
logger:
|
||||
hardware_uart: USB_CDC
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
variant: esp32c3
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
logger:
|
||||
hardware_uart: USB_SERIAL_JTAG
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
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;"
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: d1_mini_lite
|
||||
|
||||
logger:
|
||||
baud_rate: 0
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
nrf52:
|
||||
board: adafruit_itsybitsy_nrf52840
|
||||
bootloader: adafruit_nrf52_sd140_v6
|
||||
|
||||
logger:
|
||||
hardware_uart: USB_CDC
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
nrf52:
|
||||
board: adafruit_itsybitsy_nrf52840
|
||||
bootloader: adafruit_nrf52_sd140_v6
|
||||
|
||||
logger:
|
||||
hardware_uart: USB_CDC
|
||||
baud_rate: 0
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: d1_mini_lite
|
||||
|
||||
logger:
|
||||
hardware_uart: UART0_SWAP
|
||||
@@ -1,54 +0,0 @@
|
||||
#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,6 +17,7 @@ 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
|
||||
|
||||
@@ -2441,16 +2441,25 @@ def polling_component() -> None:
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("polling_component")
|
||||
@pytest.mark.parametrize(
|
||||
"name",
|
||||
["component.update", "component.suspend", "component.resume"],
|
||||
("name", "call"),
|
||||
[
|
||||
("component.update", "update()"),
|
||||
("component.suspend", "stop_poller()"),
|
||||
("component.resume", "start_poller()"),
|
||||
],
|
||||
)
|
||||
async def test_component_action_codegen(name: str) -> None:
|
||||
"""Each action is an object constructed with the component, with no interval set."""
|
||||
async def test_component_action_codegen(name: str, call: str) -> None:
|
||||
"""The call goes through ``PollingComponent`` and only once the component is ready."""
|
||||
await build_action(_component_action(name), TEMPLATE_ARG, [])
|
||||
|
||||
class_name = str(ACTION_REGISTRY[name].type_id)
|
||||
assert f"new(act) {class_name}<>(comp);" in CORE.cpp_main_section
|
||||
assert "set_update_interval" not in CORE.cpp_main_section
|
||||
assert "new(act) ApplyAction<esphome__act__fn>();" in CORE.cpp_main_section
|
||||
assert (
|
||||
" PollingComponent &esphome__component = *::comp;\n"
|
||||
" if (esphome__component.is_ready()) {\n"
|
||||
f" esphome__component.{call};\n"
|
||||
" }\n"
|
||||
) in CORE.cpp_global_section
|
||||
assert "set_update_interval" not in CORE.cpp_global_section
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -2466,14 +2475,17 @@ async def test_component_resume_constant_interval_codegen() -> None:
|
||||
)
|
||||
|
||||
assert (
|
||||
"act->set_update_interval([]() -> uint32_t {\n return 250;\n});"
|
||||
) in CORE.cpp_main_section
|
||||
" if (esphome__component.is_ready()) {\n"
|
||||
" esphome__component.set_update_interval(250);\n"
|
||||
" esphome__component.start_poller();\n"
|
||||
" }\n"
|
||||
) in CORE.cpp_global_section
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("polling_component")
|
||||
async def test_component_resume_interval_lambda_codegen() -> None:
|
||||
"""A templated interval is a lambda over the trigger args."""
|
||||
"""A templated interval is computed from the trigger args, only once ready."""
|
||||
await build_action(
|
||||
_component_action(
|
||||
"component.resume", {CONF_UPDATE_INTERVAL: Lambda("return x * 1000;")}
|
||||
@@ -2482,27 +2494,91 @@ async def test_component_resume_interval_lambda_codegen() -> None:
|
||||
[(cg.uint32, "x")],
|
||||
)
|
||||
|
||||
main = CORE.cpp_main_section
|
||||
assert "new(act) ResumeComponentAction<uint32_t>(comp);" in main
|
||||
assert (
|
||||
"act->set_update_interval([](uint32_t x) -> uint32_t {\n"
|
||||
"static void esphome__act__fn(const std::remove_cvref_t<uint32_t> & x) {\n"
|
||||
" PollingComponent &esphome__component = *::comp;\n"
|
||||
" if (esphome__component.is_ready()) {\n"
|
||||
" esphome__component.set_update_interval([](uint32_t x) -> uint32_t {\n"
|
||||
" return x * 1000;\n"
|
||||
"});"
|
||||
) in main
|
||||
" }(x));\n"
|
||||
" esphome__component.start_poller();\n"
|
||||
" }\n"
|
||||
) in CORE.cpp_global_section
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("apply_actions")
|
||||
async def test_component_update_not_ready_still_runs_merged_actions() -> None:
|
||||
"""The ready check skips only the update, never the actions merged after it."""
|
||||
cg.new_Pvariable(ID("pcomp", is_declaration=True, type=cg.PollingComponent))
|
||||
update = _component_action("component.update")
|
||||
update["component.update"][CONF_ID] = ID("pcomp", type=cg.PollingComponent)
|
||||
|
||||
result = await build_action_list(
|
||||
[update, _action("my.a", "act_a")], TEMPLATE_ARG, []
|
||||
)
|
||||
|
||||
assert [str(action) for action in result] == ["act"]
|
||||
text = CORE.cpp_global_section
|
||||
assert text.index("esphome__component.update();") < text.index("::comp->do_a();")
|
||||
# The only return is the stop check between the two actions
|
||||
assert text.count("return;") == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("apply_actions")
|
||||
async def test_callback_automation_if_with_component_update_is_direct() -> None:
|
||||
"""An ``if`` whose branches update components becomes plain C++ in the callback."""
|
||||
cg.new_Pvariable(ID("pcomp", is_declaration=True, type=cg.PollingComponent))
|
||||
|
||||
def update(id_: str) -> ConfigType:
|
||||
return {
|
||||
"component.update": {CONF_ID: ID("pcomp", type=cg.PollingComponent)},
|
||||
CONF_TYPE_ID: ID(
|
||||
id_, is_declaration=True, type=ApplyAction, is_manual=False
|
||||
),
|
||||
}
|
||||
|
||||
conf = _automation_conf(
|
||||
{
|
||||
"if": {
|
||||
CONF_CONDITION: _inline_condition(),
|
||||
CONF_THEN: [update("upd_then")],
|
||||
CONF_ELSE: [update("upd_else")],
|
||||
},
|
||||
CONF_TYPE_ID: ID(
|
||||
"act_if", is_declaration=True, type=IfAction, is_manual=False
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
await build_callback_automation(CALLBACK_PARENT, "add_cb", [], conf)
|
||||
|
||||
assert "Action" not in CORE.cpp_main_section
|
||||
assert "Condition" not in CORE.cpp_main_section
|
||||
text = CORE.cpp_global_section
|
||||
assert "if (esphome__condition) {" in text
|
||||
assert text.count("esphome__component.update();") == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("polling_component")
|
||||
@pytest.mark.parametrize("body", ["x *= 1000; return x;", "return x *= 1000;"])
|
||||
async def test_component_resume_interval_lambda_takes_args_by_value(body: str) -> None:
|
||||
"""The user's interval lambda gets the trigger args by value, so it may modify them."""
|
||||
"""The user's interval lambda gets the trigger args by value, so it may modify them.
|
||||
|
||||
It is called rather than reduced, so even a single return modifies only its own copy.
|
||||
"""
|
||||
await build_action(
|
||||
_component_action("component.resume", {CONF_UPDATE_INTERVAL: Lambda(body)}),
|
||||
cg.TemplateArguments(cg.uint32),
|
||||
[(cg.uint32, "x")],
|
||||
)
|
||||
|
||||
assert f"[](uint32_t x) -> uint32_t {{\n {body}\n}}" in CORE.cpp_main_section
|
||||
assert (
|
||||
f"set_update_interval([](uint32_t x) -> uint32_t {{\n {body}\n }}(x));"
|
||||
in CORE.cpp_global_section
|
||||
)
|
||||
|
||||
|
||||
COMPONENT_ID = ID("comp", type=cg.Component)
|
||||
|
||||
Reference in new issue
Block a user