ESPHome 2026.10.0-dev
Loading...
Searching...
No Matches
usb_uart.h
Go to the documentation of this file.
1#pragma once
2
3#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
4 defined(USE_ESP32_VARIANT_ESP32S31) || defined(USE_ESP32_VARIANT_ESP32H4)
12#include <atomic>
13#include <functional>
14
15namespace esphome::usb_uart {
16
17class USBUartTypeCdcAcm;
18class USBUartComponent;
19class USBUartChannelBase;
20class USBUartTypePL2303;
21
22static const char *const TAG = "usb_uart";
23
24static constexpr uint8_t USB_CDC_SUBCLASS_ACM = 0x02;
25static constexpr uint8_t USB_SUBCLASS_COMMON = 0x02;
26static constexpr uint8_t USB_SUBCLASS_NULL = 0x00;
27static constexpr uint8_t USB_PROTOCOL_NULL = 0x00;
28static constexpr uint8_t USB_DEVICE_PROTOCOL_IAD = 0x01;
29static constexpr uint8_t USB_VENDOR_IFC = usb_host::USB_TYPE_VENDOR | usb_host::USB_RECIP_INTERFACE;
30static constexpr uint8_t USB_VENDOR_DEV = usb_host::USB_TYPE_VENDOR | usb_host::USB_RECIP_DEVICE;
31
32struct CdcEps {
33 const usb_ep_desc_t *notify_ep;
34 const usb_ep_desc_t *in_ep;
35 const usb_ep_desc_t *out_ep;
37 // Also the wIndex target for CDC class requests (SET_LINE_CODING etc.), so it
38 // must remain valid even when the interface itself is not claimed.
41};
42
72
80
86
87static const char *const PARITY_NAMES[] = {"NONE", "ODD", "EVEN", "MARK", "SPACE"};
88static const char *const STOP_BITS_NAMES[] = {"1", "1.5", "2"};
89
91 public:
92 RingBuffer(uint16_t buffer_size) : buffer_size_(buffer_size), buffer_(new uint8_t[buffer_size]) {}
93 bool is_empty() const { return this->read_pos_ == this->insert_pos_; }
94 size_t get_available() const {
95 return (this->insert_pos_ + this->buffer_size_ - this->read_pos_) % this->buffer_size_;
96 };
97 size_t get_free_space() const { return this->buffer_size_ - 1 - this->get_available(); }
98 uint8_t peek() const { return this->buffer_[this->read_pos_]; }
99 void push(uint8_t item);
100 void push(const uint8_t *data, size_t len);
101 uint8_t pop();
102 size_t pop(uint8_t *data, size_t len);
103 void clear() { this->read_pos_ = this->insert_pos_ = 0; }
104
105 protected:
106 uint16_t insert_pos_ = 0;
107 uint16_t read_pos_ = 0;
108 uint16_t buffer_size_;
109 uint8_t *buffer_;
110};
111
112// Structure for queuing received USB data chunks
114 uint8_t data[usb_host::USB_MAX_PACKET_SIZE];
115 uint16_t length;
117
118 // Required for EventPool - no cleanup needed for POD types
119 void release() {}
120};
121
122// Structure for queuing outgoing USB data chunks (one per USB packet)
124 static constexpr size_t MAX_CHUNK_SIZE = usb_host::USB_MAX_PACKET_SIZE;
126 uint16_t length;
127
128 // Required for EventPool - no cleanup needed for POD types
129 void release() {}
130};
131
132// Common, non-final base for all USB UART channel implementations.
133// Concrete channel types (USBUartChannel for CDC-style devices, vendor-specific
134// multiplexed channels like CH934X) derive from this and are themselves final,
135// per the "configurable classes are final" convention.
136class USBUartChannelBase : public uart::UARTComponent, public Parented<USBUartComponent> {
137 friend class USBUartComponent;
138 friend class USBUartTypeCdcAcm;
139 friend class USBUartTypeCP210X;
140 friend class USBUartTypeCH34X;
141 friend class USBUartTypeFT23XX;
142 friend class USBUartTypePL2303;
143
144 public:
145 // Number of output chunk slots per channel, derived from buffer_size config.
146 // Computed as ceil(buffer_size / 64) + 1 in Python codegen; defaults to 5 (256 / 64 + 1).
147 static constexpr uint8_t USB_OUTPUT_CHUNK_COUNT = USB_UART_OUTPUT_CHUNK_COUNT;
148
149 void write_array(const uint8_t *data, size_t len) override;
150 bool peek_byte(uint8_t *data) override;
151 bool read_array(uint8_t *data, size_t len) override;
152 size_t available() override { return this->input_buffer_.get_available(); }
153 bool is_connected() override { return this->initialised_.load(); }
154 uart::UARTFlushResult flush() override;
155 // Re-apply the current line settings (baud, parity, etc) to this already-open channel.
156 void load_settings(bool dump_config) override;
157 using UARTComponent::load_settings; // also bring in the no-arg overload for convenience
158 void set_parity(UARTParityOptions parity) { this->parity_ = parity; }
159 void set_debug(bool debug) { this->debug_ = debug; }
160 void set_dummy_receiver(bool dummy_receiver) { this->dummy_receiver_ = dummy_receiver; }
161 void set_debug_prefix(const char *prefix) { this->debug_prefix_ = StringRef(prefix); }
162 void set_flush_timeout(uint32_t flush_timeout_ms) override { this->flush_timeout_ms_ = flush_timeout_ms; }
163
168 void set_rx_callback(std::function<void()> cb) { this->rx_callback_ = std::move(cb); }
169
170 protected:
171 // Not directly instantiable; construct a concrete channel type instead.
172 USBUartChannelBase(uint8_t index, uint16_t buffer_size) : input_buffer_(RingBuffer(buffer_size)), index_(index) {}
173 void check_logger_conflict() override {}
174 // Larger structures first (8+ bytes)
177 // Pool sized to queue capacity (SIZE-1) because LockFreeQueue<T,N> is a ring
178 // buffer that holds N-1 elements. This guarantees allocate() returns nullptr
179 // before push() can fail, preventing a pool slot leak.
181 std::function<void()> rx_callback_{};
184 // 4-byte fields
187 // 1-byte fields (no padding between groups)
188 std::atomic<bool> input_started_{true};
189 std::atomic<bool> output_started_{true};
190 std::atomic<bool> initialised_{false};
191 const uint8_t index_;
192 bool debug_{};
194};
195
196// Concrete channel type for CDC-style USB serial devices (2 bulk endpoints per
197// channel). All shared behavior lives in USBUartChannelBase.
198class USBUartChannel final : public USBUartChannelBase {
199 public:
200 USBUartChannel(uint8_t index, uint16_t buffer_size) : USBUartChannelBase(index, buffer_size) {}
201};
202
204 public:
205 USBUartComponent(uint16_t vid, uint16_t pid) : usb_host::USBClient(vid, pid) {}
206 void setup() override;
207 void loop() override;
208 void dump_config() override;
209 std::vector<USBUartChannelBase *> get_channels() { return this->channels_; }
210
211 void add_channel(USBUartChannelBase *channel) { this->channels_.push_back(channel); }
212
213 virtual void start_input(USBUartChannelBase *channel);
214 void start_output(USBUartChannelBase *channel);
215
216 // Begin configuring all channels (full initialisation). Called from on_connected().
217 void enable_channels();
218 // Re-apply line settings to a single, already-open channel (used by
219 // USBUartChannelBase::load_settings()).
221
222 // Called from loop() when input_buffer_ has insufficient space for the incoming chunk.
223 // Default is a no-op; override in device-specific subclasses that need resync on overflow.
224 virtual void on_rx_overflow(USBUartChannelBase *channel) {}
225
226 // Lock-free data transfer from USB task to main loop
227 static constexpr int USB_DATA_QUEUE_SIZE = 32;
229 // Pool sized to queue capacity (SIZE-1) — see USBUartChannelBase::output_pool_ comment.
231
232 protected:
233 // Issue one control transfer as part of the setup state machine. The completion
234 // callback (USB-task context) records the result/IN data, marks the step done and
235 // wakes the loop so run_config_machine_() advances on the loop thread. Call exactly
236 // once from config_step_()/config_device_step_() when issuing a step.
237 void config_transfer_(uint8_t type, uint8_t request, uint16_t value, uint16_t index,
238 const std::vector<uint8_t> &data = {});
239 // (Re)start the config state machine. reload=false runs full init over all channels;
240 // reload=true re-applies settings to cfg_single_ only.
241 void start_config_(bool reload);
242 // Advance the config state machine; called from loop(). Returns true if it did work.
243 bool run_config_machine_();
244
245 // Per-subclass per-channel settings sequence. For the given zero-based step, issue the
246 // next control transfer via config_transfer_() and return true, or return false when the
247 // channel has no more steps. reload=true ⇒ apply only baud/parity/stop/data (skip
248 // enable/reset/DTR-RTS). ok/response carry the previous step's result and IN data.
249 virtual bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok,
250 const uint8_t *response) = 0;
251 // Optional one-time device-level setup run before the per-channel phase on init only
252 // (e.g. CH34x chip detection). Same contract as config_step_(). Default: no steps.
253 virtual bool config_device_step(uint8_t step, bool ok, const uint8_t *response) { return false; }
254
255 std::vector<USBUartChannelBase *> channels_{};
256
257 // Config state machine
258 USBUartChannelBase *cfg_single_{nullptr}; // non-null: reload of a single channel
259 USBUartChannelBase *cfg_pending_reload_{nullptr}; // reload requested while the machine was busy
260 std::atomic<bool> cfg_done_{false}; // synchronizes cfg_ok_/cfg_response_ across threads
261 uint8_t cfg_response_[8]{}; // last IN transfer payload (for detection reads)
263 uint8_t cfg_step_{0};
264 bool cfg_active_{false};
265 bool cfg_reload_{false};
266 bool cfg_device_phase_{false};
267 bool cfg_in_flight_{false};
268 bool cfg_ok_{true};
269};
270
272 public:
273 USBUartTypeCdcAcm(uint16_t vid, uint16_t pid) : USBUartComponent(vid, pid) {}
274
275 protected:
276 virtual std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl);
277 void on_connected() override;
278 void on_disconnected() override;
279 bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
280};
281
283 public:
284 USBUartTypeCP210X(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
285
286 protected:
287 std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
288 bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
289};
291 public:
292 USBUartTypeCH34X(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
293 void dump_config() override;
294
295 protected:
296 bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
297 bool config_device_step(uint8_t step, bool ok, const uint8_t *response) override;
298 std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
299
300 private:
301 CH34xChipType chiptype_{CHIP_UNKNOWN};
302 const char *chip_name_{"unknown"};
303 uint8_t num_ports_{1};
304};
305
307 public:
308 USBUartTypeFT23XX(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
309
310 void start_input(USBUartChannelBase *channel) override;
311 void on_rx_overflow(USBUartChannelBase *channel) override;
312
313 protected:
314 std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
315 bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
316
317 uint8_t chip_type_{255};
318};
319
320enum Pl2303ChipType : uint8_t {
321 PL2303_TYPE_H = 0, // Legacy, max 1.2Mbaud
322 PL2303_TYPE_HX, // max 6Mbaud, divisor encoding
323 PL2303_TYPE_TA, // max 6Mbaud, alt divisor encoding
324 PL2303_TYPE_TB, // max 12Mbaud, alt divisor encoding
325 PL2303_TYPE_HXD, // max 12Mbaud, divisor encoding
326 PL2303_TYPE_HXN, // G-series, max 12Mbaud, direct encoding only
328};
329
331 friend class USBUartChannelBase;
332
333 public:
334 USBUartTypePL2303(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
335
336 protected:
337 std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
338 bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
339
341};
342
343} // namespace esphome::usb_uart
344
345#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 ||
346 // USE_ESP32_VARIANT_ESP32S31 || USE_ESP32_VARIANT_ESP32H4
Helper class to easily give an object a parent of type T.
Definition helpers.h:1910
StringRef is a reference to a string owned by something else.
Definition string_ref.h:26
void load_settings()
Load the UART settings.
USBClient(uint16_t vid, uint16_t pid)
Definition usb_host.h:132
void push(uint8_t item)
Definition usb_uart.cpp:110
RingBuffer(uint16_t buffer_size)
Definition usb_uart.h:92
size_t get_free_space() const
Definition usb_uart.h:97
size_t get_available() const
Definition usb_uart.h:94
uart::UARTFlushResult flush() override
Definition usb_uart.cpp:173
bool read_array(uint8_t *data, size_t len) override
Definition usb_uart.cpp:196
EventPool< UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT - 1 > output_pool_
Definition usb_uart.h:180
void set_parity(UARTParityOptions parity)
Definition usb_uart.h:158
bool peek_byte(uint8_t *data) override
Definition usb_uart.cpp:189
std::function< void()> rx_callback_
Definition usb_uart.h:181
void set_rx_callback(std::function< void()> cb)
Register a callback invoked immediately after data is pushed to the input ring buffer.
Definition usb_uart.h:168
USBUartChannelBase(uint8_t index, uint16_t buffer_size)
Definition usb_uart.h:172
LockFreeQueue< UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT > output_queue_
Definition usb_uart.h:176
static constexpr uint8_t USB_OUTPUT_CHUNK_COUNT
Definition usb_uart.h:147
std::atomic< bool > input_started_
Definition usb_uart.h:188
void set_dummy_receiver(bool dummy_receiver)
Definition usb_uart.h:160
void set_debug_prefix(const char *prefix)
Definition usb_uart.h:161
std::atomic< bool > output_started_
Definition usb_uart.h:189
void set_flush_timeout(uint32_t flush_timeout_ms) override
Definition usb_uart.h:162
void write_array(const uint8_t *data, size_t len) override
Definition usb_uart.cpp:139
USBUartChannel(uint8_t index, uint16_t buffer_size)
Definition usb_uart.h:200
void config_transfer_(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const std::vector< uint8_t > &data={})
Definition usb_uart.cpp:567
USBUartChannelBase * cfg_single_
Definition usb_uart.h:258
std::vector< USBUartChannelBase * > channels_
Definition usb_uart.h:255
USBUartComponent(uint16_t vid, uint16_t pid)
Definition usb_uart.h:205
std::atomic< bool > cfg_done_
Definition usb_uart.h:260
LockFreeQueue< UsbDataChunk, USB_DATA_QUEUE_SIZE > usb_data_queue_
Definition usb_uart.h:228
virtual void on_rx_overflow(USBUartChannelBase *channel)
Definition usb_uart.h:224
std::vector< USBUartChannelBase * > get_channels()
Definition usb_uart.h:209
virtual bool config_device_step(uint8_t step, bool ok, const uint8_t *response)
Definition usb_uart.h:253
virtual void start_input(USBUartChannelBase *channel)
Definition usb_uart.cpp:280
void apply_channel_settings(USBUartChannelBase *channel)
Definition usb_uart.cpp:542
void start_output(USBUartChannelBase *channel)
Definition usb_uart.cpp:349
void add_channel(USBUartChannelBase *channel)
Definition usb_uart.h:211
USBUartChannelBase * cfg_pending_reload_
Definition usb_uart.h:259
static constexpr int USB_DATA_QUEUE_SIZE
Definition usb_uart.h:227
EventPool< UsbDataChunk, USB_DATA_QUEUE_SIZE - 1 > chunk_pool_
Definition usb_uart.h:230
virtual bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response)=0
std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl) override
Definition ch34x.cpp:158
bool config_device_step(uint8_t step, bool ok, const uint8_t *response) override
Definition ch34x.cpp:53
USBUartTypeCH34X(uint16_t vid, uint16_t pid)
Definition usb_uart.h:292
bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override
Definition ch34x.cpp:98
USBUartTypeCP210X(uint16_t vid, uint16_t pid)
Definition usb_uart.h:284
std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl) override
Definition cp210x.cpp:46
bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override
Definition cp210x.cpp:100
bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override
Definition usb_uart.cpp:496
USBUartTypeCdcAcm(uint16_t vid, uint16_t pid)
Definition usb_uart.h:273
virtual std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl)
Definition usb_uart.cpp:64
USBUartTypeFT23XX(uint16_t vid, uint16_t pid)
Definition usb_uart.h:308
bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override
Definition ft23xx.cpp:344
void on_rx_overflow(USBUartChannelBase *channel) override
Definition ft23xx.cpp:339
std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl) override
Definition ft23xx.cpp:223
void start_input(USBUartChannelBase *channel) override
Definition ft23xx.cpp:273
USBUartTypePL2303(uint16_t vid, uint16_t pid)
Definition usb_uart.h:334
std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl) override
Definition pl2303.cpp:117
bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override
Definition pl2303.cpp:230
uint16_t type
const char *const TAG
Definition spi.cpp:7
UARTFlushResult
Result of a flush() call.
@ UART_CONFIG_STOP_BITS_1_5
Definition usb_uart.h:83
const void size_t len
Definition hal.h:64
static void uint32_t
const usb_ep_desc_t * out_ep
Definition usb_uart.h:35
const usb_ep_desc_t * notify_ep
Definition usb_uart.h:33
const usb_ep_desc_t * in_ep
Definition usb_uart.h:34
uint8_t interrupt_interface_number
Definition usb_uart.h:39
uint8_t data[usb_host::USB_MAX_PACKET_SIZE]
Definition usb_uart.h:114
USBUartChannelBase * channel
Definition usb_uart.h:116
uint8_t data[MAX_CHUNK_SIZE]
Definition usb_uart.h:125
static constexpr size_t MAX_CHUNK_SIZE
Definition usb_uart.h:124