Compare commits

...

4 Commits

Author SHA1 Message Date
J. Nick Koston
1688f9af0f Update esphome/components/usb_host/usb_host_client.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-12 10:42:38 -06:00
J. Nick Koston
8dd3021030 Merge branch 'dev' into usb-host-extract-cold-path 2026-02-12 10:37:51 -06:00
J. Nick Koston
cacd2b5fa3 Fix off-by-one in get_descriptor_string loop bound
bLength includes the 2-byte descriptor header, so the character count
is (bLength - 2) / 2, not bLength / 2. The old loop read one wData
entry past the actual string data. Also guard bLength < 2.
2026-02-12 10:35:42 -06:00
J. Nick Koston
9c72c022b2 [usb_host] Extract cold path from loop(), replace std::string with buffer API
Extract the USB_CLIENT_OPEN state handling into handle_open_state_() to
keep loop() hot path small (~112 B vs 440 B before). Replace
get_descriptor_string() std::string return with caller-provided
fixed-size buffer via std::span to eliminate heap allocation during
device connection.
2026-02-12 10:25:53 -06:00
2 changed files with 67 additions and 57 deletions

View File

@@ -148,6 +148,7 @@ class USBClient : public Component {
EventPool<UsbEvent, USB_EVENT_QUEUE_SIZE> event_pool; EventPool<UsbEvent, USB_EVENT_QUEUE_SIZE> event_pool;
protected: protected:
void handle_open_state_();
TransferRequest *get_trq_(); // Lock-free allocation using atomic bitmask (multi-consumer safe) TransferRequest *get_trq_(); // Lock-free allocation using atomic bitmask (multi-consumer safe)
virtual void disconnect(); virtual void disconnect();
virtual void on_connected() {} virtual void on_connected() {}

View File

@@ -9,6 +9,7 @@
#include <cinttypes> #include <cinttypes>
#include <cstring> #include <cstring>
#include <atomic> #include <atomic>
#include <span>
namespace esphome { namespace esphome {
namespace usb_host { namespace usb_host {
@@ -142,18 +143,23 @@ static void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc
} while (next_desc != NULL); } while (next_desc != NULL);
} }
#endif #endif
static std::string get_descriptor_string(const usb_str_desc_t *desc) { // USB string descriptors: bLength (uint8_t, max 255) includes the 2-byte header (bLength and bDescriptorType).
char buffer[256]; // Character count = (bLength - 2) / 2, max 126 chars + null terminator.
if (desc == nullptr) static constexpr size_t DESC_STRING_BUF_SIZE = 128;
static const char *get_descriptor_string(const usb_str_desc_t *desc, std::span<char, DESC_STRING_BUF_SIZE> buffer) {
if (desc == nullptr || desc->bLength < 2)
return "(unspecified)"; return "(unspecified)";
char *p = buffer; int char_count = (desc->bLength - 2) / 2;
for (int i = 0; i != desc->bLength / 2; i++) { char *p = buffer.data();
char *end = p + buffer.size() - 1;
for (int i = 0; i != char_count && p < end; i++) {
auto c = desc->wData[i]; auto c = desc->wData[i];
if (c < 0x100) if (c < 0x100)
*p++ = static_cast<char>(c); *p++ = static_cast<char>(c);
} }
*p = '\0'; *p = '\0';
return {buffer}; return buffer.data();
} }
// CALLBACK CONTEXT: USB task (called from usb_host_client_handle_events in USB task) // CALLBACK CONTEXT: USB task (called from usb_host_client_handle_events in USB task)
@@ -259,60 +265,63 @@ void USBClient::loop() {
ESP_LOGW(TAG, "Dropped %u USB events due to queue overflow", dropped); ESP_LOGW(TAG, "Dropped %u USB events due to queue overflow", dropped);
} }
switch (this->state_) { if (this->state_ == USB_CLIENT_OPEN) {
case USB_CLIENT_OPEN: { this->handle_open_state_();
int err; }
ESP_LOGD(TAG, "Open device %d", this->device_addr_); }
err = usb_host_device_open(this->handle_, this->device_addr_, &this->device_handle_);
if (err != ESP_OK) { void USBClient::handle_open_state_() {
ESP_LOGW(TAG, "Device open failed: %s", esp_err_to_name(err)); int err;
this->state_ = USB_CLIENT_INIT; ESP_LOGD(TAG, "Open device %d", this->device_addr_);
break; err = usb_host_device_open(this->handle_, this->device_addr_, &this->device_handle_);
} if (err != ESP_OK) {
ESP_LOGD(TAG, "Get descriptor device %d", this->device_addr_); ESP_LOGW(TAG, "Device open failed: %s", esp_err_to_name(err));
const usb_device_desc_t *desc; this->state_ = USB_CLIENT_INIT;
err = usb_host_get_device_descriptor(this->device_handle_, &desc); return;
if (err != ESP_OK) { }
ESP_LOGW(TAG, "Device get_desc failed: %s", esp_err_to_name(err)); ESP_LOGD(TAG, "Get descriptor device %d", this->device_addr_);
this->disconnect(); const usb_device_desc_t *desc;
} else { err = usb_host_get_device_descriptor(this->device_handle_, &desc);
ESP_LOGD(TAG, "Device descriptor: vid %X pid %X", desc->idVendor, desc->idProduct); if (err != ESP_OK) {
if (desc->idVendor == this->vid_ && desc->idProduct == this->pid_ || this->vid_ == 0 && this->pid_ == 0) { ESP_LOGW(TAG, "Device get_desc failed: %s", esp_err_to_name(err));
usb_device_info_t dev_info; this->disconnect();
err = usb_host_device_info(this->device_handle_, &dev_info); return;
if (err != ESP_OK) { }
ESP_LOGW(TAG, "Device info failed: %s", esp_err_to_name(err)); ESP_LOGD(TAG, "Device descriptor: vid %X pid %X", desc->idVendor, desc->idProduct);
this->disconnect(); if (desc->idVendor != this->vid_ || desc->idProduct != this->pid_) {
break; if (this->vid_ != 0 || this->pid_ != 0) {
} ESP_LOGD(TAG, "Not our device, closing");
this->state_ = USB_CLIENT_CONNECTED; this->disconnect();
ESP_LOGD(TAG, "Device connected: Manuf: %s; Prod: %s; Serial: %s", return;
get_descriptor_string(dev_info.str_desc_manufacturer).c_str(), }
get_descriptor_string(dev_info.str_desc_product).c_str(), }
get_descriptor_string(dev_info.str_desc_serial_num).c_str()); usb_device_info_t dev_info;
err = usb_host_device_info(this->device_handle_, &dev_info);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Device info failed: %s", esp_err_to_name(err));
this->disconnect();
return;
}
this->state_ = USB_CLIENT_CONNECTED;
char buf_manuf[DESC_STRING_BUF_SIZE];
char buf_product[DESC_STRING_BUF_SIZE];
char buf_serial[DESC_STRING_BUF_SIZE];
ESP_LOGD(TAG, "Device connected: Manuf: %s; Prod: %s; Serial: %s",
get_descriptor_string(dev_info.str_desc_manufacturer, buf_manuf),
get_descriptor_string(dev_info.str_desc_product, buf_product),
get_descriptor_string(dev_info.str_desc_serial_num, buf_serial));
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
const usb_device_desc_t *device_desc; const usb_device_desc_t *device_desc;
err = usb_host_get_device_descriptor(this->device_handle_, &device_desc); err = usb_host_get_device_descriptor(this->device_handle_, &device_desc);
if (err == ESP_OK) if (err == ESP_OK)
usb_client_print_device_descriptor(device_desc); usb_client_print_device_descriptor(device_desc);
const usb_config_desc_t *config_desc; const usb_config_desc_t *config_desc;
err = usb_host_get_active_config_descriptor(this->device_handle_, &config_desc); err = usb_host_get_active_config_descriptor(this->device_handle_, &config_desc);
if (err == ESP_OK) if (err == ESP_OK)
usb_client_print_config_descriptor(config_desc, nullptr); usb_client_print_config_descriptor(config_desc, nullptr);
#endif #endif
this->on_connected(); this->on_connected();
} else {
ESP_LOGD(TAG, "Not our device, closing");
this->disconnect();
}
}
break;
}
default:
break;
}
} }
void USBClient::on_opened(uint8_t addr) { void USBClient::on_opened(uint8_t addr) {