mirror of
https://github.com/esphome/esphome.git
synced 2026-06-02 03:02:19 +08:00
[usb_uart] Return flush result, expose timeout via config (#14616)
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import socket
|
from esphome.components import socket
|
||||||
from esphome.components.const import CONF_DATA_BITS, CONF_PARITY, CONF_STOP_BITS
|
from esphome.components.const import CONF_DATA_BITS, CONF_PARITY, CONF_STOP_BITS
|
||||||
from esphome.components.uart import CONF_DEBUG_PREFIX, UARTComponent
|
from esphome.components.uart import CONF_DEBUG_PREFIX, CONF_FLUSH_TIMEOUT, UARTComponent
|
||||||
from esphome.components.usb_host import register_usb_client, usb_device_schema
|
from esphome.components.usb_host import register_usb_client, usb_device_schema
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
@@ -91,6 +91,9 @@ def channel_schema(channels, baud_rate_required):
|
|||||||
cv.Optional(CONF_DUMMY_RECEIVER, default=False): cv.boolean,
|
cv.Optional(CONF_DUMMY_RECEIVER, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_DEBUG, default=False): cv.boolean,
|
cv.Optional(CONF_DEBUG, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_DEBUG_PREFIX, default=""): cv.string,
|
cv.Optional(CONF_DEBUG_PREFIX, default=""): cv.string,
|
||||||
|
cv.Optional(
|
||||||
|
CONF_FLUSH_TIMEOUT, default="100ms"
|
||||||
|
): cv.positive_time_period_milliseconds,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -129,6 +132,7 @@ async def to_code(config):
|
|||||||
cg.add(chvar.set_parity(channel[CONF_PARITY]))
|
cg.add(chvar.set_parity(channel[CONF_PARITY]))
|
||||||
cg.add(chvar.set_baud_rate(channel[CONF_BAUD_RATE]))
|
cg.add(chvar.set_baud_rate(channel[CONF_BAUD_RATE]))
|
||||||
cg.add(chvar.set_dummy_receiver(channel[CONF_DUMMY_RECEIVER]))
|
cg.add(chvar.set_dummy_receiver(channel[CONF_DUMMY_RECEIVER]))
|
||||||
|
cg.add(chvar.set_flush_timeout(channel[CONF_FLUSH_TIMEOUT]))
|
||||||
cg.add(chvar.set_debug(channel[CONF_DEBUG]))
|
cg.add(chvar.set_debug(channel[CONF_DEBUG]))
|
||||||
if channel[CONF_DEBUG_PREFIX]:
|
if channel[CONF_DEBUG_PREFIX]:
|
||||||
cg.add(chvar.set_debug_prefix(channel[CONF_DEBUG_PREFIX]))
|
cg.add(chvar.set_debug_prefix(channel[CONF_DEBUG_PREFIX]))
|
||||||
|
|||||||
@@ -169,10 +169,10 @@ void USBUartChannel::write_array(const uint8_t *data, size_t len) {
|
|||||||
uart::FlushResult USBUartChannel::flush() {
|
uart::FlushResult USBUartChannel::flush() {
|
||||||
// Spin until the output queue is drained and the last USB transfer completes.
|
// Spin until the output queue is drained and the last USB transfer completes.
|
||||||
// Safe to call from the main loop only.
|
// Safe to call from the main loop only.
|
||||||
// The 100 ms timeout guards against a device that stops responding mid-flush;
|
// The flush_timeout_ms_ timeout guards against a device that stops responding mid-flush;
|
||||||
// in that case the main loop is blocked for the full duration.
|
// in that case the main loop is blocked for the full duration.
|
||||||
uint32_t start = millis(); // 100 ms safety timeout
|
uint32_t start = millis();
|
||||||
while ((!this->output_queue_.empty() || this->output_started_.load()) && millis() - start < 100) {
|
while ((!this->output_queue_.empty() || this->output_started_.load()) && millis() - start < this->flush_timeout_ms_) {
|
||||||
// Kick start_output() in case data arrived but no transfer is in flight yet.
|
// Kick start_output() in case data arrived but no transfer is in flight yet.
|
||||||
this->parent_->start_output(this);
|
this->parent_->start_output(this);
|
||||||
yield();
|
yield();
|
||||||
@@ -260,10 +260,12 @@ void USBUartComponent::dump_config() {
|
|||||||
" Data Bits: %u\n"
|
" Data Bits: %u\n"
|
||||||
" Parity: %s\n"
|
" Parity: %s\n"
|
||||||
" Stop bits: %s\n"
|
" Stop bits: %s\n"
|
||||||
|
" Flush Timeout: %" PRIu32 " ms\n"
|
||||||
" Debug: %s\n"
|
" Debug: %s\n"
|
||||||
" Dummy receiver: %s",
|
" Dummy receiver: %s",
|
||||||
channel->index_, channel->baud_rate_, channel->data_bits_, PARITY_NAMES[channel->parity_],
|
channel->index_, channel->baud_rate_, channel->data_bits_, PARITY_NAMES[channel->parity_],
|
||||||
STOP_BITS_NAMES[channel->stop_bits_], YESNO(channel->debug_), YESNO(channel->dummy_receiver_));
|
STOP_BITS_NAMES[channel->stop_bits_], channel->flush_timeout_ms_, YESNO(channel->debug_),
|
||||||
|
YESNO(channel->dummy_receiver_));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void USBUartComponent::start_input(USBUartChannel *channel) {
|
void USBUartComponent::start_input(USBUartChannel *channel) {
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ class USBUartChannel : public uart::UARTComponent, public Parented<USBUartCompon
|
|||||||
void set_debug(bool debug) { this->debug_ = debug; }
|
void set_debug(bool debug) { this->debug_ = debug; }
|
||||||
void set_dummy_receiver(bool dummy_receiver) { this->dummy_receiver_ = dummy_receiver; }
|
void set_dummy_receiver(bool dummy_receiver) { this->dummy_receiver_ = dummy_receiver; }
|
||||||
void set_debug_prefix(const char *prefix) { this->debug_prefix_ = StringRef(prefix); }
|
void set_debug_prefix(const char *prefix) { this->debug_prefix_ = StringRef(prefix); }
|
||||||
|
void set_flush_timeout(uint32_t flush_timeout_ms) override { this->flush_timeout_ms_ = flush_timeout_ms; }
|
||||||
|
|
||||||
/// Register a callback invoked immediately after data is pushed to the input ring buffer.
|
/// Register a callback invoked immediately after data is pushed to the input ring buffer.
|
||||||
/// Called from USBUartComponent::loop() in the main loop context.
|
/// Called from USBUartComponent::loop() in the main loop context.
|
||||||
@@ -124,23 +125,23 @@ class USBUartChannel : public uart::UARTComponent, public Parented<USBUartCompon
|
|||||||
void set_rx_callback(std::function<void()> cb) { this->rx_callback_ = std::move(cb); }
|
void set_rx_callback(std::function<void()> cb) { this->rx_callback_ = std::move(cb); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Larger structures first for better alignment
|
// Larger structures first (8+ bytes)
|
||||||
RingBuffer input_buffer_;
|
RingBuffer input_buffer_;
|
||||||
LockFreeQueue<UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT> output_queue_;
|
LockFreeQueue<UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT> output_queue_;
|
||||||
EventPool<UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT> output_pool_;
|
EventPool<UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT> output_pool_;
|
||||||
std::function<void()> rx_callback_{};
|
std::function<void()> rx_callback_{};
|
||||||
CdcEps cdc_dev_{};
|
CdcEps cdc_dev_{};
|
||||||
// Enum (likely 4 bytes)
|
StringRef debug_prefix_{};
|
||||||
|
// 4-byte fields
|
||||||
UARTParityOptions parity_{UART_CONFIG_PARITY_NONE};
|
UARTParityOptions parity_{UART_CONFIG_PARITY_NONE};
|
||||||
// Group atomics together (each 1 byte)
|
uint32_t flush_timeout_ms_{100};
|
||||||
|
// 1-byte fields (no padding between groups)
|
||||||
std::atomic<bool> input_started_{true};
|
std::atomic<bool> input_started_{true};
|
||||||
std::atomic<bool> output_started_{true};
|
std::atomic<bool> output_started_{true};
|
||||||
std::atomic<bool> initialised_{false};
|
std::atomic<bool> initialised_{false};
|
||||||
// Group regular bytes together to minimize padding
|
|
||||||
const uint8_t index_;
|
const uint8_t index_;
|
||||||
bool debug_{};
|
bool debug_{};
|
||||||
bool dummy_receiver_{};
|
bool dummy_receiver_{};
|
||||||
StringRef debug_prefix_{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class USBUartComponent : public usb_host::USBClient {
|
class USBUartComponent : public usb_host::USBClient {
|
||||||
|
|||||||
Reference in New Issue
Block a user