ESPHome 2026.8.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 USBUartChannel;
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;
38};
39
69
77
83
84static const char *const PARITY_NAMES[] = {"NONE", "ODD", "EVEN", "MARK", "SPACE"};
85static const char *const STOP_BITS_NAMES[] = {"1", "1.5", "2"};
86
88 public:
89 RingBuffer(uint16_t buffer_size) : buffer_size_(buffer_size), buffer_(new uint8_t[buffer_size]) {}
90 bool is_empty() const { return this->read_pos_ == this->insert_pos_; }
91 size_t get_available() const {
92 return (this->insert_pos_ + this->buffer_size_ - this->read_pos_) % this->buffer_size_;
93 };
94 size_t get_free_space() const { return this->buffer_size_ - 1 - this->get_available(); }
95 uint8_t peek() const { return this->buffer_[this->read_pos_]; }
96 void push(uint8_t item);
97 void push(const uint8_t *data, size_t len);
98 uint8_t pop();
99 size_t pop(uint8_t *data, size_t len);
100 void clear() { this->read_pos_ = this->insert_pos_ = 0; }
101
102 protected:
103 uint16_t insert_pos_ = 0;
104 uint16_t read_pos_ = 0;
105 uint16_t buffer_size_;
106 uint8_t *buffer_;
107};
108
109// Structure for queuing received USB data chunks
111 uint8_t data[usb_host::USB_MAX_PACKET_SIZE];
112 uint16_t length;
114
115 // Required for EventPool - no cleanup needed for POD types
116 void release() {}
117};
118
119// Structure for queuing outgoing USB data chunks (one per USB packet)
121 static constexpr size_t MAX_CHUNK_SIZE = usb_host::USB_MAX_PACKET_SIZE;
123 uint16_t length;
124
125 // Required for EventPool - no cleanup needed for POD types
126 void release() {}
127};
128
129class USBUartChannel final : public uart::UARTComponent, public Parented<USBUartComponent> {
130 friend class USBUartComponent;
131 friend class USBUartTypeCdcAcm;
132 friend class USBUartTypeCP210X;
133 friend class USBUartTypeCH34X;
134 friend class USBUartTypeFT23XX;
135 friend class USBUartTypePL2303;
136
137 public:
138 // Number of output chunk slots per channel, derived from buffer_size config.
139 // Computed as ceil(buffer_size / 64) + 1 in Python codegen; defaults to 5 (256 / 64 + 1).
140 static constexpr uint8_t USB_OUTPUT_CHUNK_COUNT = USB_UART_OUTPUT_CHUNK_COUNT;
141
142 USBUartChannel(uint8_t index, uint16_t buffer_size) : input_buffer_(RingBuffer(buffer_size)), index_(index) {}
143 void write_array(const uint8_t *data, size_t len) override;
144 bool peek_byte(uint8_t *data) override;
145 bool read_array(uint8_t *data, size_t len) override;
146 size_t available() override { return this->input_buffer_.get_available(); }
147 bool is_connected() override { return this->initialised_.load(); }
148 uart::UARTFlushResult flush() override;
149 // Re-apply the current line settings (baud, parity, etc) to this already-open channel.
150 void load_settings(bool dump_config) override;
151 using UARTComponent::load_settings; // also bring in the no-arg overload for convenience
152 void set_parity(UARTParityOptions parity) { this->parity_ = parity; }
153 void set_debug(bool debug) { this->debug_ = debug; }
154 void set_dummy_receiver(bool dummy_receiver) { this->dummy_receiver_ = dummy_receiver; }
155 void set_debug_prefix(const char *prefix) { this->debug_prefix_ = StringRef(prefix); }
156 void set_flush_timeout(uint32_t flush_timeout_ms) override { this->flush_timeout_ms_ = flush_timeout_ms; }
157
162 void set_rx_callback(std::function<void()> cb) { this->rx_callback_ = std::move(cb); }
163
164 protected:
165 void check_logger_conflict() override {}
166 // Larger structures first (8+ bytes)
169 // Pool sized to queue capacity (SIZE-1) because LockFreeQueue<T,N> is a ring
170 // buffer that holds N-1 elements. This guarantees allocate() returns nullptr
171 // before push() can fail, preventing a pool slot leak.
173 std::function<void()> rx_callback_{};
176 // 4-byte fields
179 // 1-byte fields (no padding between groups)
180 std::atomic<bool> input_started_{true};
181 std::atomic<bool> output_started_{true};
182 std::atomic<bool> initialised_{false};
183 const uint8_t index_;
184 bool debug_{};
186};
187
189 public:
190 USBUartComponent(uint16_t vid, uint16_t pid) : usb_host::USBClient(vid, pid) {}
191 void setup() override;
192 void loop() override;
193 void dump_config() override;
194 std::vector<USBUartChannel *> get_channels() { return this->channels_; }
195
196 void add_channel(USBUartChannel *channel) { this->channels_.push_back(channel); }
197
198 virtual void start_input(USBUartChannel *channel);
199 void start_output(USBUartChannel *channel);
200
201 // Begin configuring all channels (full initialisation). Called from on_connected().
202 void enable_channels();
203 // Re-apply line settings to a single, already-open channel (used by
204 // USBUartChannel::load_settings()).
206
207 // Called from loop() when input_buffer_ has insufficient space for the incoming chunk.
208 // Default is a no-op; override in device-specific subclasses that need resync on overflow.
209 virtual void on_rx_overflow(USBUartChannel *channel) {}
210
211 // Lock-free data transfer from USB task to main loop
212 static constexpr int USB_DATA_QUEUE_SIZE = 32;
214 // Pool sized to queue capacity (SIZE-1) — see USBUartChannel::output_pool_ comment.
216
217 protected:
218 // Issue one control transfer as part of the setup state machine. The completion
219 // callback (USB-task context) records the result/IN data, marks the step done and
220 // wakes the loop so run_config_machine_() advances on the loop thread. Call exactly
221 // once from config_step_()/config_device_step_() when issuing a step.
222 void config_transfer_(uint8_t type, uint8_t request, uint16_t value, uint16_t index,
223 const std::vector<uint8_t> &data = {});
224 // (Re)start the config state machine. reload=false runs full init over all channels;
225 // reload=true re-applies settings to cfg_single_ only.
226 void start_config_(bool reload);
227 // Advance the config state machine; called from loop(). Returns true if it did work.
228 bool run_config_machine_();
229
230 // Per-subclass per-channel settings sequence. For the given zero-based step, issue the
231 // next control transfer via config_transfer_() and return true, or return false when the
232 // channel has no more steps. reload=true ⇒ apply only baud/parity/stop/data (skip
233 // enable/reset/DTR-RTS). ok/response carry the previous step's result and IN data.
234 virtual bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) = 0;
235 // Optional one-time device-level setup run before the per-channel phase on init only
236 // (e.g. CH34x chip detection). Same contract as config_step_(). Default: no steps.
237 virtual bool config_device_step(uint8_t step, bool ok, const uint8_t *response) { return false; }
238
239 std::vector<USBUartChannel *> channels_{};
240
241 // Config state machine
242 USBUartChannel *cfg_single_{nullptr}; // non-null: reload of a single channel
243 USBUartChannel *cfg_pending_reload_{nullptr}; // reload requested while the machine was busy
244 std::atomic<bool> cfg_done_{false}; // synchronizes cfg_ok_/cfg_response_ across threads
245 uint8_t cfg_response_[8]{}; // last IN transfer payload (for detection reads)
247 uint8_t cfg_step_{0};
248 bool cfg_active_{false};
249 bool cfg_reload_{false};
250 bool cfg_device_phase_{false};
251 bool cfg_in_flight_{false};
252 bool cfg_ok_{true};
253};
254
256 public:
257 USBUartTypeCdcAcm(uint16_t vid, uint16_t pid) : USBUartComponent(vid, pid) {}
258
259 protected:
260 virtual std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl);
261 void on_connected() override;
262 void on_disconnected() override;
263 bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
264};
265
267 public:
268 USBUartTypeCP210X(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
269
270 protected:
271 std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
272 bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
273};
275 public:
276 USBUartTypeCH34X(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
277 void dump_config() override;
278
279 protected:
280 bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
281 bool config_device_step(uint8_t step, bool ok, const uint8_t *response) override;
282 std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
283
284 private:
285 CH34xChipType chiptype_{CHIP_UNKNOWN};
286 const char *chip_name_{"unknown"};
287 uint8_t num_ports_{1};
288};
289
291 public:
292 USBUartTypeFT23XX(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
293
294 void start_input(USBUartChannel *channel) override;
295 void on_rx_overflow(USBUartChannel *channel) override;
296
297 protected:
298 std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
299 bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
300
301 uint8_t chip_type_{255};
302};
303
304enum Pl2303ChipType : uint8_t {
305 PL2303_TYPE_H = 0, // Legacy, max 1.2Mbaud
306 PL2303_TYPE_HX, // max 6Mbaud, divisor encoding
307 PL2303_TYPE_TA, // max 6Mbaud, alt divisor encoding
308 PL2303_TYPE_TB, // max 12Mbaud, alt divisor encoding
309 PL2303_TYPE_HXD, // max 12Mbaud, divisor encoding
310 PL2303_TYPE_HXN, // G-series, max 12Mbaud, direct encoding only
312};
313
315 friend class USBUartChannel;
316
317 public:
318 USBUartTypePL2303(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
319
320 protected:
321 std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
322 bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
323
325};
326
327} // namespace esphome::usb_uart
328
329#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 ||
330 // USE_ESP32_VARIANT_ESP32S31 || USE_ESP32_VARIANT_ESP32H4
Helper class to easily give an object a parent of type T.
Definition helpers.h:1881
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:89
size_t get_free_space() const
Definition usb_uart.h:94
size_t get_available() const
Definition usb_uart.h:91
void set_dummy_receiver(bool dummy_receiver)
Definition usb_uart.h:154
EventPool< UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT - 1 > output_pool_
Definition usb_uart.h:172
std::atomic< bool > input_started_
Definition usb_uart.h:180
std::atomic< bool > initialised_
Definition usb_uart.h:182
LockFreeQueue< UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT > output_queue_
Definition usb_uart.h:168
bool peek_byte(uint8_t *data) override
Definition usb_uart.cpp:189
void set_flush_timeout(uint32_t flush_timeout_ms) override
Definition usb_uart.h:156
std::function< void()> rx_callback_
Definition usb_uart.h:173
void set_parity(UARTParityOptions parity)
Definition usb_uart.h:152
void write_array(const uint8_t *data, size_t len) override
Definition usb_uart.cpp:139
void check_logger_conflict() override
Definition usb_uart.h:165
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:162
uart::UARTFlushResult flush() override
Definition usb_uart.cpp:173
static constexpr uint8_t USB_OUTPUT_CHUNK_COUNT
Definition usb_uart.h:140
bool read_array(uint8_t *data, size_t len) override
Definition usb_uart.cpp:196
void set_debug_prefix(const char *prefix)
Definition usb_uart.h:155
USBUartChannel(uint8_t index, uint16_t buffer_size)
Definition usb_uart.h:142
std::atomic< bool > output_started_
Definition usb_uart.h:181
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:565
USBUartChannel * cfg_pending_reload_
Definition usb_uart.h:243
virtual bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response)=0
void add_channel(USBUartChannel *channel)
Definition usb_uart.h:196
USBUartComponent(uint16_t vid, uint16_t pid)
Definition usb_uart.h:190
std::atomic< bool > cfg_done_
Definition usb_uart.h:244
std::vector< USBUartChannel * > channels_
Definition usb_uart.h:239
LockFreeQueue< UsbDataChunk, USB_DATA_QUEUE_SIZE > usb_data_queue_
Definition usb_uart.h:213
virtual void on_rx_overflow(USBUartChannel *channel)
Definition usb_uart.h:209
void start_output(USBUartChannel *channel)
Definition usb_uart.cpp:349
virtual bool config_device_step(uint8_t step, bool ok, const uint8_t *response)
Definition usb_uart.h:237
virtual void start_input(USBUartChannel *channel)
Definition usb_uart.cpp:280
std::vector< USBUartChannel * > get_channels()
Definition usb_uart.h:194
void apply_channel_settings(USBUartChannel *channel)
Definition usb_uart.cpp:540
static constexpr int USB_DATA_QUEUE_SIZE
Definition usb_uart.h:212
EventPool< UsbDataChunk, USB_DATA_QUEUE_SIZE - 1 > chunk_pool_
Definition usb_uart.h:215
std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl) override
Definition ch34x.cpp:158
bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override
Definition ch34x.cpp:98
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:276
bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override
Definition cp210x.cpp:100
USBUartTypeCP210X(uint16_t vid, uint16_t pid)
Definition usb_uart.h:268
std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl) override
Definition cp210x.cpp:46
USBUartTypeCdcAcm(uint16_t vid, uint16_t pid)
Definition usb_uart.h:257
virtual std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl)
Definition usb_uart.cpp:64
bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override
Definition usb_uart.cpp:494
USBUartTypeFT23XX(uint16_t vid, uint16_t pid)
Definition usb_uart.h:292
bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override
Definition ft23xx.cpp:344
void on_rx_overflow(USBUartChannel *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(USBUartChannel *channel) override
Definition ft23xx.cpp:273
USBUartTypePL2303(uint16_t vid, uint16_t pid)
Definition usb_uart.h:318
std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl) override
Definition pl2303.cpp:117
bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override
Definition pl2303.cpp:229
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:80
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:37
uint8_t data[usb_host::USB_MAX_PACKET_SIZE]
Definition usb_uart.h:111
uint8_t data[MAX_CHUNK_SIZE]
Definition usb_uart.h:122
static constexpr size_t MAX_CHUNK_SIZE
Definition usb_uart.h:121