ESPHome 2026.3.0
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 instead of waiting for select() timeout
329#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
331#endif
332 }
333
334 // On success, restart input immediately from USB task for performance
335 // The lock-free queue will handle backpressure
336 channel->input_started_.store(false);
337 this->start_input(channel);
338 };
339 if (!this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize)) {
340 ESP_LOGE(TAG, "IN transfer submission failed for ep=0x%02X", ep->bEndpointAddress);
341 channel->input_started_.store(false);
342 }
343}
344
346 // THREAD CONTEXT: Called from both main loop and USB task threads.
347 // The output_queue_ is a lock-free SPSC queue, so pop() is safe from either thread.
348 // The output_started_ atomic flag is claimed via compare_exchange to guarantee that
349 // only one thread starts a transfer at a time.
350
351 // Atomically claim the "output in progress" flag. If already set, another thread
352 // is handling the transfer; return immediately.
353 bool expected = false;
354 if (!channel->output_started_.compare_exchange_strong(expected, true, std::memory_order_acq_rel)) {
355 return;
356 }
357
358 UsbOutputChunk *chunk = channel->output_queue_.pop();
359 if (chunk == nullptr) {
360 // Nothing to send — release the flag and return.
361 channel->output_started_.store(false, std::memory_order_release);
362 return;
363 }
364
365 const auto *ep = channel->cdc_dev_.out_ep;
366 // CALLBACK CONTEXT: This lambda is executed in the USB task via transfer_callback.
367 // It releases the chunk, clears the flag, and directly restarts output without
368 // going through defer() — eliminating one full main-loop-wakeup cycle of latency.
369 auto callback = [this, channel, chunk](const usb_host::TransferStatus &status) {
370 if (!status.success) {
371 ESP_LOGW(TAG, "Output transfer failed: status %X", status.error_code);
372 } else {
373 ESP_LOGV(TAG, "Output Transfer result: length: %u; status %X", status.data_len, status.error_code);
374 }
375 channel->output_pool_.release(chunk);
376 channel->output_started_.store(false, std::memory_order_release);
377 // Restart directly from USB task — safe because output_queue_ is lock-free
378 // and transfer_out() uses thread-safe atomic slot allocation.
379 this->start_output(channel);
380 };
381
382 const uint8_t len = chunk->length;
383 if (!this->transfer_out(ep->bEndpointAddress, callback, chunk->data, len)) {
384 // Transfer submission failed — return chunk and release flag so callers can retry.
385 channel->output_pool_.release(chunk);
386 channel->output_started_.store(false, std::memory_order_release);
387 return;
388 }
389 ESP_LOGV(TAG, "Output %u bytes started", len);
390}
391
396static void fix_mps(const usb_ep_desc_t *ep) {
397 if (ep != nullptr) {
398 auto *ep_mutable = const_cast<usb_ep_desc_t *>(ep);
399 if (ep->wMaxPacketSize > 64) {
400 ESP_LOGW(TAG, "Corrected MPS of EP 0x%02X from %u to 64", static_cast<uint8_t>(ep->bEndpointAddress & 0xFF),
401 ep->wMaxPacketSize);
402 ep_mutable->wMaxPacketSize = 64;
403 }
404 }
405}
407 auto cdc_devs = this->parse_descriptors(this->device_handle_);
408 if (cdc_devs.empty()) {
409 this->status_set_error(LOG_STR("No CDC-ACM device found"));
410 this->disconnect();
411 return;
412 }
413 ESP_LOGD(TAG, "Found %zu CDC-ACM devices", cdc_devs.size());
414 size_t i = 0;
415 for (auto *channel : this->channels_) {
416 if (i == cdc_devs.size()) {
417 ESP_LOGE(TAG, "No configuration found for channel %d", channel->index_);
418 this->status_set_warning("No configuration found for channel");
419 break;
420 }
421 channel->cdc_dev_ = cdc_devs[i++];
422 fix_mps(channel->cdc_dev_.in_ep);
423 fix_mps(channel->cdc_dev_.out_ep);
424 channel->initialised_.store(true);
425 // Claim the communication (interrupt) interface so CDC class requests are accepted
426 // by the device. Some CDC ACM implementations (e.g. EFR32 NCP) require this before
427 // they enable data flow on the bulk endpoints.
428 if (channel->cdc_dev_.interrupt_interface_number != 0xFF &&
429 channel->cdc_dev_.interrupt_interface_number != channel->cdc_dev_.bulk_interface_number) {
430 auto err_comm = usb_host_interface_claim(this->handle_, this->device_handle_,
431 channel->cdc_dev_.interrupt_interface_number, 0);
432 if (err_comm != ESP_OK) {
433 ESP_LOGW(TAG, "Could not claim comm interface %d: %s", channel->cdc_dev_.interrupt_interface_number,
434 esp_err_to_name(err_comm));
435 channel->cdc_dev_.interrupt_interface_number = 0xFF; // Mark as unavailable, but continue anyway
436 } else {
437 ESP_LOGD(TAG, "Claimed comm interface %d", channel->cdc_dev_.interrupt_interface_number);
438 }
439 }
440 auto err =
441 usb_host_interface_claim(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number, 0);
442 if (err != ESP_OK) {
443 ESP_LOGE(TAG, "usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_,
444 channel->cdc_dev_.bulk_interface_number);
445 this->status_set_error(LOG_STR("usb_host_interface_claim failed"));
446 this->disconnect();
447 return;
448 }
449 }
450 this->status_clear_error();
451 this->enable_channels();
452}
453
455 for (auto *channel : this->channels_) {
456 if (channel->cdc_dev_.in_ep != nullptr) {
457 usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
458 usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
459 }
460 if (channel->cdc_dev_.out_ep != nullptr) {
461 usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
462 usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
463 }
464 if (channel->cdc_dev_.notify_ep != nullptr) {
465 usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
466 usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
467 }
468 if (channel->cdc_dev_.interrupt_interface_number != 0xFF &&
469 channel->cdc_dev_.interrupt_interface_number != channel->cdc_dev_.bulk_interface_number) {
470 usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.interrupt_interface_number);
471 channel->cdc_dev_.interrupt_interface_number = 0xFF;
472 }
473 usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number);
474 // Reset the input and output started flags to their initial state to avoid the possibility of spurious restarts
475 channel->input_started_.store(true);
476 channel->output_started_.store(true);
477 channel->input_buffer_.clear();
478 // Drain any pending output chunks and return them to the pool
479 {
480 UsbOutputChunk *chunk;
481 while ((chunk = channel->output_queue_.pop()) != nullptr) {
482 channel->output_pool_.release(chunk);
483 }
484 }
485 channel->initialised_.store(false);
486 }
487 USBClient::on_disconnected();
488}
489
491 static constexpr uint8_t CDC_REQUEST_TYPE = usb_host::USB_TYPE_CLASS | usb_host::USB_RECIP_INTERFACE;
492 static constexpr uint8_t CDC_SET_LINE_CODING = 0x20;
493 static constexpr uint8_t CDC_SET_CONTROL_LINE_STATE = 0x22;
494 static constexpr uint16_t CDC_DTR_RTS = 0x0003; // D0=DTR, D1=RTS
495
496 for (auto *channel : this->channels_) {
497 if (!channel->initialised_.load())
498 continue;
499 // Configure the bridge's UART parameters. A USB-UART bridge will not forward data
500 // at the correct speed until SET_LINE_CODING is sent; without it the UART may run
501 // at an indeterminate default rate so the NCP receives garbled bytes and never
502 // sends RSTACK.
503 uint32_t baud = channel->baud_rate_;
504 std::vector<uint8_t> line_coding = {
505 static_cast<uint8_t>(baud & 0xFF), static_cast<uint8_t>((baud >> 8) & 0xFF),
506 static_cast<uint8_t>((baud >> 16) & 0xFF), static_cast<uint8_t>((baud >> 24) & 0xFF),
507 static_cast<uint8_t>(channel->stop_bits_), // bCharFormat: 0=1stop, 1=1.5stop, 2=2stop
508 static_cast<uint8_t>(channel->parity_), // bParityType: 0=None, 1=Odd, 2=Even, 3=Mark, 4=Space
509 static_cast<uint8_t>(channel->data_bits_), // bDataBits
510 };
511 ESP_LOGD(TAG, "SET_LINE_CODING: baud=%u stop=%u parity=%u data=%u", (unsigned) baud, channel->stop_bits_,
512 (unsigned) channel->parity_, channel->data_bits_);
513 this->control_transfer(
514 CDC_REQUEST_TYPE, CDC_SET_LINE_CODING, 0, channel->cdc_dev_.interrupt_interface_number,
516 if (!status.success) {
517 ESP_LOGW(TAG, "SET_LINE_CODING failed: %X", status.error_code);
518 } else {
519 ESP_LOGD(TAG, "SET_LINE_CODING OK");
520 }
521 },
522 line_coding);
523 // Assert DTR+RTS to signal DTE is present.
524 this->control_transfer(CDC_REQUEST_TYPE, CDC_SET_CONTROL_LINE_STATE, CDC_DTR_RTS,
525 channel->cdc_dev_.interrupt_interface_number, [](const usb_host::TransferStatus &status) {
526 if (!status.success) {
527 ESP_LOGW(TAG, "SET_CONTROL_LINE_STATE failed: %X", status.error_code);
528 } else {
529 ESP_LOGD(TAG, "SET_CONTROL_LINE_STATE (DTR+RTS) OK");
530 }
531 });
532 }
533 this->start_channels();
534}
535
536void USBUartTypeCdcAcm::start_channels() {
537 for (auto *channel : this->channels_) {
538 if (!channel->initialised_.load())
539 continue;
540 channel->input_started_.store(false);
541 channel->output_started_.store(false);
542 this->start_input(channel);
543 }
544}
545
546} // namespace esphome::usb_uart
547
548#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 FreeRTOS task.
void status_set_warning(const char *message=nullptr)
void status_clear_error()
Definition component.h:260
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:164
std::atomic< bool > input_started_
Definition usb_uart.h:172
std::atomic< bool > initialised_
Definition usb_uart.h:174
LockFreeQueue< UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT > output_queue_
Definition usb_uart.h:160
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
bool read_array(uint8_t *data, size_t len) override
Definition usb_uart.cpp:195
uart::FlushResult flush() override
Definition usb_uart.cpp:172
std::atomic< bool > output_started_
Definition usb_uart.h:173
std::vector< USBUartChannel * > channels_
Definition usb_uart.h:200
LockFreeQueue< UsbDataChunk, USB_DATA_QUEUE_SIZE > usb_data_queue_
Definition usb_uart.h:195
void start_output(USBUartChannel *channel)
Definition usb_uart.cpp:345
void start_input(USBUartChannel *channel)
Definition usb_uart.cpp:274
EventPool< UsbDataChunk, USB_DATA_QUEUE_SIZE - 1 > chunk_pool_
Definition usb_uart.h:197
virtual std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl)
Definition usb_uart.cpp:62
FlushResult
Result of a flush() call.
@ TIMEOUT
Confirmed: timed out before TX completed.
@ SUCCESS
Confirmed: all bytes left the TX FIFO.
std::string size_t len
Definition helpers.h:892
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:353
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:1200
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