mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
[usb_uart] Extract non-final USBUartChannelBase from USBUartChannel (#17472)
Co-authored-by: p1ngb4ck <p@local>
This commit is contained in:
co-authored by
p1ngb4ck
parent
74fc2e367a
commit
d119ad6c60
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,18 +243,19 @@ 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
|
||||
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)
|
||||
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};
|
||||
uint8_t cfg_step_{0};
|
||||
bool cfg_active_{false};
|
||||
@@ -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};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user