ESPHome 2026.8.0-dev
Loading...
Searching...
No Matches
usb_host_client.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 defined(USE_ESP32_VARIANT_ESP32S31) || defined(USE_ESP32_VARIANT_ESP32H4)
4#include "usb_host.h"
5#include "esphome/core/log.h"
6#include "esphome/core/hal.h"
9
10#include <cinttypes>
11#include <cstring>
12#include <atomic>
13#include <span>
14namespace esphome::usb_host {
15
16#pragma GCC diagnostic ignored "-Wparentheses"
17
18using namespace bytebuffer;
19
20#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
21static void print_ep_desc(const usb_ep_desc_t *ep_desc) {
22 const char *ep_type_str;
23 int type = ep_desc->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK;
24
25 switch (type) {
26 case USB_BM_ATTRIBUTES_XFER_CONTROL:
27 ep_type_str = "CTRL";
28 break;
29 case USB_BM_ATTRIBUTES_XFER_ISOC:
30 ep_type_str = "ISOC";
31 break;
32 case USB_BM_ATTRIBUTES_XFER_BULK:
33 ep_type_str = "BULK";
34 break;
35 case USB_BM_ATTRIBUTES_XFER_INT:
36 ep_type_str = "INT";
37 break;
38 default:
39 ep_type_str = NULL;
40 break;
41 }
42
43 ESP_LOGV(TAG,
44 "\t\t*** Endpoint descriptor ***\n"
45 "\t\tbLength %d\n"
46 "\t\tbDescriptorType %d\n"
47 "\t\tbEndpointAddress 0x%x\tEP %d %s\n"
48 "\t\tbmAttributes 0x%x\t%s\n"
49 "\t\twMaxPacketSize %d\n"
50 "\t\tbInterval %d",
51 ep_desc->bLength, ep_desc->bDescriptorType, ep_desc->bEndpointAddress, USB_EP_DESC_GET_EP_NUM(ep_desc),
52 USB_EP_DESC_GET_EP_DIR(ep_desc) ? "IN" : "OUT", ep_desc->bmAttributes, ep_type_str, ep_desc->wMaxPacketSize,
53 ep_desc->bInterval);
54}
55
56static void usbh_print_intf_desc(const usb_intf_desc_t *intf_desc) {
57 ESP_LOGV(TAG,
58 "\t*** Interface descriptor ***\n"
59 "\tbLength %d\n"
60 "\tbDescriptorType %d\n"
61 "\tbInterfaceNumber %d\n"
62 "\tbAlternateSetting %d\n"
63 "\tbNumEndpoints %d\n"
64 "\tbInterfaceClass 0x%x\n"
65 "\tiInterface %d",
66 intf_desc->bLength, intf_desc->bDescriptorType, intf_desc->bInterfaceNumber, intf_desc->bAlternateSetting,
67 intf_desc->bNumEndpoints, intf_desc->bInterfaceProtocol, intf_desc->iInterface);
68}
69
70static void usbh_print_cfg_desc(const usb_config_desc_t *cfg_desc) {
71 ESP_LOGV(TAG,
72 "*** Configuration descriptor ***\n"
73 " bLength %d\n"
74 " bDescriptorType %d\n"
75 " wTotalLength %d\n"
76 " bNumInterfaces %d\n"
77 " bConfigurationValue %d\n"
78 " iConfiguration %d\n"
79 " bmAttributes 0x%x\n"
80 " bMaxPower %dmA",
81 cfg_desc->bLength, cfg_desc->bDescriptorType, cfg_desc->wTotalLength, cfg_desc->bNumInterfaces,
82 cfg_desc->bConfigurationValue, cfg_desc->iConfiguration, cfg_desc->bmAttributes, cfg_desc->bMaxPower * 2);
83}
84
85static void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) {
86 if (devc_desc == NULL) {
87 return;
88 }
89
90 ESP_LOGV(TAG,
91 "*** Device descriptor ***\n"
92 " bLength %d\n"
93 " bDescriptorType %d\n"
94 " bcdUSB %d.%d0\n"
95 " bDeviceClass 0x%x\n"
96 " bDeviceSubClass 0x%x\n"
97 " bDeviceProtocol 0x%x\n"
98 " bMaxPacketSize0 %d\n"
99 " idVendor 0x%x\n"
100 " idProduct 0x%x\n"
101 " bcdDevice %d.%d0\n"
102 " iManufacturer %d\n"
103 " iProduct %d\n"
104 " iSerialNumber %d\n"
105 " bNumConfigurations %d",
106 devc_desc->bLength, devc_desc->bDescriptorType, ((devc_desc->bcdUSB >> 8) & 0xF),
107 ((devc_desc->bcdUSB >> 4) & 0xF), devc_desc->bDeviceClass, devc_desc->bDeviceSubClass,
108 devc_desc->bDeviceProtocol, devc_desc->bMaxPacketSize0, devc_desc->idVendor, devc_desc->idProduct,
109 ((devc_desc->bcdDevice >> 8) & 0xF), ((devc_desc->bcdDevice >> 4) & 0xF), devc_desc->iManufacturer,
110 devc_desc->iProduct, devc_desc->iSerialNumber, devc_desc->bNumConfigurations);
111}
112
113static void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc,
114 print_class_descriptor_cb class_specific_cb) {
115 if (cfg_desc == nullptr) {
116 return;
117 }
118
119 int offset = 0;
120 uint16_t w_total_length = cfg_desc->wTotalLength;
121 const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *) cfg_desc;
122
123 do {
124 switch (next_desc->bDescriptorType) {
125 case USB_W_VALUE_DT_CONFIG:
126 usbh_print_cfg_desc((const usb_config_desc_t *) next_desc);
127 break;
128 case USB_W_VALUE_DT_INTERFACE:
129 usbh_print_intf_desc((const usb_intf_desc_t *) next_desc);
130 break;
131 case USB_W_VALUE_DT_ENDPOINT:
132 print_ep_desc((const usb_ep_desc_t *) next_desc);
133 break;
134 default:
135 if (class_specific_cb) {
136 class_specific_cb(next_desc);
137 }
138 break;
139 }
140
141 next_desc = usb_parse_next_descriptor(next_desc, w_total_length, &offset);
142
143 } while (next_desc != NULL);
144}
145#endif
146// USB string descriptors: bLength (uint8_t, max 255) includes the 2-byte header (bLength and bDescriptorType).
147// Character count = (bLength - 2) / 2, max 126 chars + null terminator.
148static constexpr size_t DESC_STRING_BUF_SIZE = 128;
149
150static const char *get_descriptor_string(const usb_str_desc_t *desc, std::span<char, DESC_STRING_BUF_SIZE> buffer) {
151 if (desc == nullptr || desc->bLength < 2)
152 return "(unspecified)";
153 int char_count = (desc->bLength - 2) / 2;
154 char *p = buffer.data();
155 char *end = p + buffer.size() - 1;
156 for (int i = 0; i != char_count && p < end; i++) {
157 auto c = desc->wData[i];
158 if (c < 0x100)
159 *p++ = static_cast<char>(c);
160 }
161 *p = '\0';
162 return buffer.data();
163}
164
165// CALLBACK CONTEXT: USB task (called from usb_host_client_handle_events in USB task)
166static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void *ptr) {
167 auto *client = static_cast<USBClient *>(ptr);
168
169 // Allocate event from pool
170 UsbEvent *event = client->event_pool.allocate();
171 if (event == nullptr) {
172 // No events available - increment counter for periodic logging
173 client->event_queue.increment_dropped_count();
174 return;
175 }
176
177 // Queue events to be processed in main loop
178 switch (event_msg->event) {
179 case USB_HOST_CLIENT_EVENT_NEW_DEV: {
180 ESP_LOGD(TAG, "New device %d", event_msg->new_dev.address);
181 event->type = EVENT_DEVICE_NEW;
182 event->data.device_new.address = event_msg->new_dev.address;
183 break;
184 }
185 case USB_HOST_CLIENT_EVENT_DEV_GONE: {
186 ESP_LOGD(TAG, "Device gone");
187 event->type = EVENT_DEVICE_GONE;
188 event->data.device_gone.handle = event_msg->dev_gone.dev_hdl;
189 break;
190 }
191 default:
192 ESP_LOGD(TAG, "Unknown event %d", event_msg->event);
193 client->event_pool.release(event);
194 return;
195 }
196
197 // Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
198 // allocate() returned non-null, the queue cannot be full.
199 client->event_queue.push(event);
200
201 // Re-enable component loop to process the queued event
202 client->enable_loop_soon_any_context();
203
204 // Wake main loop immediately to process USB event
206}
208 usb_host_client_config_t config{.is_synchronous = false,
209 .max_num_event_msg = 5,
210 .async = {.client_event_callback = client_event_cb, .callback_arg = this}};
211 auto err = usb_host_client_register(&config, &this->handle_);
212 if (err != ESP_OK) {
213 ESP_LOGE(TAG, "client register failed: %s", esp_err_to_name(err));
214 this->status_set_error(LOG_STR("Client register failed"));
215 this->mark_failed();
216 return;
217 }
218 // Pre-allocate USB transfer buffers for all slots at startup
219 // This avoids any dynamic allocation during runtime
220 for (auto &request : this->requests_) {
221 usb_host_transfer_alloc(USB_MAX_PACKET_SIZE, 0, &request.transfer);
222 request.client = this; // Set once, never changes
223 }
224
225 // Create and start USB task
226 xTaskCreate(usb_task_fn, "usb_task",
227 USB_TASK_STACK_SIZE, // Stack size
228 this, // Task parameter
229 USB_TASK_PRIORITY, // Priority (higher than main loop)
230 &this->usb_task_handle_);
231
232 if (this->usb_task_handle_ == nullptr) {
233 ESP_LOGE(TAG, "Failed to create USB task");
234 this->mark_failed();
235 }
236}
237
238void USBClient::usb_task_fn(void *arg) {
239 auto *client = static_cast<USBClient *>(arg);
240 client->usb_task_loop_();
241}
243 while (true) {
244 usb_host_client_handle_events(this->handle_, portMAX_DELAY);
245 }
246}
247
249 bool had_work = false;
250
251 // Process any events from the USB task
252 UsbEvent *event;
253 while ((event = this->event_queue.pop()) != nullptr) {
254 had_work = true;
255 switch (event->type) {
256 case EVENT_DEVICE_NEW:
257 this->on_opened(event->data.device_new.address);
258 break;
260 this->on_removed(event->data.device_gone.handle);
261 break;
262 }
263 // Return event to pool for reuse
264 this->event_pool.release(event);
265 }
266
267 // Log dropped events periodically
268 uint16_t dropped = this->event_queue.get_and_reset_dropped_count();
269 if (dropped > 0) {
270 ESP_LOGW(TAG, "Dropped %u USB events due to queue overflow", dropped);
271 }
272
273 if (this->state_ == USB_CLIENT_OPEN) {
274 had_work = true;
275 this->handle_open_state_();
276 }
277
278 return had_work;
279}
280
282 if (!this->process_usb_events_()) {
283 this->disable_loop();
284 }
285}
286
288 int err;
289 ESP_LOGD(TAG, "Open device %d", this->device_addr_);
290 err = usb_host_device_open(this->handle_, this->device_addr_, &this->device_handle_);
291 if (err != ESP_OK) {
292 ESP_LOGW(TAG, "Device open failed: %s", esp_err_to_name(err));
293 this->state_ = USB_CLIENT_INIT;
294 return;
295 }
296 ESP_LOGD(TAG, "Get descriptor device %d", this->device_addr_);
297 const usb_device_desc_t *desc;
298 err = usb_host_get_device_descriptor(this->device_handle_, &desc);
299 if (err != ESP_OK) {
300 ESP_LOGW(TAG, "Device get_desc failed: %s", esp_err_to_name(err));
301 this->disconnect();
302 return;
303 }
304 ESP_LOGD(TAG, "Device descriptor: vid %X pid %X", desc->idVendor, desc->idProduct);
305 if (desc->idVendor != this->vid_ || desc->idProduct != this->pid_) {
306 if (this->vid_ != 0 || this->pid_ != 0) {
307 ESP_LOGD(TAG, "Not our device, closing");
308 this->disconnect();
309 return;
310 }
311 }
312 usb_device_info_t dev_info;
313 err = usb_host_device_info(this->device_handle_, &dev_info);
314 if (err != ESP_OK) {
315 ESP_LOGW(TAG, "Device info failed: %s", esp_err_to_name(err));
316 this->disconnect();
317 return;
318 }
320 char buf_manuf[DESC_STRING_BUF_SIZE];
321 char buf_product[DESC_STRING_BUF_SIZE];
322 char buf_serial[DESC_STRING_BUF_SIZE];
323 ESP_LOGD(TAG, "Device connected: Manuf: %s; Prod: %s; Serial: %s",
324 get_descriptor_string(dev_info.str_desc_manufacturer, buf_manuf),
325 get_descriptor_string(dev_info.str_desc_product, buf_product),
326 get_descriptor_string(dev_info.str_desc_serial_num, buf_serial));
327
328#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
329 const usb_device_desc_t *device_desc;
330 err = usb_host_get_device_descriptor(this->device_handle_, &device_desc);
331 if (err == ESP_OK)
332 usb_client_print_device_descriptor(device_desc);
333 const usb_config_desc_t *config_desc;
334 err = usb_host_get_active_config_descriptor(this->device_handle_, &config_desc);
335 if (err == ESP_OK)
336 usb_client_print_config_descriptor(config_desc, nullptr);
337#endif
338 this->on_connected();
339}
340
341void USBClient::on_opened(uint8_t addr) {
342 if (this->state_ == USB_CLIENT_INIT) {
343 this->device_addr_ = addr;
344 this->state_ = USB_CLIENT_OPEN;
345 }
346}
347void USBClient::on_removed(usb_device_handle_t handle) {
348 if (this->device_handle_ == handle) {
349 this->disconnect();
350 }
351}
352
353// CALLBACK CONTEXT: USB task (called from usb_host_client_handle_events in USB task)
354static void control_callback(const usb_transfer_t *xfer) {
355 auto *trq = static_cast<TransferRequest *>(xfer->context);
356 trq->status.error_code = xfer->status;
357 trq->status.success = xfer->status == USB_TRANSFER_STATUS_COMPLETED;
358 trq->status.endpoint = xfer->bEndpointAddress;
359 trq->status.data = xfer->data_buffer;
360 trq->status.data_len = xfer->actual_num_bytes;
361
362 // Execute callback in USB task context
363 if (trq->callback != nullptr) {
364 trq->callback(trq->status);
365 }
366
367 // Release transfer slot immediately in USB task
368 // The release_trq() uses thread-safe atomic operations
369 trq->client->release_trq(trq);
370}
371
372// THREAD CONTEXT: Called from both USB task and main loop threads (multi-consumer)
373// - USB task: USB UART input callbacks restart transfers for immediate data reception
374// - Main loop: Output transfers and flow-controlled input restarts after consuming data
375//
376// THREAD SAFETY: Lock-free using atomic compare-and-swap on bitmask
377// This multi-threaded access is intentional for performance - USB task can
378// immediately restart transfers without waiting for main loop scheduling.
380 trq_bitmask_t mask = this->trq_in_use_.load(std::memory_order_acquire);
381
382 // Find first available slot (bit = 0) and try to claim it atomically
383 // We use a while loop to allow retrying the same slot after CAS failure
384 for (;;) {
385 if (mask == ALL_REQUESTS_IN_USE) {
386 ESP_LOGE(TAG, "All %zu transfer slots in use", MAX_REQUESTS);
387 return nullptr;
388 }
389 // find the least significant zero bit
390 trq_bitmask_t lsb = ~mask & (mask + 1);
391
392 // Slot i appears available, try to claim it atomically
393 trq_bitmask_t desired = mask | lsb;
394
395 if (this->trq_in_use_.compare_exchange_weak(mask, desired, std::memory_order::acquire)) {
396 auto i = __builtin_ctz(lsb); // count trailing zeroes
397 // Successfully claimed slot i - prepare the TransferRequest
398 auto *trq = &this->requests_[i];
399 trq->transfer->context = trq;
400 trq->transfer->device_handle = this->device_handle_;
401 return trq;
402 }
403 // CAS failed - another thread modified the bitmask
404 // mask was already updated by compare_exchange_weak with the current value
405 }
406}
407
409 this->on_disconnected();
410 auto err = usb_host_device_close(this->handle_, this->device_handle_);
411 if (err != ESP_OK) {
412 ESP_LOGE(TAG, "Device close failed: %s", esp_err_to_name(err));
413 }
414 this->state_ = USB_CLIENT_INIT;
415 this->device_handle_ = nullptr;
416 this->device_addr_ = -1;
417}
418
419// THREAD CONTEXT: Called from main loop thread only
420// - Used for device configuration and control operations
421bool USBClient::control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index,
422 const transfer_cb_t &callback, const std::vector<uint8_t> &data) {
423 auto *trq = this->get_trq_();
424 if (trq == nullptr)
425 return false;
426 auto length = data.size();
427 if (length > trq->transfer->data_buffer_size - SETUP_PACKET_SIZE) {
428 ESP_LOGE(TAG, "Control transfer data size too large: %u > %u", length,
429 trq->transfer->data_buffer_size - SETUP_PACKET_SIZE);
430 this->release_trq(trq);
431 return false;
432 }
433 auto control_packet = ByteBuffer(SETUP_PACKET_SIZE, LITTLE);
434 control_packet.put_uint8(type);
435 control_packet.put_uint8(request);
436 control_packet.put_uint16(value);
437 control_packet.put_uint16(index);
438 control_packet.put_uint16(length);
439 memcpy(trq->transfer->data_buffer, control_packet.get_data().data(), SETUP_PACKET_SIZE);
440 if (length != 0 && !(type & USB_DIR_IN)) {
441 memcpy(trq->transfer->data_buffer + SETUP_PACKET_SIZE, data.data(), length);
442 }
443 trq->callback = callback;
444 trq->transfer->bEndpointAddress = type & USB_DIR_MASK;
445 trq->transfer->num_bytes = static_cast<int>(length + SETUP_PACKET_SIZE);
446 trq->transfer->callback = reinterpret_cast<usb_transfer_cb_t>(control_callback);
447 auto err = usb_host_transfer_submit_control(this->handle_, trq->transfer);
448 if (err != ESP_OK) {
449 ESP_LOGE(TAG, "Failed to submit control transfer, err=%s", esp_err_to_name(err));
450 this->release_trq(trq);
451 return false;
452 }
453 return true;
454}
455
456// CALLBACK CONTEXT: USB task (called from usb_host_client_handle_events in USB task)
457static void transfer_callback(usb_transfer_t *xfer) {
458 auto *trq = static_cast<TransferRequest *>(xfer->context);
459 trq->status.error_code = xfer->status;
460 trq->status.success = xfer->status == USB_TRANSFER_STATUS_COMPLETED;
461 trq->status.endpoint = xfer->bEndpointAddress;
462 trq->status.data = xfer->data_buffer;
463 trq->status.data_len = xfer->actual_num_bytes;
464
465 // Always execute callback in USB task context
466 // Callbacks should be fast and non-blocking (e.g., copy data to queue)
467 if (trq->callback != nullptr) {
468 trq->callback(trq->status);
469 }
470
471 // Release transfer slot AFTER callback completes to prevent slot exhaustion
472 // This is critical for high-throughput transfers (e.g., USB UART at 115200 baud)
473 // The callback has finished accessing xfer->data_buffer, so it's safe to release
474 // The release_trq() uses thread-safe atomic operations
475 trq->client->release_trq(trq);
476}
489bool USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) {
490 auto *trq = this->get_trq_();
491 if (trq == nullptr) {
492 ESP_LOGE(TAG, "Too many requests queued");
493 return false;
494 }
495 if (length > trq->transfer->data_buffer_size) {
496 ESP_LOGE(TAG, "transfer_in: data length %u exceeds buffer size %u", length, trq->transfer->data_buffer_size);
497 this->release_trq(trq);
498 return false;
499 }
500 trq->callback = callback;
501 trq->transfer->callback = transfer_callback;
502 trq->transfer->bEndpointAddress = ep_address | USB_DIR_IN;
503 trq->transfer->num_bytes = length;
504 auto err = usb_host_transfer_submit(trq->transfer);
505 if (err != ESP_OK) {
506 ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err);
507 this->release_trq(trq);
508 return false;
509 }
510 return true;
511}
512
530bool USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) {
531 auto *trq = this->get_trq_();
532 if (trq == nullptr) {
533 ESP_LOGE(TAG, "Too many requests queued");
534 return false;
535 }
536 if (length > trq->transfer->data_buffer_size) {
537 ESP_LOGE(TAG, "transfer_out: data length %u exceeds buffer size %u", length, trq->transfer->data_buffer_size);
538 this->release_trq(trq);
539 return false;
540 }
541 trq->callback = callback;
542 trq->transfer->callback = transfer_callback;
543 trq->transfer->bEndpointAddress = ep_address | USB_DIR_OUT;
544 trq->transfer->num_bytes = length;
545 memcpy(trq->transfer->data_buffer, data, length);
546 auto err = usb_host_transfer_submit(trq->transfer);
547 if (err != ESP_OK) {
548 ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err);
549 this->release_trq(trq);
550 return false;
551 }
552 return true;
553}
555 ESP_LOGCONFIG(TAG,
556 "USBClient\n"
557 " Vendor id %04X\n"
558 " Product id %04X",
559 this->vid_, this->pid_);
560}
561// THREAD CONTEXT: Called from both USB task and main loop threads
562// - USB task: Immediately after transfer callback completes
563// - Main loop: When transfer submission fails
564//
565// THREAD SAFETY: Lock-free using atomic AND to clear bit
566// Thread-safe atomic operation allows multithreaded deallocation
568 if (trq == nullptr)
569 return;
570
571 // Calculate index from pointer arithmetic
572 size_t index = trq - this->requests_;
573 if (index >= MAX_REQUESTS) {
574 ESP_LOGE(TAG, "Invalid TransferRequest pointer");
575 return;
576 }
577
578 // Atomically clear the bit to mark slot as available
579 // fetch_and with inverted bitmask clears the bit atomically
580 trq_bitmask_t mask = ~(static_cast<trq_bitmask_t>(1) << index);
581 this->trq_in_use_.fetch_and(mask, std::memory_order_release);
582}
583
584} // namespace esphome::usb_host
585#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 ||
586 // USE_ESP32_VARIANT_ESP32S31 || USE_ESP32_VARIANT_ESP32H4
void wake_loop_threadsafe()
Wake the main event loop from another thread or callback.
void mark_failed()
Mark this component as failed.
void disable_loop()
Disable this component's loop.
A class modelled on the Java ByteBuffer class.
Definition bytebuffer.h:37
usb_host_client_handle_t handle_
Definition usb_host.h:176
TransferRequest requests_[MAX_REQUESTS]
Definition usb_host.h:174
bool transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length)
Performs an output transfer operation.
void release_trq(TransferRequest *trq)
static void usb_task_fn(void *arg)
virtual void on_connected()
Definition usb_host.h:163
EventPool< UsbEvent, USB_EVENT_QUEUE_SIZE - 1 > event_pool
Definition usb_host.h:153
TaskHandle_t usb_task_handle_
Definition usb_host.h:175
virtual void on_disconnected()
Definition usb_host.h:164
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.
void on_removed(usb_device_handle_t handle)
usb_device_handle_t device_handle_
Definition usb_host.h:177
std::atomic< trq_bitmask_t > trq_in_use_
Definition usb_host.h:183
LockFreeQueue< UsbEvent, USB_EVENT_QUEUE_SIZE > event_queue
Definition usb_host.h:149
uint16_t type
std::conditional<(MAX_REQUESTS<=16), uint16_t, uint32_t >::type trq_bitmask_t
Definition usb_host.h:67
std::function< void(const TransferStatus &)> transfer_cb_t
Definition usb_host.h:86
Application App
Global storage of Application pointer - only one Application can exist.
usb_device_handle_t handle
Definition usb_host.h:110
union esphome::usb_host::UsbEvent::@180 data
struct esphome::usb_host::UsbEvent::@180::@181 device_new
struct esphome::usb_host::UsbEvent::@180::@182 device_gone
uint8_t end[39]
Definition sun_gtil2.cpp:17
uint16_t length
Definition tt21100.cpp:0
spi_device_handle_t handle