[usb_uart] Extract non-final USBUartChannelBase from USBUartChannel (#17472)

Co-authored-by: p1ngb4ck <p@local>
This commit is contained in:
Oliver Kleinecke
2026-08-22 10:47:46 -05:00
committed by GitHub
co-authored by p1ngb4ck
parent 74fc2e367a
commit d119ad6c60
6 changed files with 55 additions and 42 deletions
+1 -1
View File
@@ -95,7 +95,7 @@ void USBUartTypeCH34X::dump_config() {
ESP_LOGCONFIG(TAG, " CH34x chip: %s", this->chip_name_);
}
bool USBUartTypeCH34X::config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok,
bool USBUartTypeCH34X::config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok,
const uint8_t *response) {
uint8_t cmd = 0xA1 + channel->index_;
if (channel->index_ >= 2)
+1 -1
View File
@@ -97,7 +97,7 @@ std::vector<CdcEps> USBUartTypeCP210X::parse_descriptors(usb_device_handle_t dev
return cdc_devs;
}
bool USBUartTypeCP210X::config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok,
bool USBUartTypeCP210X::config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok,
const uint8_t *response) {
// On reload, skip the one-time IFC_ENABLE step (the interface is already enabled).
if (reload)
+3 -3
View File
@@ -270,7 +270,7 @@ std::vector<CdcEps> USBUartTypeFT23XX::parse_descriptors(usb_device_handle_t dev
return cdc_devs;
}
void USBUartTypeFT23XX::start_input(USBUartChannel *channel) {
void USBUartTypeFT23XX::start_input(USBUartChannelBase *channel) {
if (!channel->initialised_.load())
return;
@@ -336,12 +336,12 @@ void USBUartTypeFT23XX::start_input(USBUartChannel *channel) {
}
}
void USBUartTypeFT23XX::on_rx_overflow(USBUartChannel *channel) {
void USBUartTypeFT23XX::on_rx_overflow(USBUartChannelBase *channel) {
ESP_LOGW(TAG, "RX buffer overflow on channel %d, clearing to resync", channel->index_);
channel->input_buffer_.clear();
}
bool USBUartTypeFT23XX::config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok,
bool USBUartTypeFT23XX::config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok,
const uint8_t *response) {
// On reload (settings change on an open channel) skip the SIO reset; the FTDI set_termios
// path only re-applies baud + line properties and does not re-assert DTR/RTS.
+1 -1
View File
@@ -226,7 +226,7 @@ static const Pl2303InitStep PL2303_INIT[] = {
};
static constexpr uint8_t PL2303_INIT_COUNT = sizeof(PL2303_INIT) / sizeof(PL2303_INIT[0]);
bool USBUartTypePL2303::config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok,
bool USBUartTypePL2303::config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok,
const uint8_t *response) {
bool is_legacy = (this->chip_type_ == PL2303_TYPE_H);
bool is_hxn = (this->chip_type_ == PL2303_TYPE_HXN);
+10 -10
View File
@@ -136,7 +136,7 @@ size_t RingBuffer::pop(uint8_t *data, size_t len) {
}
return len;
}
void USBUartChannel::write_array(const uint8_t *data, size_t len) {
void USBUartChannelBase::write_array(const uint8_t *data, size_t len) {
if (!this->initialised_.load()) {
ESP_LOGD(TAG, "Channel not initialised - write ignored");
return;
@@ -170,7 +170,7 @@ void USBUartChannel::write_array(const uint8_t *data, size_t len) {
this->parent_->start_output(this);
}
uart::UARTFlushResult USBUartChannel::flush() {
uart::UARTFlushResult USBUartChannelBase::flush() {
// Spin until the output queue is drained and the last USB transfer completes.
// Safe to call from the main loop only.
// The flush_timeout_ms_ timeout guards against a device that stops responding mid-flush;
@@ -186,14 +186,14 @@ uart::UARTFlushResult USBUartChannel::flush() {
return uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS;
}
bool USBUartChannel::peek_byte(uint8_t *data) {
bool USBUartChannelBase::peek_byte(uint8_t *data) {
if (this->input_buffer_.is_empty()) {
return false;
}
*data = this->input_buffer_.peek();
return true;
}
bool USBUartChannel::read_array(uint8_t *data, size_t len) {
bool USBUartChannelBase::read_array(uint8_t *data, size_t len) {
if (!this->initialised_.load()) {
ESP_LOGV(TAG, "Channel not initialised - read ignored");
return false;
@@ -277,7 +277,7 @@ void USBUartComponent::dump_config() {
YESNO(channel->dummy_receiver_));
}
}
void USBUartComponent::start_input(USBUartChannel *channel) {
void USBUartComponent::start_input(USBUartChannelBase *channel) {
if (!channel->initialised_.load())
return;
// THREAD CONTEXT: Called from both USB task and main loop threads
@@ -346,7 +346,7 @@ void USBUartComponent::start_input(USBUartChannel *channel) {
}
}
void USBUartComponent::start_output(USBUartChannel *channel) {
void USBUartComponent::start_output(USBUartChannelBase *channel) {
// THREAD CONTEXT: Called from both main loop and USB task threads.
// The output_queue_ is a lock-free SPSC queue, so pop() is safe from either thread.
// The output_started_ atomic flag is claimed via compare_exchange to guarantee that
@@ -491,7 +491,7 @@ void USBUartTypeCdcAcm::on_disconnected() {
USBClient::on_disconnected();
}
bool USBUartTypeCdcAcm::config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok,
bool USBUartTypeCdcAcm::config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok,
const uint8_t *response) {
static constexpr uint8_t CDC_REQUEST_TYPE = usb_host::USB_TYPE_CLASS | usb_host::USB_RECIP_INTERFACE;
static constexpr uint8_t CDC_SET_LINE_CODING = 0x20;
@@ -537,7 +537,7 @@ void USBUartComponent::enable_channels() {
this->start_config_(false);
}
void USBUartComponent::apply_channel_settings(USBUartChannel *channel) {
void USBUartComponent::apply_channel_settings(USBUartChannelBase *channel) {
if (this->cfg_active_) {
// A config sequence is already running. Defer this reload until it finishes to preserve
// the one-control-transfer-at-a-time guarantee (restarting mid-flight would let an
@@ -620,7 +620,7 @@ bool USBUartComponent::run_config_machine_() {
this->cfg_ok_ = true;
}
USBUartChannel *channel =
USBUartChannelBase *channel =
this->cfg_single_ != nullptr
? this->cfg_single_
: (this->cfg_channel_idx_ < this->channels_.size() ? this->channels_[this->cfg_channel_idx_] : nullptr);
@@ -664,7 +664,7 @@ bool USBUartComponent::run_config_machine_() {
return true;
}
void USBUartChannel::load_settings(bool /*dump_config*/) {
void USBUartChannelBase::load_settings(bool /*dump_config*/) {
// The per-channel control transfers already log their values at debug level.
this->parent_->apply_channel_settings(this);
}
+37 -24
View File
@@ -16,7 +16,7 @@ namespace esphome::usb_uart {
class USBUartTypeCdcAcm;
class USBUartComponent;
class USBUartChannel;
class USBUartChannelBase;
class USBUartTypePL2303;
static const char *const TAG = "usb_uart";
@@ -110,7 +110,7 @@ class RingBuffer {
struct UsbDataChunk {
uint8_t data[usb_host::USB_MAX_PACKET_SIZE];
uint16_t length;
USBUartChannel *channel;
USBUartChannelBase *channel;
// Required for EventPool - no cleanup needed for POD types
void release() {}
@@ -126,7 +126,11 @@ struct UsbOutputChunk {
void release() {}
};
class USBUartChannel final : public uart::UARTComponent, public Parented<USBUartComponent> {
// Common, non-final base for all USB UART channel implementations.
// Concrete channel types (USBUartChannel for CDC-style devices, vendor-specific
// multiplexed channels like CH934X) derive from this and are themselves final,
// per the "configurable classes are final" convention.
class USBUartChannelBase : public uart::UARTComponent, public Parented<USBUartComponent> {
friend class USBUartComponent;
friend class USBUartTypeCdcAcm;
friend class USBUartTypeCP210X;
@@ -139,7 +143,6 @@ class USBUartChannel final : public uart::UARTComponent, public Parented<USBUart
// Computed as ceil(buffer_size / 64) + 1 in Python codegen; defaults to 5 (256 / 64 + 1).
static constexpr uint8_t USB_OUTPUT_CHUNK_COUNT = USB_UART_OUTPUT_CHUNK_COUNT;
USBUartChannel(uint8_t index, uint16_t buffer_size) : input_buffer_(RingBuffer(buffer_size)), index_(index) {}
void write_array(const uint8_t *data, size_t len) override;
bool peek_byte(uint8_t *data) override;
bool read_array(uint8_t *data, size_t len) override;
@@ -162,6 +165,8 @@ class USBUartChannel final : public uart::UARTComponent, public Parented<USBUart
void set_rx_callback(std::function<void()> cb) { this->rx_callback_ = std::move(cb); }
protected:
// Not directly instantiable; construct a concrete channel type instead.
USBUartChannelBase(uint8_t index, uint16_t buffer_size) : input_buffer_(RingBuffer(buffer_size)), index_(index) {}
void check_logger_conflict() override {}
// Larger structures first (8+ bytes)
RingBuffer input_buffer_;
@@ -185,33 +190,40 @@ class USBUartChannel final : public uart::UARTComponent, public Parented<USBUart
bool dummy_receiver_{};
};
// Concrete channel type for CDC-style USB serial devices (2 bulk endpoints per
// channel). All shared behavior lives in USBUartChannelBase.
class USBUartChannel final : public USBUartChannelBase {
public:
USBUartChannel(uint8_t index, uint16_t buffer_size) : USBUartChannelBase(index, buffer_size) {}
};
class USBUartComponent : public usb_host::USBClient {
public:
USBUartComponent(uint16_t vid, uint16_t pid) : usb_host::USBClient(vid, pid) {}
void setup() override;
void loop() override;
void dump_config() override;
std::vector<USBUartChannel *> get_channels() { return this->channels_; }
std::vector<USBUartChannelBase *> get_channels() { return this->channels_; }
void add_channel(USBUartChannel *channel) { this->channels_.push_back(channel); }
void add_channel(USBUartChannelBase *channel) { this->channels_.push_back(channel); }
virtual void start_input(USBUartChannel *channel);
void start_output(USBUartChannel *channel);
virtual void start_input(USBUartChannelBase *channel);
void start_output(USBUartChannelBase *channel);
// Begin configuring all channels (full initialisation). Called from on_connected().
void enable_channels();
// Re-apply line settings to a single, already-open channel (used by
// USBUartChannel::load_settings()).
void apply_channel_settings(USBUartChannel *channel);
// USBUartChannelBase::load_settings()).
void apply_channel_settings(USBUartChannelBase *channel);
// Called from loop() when input_buffer_ has insufficient space for the incoming chunk.
// Default is a no-op; override in device-specific subclasses that need resync on overflow.
virtual void on_rx_overflow(USBUartChannel *channel) {}
virtual void on_rx_overflow(USBUartChannelBase *channel) {}
// Lock-free data transfer from USB task to main loop
static constexpr int USB_DATA_QUEUE_SIZE = 32;
LockFreeQueue<UsbDataChunk, USB_DATA_QUEUE_SIZE> usb_data_queue_;
// Pool sized to queue capacity (SIZE-1) — see USBUartChannel::output_pool_ comment.
// Pool sized to queue capacity (SIZE-1) — see USBUartChannelBase::output_pool_ comment.
EventPool<UsbDataChunk, USB_DATA_QUEUE_SIZE - 1> chunk_pool_;
protected:
@@ -231,16 +243,17 @@ class USBUartComponent : public usb_host::USBClient {
// next control transfer via config_transfer_() and return true, or return false when the
// channel has no more steps. reload=true ⇒ apply only baud/parity/stop/data (skip
// enable/reset/DTR-RTS). ok/response carry the previous step's result and IN data.
virtual bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) = 0;
virtual bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok,
const uint8_t *response) = 0;
// Optional one-time device-level setup run before the per-channel phase on init only
// (e.g. CH34x chip detection). Same contract as config_step_(). Default: no steps.
virtual bool config_device_step(uint8_t step, bool ok, const uint8_t *response) { return false; }
std::vector<USBUartChannel *> channels_{};
std::vector<USBUartChannelBase *> channels_{};
// Config state machine
USBUartChannel *cfg_single_{nullptr}; // non-null: reload of a single channel
USBUartChannel *cfg_pending_reload_{nullptr}; // reload requested while the machine was busy
USBUartChannelBase *cfg_single_{nullptr}; // non-null: reload of a single channel
USBUartChannelBase *cfg_pending_reload_{nullptr}; // reload requested while the machine was busy
std::atomic<bool> cfg_done_{false}; // synchronizes cfg_ok_/cfg_response_ across threads
uint8_t cfg_response_[8]{}; // last IN transfer payload (for detection reads)
uint8_t cfg_channel_idx_{0};
@@ -260,7 +273,7 @@ class USBUartTypeCdcAcm : public USBUartComponent {
virtual std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl);
void on_connected() override;
void on_disconnected() override;
bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
};
class USBUartTypeCP210X : public USBUartTypeCdcAcm {
@@ -269,7 +282,7 @@ class USBUartTypeCP210X : public USBUartTypeCdcAcm {
protected:
std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
};
class USBUartTypeCH34X : public USBUartTypeCdcAcm {
public:
@@ -277,7 +290,7 @@ class USBUartTypeCH34X : public USBUartTypeCdcAcm {
void dump_config() override;
protected:
bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
bool config_device_step(uint8_t step, bool ok, const uint8_t *response) override;
std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
@@ -291,12 +304,12 @@ class USBUartTypeFT23XX : public USBUartTypeCdcAcm {
public:
USBUartTypeFT23XX(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
void start_input(USBUartChannel *channel) override;
void on_rx_overflow(USBUartChannel *channel) override;
void start_input(USBUartChannelBase *channel) override;
void on_rx_overflow(USBUartChannelBase *channel) override;
protected:
std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
uint8_t chip_type_{255};
};
@@ -312,14 +325,14 @@ enum Pl2303ChipType : uint8_t {
};
class USBUartTypePL2303 : public USBUartTypeCdcAcm {
friend class USBUartChannel;
friend class USBUartChannelBase;
public:
USBUartTypePL2303(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
protected:
std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
Pl2303ChipType chip_type_{PL2303_TYPE_UNKNOWN};
};