diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 7b0d9726b8..a1c78dd45c 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass from logging import getLogger import math import re @@ -32,13 +33,15 @@ from esphome.const import ( PLATFORM_HOST, PlatformFramework, ) -from esphome.core import CORE, ID +from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority import esphome.final_validate as fv from esphome.yaml_util import make_data_base _LOGGER = getLogger(__name__) CODEOWNERS = ["@esphome/core"] +DOMAIN = "uart" + uart_ns = cg.esphome_ns.namespace("uart") UARTComponent = uart_ns.class_("UARTComponent") @@ -52,6 +55,7 @@ LibreTinyUARTComponent = uart_ns.class_( ) HostUartComponent = uart_ns.class_("HostUartComponent", UARTComponent, cg.Component) + NATIVE_UART_CLASSES = ( str(IDFUARTComponent), str(ESP8266UartComponent), @@ -100,6 +104,30 @@ MULTI_CONF = True MULTI_CONF_NO_DEFAULT = True +@dataclass +class UARTData: + """State data for UART component configuration generation.""" + + wake_loop_on_rx: bool = False + + +def _get_data() -> UARTData: + """Get UART component data from CORE.data.""" + if DOMAIN not in CORE.data: + CORE.data[DOMAIN] = UARTData() + return CORE.data[DOMAIN] + + +def request_wake_loop_on_rx() -> None: + """Request that the UART wake the main loop when data is received. + + Components that need low-latency notification of incoming UART data + should call this function during their code generation. + This enables the RX event task which wakes the main loop when data arrives. + """ + _get_data().wake_loop_on_rx = True + + def validate_raw_data(value): if isinstance(value, str): return value.encode("utf-8") @@ -335,6 +363,8 @@ async def to_code(config): if CONF_DEBUG in config: await debug_to_code(config[CONF_DEBUG], var) + CORE.add_job(final_step) + # A schema to use for all UART devices, all UART integrations must extend this! UART_DEVICE_SCHEMA = cv.Schema( @@ -472,6 +502,13 @@ async def uart_write_to_code(config, action_id, template_arg, args): return var +@coroutine_with_priority(CoroPriority.FINAL) +async def final_step(): + """Final code generation step to configure optional UART features.""" + if _get_data().wake_loop_on_rx: + cg.add_define("USE_UART_WAKE_LOOP_ON_RX") + + FILTER_SOURCE_FILES = filter_source_files_from_platform( { "uart_component_esp_idf.cpp": { diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 61ca8c1c0c..c6efe862c4 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -112,6 +112,12 @@ void IDFUARTComponent::load_settings(bool dump_config) { esp_err_t err; if (uart_is_driver_installed(this->uart_num_)) { +#ifdef USE_UART_WAKE_LOOP_ON_RX + if (this->rx_event_task_handle_ != nullptr) { + vTaskDelete(this->rx_event_task_handle_); + this->rx_event_task_handle_ = nullptr; + } +#endif err = uart_driver_delete(this->uart_num_); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err)); @@ -204,6 +210,11 @@ void IDFUARTComponent::load_settings(bool dump_config) { return; } +#ifdef USE_UART_WAKE_LOOP_ON_RX + // Start the RX event task to enable low-latency data notifications + this->start_rx_event_task_(); +#endif // USE_UART_WAKE_LOOP_ON_RX + if (dump_config) { ESP_LOGCONFIG(TAG, "Reloaded UART %u", this->uart_num_); this->dump_config(); @@ -226,7 +237,11 @@ void IDFUARTComponent::dump_config() { " Baud Rate: %" PRIu32 " baud\n" " Data Bits: %u\n" " Parity: %s\n" - " Stop bits: %u", + " Stop bits: %u" +#ifdef USE_UART_WAKE_LOOP_ON_RX + "\n Wake on data RX: ENABLED" +#endif + , this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_); this->check_logger_conflict(); } @@ -337,6 +352,59 @@ void IDFUARTComponent::flush() { void IDFUARTComponent::check_logger_conflict() {} +#ifdef USE_UART_WAKE_LOOP_ON_RX +void IDFUARTComponent::start_rx_event_task_() { + // Create FreeRTOS task to monitor UART events + BaseType_t result = xTaskCreate(rx_event_task_func, // Task function + "uart_rx_evt", // Task name (max 16 chars) + 2240, // Stack size in bytes (~2.2KB); increase if needed for logging + this, // Task parameter (this pointer) + tskIDLE_PRIORITY + 1, // Priority (low, just above idle) + &this->rx_event_task_handle_ // Task handle + ); + + if (result != pdPASS) { + ESP_LOGE(TAG, "Failed to create RX event task"); + return; + } + + ESP_LOGV(TAG, "RX event task started"); +} + +void IDFUARTComponent::rx_event_task_func(void *param) { + auto *self = static_cast(param); + uart_event_t event; + + ESP_LOGV(TAG, "RX event task running"); + + // Run forever - task lifecycle matches component lifecycle + while (true) { + // Wait for UART events (blocks efficiently) + if (xQueueReceive(self->uart_event_queue_, &event, portMAX_DELAY) == pdTRUE) { + switch (event.type) { + case UART_DATA: + // Data available in UART RX buffer - wake the main loop + ESP_LOGVV(TAG, "Data event: %d bytes", event.size); + App.wake_loop_threadsafe(); + break; + + case UART_FIFO_OVF: + case UART_BUFFER_FULL: + ESP_LOGW(TAG, "FIFO overflow or ring buffer full - clearing"); + uart_flush_input(self->uart_num_); + App.wake_loop_threadsafe(); + break; + + default: + // Ignore other event types + ESP_LOGVV(TAG, "Event type: %d", event.type); + break; + } + } + } +} +#endif // USE_UART_WAKE_LOOP_ON_RX + } // namespace uart } // namespace esphome diff --git a/esphome/components/uart/uart_component_esp_idf.h b/esphome/components/uart/uart_component_esp_idf.h index a2ba2aa968..e47a323979 100644 --- a/esphome/components/uart/uart_component_esp_idf.h +++ b/esphome/components/uart/uart_component_esp_idf.h @@ -53,6 +53,14 @@ class IDFUARTComponent : public UARTComponent, public Component { bool has_peek_{false}; uint8_t peek_byte_; + +#ifdef USE_UART_WAKE_LOOP_ON_RX + // RX notification support + void start_rx_event_task_(); + static void rx_event_task_func(void *param); + + TaskHandle_t rx_event_task_handle_{nullptr}; +#endif // USE_UART_WAKE_LOOP_ON_RX }; } // namespace uart diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 538d4e3d6e..12dfdba5ce 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -107,6 +107,7 @@ #define USE_TIME #define USE_TOUCHSCREEN #define USE_UART_DEBUGGER +#define USE_UART_WAKE_LOOP_ON_RX #define USE_UPDATE #define USE_VALVE #define USE_ZWAVE_PROXY