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