ESPHome 2026.5.0-dev
Loading...
Searching...
No Matches
usb_uart.cpp
Go to the documentation of this file.
1// Should not be needed, but it's required to pass CI clang-tidy checks
2#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
3#include "usb_uart.h"
4#include "esphome/core/log.h"
6
7#include <cinttypes>
8
9namespace esphome::usb_uart {
10
18static optional<CdcEps> get_cdc(const usb_config_desc_t *config_desc, uint8_t intf_idx) {
19 int conf_offset, ep_offset;
20 // look for an interface with an interrupt endpoint (notify), and one with two bulk endpoints (data in/out)
21 CdcEps eps{};
22 eps.bulk_interface_number = 0xFF;
23 eps.interrupt_interface_number = 0xFF;
24 for (;;) {
25 const auto *intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx++, 0, &conf_offset);
26 if (!intf_desc) {
27 ESP_LOGE(TAG, "usb_parse_interface_descriptor failed");
28 return nullopt;
29 }
30 ESP_LOGD(TAG, "intf_desc: bInterfaceClass=%02X, bInterfaceSubClass=%02X, bInterfaceProtocol=%02X, bNumEndpoints=%d",
31 intf_desc->bInterfaceClass, intf_desc->bInterfaceSubClass, intf_desc->bInterfaceProtocol,
32 intf_desc->bNumEndpoints);
33 for (uint8_t i = 0; i != intf_desc->bNumEndpoints; i++) {
34 ep_offset = conf_offset;
35 const auto *ep = usb_parse_endpoint_descriptor_by_index(intf_desc, i, config_desc->wTotalLength, &ep_offset);
36 if (!ep) {
37 ESP_LOGE(TAG, "Ran out of interfaces at %d before finding all endpoints", i);
38 return nullopt;
39 }
40 ESP_LOGD(TAG, "ep: bEndpointAddress=%02X, bmAttributes=%02X", ep->bEndpointAddress, ep->bmAttributes);
41 if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_INT) {
42 eps.notify_ep = ep;
43 eps.interrupt_interface_number = intf_desc->bInterfaceNumber;
44 } else if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_BULK && ep->bEndpointAddress & usb_host::USB_DIR_IN &&
45 (eps.bulk_interface_number == 0xFF || eps.bulk_interface_number == intf_desc->bInterfaceNumber)) {
46 eps.in_ep = ep;
47 eps.bulk_interface_number = intf_desc->bInterfaceNumber;
48 } else if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_BULK && !(ep->bEndpointAddress & usb_host::USB_DIR_IN) &&
49 (eps.bulk_interface_number == 0xFF || eps.bulk_interface_number == intf_desc->bInterfaceNumber)) {
50 eps.out_ep = ep;
51 eps.bulk_interface_number = intf_desc->bInterfaceNumber;
52 } else {
53 ESP_LOGE(TAG, "Unexpected endpoint attributes: %02X", ep->bmAttributes);
54 continue;
55 }
56 }
57 if (eps.in_ep != nullptr && eps.out_ep != nullptr && eps.notify_ep != nullptr)
58 return eps;
59 }
60}
61
62std::vector<CdcEps> USBUartTypeCdcAcm::parse_descriptors(usb_device_handle_t dev_hdl) {
63 const usb_config_desc_t *config_desc;
64 const usb_device_desc_t *device_desc;
65 int desc_offset = 0;
66 std::vector<CdcEps> cdc_devs{};
67
68 // Get required descriptors
69 if (usb_host_get_device_descriptor(dev_hdl, &device_desc) != ESP_OK) {
70 ESP_LOGE(TAG, "get_device_descriptor failed");
71 return {};
72 }
73 if (usb_host_get_active_config_descriptor(dev_hdl, &config_desc) != ESP_OK) {
74 ESP_LOGE(TAG, "get_active_config_descriptor failed");
75 return {};
76 }
77 if (device_desc->bDeviceClass == USB_CLASS_COMM || device_desc->bDeviceClass == USB_CLASS_VENDOR_SPEC) {
78 // single CDC-ACM device
79 if (auto eps = get_cdc(config_desc, 0)) {
80 ESP_LOGV(TAG, "Found CDC-ACM device");
81 cdc_devs.push_back(*eps);
82 }
83 return cdc_devs;
84 }
85 if (((device_desc->bDeviceClass == USB_CLASS_MISC) && (device_desc->bDeviceSubClass == USB_SUBCLASS_COMMON) &&
86 (device_desc->bDeviceProtocol == USB_DEVICE_PROTOCOL_IAD)) ||
87 ((device_desc->bDeviceClass == USB_CLASS_PER_INTERFACE) && (device_desc->bDeviceSubClass == USB_SUBCLASS_NULL) &&
88 (device_desc->bDeviceProtocol == USB_PROTOCOL_NULL))) {
89 // This is a composite device, that uses Interface Association Descriptor
90 const auto *this_desc = reinterpret_cast<const usb_standard_desc_t *>(config_desc);
91 for (;;) {
92 this_desc = usb_parse_next_descriptor_of_type(this_desc, config_desc->wTotalLength,
93 USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION, &desc_offset);
94 if (!this_desc)
95 break;
96 const auto *iad_desc = reinterpret_cast<const usb_iad_desc_t *>(this_desc);
97
98 if (iad_desc->bFunctionClass == USB_CLASS_COMM && iad_desc->bFunctionSubClass == USB_CDC_SUBCLASS_ACM) {
99 ESP_LOGV(TAG, "Found CDC-ACM device in composite device");
100 if (auto eps = get_cdc(config_desc, iad_desc->bFirstInterface))
101 cdc_devs.push_back(*eps);
102 }
103 }
104 }
105 return cdc_devs;
106}
107
108void RingBuffer::push(uint8_t item) {
109 if (this->get_free_space() == 0)
110 return;
111 this->buffer_[this->insert_pos_] = item;
112 this->insert_pos_ = (this->insert_pos_ + 1) % this->buffer_size_;
113}
114void RingBuffer::push(const uint8_t *data, size_t len) {
115 size_t free = this->get_free_space();
116 if (len > free)
117 len = free;
118 for (size_t i = 0; i != len; i++) {
119 this->buffer_[this->insert_pos_] = *data++;
120 this->insert_pos_ = (this->insert_pos_ + 1) % this->buffer_size_;
121 }
122}
123
125 uint8_t item = this->buffer_[this->read_pos_];
126 this->read_pos_ = (this->read_pos_ + 1) % this->buffer_size_;
127 return item;
128}
129size_t RingBuffer::pop(uint8_t *data, size_t len) {
130 len = std::min(len, this->get_available());
131 for (size_t i = 0; i != len; i++) {
132 *data++ = this->buffer_[this->read_pos_];
133 this->read_pos_ = (this->read_pos_ + 1) % this->buffer_size_;
134 }
135 return len;
136}
137void USBUartChannel::write_array(const uint8_t *data, size_t len) {
138 if (!this->initialised_.load()) {
139 ESP_LOGD(TAG, "Channel not initialised - write ignored");
140 return;
141 }
142#ifdef USE_UART_DEBUGGER
143 if (this->debug_) {
144 constexpr size_t BATCH = 16;
145 char buf[4 + format_hex_pretty_size(BATCH)]; // ">>> " + "XX,XX,...,XX\0"
146 for (size_t off = 0; off < len; off += BATCH) {
147 size_t n = std::min(len - off, BATCH);
148 memcpy(buf, ">>> ", 4);
149 format_hex_pretty_to(buf + 4, sizeof(buf) - 4, data + off, n, ',');
150 ESP_LOGD(TAG, "%s%s", this->debug_prefix_.c_str(), buf);
151 }
152 }
153#endif
154 while (len > 0) {
155 UsbOutputChunk *chunk = this->output_pool_.allocate();
156 if (chunk == nullptr) {
157 ESP_LOGE(TAG, "Output pool full - lost %zu bytes", len);
158 break;
159 }
160 size_t chunk_len = std::min(len, UsbOutputChunk::MAX_CHUNK_SIZE);
161 memcpy(chunk->data, data, chunk_len);
162 chunk->length = static_cast<uint8_t>(chunk_len);
163 // Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
164 // allocate() returned non-null, the queue cannot be full.
165 this->output_queue_.push(chunk);
166 data += chunk_len;
167 len -= chunk_len;
168 }
169 this->parent_->start_output(this);
170}
171
173 // Spin until the output queue is drained and the last USB transfer completes.
174 // Safe to call from the main loop only.
175 // The flush_timeout_ms_ timeout guards against a device that stops responding mid-flush;
176 // in that case the main loop is blocked for the full duration.
177 uint32_t start = millis();
178 while ((!this->output_queue_.empty() || this->output_started_.load()) && millis() - start < this->flush_timeout_ms_) {
179 // Kick start_output() in case data arrived but no transfer is in flight yet.
180 this->parent_->start_output(this);
181 yield();
182 }
183 if (!this->output_queue_.empty() || this->output_started_.load())
186}
187
188bool USBUartChannel::peek_byte(uint8_t *data) {
189 if (this->input_buffer_.is_empty()) {
190 return false;
191 }
192 *data = this->input_buffer_.peek();
193 return true;
194}
195bool USBUartChannel::read_array(uint8_t *data, size_t len) {
196 if (!this->initialised_.load()) {
197 ESP_LOGV(TAG, "Channel not initialised - read ignored");
198 return false;
199 }
200 auto available = this->available();
201 bool status = true;
202 if (len > available) {
203 ESP_LOGV(TAG, "underflow: requested %zu but returned %d, bytes", len, available);
204 len = available;
205 status = false;
206 }
207 for (size_t i = 0; i != len; i++) {
208 *data++ = this->input_buffer_.pop();
209 }
210 this->parent_->start_input(this);
211 return status;
212}
213void USBUartComponent::setup() { USBClient::setup(); }
215 bool had_work = this->process_usb_events_();
216
217 // Process USB data from the lock-free queue
218 UsbDataChunk *chunk;
219 while ((chunk = this->usb_data_queue_.pop()) != nullptr) {
220 had_work = true;
221 auto *channel = chunk->channel;
222
223#ifdef USE_UART_DEBUGGER
224 if (channel->debug_) {
225 char buf[4 + format_hex_pretty_size(UsbDataChunk::MAX_CHUNK_SIZE)]; // "<<< " + hex
226 memcpy(buf, "<<< ", 4);
227 format_hex_pretty_to(buf + 4, sizeof(buf) - 4, chunk->data, chunk->length, ',');
228 ESP_LOGD(TAG, "%s%s", channel->debug_prefix_.c_str(), buf);
229 }
230#endif
231
232 // Push data to ring buffer (now safe in main loop)
233 channel->input_buffer_.push(chunk->data, chunk->length);
234
235 // Return chunk to pool for reuse
236 this->chunk_pool_.release(chunk);
237
238 // Invoke the RX callback (if registered) immediately after data lands in the
239 // ring buffer. This lets consumers such as ZigbeeProxy process incoming bytes
240 // in the same loop iteration they are delivered, avoiding an extra wakeup cycle.
241 if (channel->rx_callback_) {
242 channel->rx_callback_();
243 }
244 }
245
246 // Log dropped USB data periodically
247 uint16_t dropped = this->usb_data_queue_.get_and_reset_dropped_count();
248 if (dropped > 0) {
249 ESP_LOGW(TAG, "Dropped %u USB data chunks due to buffer overflow", dropped);
250 }
251
252 // Disable loop when idle. Callbacks re-enable via enable_loop_soon_any_context().
253 if (!had_work) {
254 this->disable_loop();
255 }
256}
258 USBClient::dump_config();
259 for (auto &channel : this->channels_) {
260 ESP_LOGCONFIG(TAG,
261 " UART Channel %d\n"
262 " Baud Rate: %" PRIu32 " baud\n"
263 " Data Bits: %u\n"
264 " Parity: %s\n"
265 " Stop bits: %s\n"
266 " Flush Timeout: %" PRIu32 " ms\n"
267 " Debug: %s\n"
268 " Dummy receiver: %s",
269 channel->index_, channel->baud_rate_, channel->data_bits_, PARITY_NAMES[channel->parity_],
270 STOP_BITS_NAMES[channel->stop_bits_], channel->flush_timeout_ms_, YESNO(channel->debug_),
271 YESNO(channel->dummy_receiver_));
272 }
273}
275 if (!channel->initialised_.load())
276 return;
277 // THREAD CONTEXT: Called from both USB task and main loop threads
278 // - USB task: Immediate restart after successful transfer for continuous data flow
279 // - Main loop: Controlled restart after consuming data (backpressure mechanism)
280 //
281 // This dual-thread access is intentional for performance:
282 // - USB task restarts avoid context switch delays for high-speed data
283 // - Main loop restarts provide flow control when buffers are full
284 //
285 // The underlying transfer_in() uses lock-free atomic allocation from the
286 // TransferRequest pool, making this multi-threaded access safe
287
288 // Use compare_exchange_strong to avoid spurious failures: a missed submit here is
289 // never retried by read_array() because no data will ever arrive to trigger it.
290 auto started = false;
291 if (!channel->input_started_.compare_exchange_strong(started, true))
292 return;
293 const auto *ep = channel->cdc_dev_.in_ep;
294 // CALLBACK CONTEXT: This lambda is executed in USB task via transfer_callback
295 auto callback = [this, channel](const usb_host::TransferStatus &status) {
296 ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code);
297 if (!status.success) {
298 ESP_LOGE(TAG, "Input transfer failed, status=%s", esp_err_to_name(status.error_code));
299 // On failure, don't restart - let next read_array() trigger it
300 channel->input_started_.store(false);
301 return;
302 }
303
304 if (!channel->dummy_receiver_ && status.data_len > 0) {
305 // Allocate a chunk from the pool
306 UsbDataChunk *chunk = this->chunk_pool_.allocate();
307 if (chunk == nullptr) {
308 // No chunks available - queue is full or we're out of memory
309 this->usb_data_queue_.increment_dropped_count();
310 // Mark input as not started so we can retry
311 channel->input_started_.store(false);
312 return;
313 }
314
315 // Copy data to chunk (this is fast, happens in USB task)
316 memcpy(chunk->data, status.data, status.data_len);
317 chunk->length = status.data_len;
318 chunk->channel = channel;
319
320 // Push to lock-free queue for main loop processing
321 // Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
322 // allocate() returned non-null, the queue cannot be full.
323 this->usb_data_queue_.push(chunk);
324
325 // Re-enable component loop to process the queued data
327
328 // Wake main loop immediately to process USB data
330 }
331
332 // On success, restart input immediately from USB task for performance
333 // The lock-free queue will handle backpressure
334 channel->input_started_.store(false);
335 this->start_input(channel);
336 };
337 if (!this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize)) {
338 ESP_LOGE(TAG, "IN transfer submission failed for ep=0x%02X", ep->bEndpointAddress);
339 channel->input_started_.store(false);
340 }
341}
342
344 // THREAD CONTEXT: Called from both main loop and USB task threads.
345 // The output_queue_ is a lock-free SPSC queue, so pop() is safe from either thread.
346 // The output_started_ atomic flag is claimed via compare_exchange to guarantee that
347 // only one thread starts a transfer at a time.
348
349 // Atomically claim the "output in progress" flag. If already set, another thread
350 // is handling the transfer; return immediately.
351 bool expected = false;
352 if (!channel->output_started_.compare_exchange_strong(expected, true, std::memory_order_acq_rel)) {
353 return;
354 }
355
356 UsbOutputChunk *chunk = channel->output_queue_.pop();
357 if (chunk == nullptr) {
358 // Nothing to send — release the flag and return.
359 channel->output_started_.store(false, std::memory_order_release);
360 return;
361 }
362
363 const auto *ep = channel->cdc_dev_.out_ep;
364 // CALLBACK CONTEXT: This lambda is executed in the USB task via transfer_callback.
365 // It releases the chunk, clears the flag, and directly restarts output without
366 // going through defer() — eliminating one full main-loop-wakeup cycle of latency.
367 auto callback = [this, channel, chunk](const usb_host::TransferStatus &status) {
368 if (!status.success) {
369 ESP_LOGW(TAG, "Output transfer failed: status %X", status.error_code);
370 } else {
371 ESP_LOGV(TAG, "Output Transfer result: length: %u; status %X", status.data_len, status.error_code);
372 }
373 channel->output_pool_.release(chunk);
374 channel->output_started_.store(false, std::memory_order_release);
375 // Restart directly from USB task — safe because output_queue_ is lock-free
376 // and transfer_out() uses thread-safe atomic slot allocation.
377 this->start_output(channel);
378 };
379
380 const uint8_t len = chunk->length;
381 if (!this->transfer_out(ep->bEndpointAddress, callback, chunk->data, len)) {
382 // Transfer submission failed — return chunk and release flag so callers can retry.
383 channel->output_pool_.release(chunk);
384 channel->output_started_.store(false, std::memory_order_release);
385 return;
386 }
387 ESP_LOGV(TAG, "Output %u bytes started", len);
388}
389
394static void fix_mps(const usb_ep_desc_t *ep) {
395 if (ep != nullptr) {
396 auto *ep_mutable = const_cast<usb_ep_desc_t *>(ep);
397 if (ep->wMaxPacketSize > 64) {
398 ESP_LOGW(TAG, "Corrected MPS of EP 0x%02X from %u to 64", static_cast<uint8_t>(ep->bEndpointAddress & 0xFF),
399 ep->wMaxPacketSize);
400 ep_mutable->wMaxPacketSize = 64;
401 }
402 }
403}
405 auto cdc_devs = this->parse_descriptors(this->device_handle_);
406 if (cdc_devs.empty()) {
407 this->status_set_error(LOG_STR("No CDC-ACM device found"));
408 this->disconnect();
409 return;
410 }
411 ESP_LOGD(TAG, "Found %zu CDC-ACM devices", cdc_devs.size());
412 size_t i = 0;
413 for (auto *channel : this->channels_) {
414 if (i == cdc_devs.size()) {
415 ESP_LOGE(TAG, "No configuration found for channel %d", channel->index_);
416 this->status_set_warning(LOG_STR("No configuration found for channel"));
417 break;
418 }
419 channel->cdc_dev_ = cdc_devs[i++];
420 fix_mps(channel->cdc_dev_.in_ep);
421 fix_mps(channel->cdc_dev_.out_ep);
422 channel->initialised_.store(true);
423 // Claim the communication (interrupt) interface so CDC class requests are accepted
424 // by the device. Some CDC ACM implementations (e.g. EFR32 NCP) require this before
425 // they enable data flow on the bulk endpoints.
426 if (channel->cdc_dev_.interrupt_interface_number != 0xFF &&
427 channel->cdc_dev_.interrupt_interface_number != channel->cdc_dev_.bulk_interface_number) {
428 auto err_comm = usb_host_interface_claim(this->handle_, this->device_handle_,
429 channel->cdc_dev_.interrupt_interface_number, 0);
430 if (err_comm != ESP_OK) {
431 ESP_LOGW(TAG, "Could not claim comm interface %d: %s", channel->cdc_dev_.interrupt_interface_number,
432 esp_err_to_name(err_comm));
433 channel->cdc_dev_.interrupt_interface_number = 0xFF; // Mark as unavailable, but continue anyway
434 } else {
435 ESP_LOGD(TAG, "Claimed comm interface %d", channel->cdc_dev_.interrupt_interface_number);
436 }
437 }
438 auto err =
439 usb_host_interface_claim(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number, 0);
440 if (err != ESP_OK) {
441 ESP_LOGE(TAG, "usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_,
442 channel->cdc_dev_.bulk_interface_number);
443 this->status_set_error(LOG_STR("usb_host_interface_claim failed"));
444 this->disconnect();
445 return;
446 }
447 }
448 this->status_clear_error();
449 this->enable_channels();
450}
451
453 for (auto *channel : this->channels_) {
454 if (channel->cdc_dev_.in_ep != nullptr) {
455 usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
456 usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
457 }
458 if (channel->cdc_dev_.out_ep != nullptr) {
459 usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
460 usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
461 }
462 if (channel->cdc_dev_.notify_ep != nullptr) {
463 usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
464 usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
465 }
466 if (channel->cdc_dev_.interrupt_interface_number != 0xFF &&
467 channel->cdc_dev_.interrupt_interface_number != channel->cdc_dev_.bulk_interface_number) {
468 usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.interrupt_interface_number);
469 channel->cdc_dev_.interrupt_interface_number = 0xFF;
470 }
471 usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number);
472 // Reset the input and output started flags to their initial state to avoid the possibility of spurious restarts
473 channel->input_started_.store(true);
474 channel->output_started_.store(true);
475 channel->input_buffer_.clear();
476 // Drain any pending output chunks and return them to the pool
477 {
478 UsbOutputChunk *chunk;
479 while ((chunk = channel->output_queue_.pop()) != nullptr) {
480 channel->output_pool_.release(chunk);
481 }
482 }
483 channel->initialised_.store(false);
484 }
485 USBClient::on_disconnected();
486}
487
489 static constexpr uint8_t CDC_REQUEST_TYPE = usb_host::USB_TYPE_CLASS | usb_host::USB_RECIP_INTERFACE;
490 static constexpr uint8_t CDC_SET_LINE_CODING = 0x20;
491 static constexpr uint8_t CDC_SET_CONTROL_LINE_STATE = 0x22;
492 static constexpr uint16_t CDC_DTR_RTS = 0x0003; // D0=DTR, D1=RTS
493
494 for (auto *channel : this->channels_) {
495 if (!channel->initialised_.load())
496 continue;
497 // Configure the bridge's UART parameters. A USB-UART bridge will not forward data
498 // at the correct speed until SET_LINE_CODING is sent; without it the UART may run
499 // at an indeterminate default rate so the NCP receives garbled bytes and never
500 // sends RSTACK.
501 uint32_t baud = channel->baud_rate_;
502 std::vector<uint8_t> line_coding = {
503 static_cast<uint8_t>(baud & 0xFF), static_cast<uint8_t>((baud >> 8) & 0xFF),
504 static_cast<uint8_t>((baud >> 16) & 0xFF), static_cast<uint8_t>((baud >> 24) & 0xFF),
505 static_cast<uint8_t>(channel->stop_bits_), // bCharFormat: 0=1stop, 1=1.5stop, 2=2stop
506 static_cast<uint8_t>(channel->parity_), // bParityType: 0=None, 1=Odd, 2=Even, 3=Mark, 4=Space
507 static_cast<uint8_t>(channel->data_bits_), // bDataBits
508 };
509 ESP_LOGD(TAG, "SET_LINE_CODING: baud=%u stop=%u parity=%u data=%u", (unsigned) baud, channel->stop_bits_,
510 (unsigned) channel->parity_, channel->data_bits_);
511 this->control_transfer(
512 CDC_REQUEST_TYPE, CDC_SET_LINE_CODING, 0, channel->cdc_dev_.interrupt_interface_number,
514 if (!status.success) {
515 ESP_LOGW(TAG, "SET_LINE_CODING failed: %X", status.error_code);
516 } else {
517 ESP_LOGD(TAG, "SET_LINE_CODING OK");
518 }
519 },
520 line_coding);
521 // Assert DTR+RTS to signal DTE is present.
522 this->control_transfer(CDC_REQUEST_TYPE, CDC_SET_CONTROL_LINE_STATE, CDC_DTR_RTS,
523 channel->cdc_dev_.interrupt_interface_number, [](const usb_host::TransferStatus &status) {
524 if (!status.success) {
525 ESP_LOGW(TAG, "SET_CONTROL_LINE_STATE failed: %X", status.error_code);
526 } else {
527 ESP_LOGD(TAG, "SET_CONTROL_LINE_STATE (DTR+RTS) OK");
528 }
529 });
530 }
531 this->start_channels();
532}
533
534void USBUartTypeCdcAcm::start_channels() {
535 for (auto *channel : this->channels_) {
536 if (!channel->initialised_.load())
537 continue;
538 channel->input_started_.store(false);
539 channel->output_started_.store(false);
540 this->start_input(channel);
541 }
542}
543
544} // namespace esphome::usb_uart
545
546#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
uint8_t status
Definition bl0942.h:8
void wake_loop_threadsafe()
Wake the main event loop from another thread or callback.
void status_clear_error()
Definition component.h:312
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void disable_loop()
Disable this component's loop.
constexpr const char * c_str() const
Definition string_ref.h:73
usb_host_client_handle_t handle_
Definition usb_host.h:173
bool transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length)
Performs an output transfer operation.
bool control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const transfer_cb_t &callback, const std::vector< uint8_t > &data={})
bool transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length)
Performs a transfer input operation.
usb_device_handle_t device_handle_
Definition usb_host.h:174
void push(uint8_t item)
Definition usb_uart.cpp:108
size_t get_free_space() const
Definition usb_uart.h:92
size_t get_available() const
Definition usb_uart.h:89
EventPool< UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT - 1 > output_pool_
Definition usb_uart.h:165
std::atomic< bool > input_started_
Definition usb_uart.h:173
std::atomic< bool > initialised_
Definition usb_uart.h:175
LockFreeQueue< UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT > output_queue_
Definition usb_uart.h:161
bool peek_byte(uint8_t *data) override
Definition usb_uart.cpp:188
void write_array(const uint8_t *data, size_t len) override
Definition usb_uart.cpp:137
uart::UARTFlushResult flush() override
Definition usb_uart.cpp:172
bool read_array(uint8_t *data, size_t len) override
Definition usb_uart.cpp:195
std::atomic< bool > output_started_
Definition usb_uart.h:174
std::vector< USBUartChannel * > channels_
Definition usb_uart.h:201
LockFreeQueue< UsbDataChunk, USB_DATA_QUEUE_SIZE > usb_data_queue_
Definition usb_uart.h:196
void start_output(USBUartChannel *channel)
Definition usb_uart.cpp:343
void start_input(USBUartChannel *channel)
Definition usb_uart.cpp:274
EventPool< UsbDataChunk, USB_DATA_QUEUE_SIZE - 1 > chunk_pool_
Definition usb_uart.h:198
virtual std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl)
Definition usb_uart.cpp:62
UARTFlushResult
Result of a flush() call.
@ UART_FLUSH_RESULT_SUCCESS
Confirmed: all bytes left the TX FIFO.
@ UART_FLUSH_RESULT_TIMEOUT
Confirmed: timed out before TX completed.
std::string size_t len
Definition helpers.h:1045
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:409
void HOT yield()
Definition core.cpp:25
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:1368
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
const usb_ep_desc_t * out_ep
Definition usb_uart.h:33
const usb_ep_desc_t * in_ep
Definition usb_uart.h:32
uint8_t data[MAX_CHUNK_SIZE]
Definition usb_uart.h:110
static constexpr size_t MAX_CHUNK_SIZE
Definition usb_uart.h:109
uint8_t data[MAX_CHUNK_SIZE]
Definition usb_uart.h:121
static constexpr size_t MAX_CHUNK_SIZE
Definition usb_uart.h:120