mirror of
https://github.com/esphome/esphome.git
synced 2025-09-10 23:32:23 +01:00
[usb_uart] Implement USB Host mode UART (#8334)
This commit is contained in:
64
esphome/components/usb_host/__init__.py
Normal file
64
esphome/components/usb_host/__init__.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
add_idf_sdkconfig_option,
|
||||
only_on_variant,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.cpp_types import Component
|
||||
|
||||
AUTO_LOAD = ["bytebuffer"]
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
usb_host_ns = cg.esphome_ns.namespace("usb_host")
|
||||
USBHost = usb_host_ns.class_("USBHost", Component)
|
||||
USBClient = usb_host_ns.class_("USBClient", Component)
|
||||
|
||||
CONF_DEVICES = "devices"
|
||||
CONF_VID = "vid"
|
||||
CONF_PID = "pid"
|
||||
|
||||
|
||||
def usb_device_schema(cls=USBClient, vid: int = None, pid: [int] = None) -> cv.Schema:
|
||||
schema = cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(cls),
|
||||
}
|
||||
)
|
||||
if vid:
|
||||
schema = schema.extend({cv.Optional(CONF_VID, default=vid): cv.hex_uint16_t})
|
||||
else:
|
||||
schema = schema.extend({cv.Required(CONF_VID): cv.hex_uint16_t})
|
||||
if pid:
|
||||
schema = schema.extend({cv.Optional(CONF_PID, default=pid): cv.hex_uint16_t})
|
||||
else:
|
||||
schema = schema.extend({cv.Required(CONF_PID): cv.hex_uint16_t})
|
||||
return schema
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(USBHost),
|
||||
cv.Optional(CONF_DEVICES): cv.ensure_list(usb_device_schema()),
|
||||
}
|
||||
),
|
||||
cv.only_with_esp_idf,
|
||||
only_on_variant(supported=[VARIANT_ESP32S2, VARIANT_ESP32S3]),
|
||||
)
|
||||
|
||||
|
||||
async def register_usb_client(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_VID], config[CONF_PID])
|
||||
await cg.register_component(var, config)
|
||||
return var
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
add_idf_sdkconfig_option("CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE", 1024)
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
for device in config.get(CONF_DEVICES) or ():
|
||||
await register_usb_client(device)
|
116
esphome/components/usb_host/usb_host.h
Normal file
116
esphome/components/usb_host/usb_host.h
Normal file
@@ -0,0 +1,116 @@
|
||||
#pragma once
|
||||
|
||||
// Should not be needed, but it's required to pass CI clang-tidy checks
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#include "esphome/core/component.h"
|
||||
#include <vector>
|
||||
#include "usb/usb_host.h"
|
||||
|
||||
#include <list>
|
||||
|
||||
namespace esphome {
|
||||
namespace usb_host {
|
||||
|
||||
static const char *const TAG = "usb_host";
|
||||
|
||||
// constants for setup packet type
|
||||
static const uint8_t USB_RECIP_DEVICE = 0;
|
||||
static const uint8_t USB_RECIP_INTERFACE = 1;
|
||||
static const uint8_t USB_RECIP_ENDPOINT = 2;
|
||||
static const uint8_t USB_TYPE_STANDARD = 0 << 5;
|
||||
static const uint8_t USB_TYPE_CLASS = 1 << 5;
|
||||
static const uint8_t USB_TYPE_VENDOR = 2 << 5;
|
||||
static const uint8_t USB_DIR_MASK = 1 << 7;
|
||||
static const uint8_t USB_DIR_IN = 1 << 7;
|
||||
static const uint8_t USB_DIR_OUT = 0;
|
||||
static const size_t SETUP_PACKET_SIZE = 8;
|
||||
|
||||
static const size_t MAX_REQUESTS = 16; // maximum number of outstanding requests possible.
|
||||
|
||||
// used to report a transfer status
|
||||
struct TransferStatus {
|
||||
bool success;
|
||||
uint16_t error_code;
|
||||
uint8_t *data;
|
||||
size_t data_len;
|
||||
uint8_t endpoint;
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
using transfer_cb_t = std::function<void(const TransferStatus &)>;
|
||||
|
||||
class USBClient;
|
||||
|
||||
// struct used to capture all data needed for a transfer
|
||||
struct TransferRequest {
|
||||
usb_transfer_t *transfer;
|
||||
transfer_cb_t callback;
|
||||
TransferStatus status;
|
||||
USBClient *client;
|
||||
};
|
||||
|
||||
// callback function type.
|
||||
|
||||
enum ClientState {
|
||||
USB_CLIENT_INIT = 0,
|
||||
USB_CLIENT_OPEN,
|
||||
USB_CLIENT_CLOSE,
|
||||
USB_CLIENT_GET_DESC,
|
||||
USB_CLIENT_GET_INFO,
|
||||
USB_CLIENT_CONNECTED,
|
||||
};
|
||||
class USBClient : public Component {
|
||||
friend class USBHost;
|
||||
|
||||
public:
|
||||
USBClient(uint16_t vid, uint16_t pid) : vid_(vid), pid_(pid) { init_pool(); }
|
||||
|
||||
void init_pool() {
|
||||
this->trq_pool_.clear();
|
||||
for (size_t i = 0; i != MAX_REQUESTS; i++)
|
||||
this->trq_pool_.push_back(&this->requests_[i]);
|
||||
}
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
// setup must happen after the host bus has been setup
|
||||
float get_setup_priority() const override { return setup_priority::IO; }
|
||||
void on_opened(uint8_t addr);
|
||||
void on_removed(usb_device_handle_t handle);
|
||||
void control_transfer_callback(const usb_transfer_t *xfer) const;
|
||||
void transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length);
|
||||
void transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length);
|
||||
void dump_config() override;
|
||||
void release_trq(TransferRequest *trq);
|
||||
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 = {});
|
||||
|
||||
protected:
|
||||
bool register_();
|
||||
TransferRequest *get_trq_();
|
||||
virtual void disconnect();
|
||||
virtual void on_connected() {}
|
||||
virtual void on_disconnected() { this->init_pool(); }
|
||||
|
||||
usb_host_client_handle_t handle_{};
|
||||
usb_device_handle_t device_handle_{};
|
||||
int device_addr_{-1};
|
||||
int state_{USB_CLIENT_INIT};
|
||||
uint16_t vid_{};
|
||||
uint16_t pid_{};
|
||||
std::list<TransferRequest *> trq_pool_{};
|
||||
TransferRequest requests_[MAX_REQUESTS]{};
|
||||
};
|
||||
class USBHost : public Component {
|
||||
public:
|
||||
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||
void loop() override;
|
||||
void setup() override;
|
||||
|
||||
protected:
|
||||
std::vector<USBClient *> clients_{};
|
||||
};
|
||||
|
||||
} // namespace usb_host
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
392
esphome/components/usb_host/usb_host_client.cpp
Normal file
392
esphome/components/usb_host/usb_host_client.cpp
Normal file
@@ -0,0 +1,392 @@
|
||||
// Should not be needed, but it's required to pass CI clang-tidy checks
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#include "usb_host.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/bytebuffer/bytebuffer.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
namespace esphome {
|
||||
namespace usb_host {
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wparentheses"
|
||||
|
||||
using namespace bytebuffer;
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
static void print_ep_desc(const usb_ep_desc_t *ep_desc) {
|
||||
const char *ep_type_str;
|
||||
int type = ep_desc->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK;
|
||||
|
||||
switch (type) {
|
||||
case USB_BM_ATTRIBUTES_XFER_CONTROL:
|
||||
ep_type_str = "CTRL";
|
||||
break;
|
||||
case USB_BM_ATTRIBUTES_XFER_ISOC:
|
||||
ep_type_str = "ISOC";
|
||||
break;
|
||||
case USB_BM_ATTRIBUTES_XFER_BULK:
|
||||
ep_type_str = "BULK";
|
||||
break;
|
||||
case USB_BM_ATTRIBUTES_XFER_INT:
|
||||
ep_type_str = "INT";
|
||||
break;
|
||||
default:
|
||||
ep_type_str = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "\t\t*** Endpoint descriptor ***");
|
||||
ESP_LOGV(TAG, "\t\tbLength %d", ep_desc->bLength);
|
||||
ESP_LOGV(TAG, "\t\tbDescriptorType %d", ep_desc->bDescriptorType);
|
||||
ESP_LOGV(TAG, "\t\tbEndpointAddress 0x%x\tEP %d %s", ep_desc->bEndpointAddress, USB_EP_DESC_GET_EP_NUM(ep_desc),
|
||||
USB_EP_DESC_GET_EP_DIR(ep_desc) ? "IN" : "OUT");
|
||||
ESP_LOGV(TAG, "\t\tbmAttributes 0x%x\t%s", ep_desc->bmAttributes, ep_type_str);
|
||||
ESP_LOGV(TAG, "\t\twMaxPacketSize %d", ep_desc->wMaxPacketSize);
|
||||
ESP_LOGV(TAG, "\t\tbInterval %d", ep_desc->bInterval);
|
||||
}
|
||||
|
||||
static void usbh_print_intf_desc(const usb_intf_desc_t *intf_desc) {
|
||||
ESP_LOGV(TAG, "\t*** Interface descriptor ***");
|
||||
ESP_LOGV(TAG, "\tbLength %d", intf_desc->bLength);
|
||||
ESP_LOGV(TAG, "\tbDescriptorType %d", intf_desc->bDescriptorType);
|
||||
ESP_LOGV(TAG, "\tbInterfaceNumber %d", intf_desc->bInterfaceNumber);
|
||||
ESP_LOGV(TAG, "\tbAlternateSetting %d", intf_desc->bAlternateSetting);
|
||||
ESP_LOGV(TAG, "\tbNumEndpoints %d", intf_desc->bNumEndpoints);
|
||||
ESP_LOGV(TAG, "\tbInterfaceClass 0x%x", intf_desc->bInterfaceProtocol);
|
||||
ESP_LOGV(TAG, "\tiInterface %d", intf_desc->iInterface);
|
||||
}
|
||||
|
||||
static void usbh_print_cfg_desc(const usb_config_desc_t *cfg_desc) {
|
||||
ESP_LOGV(TAG, "*** Configuration descriptor ***");
|
||||
ESP_LOGV(TAG, "bLength %d", cfg_desc->bLength);
|
||||
ESP_LOGV(TAG, "bDescriptorType %d", cfg_desc->bDescriptorType);
|
||||
ESP_LOGV(TAG, "wTotalLength %d", cfg_desc->wTotalLength);
|
||||
ESP_LOGV(TAG, "bNumInterfaces %d", cfg_desc->bNumInterfaces);
|
||||
ESP_LOGV(TAG, "bConfigurationValue %d", cfg_desc->bConfigurationValue);
|
||||
ESP_LOGV(TAG, "iConfiguration %d", cfg_desc->iConfiguration);
|
||||
ESP_LOGV(TAG, "bmAttributes 0x%x", cfg_desc->bmAttributes);
|
||||
ESP_LOGV(TAG, "bMaxPower %dmA", cfg_desc->bMaxPower * 2);
|
||||
}
|
||||
|
||||
void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) {
|
||||
if (devc_desc == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "*** Device descriptor ***");
|
||||
ESP_LOGV(TAG, "bLength %d", devc_desc->bLength);
|
||||
ESP_LOGV(TAG, "bDescriptorType %d", devc_desc->bDescriptorType);
|
||||
ESP_LOGV(TAG, "bcdUSB %d.%d0", ((devc_desc->bcdUSB >> 8) & 0xF), ((devc_desc->bcdUSB >> 4) & 0xF));
|
||||
ESP_LOGV(TAG, "bDeviceClass 0x%x", devc_desc->bDeviceClass);
|
||||
ESP_LOGV(TAG, "bDeviceSubClass 0x%x", devc_desc->bDeviceSubClass);
|
||||
ESP_LOGV(TAG, "bDeviceProtocol 0x%x", devc_desc->bDeviceProtocol);
|
||||
ESP_LOGV(TAG, "bMaxPacketSize0 %d", devc_desc->bMaxPacketSize0);
|
||||
ESP_LOGV(TAG, "idVendor 0x%x", devc_desc->idVendor);
|
||||
ESP_LOGV(TAG, "idProduct 0x%x", devc_desc->idProduct);
|
||||
ESP_LOGV(TAG, "bcdDevice %d.%d0", ((devc_desc->bcdDevice >> 8) & 0xF), ((devc_desc->bcdDevice >> 4) & 0xF));
|
||||
ESP_LOGV(TAG, "iManufacturer %d", devc_desc->iManufacturer);
|
||||
ESP_LOGV(TAG, "iProduct %d", devc_desc->iProduct);
|
||||
ESP_LOGV(TAG, "iSerialNumber %d", devc_desc->iSerialNumber);
|
||||
ESP_LOGV(TAG, "bNumConfigurations %d", devc_desc->bNumConfigurations);
|
||||
}
|
||||
|
||||
void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc,
|
||||
print_class_descriptor_cb class_specific_cb) {
|
||||
if (cfg_desc == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
uint16_t w_total_length = cfg_desc->wTotalLength;
|
||||
const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *) cfg_desc;
|
||||
|
||||
do {
|
||||
switch (next_desc->bDescriptorType) {
|
||||
case USB_W_VALUE_DT_CONFIG:
|
||||
usbh_print_cfg_desc((const usb_config_desc_t *) next_desc);
|
||||
break;
|
||||
case USB_W_VALUE_DT_INTERFACE:
|
||||
usbh_print_intf_desc((const usb_intf_desc_t *) next_desc);
|
||||
break;
|
||||
case USB_W_VALUE_DT_ENDPOINT:
|
||||
print_ep_desc((const usb_ep_desc_t *) next_desc);
|
||||
break;
|
||||
default:
|
||||
if (class_specific_cb) {
|
||||
class_specific_cb(next_desc);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
next_desc = usb_parse_next_descriptor(next_desc, w_total_length, &offset);
|
||||
|
||||
} while (next_desc != NULL);
|
||||
}
|
||||
#endif
|
||||
static std::string get_descriptor_string(const usb_str_desc_t *desc) {
|
||||
char buffer[256];
|
||||
if (desc == nullptr)
|
||||
return "(unknown)";
|
||||
char *p = buffer;
|
||||
for (size_t i = 0; i != desc->bLength / 2; i++) {
|
||||
auto c = desc->wData[i];
|
||||
if (c < 0x100)
|
||||
*p++ = static_cast<char>(c);
|
||||
}
|
||||
*p = '\0';
|
||||
return {buffer};
|
||||
}
|
||||
|
||||
static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void *ptr) {
|
||||
auto *client = static_cast<USBClient *>(ptr);
|
||||
switch (event_msg->event) {
|
||||
case USB_HOST_CLIENT_EVENT_NEW_DEV: {
|
||||
auto addr = event_msg->new_dev.address;
|
||||
ESP_LOGD(TAG, "New device %d", event_msg->new_dev.address);
|
||||
client->on_opened(addr);
|
||||
break;
|
||||
}
|
||||
case USB_HOST_CLIENT_EVENT_DEV_GONE: {
|
||||
client->on_removed(event_msg->dev_gone.dev_hdl);
|
||||
ESP_LOGD(TAG, "Device gone %d", event_msg->new_dev.address);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGD(TAG, "Unknown event %d", event_msg->event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
void USBClient::setup() {
|
||||
usb_host_client_config_t config{.is_synchronous = false,
|
||||
.max_num_event_msg = 5,
|
||||
.async = {.client_event_callback = client_event_cb, .callback_arg = this}};
|
||||
auto err = usb_host_client_register(&config, &this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "client register failed: %s", esp_err_to_name(err));
|
||||
this->status_set_error("Client register failed");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
for (auto trq : this->trq_pool_) {
|
||||
usb_host_transfer_alloc(64, 0, &trq->transfer);
|
||||
trq->client = this;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "client setup complete");
|
||||
}
|
||||
|
||||
void USBClient::loop() {
|
||||
switch (this->state_) {
|
||||
case USB_CLIENT_OPEN: {
|
||||
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) {
|
||||
ESP_LOGW(TAG, "Device open failed: %s", esp_err_to_name(err));
|
||||
this->state_ = USB_CLIENT_INIT;
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, "Get descriptor device %d", this->device_addr_);
|
||||
const usb_device_desc_t *desc;
|
||||
err = usb_host_get_device_descriptor(this->device_handle_, &desc);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Device get_desc failed: %s", esp_err_to_name(err));
|
||||
this->disconnect();
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Device descriptor: vid %X pid %X", desc->idVendor, desc->idProduct);
|
||||
if (desc->idVendor == this->vid_ && desc->idProduct == this->pid_ || this->vid_ == 0 && this->pid_ == 0) {
|
||||
usb_device_info_t dev_info;
|
||||
if ((err = usb_host_device_info(this->device_handle_, &dev_info)) != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Device info failed: %s", esp_err_to_name(err));
|
||||
this->disconnect();
|
||||
break;
|
||||
}
|
||||
this->state_ = USB_CLIENT_CONNECTED;
|
||||
ESP_LOGD(TAG, "Device connected: Manuf: %s; Prod: %s; Serial: %s",
|
||||
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());
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
const usb_device_desc_t *device_desc;
|
||||
err = usb_host_get_device_descriptor(this->device_handle_, &device_desc);
|
||||
if (err == ESP_OK)
|
||||
usb_client_print_device_descriptor(device_desc);
|
||||
const usb_config_desc_t *config_desc;
|
||||
err = usb_host_get_active_config_descriptor(this->device_handle_, &config_desc);
|
||||
if (err == ESP_OK)
|
||||
usb_client_print_config_descriptor(config_desc, nullptr);
|
||||
#endif
|
||||
this->on_connected();
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Not our device, closing");
|
||||
this->disconnect();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
usb_host_client_handle_events(this->handle_, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void USBClient::on_opened(uint8_t addr) {
|
||||
if (this->state_ == USB_CLIENT_INIT) {
|
||||
this->device_addr_ = addr;
|
||||
this->state_ = USB_CLIENT_OPEN;
|
||||
}
|
||||
}
|
||||
void USBClient::on_removed(usb_device_handle_t handle) {
|
||||
if (this->device_handle_ == handle) {
|
||||
this->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
static void control_callback(const usb_transfer_t *xfer) {
|
||||
auto *trq = static_cast<TransferRequest *>(xfer->context);
|
||||
trq->status.error_code = xfer->status;
|
||||
trq->status.success = xfer->status == USB_TRANSFER_STATUS_COMPLETED;
|
||||
trq->status.endpoint = xfer->bEndpointAddress;
|
||||
trq->status.data = xfer->data_buffer;
|
||||
trq->status.data_len = xfer->actual_num_bytes;
|
||||
if (trq->callback != nullptr)
|
||||
trq->callback(trq->status);
|
||||
trq->client->release_trq(trq);
|
||||
}
|
||||
|
||||
TransferRequest *USBClient::get_trq_() {
|
||||
if (this->trq_pool_.empty()) {
|
||||
ESP_LOGE(TAG, "Too many requests queued");
|
||||
return nullptr;
|
||||
}
|
||||
auto *trq = this->trq_pool_.front();
|
||||
this->trq_pool_.pop_front();
|
||||
trq->client = this;
|
||||
trq->transfer->context = trq;
|
||||
trq->transfer->device_handle = this->device_handle_;
|
||||
return trq;
|
||||
}
|
||||
void USBClient::disconnect() {
|
||||
this->on_disconnected();
|
||||
auto err = usb_host_device_close(this->handle_, this->device_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Device close failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
this->state_ = USB_CLIENT_INIT;
|
||||
this->device_handle_ = nullptr;
|
||||
this->device_addr_ = -1;
|
||||
}
|
||||
|
||||
bool USBClient::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) {
|
||||
auto *trq = this->get_trq_();
|
||||
if (trq == nullptr)
|
||||
return false;
|
||||
auto length = data.size();
|
||||
if (length > sizeof(trq->transfer->data_buffer_size) - SETUP_PACKET_SIZE) {
|
||||
ESP_LOGE(TAG, "Control transfer data size too large: %u > %u", length,
|
||||
sizeof(trq->transfer->data_buffer_size) - sizeof(usb_setup_packet_t));
|
||||
this->release_trq(trq);
|
||||
return false;
|
||||
}
|
||||
auto control_packet = ByteBuffer(SETUP_PACKET_SIZE, LITTLE);
|
||||
control_packet.put_uint8(type);
|
||||
control_packet.put_uint8(request);
|
||||
control_packet.put_uint16(value);
|
||||
control_packet.put_uint16(index);
|
||||
control_packet.put_uint16(length);
|
||||
memcpy(trq->transfer->data_buffer, control_packet.get_data().data(), SETUP_PACKET_SIZE);
|
||||
if (length != 0 && !(type & USB_DIR_IN)) {
|
||||
memcpy(trq->transfer->data_buffer + SETUP_PACKET_SIZE, data.data(), length);
|
||||
}
|
||||
trq->callback = callback;
|
||||
trq->transfer->bEndpointAddress = type & USB_DIR_MASK;
|
||||
trq->transfer->num_bytes = static_cast<int>(length + SETUP_PACKET_SIZE);
|
||||
trq->transfer->callback = reinterpret_cast<usb_transfer_cb_t>(control_callback);
|
||||
auto err = usb_host_transfer_submit_control(this->handle_, trq->transfer);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to submit control transfer, err=%s", esp_err_to_name(err));
|
||||
this->release_trq(trq);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void transfer_callback(usb_transfer_t *xfer) {
|
||||
auto *trq = static_cast<TransferRequest *>(xfer->context);
|
||||
trq->status.error_code = xfer->status;
|
||||
trq->status.success = xfer->status == USB_TRANSFER_STATUS_COMPLETED;
|
||||
trq->status.endpoint = xfer->bEndpointAddress;
|
||||
trq->status.data = xfer->data_buffer;
|
||||
trq->status.data_len = xfer->actual_num_bytes;
|
||||
if (trq->callback != nullptr)
|
||||
trq->callback(trq->status);
|
||||
trq->client->release_trq(trq);
|
||||
}
|
||||
/**
|
||||
* Performs a transfer input operation.
|
||||
*
|
||||
* @param ep_address The endpoint address.
|
||||
* @param callback The callback function to be called when the transfer is complete.
|
||||
* @param length The length of the data to be transferred.
|
||||
*
|
||||
* @throws None.
|
||||
*/
|
||||
void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) {
|
||||
auto trq = this->get_trq_();
|
||||
if (trq == nullptr) {
|
||||
ESP_LOGE(TAG, "Too many requests queued");
|
||||
return;
|
||||
}
|
||||
trq->callback = callback;
|
||||
trq->transfer->callback = transfer_callback;
|
||||
trq->transfer->bEndpointAddress = ep_address | USB_DIR_IN;
|
||||
trq->transfer->num_bytes = length;
|
||||
auto err = usb_host_transfer_submit(trq->transfer);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err);
|
||||
this->release_trq(trq);
|
||||
this->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an output transfer operation.
|
||||
*
|
||||
* @param ep_address The endpoint address.
|
||||
* @param callback The callback function to be called when the transfer is complete.
|
||||
* @param data The data to be transferred.
|
||||
* @param length The length of the data to be transferred.
|
||||
*
|
||||
* @throws None.
|
||||
*/
|
||||
void USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) {
|
||||
auto trq = this->get_trq_();
|
||||
if (trq == nullptr) {
|
||||
ESP_LOGE(TAG, "Too many requests queued");
|
||||
return;
|
||||
}
|
||||
trq->callback = callback;
|
||||
trq->transfer->callback = transfer_callback;
|
||||
trq->transfer->bEndpointAddress = ep_address | USB_DIR_OUT;
|
||||
trq->transfer->num_bytes = length;
|
||||
memcpy(trq->transfer->data_buffer, data, length);
|
||||
auto err = usb_host_transfer_submit(trq->transfer);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err);
|
||||
this->release_trq(trq);
|
||||
}
|
||||
}
|
||||
void USBClient::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "USBClient");
|
||||
ESP_LOGCONFIG(TAG, " Vendor id %04X", this->vid_);
|
||||
ESP_LOGCONFIG(TAG, " Product id %04X", this->pid_);
|
||||
}
|
||||
void USBClient::release_trq(TransferRequest *trq) { this->trq_pool_.push_back(trq); }
|
||||
|
||||
} // namespace usb_host
|
||||
} // namespace esphome
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
35
esphome/components/usb_host/usb_host_component.cpp
Normal file
35
esphome/components/usb_host/usb_host_component.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
// Should not be needed, but it's required to pass CI clang-tidy checks
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#include "usb_host.h"
|
||||
#include <cinttypes>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace usb_host {
|
||||
|
||||
void USBHost::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setup starts");
|
||||
usb_host_config_t config{};
|
||||
|
||||
if (usb_host_install(&config) != ESP_OK) {
|
||||
this->status_set_error("usb_host_install failed");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
void USBHost::loop() {
|
||||
int err;
|
||||
uint32_t event_flags;
|
||||
err = usb_host_lib_handle_events(0, &event_flags);
|
||||
if (err != ESP_OK && err != ESP_ERR_TIMEOUT) {
|
||||
ESP_LOGD(TAG, "lib_handle_events failed failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
if (event_flags != 0) {
|
||||
ESP_LOGD(TAG, "Event flags %" PRIu32 "X", event_flags);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace usb_host
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
Reference in New Issue
Block a user