ESPHome 2026.6.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[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 format_hex_pretty_to(buf, data + off, n, ',');
149 ESP_LOGD(TAG, "%s>>> %s", this->debug_prefix_.c_str(), buf);
150 }
151 }
152#endif
153 while (len > 0) {
154 UsbOutputChunk *chunk = this->output_pool_.allocate();
155 if (chunk == nullptr) {
156 ESP_LOGE(TAG, "Output pool full - lost %zu bytes", len);
157 break;
158 }
159 uint16_t chunk_len = std::min(len, UsbOutputChunk::MAX_CHUNK_SIZE);
160 memcpy(chunk->data, data, chunk_len);
161 chunk->length = static_cast<uint8_t>(chunk_len);
162 // Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
163 // allocate() returned non-null, the queue cannot be full.
164 this->output_queue_.push(chunk);
165 data += chunk_len;
166 len -= chunk_len;
167 }
168 this->parent_->start_output(this);
169}
170
172 // Spin until the output queue is drained and the last USB transfer completes.
173 // Safe to call from the main loop only.
174 // The flush_timeout_ms_ timeout guards against a device that stops responding mid-flush;
175 // in that case the main loop is blocked for the full duration.
176 uint32_t start = millis();
177 while ((!this->output_queue_.empty() || this->output_started_.load()) && millis() - start < this->flush_timeout_ms_) {
178 // Kick start_output() in case data arrived but no transfer is in flight yet.
179 this->parent_->start_output(this);
180 yield();
181 }
182 if (!this->output_queue_.empty() || this->output_started_.load())
185}
186
187bool USBUartChannel::peek_byte(uint8_t *data) {
188 if (this->input_buffer_.is_empty()) {
189 return false;
190 }
191 *data = this->input_buffer_.peek();
192 return true;
193}
194bool USBUartChannel::read_array(uint8_t *data, size_t len) {
195 if (!this->initialised_.load()) {
196 ESP_LOGV(TAG, "Channel not initialised - read ignored");
197 return false;
198 }
199 auto available = this->available();
200 bool status = true;
201 if (len > available) {
202 ESP_LOGV(TAG, "underflow: requested %zu but returned %d, bytes", len, available);
203 len = available;
204 status = false;
205 }
206 for (size_t i = 0; i != len; i++) {
207 *data++ = this->input_buffer_.pop();
208 }
209 this->parent_->start_input(this);
210 return status;
211}
212void USBUartComponent::setup() { USBClient::setup(); }
214 bool had_work = this->process_usb_events_();
215
216 // Process USB data from the lock-free queue
217 UsbDataChunk *chunk;
218 while ((chunk = this->usb_data_queue_.pop()) != nullptr) {
219 had_work = true;
220 auto *channel = chunk->channel;
221
222#ifdef USE_UART_DEBUGGER
223 if (channel->debug_) {
224 char buf[format_hex_pretty_size(usb_host::USB_MAX_PACKET_SIZE)]; // "XX,XX,...,XX\0"
225 format_hex_pretty_to(buf, chunk->data, chunk->length, ',');
226 ESP_LOGD(TAG, "%s<<< %s", channel->debug_prefix_.c_str(), buf);
227 }
228#endif
229
230 // Push data to ring buffer (now safe in main loop)
231 channel->input_buffer_.push(chunk->data, chunk->length);
232
233 // Return chunk to pool for reuse
234 this->chunk_pool_.release(chunk);
235
236 // Invoke the RX callback (if registered) immediately after data lands in the
237 // ring buffer. This lets consumers such as ZigbeeProxy process incoming bytes
238 // in the same loop iteration they are delivered, avoiding an extra wakeup cycle.
239 if (channel->rx_callback_) {
240 channel->rx_callback_();
241 }
242 }
243
244 // Log dropped USB data periodically
245 uint16_t dropped = this->usb_data_queue_.get_and_reset_dropped_count();
246 if (dropped > 0) {
247 ESP_LOGW(TAG, "Dropped %u USB data chunks due to buffer overflow", dropped);
248 }
249
250 // Disable loop when idle. Callbacks re-enable via enable_loop_soon_any_context().
251 if (!had_work) {
252 this->disable_loop();
253 }
254}
256 USBClient::dump_config();
257 for (auto &channel : this->channels_) {
258 ESP_LOGCONFIG(TAG,
259 " UART Channel %d\n"
260 " Baud Rate: %" PRIu32 " baud\n"
261 " Data Bits: %u\n"
262 " Parity: %s\n"
263 " Stop bits: %s\n"
264 " Flush Timeout: %" PRIu32 " ms\n"
265 " Debug: %s\n"
266 " Dummy receiver: %s",
267 channel->index_, channel->baud_rate_, channel->data_bits_, PARITY_NAMES[channel->parity_],
268 STOP_BITS_NAMES[channel->stop_bits_], channel->flush_timeout_ms_, YESNO(channel->debug_),
269 YESNO(channel->dummy_receiver_));
270 }
271}
273 if (!channel->initialised_.load())
274 return;
275 // THREAD CONTEXT: Called from both USB task and main loop threads
276 // - USB task: Immediate restart after successful transfer for continuous data flow
277 // - Main loop: Controlled restart after consuming data (backpressure mechanism)
278 //
279 // This dual-thread access is intentional for performance:
280 // - USB task restarts avoid context switch delays for high-speed data
281 // - Main loop restarts provide flow control when buffers are full
282 //
283 // The underlying transfer_in() uses lock-free atomic allocation from the
284 // TransferRequest pool, making this multi-threaded access safe
285
286 // Use compare_exchange_strong to avoid spurious failures: a missed submit here is
287 // never retried by read_array() because no data will ever arrive to trigger it.
288 auto started = false;
289 if (!channel->input_started_.compare_exchange_strong(started, true))
290 return;
291 const auto *ep = channel->cdc_dev_.in_ep;
292 // CALLBACK CONTEXT: This lambda is executed in USB task via transfer_callback
293 auto callback = [this, channel](const usb_host::TransferStatus &status) {
294 ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code);
295 if (!status.success) {
296 ESP_LOGE(TAG, "Input transfer failed, status=%s", esp_err_to_name(status.error_code));
297 // On failure, don't restart - let next read_array() trigger it
298 channel->input_started_.store(false);
299 return;
300 }
301
302 if (!channel->dummy_receiver_ && status.data_len > 0) {
303 // Allocate a chunk from the pool
304 UsbDataChunk *chunk = this->chunk_pool_.allocate();
305 if (chunk == nullptr) {
306 // No chunks available - queue is full or we're out of memory
307 this->usb_data_queue_.increment_dropped_count();
308 // Mark input as not started so we can retry
309 channel->input_started_.store(false);
310 return;
311 }
312
313 // Copy data to chunk (this is fast, happens in USB task)
314 memcpy(chunk->data, status.data, status.data_len);
315 chunk->length = status.data_len;
316 chunk->channel = channel;
317
318 // Push to lock-free queue for main loop processing
319 // Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
320 // allocate() returned non-null, the queue cannot be full.
321 this->usb_data_queue_.push(chunk);
322
323 // Re-enable component loop to process the queued data
325
326 // Wake main loop immediately to process USB data
328 }
329
330 // On success, restart input immediately from USB task for performance
331 // The lock-free queue will handle backpressure
332 channel->input_started_.store(false);
333 this->start_input(channel);
334 };
335 if (!this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize)) {
336 ESP_LOGE(TAG, "IN transfer submission failed for ep=0x%02X", ep->bEndpointAddress);
337 channel->input_started_.store(false);
338 }
339}
340
342 // THREAD CONTEXT: Called from both main loop and USB task threads.
343 // The output_queue_ is a lock-free SPSC queue, so pop() is safe from either thread.
344 // The output_started_ atomic flag is claimed via compare_exchange to guarantee that
345 // only one thread starts a transfer at a time.
346
347 // Atomically claim the "output in progress" flag. If already set, another thread
348 // is handling the transfer; return immediately.
349 bool expected = false;
350 if (!channel->output_started_.compare_exchange_strong(expected, true, std::memory_order_acq_rel)) {
351 return;
352 }
353
354 UsbOutputChunk *chunk = channel->output_queue_.pop();
355 if (chunk == nullptr) {
356 // Nothing to send — release the flag and return.
357 channel->output_started_.store(false, std::memory_order_release);
358 return;
359 }
360
361 const auto *ep = channel->cdc_dev_.out_ep;
362 // CALLBACK CONTEXT: This lambda is executed in the USB task via transfer_callback.
363 // It releases the chunk, clears the flag, and directly restarts output without
364 // going through defer() — eliminating one full main-loop-wakeup cycle of latency.
365 auto callback = [this, channel, chunk](const usb_host::TransferStatus &status) {
366 if (!status.success) {
367 ESP_LOGW(TAG, "Output transfer failed: status %X", status.error_code);
368 } else {
369 ESP_LOGV(TAG, "Output Transfer result: length: %u; status %X", status.data_len, status.error_code);
370 }
371 channel->output_pool_.release(chunk);
372 channel->output_started_.store(false, std::memory_order_release);
373 // Restart directly from USB task — safe because output_queue_ is lock-free
374 // and transfer_out() uses thread-safe atomic slot allocation.
375 this->start_output(channel);
376 };
377
378 const auto len = chunk->length;
379 if (!this->transfer_out(ep->bEndpointAddress, callback, chunk->data, len)) {
380 // Transfer submission failed — return chunk and release flag so callers can retry.
381 channel->output_pool_.release(chunk);
382 channel->output_started_.store(false, std::memory_order_release);
383 return;
384 }
385 ESP_LOGV(TAG, "Output %u bytes started", len);
386}
387
392static void fix_mps(const usb_ep_desc_t *ep) {
393 if (ep != nullptr) {
394 auto *ep_mutable = const_cast<usb_ep_desc_t *>(ep);
395 if (ep->wMaxPacketSize > usb_host::USB_MAX_PACKET_SIZE) {
396 ESP_LOGW(TAG, "Corrected MPS of EP 0x%02X from %u to %u", static_cast<uint8_t>(ep->bEndpointAddress & 0xFF),
397 ep->wMaxPacketSize, usb_host::USB_MAX_PACKET_SIZE);
398 ep_mutable->wMaxPacketSize = usb_host::USB_MAX_PACKET_SIZE;
399 }
400 }
401}
403 auto cdc_devs = this->parse_descriptors(this->device_handle_);
404 if (cdc_devs.empty()) {
405 this->status_set_error(LOG_STR("No CDC-ACM device found"));
406 this->disconnect();
407 return;
408 }
409 ESP_LOGD(TAG, "Found %zu CDC-ACM devices", cdc_devs.size());
410 size_t i = 0;
411 for (auto *channel : this->channels_) {
412 if (i == cdc_devs.size()) {
413 ESP_LOGE(TAG, "No configuration found for channel %d", channel->index_);
414 this->status_set_warning(LOG_STR("No configuration found for channel"));
415 break;
416 }
417 channel->cdc_dev_ = cdc_devs[i++];
418 fix_mps(channel->cdc_dev_.in_ep);
419 fix_mps(channel->cdc_dev_.out_ep);
420 channel->initialised_.store(true);
421 // Claim the communication (interrupt) interface so CDC class requests are accepted
422 // by the device. Some CDC ACM implementations (e.g. EFR32 NCP) require this before
423 // they enable data flow on the bulk endpoints.
424 if (channel->cdc_dev_.interrupt_interface_number != 0xFF &&
425 channel->cdc_dev_.interrupt_interface_number != channel->cdc_dev_.bulk_interface_number) {
426 auto err_comm = usb_host_interface_claim(this->handle_, this->device_handle_,
427 channel->cdc_dev_.interrupt_interface_number, 0);
428 if (err_comm != ESP_OK) {
429 ESP_LOGW(TAG, "Could not claim comm interface %d: %s", channel->cdc_dev_.interrupt_interface_number,
430 esp_err_to_name(err_comm));
431 channel->cdc_dev_.interrupt_interface_number = 0xFF; // Mark as unavailable, but continue anyway
432 } else {
433 ESP_LOGD(TAG, "Claimed comm interface %d", channel->cdc_dev_.interrupt_interface_number);
434 }
435 }
436 auto err =
437 usb_host_interface_claim(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number, 0);
438 if (err != ESP_OK) {
439 ESP_LOGE(TAG, "usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_,
440 channel->cdc_dev_.bulk_interface_number);
441 this->status_set_error(LOG_STR("usb_host_interface_claim failed"));
442 this->disconnect();
443 return;
444 }
445 }
446 this->status_clear_error();
447 this->enable_channels();
448}
449
451 for (auto *channel : this->channels_) {
452 if (channel->cdc_dev_.in_ep != nullptr) {
453 usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
454 usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
455 }
456 if (channel->cdc_dev_.out_ep != nullptr) {
457 usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
458 usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
459 }
460 if (channel->cdc_dev_.notify_ep != nullptr) {
461 usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
462 usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
463 }
464 if (channel->cdc_dev_.interrupt_interface_number != 0xFF &&
465 channel->cdc_dev_.interrupt_interface_number != channel->cdc_dev_.bulk_interface_number) {
466 usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.interrupt_interface_number);
467 channel->cdc_dev_.interrupt_interface_number = 0xFF;
468 }
469 usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number);
470 // Reset the input and output started flags to their initial state to avoid the possibility of spurious restarts
471 channel->input_started_.store(true);
472 channel->output_started_.store(true);
473 channel->input_buffer_.clear();
474 // Drain any pending output chunks and return them to the pool
475 {
476 UsbOutputChunk *chunk;
477 while ((chunk = channel->output_queue_.pop()) != nullptr) {
478 channel->output_pool_.release(chunk);
479 }
480 }
481 channel->initialised_.store(false);
482 }
483 USBClient::on_disconnected();
484}
485
487 static constexpr uint8_t CDC_REQUEST_TYPE = usb_host::USB_TYPE_CLASS | usb_host::USB_RECIP_INTERFACE;
488 static constexpr uint8_t CDC_SET_LINE_CODING = 0x20;
489 static constexpr uint8_t CDC_SET_CONTROL_LINE_STATE = 0x22;
490 static constexpr uint16_t CDC_DTR_RTS = 0x0003; // D0=DTR, D1=RTS
491
492 for (auto *channel : this->channels_) {
493 if (!channel->initialised_.load())
494 continue;
495 // Configure the bridge's UART parameters. A USB-UART bridge will not forward data
496 // at the correct speed until SET_LINE_CODING is sent; without it the UART may run
497 // at an indeterminate default rate so the NCP receives garbled bytes and never
498 // sends RSTACK.
499 uint32_t baud = channel->baud_rate_;
500 std::vector<uint8_t> line_coding = {
501 static_cast<uint8_t>(baud & 0xFF), static_cast<uint8_t>((baud >> 8) & 0xFF),
502 static_cast<uint8_t>((baud >> 16) & 0xFF), static_cast<uint8_t>((baud >> 24) & 0xFF),
503 static_cast<uint8_t>(channel->stop_bits_), // bCharFormat: 0=1stop, 1=1.5stop, 2=2stop
504 static_cast<uint8_t>(channel->parity_), // bParityType: 0=None, 1=Odd, 2=Even, 3=Mark, 4=Space
505 static_cast<uint8_t>(channel->data_bits_), // bDataBits
506 };
507 ESP_LOGD(TAG, "SET_LINE_CODING: baud=%u stop=%u parity=%u data=%u", (unsigned) baud, channel->stop_bits_,
508 (unsigned) channel->parity_, channel->data_bits_);
509 this->control_transfer(
510 CDC_REQUEST_TYPE, CDC_SET_LINE_CODING, 0, channel->cdc_dev_.interrupt_interface_number,
512 if (!status.success) {
513 ESP_LOGW(TAG, "SET_LINE_CODING failed: %X", status.error_code);
514 } else {
515 ESP_LOGD(TAG, "SET_LINE_CODING OK");
516 }
517 },
518 line_coding);
519 // Assert DTR+RTS to signal DTE is present.
520 this->control_transfer(CDC_REQUEST_TYPE, CDC_SET_CONTROL_LINE_STATE, CDC_DTR_RTS,
521 channel->cdc_dev_.interrupt_interface_number, [](const usb_host::TransferStatus &status) {
522 if (!status.success) {
523 ESP_LOGW(TAG, "SET_CONTROL_LINE_STATE failed: %X", status.error_code);
524 } else {
525 ESP_LOGD(TAG, "SET_CONTROL_LINE_STATE (DTR+RTS) OK");
526 }
527 });
528 }
529 this->start_channels_();
530}
531
532void USBUartTypeCdcAcm::start_channels_() {
533 for (auto *channel : this->channels_) {
534 if (!channel->initialised_.load())
535 continue;
536 channel->input_started_.store(false);
537 channel->output_started_.store(false);
538 this->start_input(channel);
539 }
540}
541
542} // namespace esphome::usb_uart
543
544#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:295
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:175
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:176
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:166
std::atomic< bool > input_started_
Definition usb_uart.h:174
std::atomic< bool > initialised_
Definition usb_uart.h:176
LockFreeQueue< UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT > output_queue_
Definition usb_uart.h:162
bool peek_byte(uint8_t *data) override
Definition usb_uart.cpp:187
void write_array(const uint8_t *data, size_t len) override
Definition usb_uart.cpp:137
uart::UARTFlushResult flush() override
Definition usb_uart.cpp:171
bool read_array(uint8_t *data, size_t len) override
Definition usb_uart.cpp:194
std::atomic< bool > output_started_
Definition usb_uart.h:175
std::vector< USBUartChannel * > channels_
Definition usb_uart.h:202
LockFreeQueue< UsbDataChunk, USB_DATA_QUEUE_SIZE > usb_data_queue_
Definition usb_uart.h:197
void start_output(USBUartChannel *channel)
Definition usb_uart.cpp:341
void start_input(USBUartChannel *channel)
Definition usb_uart.cpp:272
EventPool< UsbDataChunk, USB_DATA_QUEUE_SIZE - 1 > chunk_pool_
Definition usb_uart.h:199
virtual std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl)
Definition usb_uart.cpp:62
void yield(void)
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.
const void size_t len
Definition hal.h:64
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:340
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:1386
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
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[usb_host::USB_MAX_PACKET_SIZE]
Definition usb_uart.h:109
uint8_t data[MAX_CHUNK_SIZE]
Definition usb_uart.h:120
static constexpr size_t MAX_CHUNK_SIZE
Definition usb_uart.h:119