From 71bb94524e0b6733d2c0df159852e49493adf1fd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 27 Nov 2025 16:33:08 -0600 Subject: [PATCH] [usb_uart] Wake main loop immediately when USB data arrives (#12148) --- esphome/components/usb_uart/__init__.py | 7 ++++++- esphome/components/usb_uart/usb_uart.cpp | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/esphome/components/usb_uart/__init__.py b/esphome/components/usb_uart/__init__.py index a852e1f78..d9bb58ae3 100644 --- a/esphome/components/usb_uart/__init__.py +++ b/esphome/components/usb_uart/__init__.py @@ -1,4 +1,5 @@ import esphome.codegen as cg +from esphome.components import socket from esphome.components.uart import ( CONF_DATA_BITS, CONF_PARITY, @@ -17,7 +18,7 @@ from esphome.const import ( ) from esphome.cpp_types import Component -AUTO_LOAD = ["uart", "usb_host", "bytebuffer"] +AUTO_LOAD = ["uart", "usb_host", "bytebuffer", "socket"] CODEOWNERS = ["@clydebarrow"] usb_uart_ns = cg.esphome_ns.namespace("usb_uart") @@ -116,6 +117,10 @@ CONFIG_SCHEMA = cv.ensure_list( async def to_code(config): + # Enable wake_loop_threadsafe for low-latency USB data processing + # The USB task queues data events that need immediate processing + socket.require_wake_loop_threadsafe() + for device in config: var = await register_usb_client(device) for index, channel in enumerate(device[CONF_CHANNELS]): diff --git a/esphome/components/usb_uart/usb_uart.cpp b/esphome/components/usb_uart/usb_uart.cpp index c24fffb11..2def7c81c 100644 --- a/esphome/components/usb_uart/usb_uart.cpp +++ b/esphome/components/usb_uart/usb_uart.cpp @@ -2,6 +2,7 @@ #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) #include "usb_uart.h" #include "esphome/core/log.h" +#include "esphome/core/application.h" #include "esphome/components/uart/uart_debugger.h" #include @@ -262,6 +263,11 @@ void USBUartComponent::start_input(USBUartChannel *channel) { // Push to lock-free queue for main loop processing // Push always succeeds because pool size == queue size this->usb_data_queue_.push(chunk); + + // Wake main loop immediately to process USB data instead of waiting for select() timeout +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + App.wake_loop_threadsafe(); +#endif } // On success, restart input immediately from USB task for performance