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