[uart] Add wake_loop_on_rx flag for low latency processing (#12172)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
@@ -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": {
|
||||
|
||||
@@ -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<IDFUARTComponent *>(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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user