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