mirror of
https://github.com/esphome/esphome.git
synced 2025-07-27 21:56:34 +00:00
[usb_uart] Implement USB Host mode UART (#8334)
This commit is contained in:
parent
d1e55252d0
commit
1ec57a74b5
@ -479,6 +479,8 @@ esphome/components/ufire_ise/* @pvizeli
|
||||
esphome/components/ultrasonic/* @OttoWinter
|
||||
esphome/components/update/* @jesserockz
|
||||
esphome/components/uponor_smatrix/* @kroimon
|
||||
esphome/components/usb_host/* @clydebarrow
|
||||
esphome/components/usb_uart/* @clydebarrow
|
||||
esphome/components/valve/* @esphome/core
|
||||
esphome/components/vbus/* @ssieb
|
||||
esphome/components/veml3235/* @kbx81
|
||||
|
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
|
134
esphome/components/usb_uart/__init__.py
Normal file
134
esphome/components/usb_uart/__init__.py
Normal file
@ -0,0 +1,134 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.uart import (
|
||||
CONF_DATA_BITS,
|
||||
CONF_PARITY,
|
||||
CONF_STOP_BITS,
|
||||
UARTComponent,
|
||||
)
|
||||
from esphome.components.usb_host import register_usb_client, usb_device_schema
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BAUD_RATE,
|
||||
CONF_BUFFER_SIZE,
|
||||
CONF_CHANNELS,
|
||||
CONF_DEBUG,
|
||||
CONF_DUMMY_RECEIVER,
|
||||
CONF_ID,
|
||||
)
|
||||
from esphome.cpp_types import Component
|
||||
|
||||
AUTO_LOAD = ["uart", "usb_host", "bytebuffer"]
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
|
||||
usb_uart_ns = cg.esphome_ns.namespace("usb_uart")
|
||||
USBUartComponent = usb_uart_ns.class_("USBUartComponent", Component)
|
||||
USBUartChannel = usb_uart_ns.class_("USBUartChannel", UARTComponent)
|
||||
|
||||
|
||||
UARTParityOptions = usb_uart_ns.enum("UARTParityOptions")
|
||||
UART_PARITY_OPTIONS = {
|
||||
"NONE": UARTParityOptions.UART_CONFIG_PARITY_NONE,
|
||||
"EVEN": UARTParityOptions.UART_CONFIG_PARITY_EVEN,
|
||||
"ODD": UARTParityOptions.UART_CONFIG_PARITY_ODD,
|
||||
"MARK": UARTParityOptions.UART_CONFIG_PARITY_MARK,
|
||||
"SPACE": UARTParityOptions.UART_CONFIG_PARITY_SPACE,
|
||||
}
|
||||
|
||||
UARTStopBitsOptions = usb_uart_ns.enum("UARTStopBitsOptions")
|
||||
UART_STOP_BITS_OPTIONS = {
|
||||
"1": UARTStopBitsOptions.UART_CONFIG_STOP_BITS_1,
|
||||
"1.5": UARTStopBitsOptions.UART_CONFIG_STOP_BITS_1_5,
|
||||
"2": UARTStopBitsOptions.UART_CONFIG_STOP_BITS_2,
|
||||
}
|
||||
|
||||
DEFAULT_BAUD_RATE = 9600
|
||||
|
||||
|
||||
class Type:
|
||||
def __init__(self, name, vid, pid, cls, max_channels=1, baud_rate_required=True):
|
||||
self.name = name
|
||||
cls = cls or name
|
||||
self.vid = vid
|
||||
self.pid = pid
|
||||
self.cls = usb_uart_ns.class_(f"USBUartType{cls}", USBUartComponent)
|
||||
self.max_channels = max_channels
|
||||
self.baud_rate_required = baud_rate_required
|
||||
|
||||
|
||||
uart_types = (
|
||||
Type("CH34X", 0x1A86, 0x55D5, "CH34X", 3),
|
||||
Type("CH340", 0x1A86, 0x7523, "CH34X", 1),
|
||||
Type("ESP_JTAG", 0x303A, 0x1001, "CdcAcm", 1, baud_rate_required=False),
|
||||
Type("STM32_VCP", 0x0483, 0x5740, "CdcAcm", 1, baud_rate_required=False),
|
||||
Type("CDC_ACM", 0, 0, "CdcAcm", 1, baud_rate_required=False),
|
||||
Type("CP210X", 0x10C4, 0xEA60, "CP210X", 3),
|
||||
)
|
||||
|
||||
|
||||
def channel_schema(channels, baud_rate_required):
|
||||
return cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CHANNELS): cv.All(
|
||||
cv.ensure_list(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(USBUartChannel),
|
||||
cv.Optional(CONF_BUFFER_SIZE, default=256): cv.int_range(
|
||||
min=64, max=8192
|
||||
),
|
||||
(
|
||||
cv.Required(CONF_BAUD_RATE)
|
||||
if baud_rate_required
|
||||
else cv.Optional(
|
||||
CONF_BAUD_RATE, default=DEFAULT_BAUD_RATE
|
||||
)
|
||||
): cv.int_range(min=300, max=1000000),
|
||||
cv.Optional(CONF_STOP_BITS, default="1"): cv.enum(
|
||||
UART_STOP_BITS_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_PARITY, default="NONE"): cv.enum(
|
||||
UART_PARITY_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_DATA_BITS, default=8): cv.int_range(
|
||||
min=5, max=8
|
||||
),
|
||||
cv.Optional(CONF_DUMMY_RECEIVER, default=False): cv.boolean,
|
||||
cv.Optional(CONF_DEBUG, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
),
|
||||
cv.Length(max=channels),
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.ensure_list(
|
||||
cv.typed_schema(
|
||||
{
|
||||
it.name: usb_device_schema(it.cls, it.vid, it.pid).extend(
|
||||
channel_schema(it.max_channels, it.baud_rate_required)
|
||||
)
|
||||
for it in uart_types
|
||||
},
|
||||
upper=True,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
for device in config:
|
||||
var = await register_usb_client(device)
|
||||
for index, channel in enumerate(device[CONF_CHANNELS]):
|
||||
chvar = cg.new_Pvariable(channel[CONF_ID], index, channel[CONF_BUFFER_SIZE])
|
||||
await cg.register_parented(chvar, var)
|
||||
cg.add(chvar.set_rx_buffer_size(channel[CONF_BUFFER_SIZE]))
|
||||
cg.add(chvar.set_stop_bits(channel[CONF_STOP_BITS]))
|
||||
cg.add(chvar.set_data_bits(channel[CONF_DATA_BITS]))
|
||||
cg.add(chvar.set_parity(channel[CONF_PARITY]))
|
||||
cg.add(chvar.set_baud_rate(channel[CONF_BAUD_RATE]))
|
||||
cg.add(chvar.set_dummy_receiver(channel[CONF_DUMMY_RECEIVER]))
|
||||
cg.add(chvar.set_debug(channel[CONF_DEBUG]))
|
||||
cg.add(var.add_channel(chvar))
|
||||
if channel[CONF_DEBUG]:
|
||||
cg.add_define("USE_UART_DEBUGGER")
|
80
esphome/components/usb_uart/ch34x.cpp
Normal file
80
esphome/components/usb_uart/ch34x.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#include "usb_uart.h"
|
||||
#include "usb/usb_host.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "esphome/components/bytebuffer/bytebuffer.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace usb_uart {
|
||||
|
||||
using namespace bytebuffer;
|
||||
/**
|
||||
* CH34x
|
||||
*/
|
||||
|
||||
void USBUartTypeCH34X::enable_channels() {
|
||||
// enable the channels
|
||||
for (auto channel : this->channels_) {
|
||||
if (!channel->initialised_)
|
||||
continue;
|
||||
usb_host::transfer_cb_t callback = [=](const usb_host::TransferStatus &status) {
|
||||
if (!status.success) {
|
||||
ESP_LOGE(TAG, "Control transfer failed, status=%s", esp_err_to_name(status.error_code));
|
||||
channel->initialised_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
uint8_t divisor = 7;
|
||||
uint32_t clk = 12000000;
|
||||
|
||||
auto baud_rate = channel->baud_rate_;
|
||||
if (baud_rate < 256000) {
|
||||
if (baud_rate > 6000000 / 255) {
|
||||
divisor = 3;
|
||||
clk = 6000000;
|
||||
} else if (baud_rate > 750000 / 255) {
|
||||
divisor = 2;
|
||||
clk = 750000;
|
||||
} else if (baud_rate > 93750 / 255) {
|
||||
divisor = 1;
|
||||
clk = 93750;
|
||||
} else {
|
||||
divisor = 0;
|
||||
clk = 11719;
|
||||
}
|
||||
}
|
||||
ESP_LOGV(TAG, "baud_rate: %" PRIu32 ", divisor: %d, clk: %" PRIu32, baud_rate, divisor, clk);
|
||||
auto factor = static_cast<uint8_t>(clk / baud_rate);
|
||||
if (factor == 0 || factor == 0xFF) {
|
||||
ESP_LOGE(TAG, "Invalid baud rate %" PRIu32, baud_rate);
|
||||
channel->initialised_ = false;
|
||||
continue;
|
||||
}
|
||||
if ((clk / factor - baud_rate) > (baud_rate - clk / (factor + 1)))
|
||||
factor++;
|
||||
factor = 256 - factor;
|
||||
|
||||
uint16_t value = 0xC0;
|
||||
if (channel->stop_bits_ == UART_CONFIG_STOP_BITS_2)
|
||||
value |= 4;
|
||||
switch (channel->parity_) {
|
||||
case UART_CONFIG_PARITY_NONE:
|
||||
break;
|
||||
default:
|
||||
value |= 8 | ((channel->parity_ - 1) << 4);
|
||||
break;
|
||||
}
|
||||
value |= channel->data_bits_ - 5;
|
||||
value <<= 8;
|
||||
value |= 0x8C;
|
||||
uint8_t cmd = 0xA1 + channel->index_;
|
||||
if (channel->index_ >= 2)
|
||||
cmd += 0xE;
|
||||
this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, cmd, value, (factor << 8) | divisor, callback);
|
||||
}
|
||||
USBUartTypeCdcAcm::enable_channels();
|
||||
}
|
||||
} // namespace usb_uart
|
||||
} // namespace esphome
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
126
esphome/components/usb_uart/cp210x.cpp
Normal file
126
esphome/components/usb_uart/cp210x.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#include "usb_uart.h"
|
||||
#include "usb/usb_host.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "esphome/components/bytebuffer/bytebuffer.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace usb_uart {
|
||||
|
||||
using namespace bytebuffer;
|
||||
/**
|
||||
* Silabs CP210x Commands
|
||||
*/
|
||||
|
||||
static constexpr uint8_t IFC_ENABLE = 0x00; // Enable or disable the interface.
|
||||
static constexpr uint8_t SET_BAUDDIV = 0x01; // Set the baud rate divisor.
|
||||
static constexpr uint8_t GET_BAUDDIV = 0x02; // Get the baud rate divisor.
|
||||
static constexpr uint8_t SET_LINE_CTL = 0x03; // Set the line control.
|
||||
static constexpr uint8_t GET_LINE_CTL = 0x04; // Get the line control.
|
||||
static constexpr uint8_t SET_BREAK = 0x05; // Set a BREAK.
|
||||
static constexpr uint8_t IMM_CHAR = 0x06; // Send character out of order.
|
||||
static constexpr uint8_t SET_MHS = 0x07; // Set modem handshaking.
|
||||
static constexpr uint8_t GET_MDMSTS = 0x08; // Get modem status.
|
||||
static constexpr uint8_t SET_XON = 0x09; // Emulate XON.
|
||||
static constexpr uint8_t SET_XOFF = 0x0A; // Emulate XOFF.
|
||||
static constexpr uint8_t SET_EVENTMASK = 0x0B; // Set the event mask.
|
||||
static constexpr uint8_t GET_EVENTMASK = 0x0C; // Get the event mask.
|
||||
static constexpr uint8_t GET_EVENTSTATE = 0x16; // Get the event state.
|
||||
static constexpr uint8_t SET_RECEIVE = 0x17; // Set receiver max timeout.
|
||||
static constexpr uint8_t GET_RECEIVE = 0x18; // Get receiver max timeout.
|
||||
static constexpr uint8_t SET_CHAR = 0x0D; // Set special character individually.
|
||||
static constexpr uint8_t GET_CHARS = 0x0E; // Get special characters.
|
||||
static constexpr uint8_t GET_PROPS = 0x0F; // Get properties.
|
||||
static constexpr uint8_t GET_COMM_STATUS = 0x10; // Get the serial status.
|
||||
static constexpr uint8_t RESET = 0x11; // Reset.
|
||||
static constexpr uint8_t PURGE = 0x12; // Purge.
|
||||
static constexpr uint8_t SET_FLOW = 0x13; // Set flow control.
|
||||
static constexpr uint8_t GET_FLOW = 0x14; // Get flow control.
|
||||
static constexpr uint8_t EMBED_EVENTS = 0x15; // Control embedding of events in the data stream.
|
||||
static constexpr uint8_t GET_BAUDRATE = 0x1D; // Get the baud rate.
|
||||
static constexpr uint8_t SET_BAUDRATE = 0x1E; // Set the baud rate.
|
||||
static constexpr uint8_t SET_CHARS = 0x19; // Set special characters.
|
||||
static constexpr uint8_t VENDOR_SPECIFIC = 0xFF; // Vendor specific command.
|
||||
|
||||
std::vector<CdcEps> USBUartTypeCP210X::parse_descriptors_(usb_device_handle_t dev_hdl) {
|
||||
const usb_config_desc_t *config_desc;
|
||||
const usb_device_desc_t *device_desc;
|
||||
int conf_offset = 0, ep_offset;
|
||||
std::vector<CdcEps> cdc_devs{};
|
||||
|
||||
// Get required descriptors
|
||||
if (usb_host_get_device_descriptor(dev_hdl, &device_desc) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "get_device_descriptor failed");
|
||||
return {};
|
||||
}
|
||||
if (usb_host_get_active_config_descriptor(dev_hdl, &config_desc) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "get_active_config_descriptor failed");
|
||||
return {};
|
||||
}
|
||||
ESP_LOGD(TAG, "bDeviceClass: %u, bDeviceSubClass: %u", device_desc->bDeviceClass, device_desc->bDeviceSubClass);
|
||||
ESP_LOGD(TAG, "bNumInterfaces: %u", config_desc->bNumInterfaces);
|
||||
if (device_desc->bDeviceClass != 0) {
|
||||
ESP_LOGE(TAG, "bDeviceClass != 0");
|
||||
return {};
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i != config_desc->bNumInterfaces; i++) {
|
||||
auto data_desc = usb_parse_interface_descriptor(config_desc, 0, 0, &conf_offset);
|
||||
if (!data_desc) {
|
||||
ESP_LOGE(TAG, "data_desc: usb_parse_interface_descriptor failed");
|
||||
break;
|
||||
}
|
||||
if (data_desc->bNumEndpoints != 2 || data_desc->bInterfaceClass != USB_CLASS_VENDOR_SPEC) {
|
||||
ESP_LOGE(TAG, "data_desc: bInterfaceClass == %u, bInterfaceSubClass == %u, bNumEndpoints == %u",
|
||||
data_desc->bInterfaceClass, data_desc->bInterfaceSubClass, data_desc->bNumEndpoints);
|
||||
continue;
|
||||
}
|
||||
ep_offset = conf_offset;
|
||||
auto out_ep = usb_parse_endpoint_descriptor_by_index(data_desc, 0, config_desc->wTotalLength, &ep_offset);
|
||||
if (!out_ep) {
|
||||
ESP_LOGE(TAG, "out_ep: usb_parse_endpoint_descriptor_by_index failed");
|
||||
continue;
|
||||
}
|
||||
ep_offset = conf_offset;
|
||||
auto in_ep = usb_parse_endpoint_descriptor_by_index(data_desc, 1, config_desc->wTotalLength, &ep_offset);
|
||||
if (!in_ep) {
|
||||
ESP_LOGE(TAG, "in_ep: usb_parse_endpoint_descriptor_by_index failed");
|
||||
continue;
|
||||
}
|
||||
if (in_ep->bEndpointAddress & usb_host::USB_DIR_IN) {
|
||||
cdc_devs.push_back({CdcEps{nullptr, in_ep, out_ep, data_desc->bInterfaceNumber}});
|
||||
} else {
|
||||
cdc_devs.push_back({CdcEps{nullptr, out_ep, in_ep, data_desc->bInterfaceNumber}});
|
||||
}
|
||||
}
|
||||
return cdc_devs;
|
||||
}
|
||||
|
||||
void USBUartTypeCP210X::enable_channels() {
|
||||
// enable the channels
|
||||
for (auto channel : this->channels_) {
|
||||
if (!channel->initialised_)
|
||||
continue;
|
||||
usb_host::transfer_cb_t callback = [=](const usb_host::TransferStatus &status) {
|
||||
if (!status.success) {
|
||||
ESP_LOGE(TAG, "Control transfer failed, status=%s", esp_err_to_name(status.error_code));
|
||||
channel->initialised_ = false;
|
||||
}
|
||||
};
|
||||
this->control_transfer(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, IFC_ENABLE, 1, channel->index_, callback);
|
||||
uint16_t line_control = channel->stop_bits_;
|
||||
line_control |= static_cast<uint8_t>(channel->parity_) << 4;
|
||||
line_control |= channel->data_bits_ << 8;
|
||||
ESP_LOGD(TAG, "Line control value 0x%X", line_control);
|
||||
this->control_transfer(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, SET_LINE_CTL, line_control, channel->index_,
|
||||
callback);
|
||||
auto baud = ByteBuffer::wrap(channel->baud_rate_, LITTLE);
|
||||
this->control_transfer(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, SET_BAUDRATE, 0, channel->index_, callback,
|
||||
baud.get_data());
|
||||
}
|
||||
USBUartTypeCdcAcm::enable_channels();
|
||||
}
|
||||
} // namespace usb_uart
|
||||
} // namespace esphome
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
325
esphome/components/usb_uart/usb_uart.cpp
Normal file
325
esphome/components/usb_uart/usb_uart.cpp
Normal file
@ -0,0 +1,325 @@
|
||||
// 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_uart.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/uart/uart_debugger.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace usb_uart {
|
||||
|
||||
/**
|
||||
*
|
||||
* Given a configuration, look for the required interfaces defining a CDC-ACM device
|
||||
* @param config_desc The configuration descriptor
|
||||
* @param intf_idx The index of the interface to be examined
|
||||
* @return
|
||||
*/
|
||||
static optional<CdcEps> get_cdc(const usb_config_desc_t *config_desc, uint8_t intf_idx) {
|
||||
int conf_offset, ep_offset;
|
||||
const usb_ep_desc_t *notify_ep{}, *in_ep{}, *out_ep{};
|
||||
uint8_t interface_number = 0;
|
||||
// look for an interface with one interrupt endpoint (notify), and an interface with two bulk endpoints (data in/out)
|
||||
for (;;) {
|
||||
auto intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx++, 0, &conf_offset);
|
||||
if (!intf_desc) {
|
||||
ESP_LOGE(TAG, "usb_parse_interface_descriptor failed");
|
||||
return nullopt;
|
||||
}
|
||||
if (intf_desc->bNumEndpoints == 1) {
|
||||
ep_offset = conf_offset;
|
||||
notify_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 0, config_desc->wTotalLength, &ep_offset);
|
||||
if (!notify_ep) {
|
||||
ESP_LOGE(TAG, "notify_ep: usb_parse_endpoint_descriptor_by_index failed");
|
||||
return nullopt;
|
||||
}
|
||||
if (notify_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_INT)
|
||||
notify_ep = nullptr;
|
||||
} else if (USB_CLASS_CDC_DATA && intf_desc->bNumEndpoints == 2) {
|
||||
interface_number = intf_desc->bInterfaceNumber;
|
||||
ep_offset = conf_offset;
|
||||
out_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 0, config_desc->wTotalLength, &ep_offset);
|
||||
if (!out_ep) {
|
||||
ESP_LOGE(TAG, "out_ep: usb_parse_endpoint_descriptor_by_index failed");
|
||||
return nullopt;
|
||||
}
|
||||
if (out_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_BULK)
|
||||
out_ep = nullptr;
|
||||
ep_offset = conf_offset;
|
||||
in_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 1, config_desc->wTotalLength, &ep_offset);
|
||||
if (!in_ep) {
|
||||
ESP_LOGE(TAG, "in_ep: usb_parse_endpoint_descriptor_by_index failed");
|
||||
return nullopt;
|
||||
}
|
||||
if (in_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_BULK)
|
||||
in_ep = nullptr;
|
||||
}
|
||||
if (in_ep != nullptr && out_ep != nullptr && notify_ep != nullptr)
|
||||
break;
|
||||
}
|
||||
if (in_ep->bEndpointAddress & usb_host::USB_DIR_IN)
|
||||
return CdcEps{notify_ep, in_ep, out_ep, interface_number};
|
||||
return CdcEps{notify_ep, out_ep, in_ep, interface_number};
|
||||
}
|
||||
|
||||
std::vector<CdcEps> USBUartTypeCdcAcm::parse_descriptors_(usb_device_handle_t dev_hdl) {
|
||||
const usb_config_desc_t *config_desc;
|
||||
const usb_device_desc_t *device_desc;
|
||||
int desc_offset = 0;
|
||||
std::vector<CdcEps> cdc_devs{};
|
||||
|
||||
// Get required descriptors
|
||||
if (usb_host_get_device_descriptor(dev_hdl, &device_desc) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "get_device_descriptor failed");
|
||||
return {};
|
||||
}
|
||||
if (usb_host_get_active_config_descriptor(dev_hdl, &config_desc) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "get_active_config_descriptor failed");
|
||||
return {};
|
||||
}
|
||||
if (device_desc->bDeviceClass == USB_CLASS_COMM) {
|
||||
// single CDC-ACM device
|
||||
if (auto eps = get_cdc(config_desc, 0)) {
|
||||
ESP_LOGV(TAG, "Found CDC-ACM device");
|
||||
cdc_devs.push_back(*eps);
|
||||
}
|
||||
return cdc_devs;
|
||||
}
|
||||
if (((device_desc->bDeviceClass == USB_CLASS_MISC) && (device_desc->bDeviceSubClass == USB_SUBCLASS_COMMON) &&
|
||||
(device_desc->bDeviceProtocol == USB_DEVICE_PROTOCOL_IAD)) ||
|
||||
((device_desc->bDeviceClass == USB_CLASS_PER_INTERFACE) && (device_desc->bDeviceSubClass == USB_SUBCLASS_NULL) &&
|
||||
(device_desc->bDeviceProtocol == USB_PROTOCOL_NULL))) {
|
||||
// This is a composite device, that uses Interface Association Descriptor
|
||||
const auto *this_desc = reinterpret_cast<const usb_standard_desc_t *>(config_desc);
|
||||
for (;;) {
|
||||
this_desc = usb_parse_next_descriptor_of_type(this_desc, config_desc->wTotalLength,
|
||||
USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION, &desc_offset);
|
||||
if (!this_desc)
|
||||
break;
|
||||
const auto *iad_desc = reinterpret_cast<const usb_iad_desc_t *>(this_desc);
|
||||
|
||||
if (iad_desc->bFunctionClass == USB_CLASS_COMM && iad_desc->bFunctionSubClass == USB_CDC_SUBCLASS_ACM) {
|
||||
ESP_LOGV(TAG, "Found CDC-ACM device in composite device");
|
||||
if (auto eps = get_cdc(config_desc, iad_desc->bFirstInterface))
|
||||
cdc_devs.push_back(*eps);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cdc_devs;
|
||||
}
|
||||
|
||||
void RingBuffer::push(uint8_t item) {
|
||||
this->buffer_[this->insert_pos_] = item;
|
||||
this->insert_pos_ = (this->insert_pos_ + 1) % this->buffer_size_;
|
||||
}
|
||||
void RingBuffer::push(const uint8_t *data, size_t len) {
|
||||
for (size_t i = 0; i != len; i++) {
|
||||
this->buffer_[this->insert_pos_] = *data++;
|
||||
this->insert_pos_ = (this->insert_pos_ + 1) % this->buffer_size_;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t RingBuffer::pop() {
|
||||
uint8_t item = this->buffer_[this->read_pos_];
|
||||
this->read_pos_ = (this->read_pos_ + 1) % this->buffer_size_;
|
||||
return item;
|
||||
}
|
||||
size_t RingBuffer::pop(uint8_t *data, size_t len) {
|
||||
len = std::min(len, this->get_available());
|
||||
for (size_t i = 0; i != len; i++) {
|
||||
*data++ = this->buffer_[this->read_pos_];
|
||||
this->read_pos_ = (this->read_pos_ + 1) % this->buffer_size_;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
void USBUartChannel::write_array(const uint8_t *data, size_t len) {
|
||||
if (!this->initialised_) {
|
||||
ESP_LOGV(TAG, "Channel not initialised - write ignored");
|
||||
return;
|
||||
}
|
||||
while (this->output_buffer_.get_free_space() != 0 && len-- != 0) {
|
||||
this->output_buffer_.push(*data++);
|
||||
}
|
||||
len++;
|
||||
if (len > 0) {
|
||||
ESP_LOGE(TAG, "Buffer full - failed to write %d bytes", len);
|
||||
}
|
||||
this->parent_->start_output(this);
|
||||
}
|
||||
|
||||
bool USBUartChannel::peek_byte(uint8_t *data) {
|
||||
if (this->input_buffer_.is_empty()) {
|
||||
return false;
|
||||
}
|
||||
*data = this->input_buffer_.peek();
|
||||
return true;
|
||||
}
|
||||
bool USBUartChannel::read_array(uint8_t *data, size_t len) {
|
||||
if (!this->initialised_) {
|
||||
ESP_LOGV(TAG, "Channel not initialised - read ignored");
|
||||
return false;
|
||||
}
|
||||
auto available = this->available();
|
||||
bool status = true;
|
||||
if (len > available) {
|
||||
ESP_LOGV(TAG, "underflow: requested %zu but returned %d, bytes", len, available);
|
||||
len = available;
|
||||
status = false;
|
||||
}
|
||||
for (size_t i = 0; i != len; i++) {
|
||||
*data++ = this->input_buffer_.pop();
|
||||
}
|
||||
this->parent_->start_input(this);
|
||||
return status;
|
||||
}
|
||||
void USBUartComponent::setup() { USBClient::setup(); }
|
||||
void USBUartComponent::loop() { USBClient::loop(); }
|
||||
void USBUartComponent::dump_config() {
|
||||
USBClient::dump_config();
|
||||
for (auto &channel : this->channels_) {
|
||||
ESP_LOGCONFIG(TAG, " UART Channel %d", channel->index_);
|
||||
ESP_LOGCONFIG(TAG, " Baud Rate: %" PRIu32 " baud", channel->baud_rate_);
|
||||
ESP_LOGCONFIG(TAG, " Data Bits: %u", channel->data_bits_);
|
||||
ESP_LOGCONFIG(TAG, " Parity: %s", PARITY_NAMES[channel->parity_]);
|
||||
ESP_LOGCONFIG(TAG, " Stop bits: %s", STOP_BITS_NAMES[channel->stop_bits_]);
|
||||
ESP_LOGCONFIG(TAG, " Debug: %s", YESNO(channel->debug_));
|
||||
ESP_LOGCONFIG(TAG, " Dummy receiver: %s", YESNO(channel->dummy_receiver_));
|
||||
}
|
||||
}
|
||||
void USBUartComponent::start_input(USBUartChannel *channel) {
|
||||
if (!channel->initialised_ || channel->input_started_ ||
|
||||
channel->input_buffer_.get_free_space() < channel->cdc_dev_.in_ep->wMaxPacketSize)
|
||||
return;
|
||||
auto ep = channel->cdc_dev_.in_ep;
|
||||
auto callback = [this, channel](const usb_host::TransferStatus &status) {
|
||||
ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code);
|
||||
if (!status.success) {
|
||||
ESP_LOGE(TAG, "Control transfer failed, status=%s", esp_err_to_name(status.error_code));
|
||||
return;
|
||||
}
|
||||
#ifdef USE_UART_DEBUGGER
|
||||
if (channel->debug_) {
|
||||
uart::UARTDebug::log_hex(uart::UART_DIRECTION_RX,
|
||||
std::vector<uint8_t>(status.data, status.data + status.data_len), ','); // NOLINT()
|
||||
}
|
||||
#endif
|
||||
channel->input_started_ = false;
|
||||
if (!channel->dummy_receiver_) {
|
||||
for (size_t i = 0; i != status.data_len; i++) {
|
||||
channel->input_buffer_.push(status.data[i]);
|
||||
}
|
||||
}
|
||||
if (channel->input_buffer_.get_free_space() >= channel->cdc_dev_.in_ep->wMaxPacketSize) {
|
||||
this->defer([this, channel] { this->start_input(channel); });
|
||||
}
|
||||
};
|
||||
channel->input_started_ = true;
|
||||
this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize);
|
||||
}
|
||||
|
||||
void USBUartComponent::start_output(USBUartChannel *channel) {
|
||||
if (channel->output_started_)
|
||||
return;
|
||||
if (channel->output_buffer_.is_empty()) {
|
||||
return;
|
||||
}
|
||||
auto ep = channel->cdc_dev_.out_ep;
|
||||
auto callback = [this, channel](const usb_host::TransferStatus &status) {
|
||||
ESP_LOGV(TAG, "Output Transfer result: length: %u; status %X", status.data_len, status.error_code);
|
||||
channel->output_started_ = false;
|
||||
this->defer([this, channel] { this->start_output(channel); });
|
||||
};
|
||||
channel->output_started_ = true;
|
||||
uint8_t data[ep->wMaxPacketSize];
|
||||
auto len = channel->output_buffer_.pop(data, ep->wMaxPacketSize);
|
||||
this->transfer_out(ep->bEndpointAddress, callback, data, len);
|
||||
#ifdef USE_UART_DEBUGGER
|
||||
if (channel->debug_) {
|
||||
uart::UARTDebug::log_hex(uart::UART_DIRECTION_TX, std::vector<uint8_t>(data, data + len), ','); // NOLINT()
|
||||
}
|
||||
#endif
|
||||
ESP_LOGV(TAG, "Output %d bytes started", len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hacky fix for some devices that report incorrect MPS values
|
||||
* @param ep The endpoint descriptor
|
||||
*/
|
||||
static void fix_mps(const usb_ep_desc_t *ep) {
|
||||
if (ep != nullptr) {
|
||||
auto *ep_mutable = const_cast<usb_ep_desc_t *>(ep);
|
||||
if (ep->wMaxPacketSize > 64) {
|
||||
ESP_LOGW(TAG, "Corrected MPS of EP %u from %u to 64", ep->bEndpointAddress, ep->wMaxPacketSize);
|
||||
ep_mutable->wMaxPacketSize = 64;
|
||||
}
|
||||
}
|
||||
}
|
||||
void USBUartTypeCdcAcm::on_connected() {
|
||||
auto cdc_devs = this->parse_descriptors_(this->device_handle_);
|
||||
if (cdc_devs.empty()) {
|
||||
this->status_set_error("No CDC-ACM device found");
|
||||
this->disconnect();
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Found %zu CDC-ACM devices", cdc_devs.size());
|
||||
auto i = 0;
|
||||
for (auto channel : this->channels_) {
|
||||
if (i == cdc_devs.size()) {
|
||||
ESP_LOGE(TAG, "No configuration found for channel %d", channel->index_);
|
||||
this->status_set_warning("No configuration found for channel");
|
||||
break;
|
||||
}
|
||||
channel->cdc_dev_ = cdc_devs[i++];
|
||||
fix_mps(channel->cdc_dev_.in_ep);
|
||||
fix_mps(channel->cdc_dev_.out_ep);
|
||||
channel->initialised_ = true;
|
||||
auto err = usb_host_interface_claim(this->handle_, this->device_handle_, channel->cdc_dev_.interface_number, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_,
|
||||
channel->cdc_dev_.interface_number);
|
||||
this->status_set_error("usb_host_interface_claim failed");
|
||||
this->disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->enable_channels();
|
||||
}
|
||||
|
||||
void USBUartTypeCdcAcm::on_disconnected() {
|
||||
for (auto channel : this->channels_) {
|
||||
if (channel->cdc_dev_.in_ep != nullptr) {
|
||||
usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
|
||||
usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
|
||||
}
|
||||
if (channel->cdc_dev_.out_ep != nullptr) {
|
||||
usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
|
||||
usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
|
||||
}
|
||||
if (channel->cdc_dev_.notify_ep != nullptr) {
|
||||
usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
|
||||
usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
|
||||
}
|
||||
usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.interface_number);
|
||||
channel->initialised_ = false;
|
||||
channel->input_started_ = false;
|
||||
channel->output_started_ = false;
|
||||
channel->input_buffer_.clear();
|
||||
channel->output_buffer_.clear();
|
||||
}
|
||||
USBClient::on_disconnected();
|
||||
}
|
||||
|
||||
void USBUartTypeCdcAcm::enable_channels() {
|
||||
for (auto channel : this->channels_) {
|
||||
if (!channel->initialised_)
|
||||
continue;
|
||||
channel->input_started_ = false;
|
||||
channel->output_started_ = false;
|
||||
this->start_input(channel);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace usb_uart
|
||||
} // namespace esphome
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
151
esphome/components/usb_uart/usb_uart.h
Normal file
151
esphome/components/usb_uart/usb_uart.h
Normal file
@ -0,0 +1,151 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/uart/uart_component.h"
|
||||
#include "esphome/components/usb_host/usb_host.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace usb_uart {
|
||||
class USBUartTypeCdcAcm;
|
||||
class USBUartComponent;
|
||||
|
||||
static const char *const TAG = "usb_uart";
|
||||
|
||||
static constexpr uint8_t USB_CDC_SUBCLASS_ACM = 0x02;
|
||||
static constexpr uint8_t USB_SUBCLASS_COMMON = 0x02;
|
||||
static constexpr uint8_t USB_SUBCLASS_NULL = 0x00;
|
||||
static constexpr uint8_t USB_PROTOCOL_NULL = 0x00;
|
||||
static constexpr uint8_t USB_DEVICE_PROTOCOL_IAD = 0x01;
|
||||
static constexpr uint8_t USB_VENDOR_IFC = usb_host::USB_TYPE_VENDOR | usb_host::USB_RECIP_INTERFACE;
|
||||
static constexpr uint8_t USB_VENDOR_DEV = usb_host::USB_TYPE_VENDOR | usb_host::USB_RECIP_DEVICE;
|
||||
|
||||
struct CdcEps {
|
||||
const usb_ep_desc_t *notify_ep;
|
||||
const usb_ep_desc_t *in_ep;
|
||||
const usb_ep_desc_t *out_ep;
|
||||
uint8_t interface_number;
|
||||
};
|
||||
|
||||
enum UARTParityOptions {
|
||||
UART_CONFIG_PARITY_NONE = 0,
|
||||
UART_CONFIG_PARITY_ODD,
|
||||
UART_CONFIG_PARITY_EVEN,
|
||||
UART_CONFIG_PARITY_MARK,
|
||||
UART_CONFIG_PARITY_SPACE,
|
||||
};
|
||||
|
||||
enum UARTStopBitsOptions {
|
||||
UART_CONFIG_STOP_BITS_1 = 0,
|
||||
UART_CONFIG_STOP_BITS_1_5,
|
||||
UART_CONFIG_STOP_BITS_2,
|
||||
};
|
||||
|
||||
static const char *const PARITY_NAMES[] = {"NONE", "ODD", "EVEN", "MARK", "SPACE"};
|
||||
static const char *const STOP_BITS_NAMES[] = {"1", "1.5", "2"};
|
||||
|
||||
class RingBuffer {
|
||||
public:
|
||||
RingBuffer(uint16_t buffer_size) : buffer_size_(buffer_size), buffer_(new uint8_t[buffer_size]) {}
|
||||
bool is_empty() const { return this->read_pos_ == this->insert_pos_; }
|
||||
size_t get_available() const {
|
||||
return (this->insert_pos_ + this->buffer_size_ - this->read_pos_) % this->buffer_size_;
|
||||
};
|
||||
size_t get_free_space() const { return this->buffer_size_ - 1 - this->get_available(); }
|
||||
uint8_t peek() const { return this->buffer_[this->read_pos_]; }
|
||||
void push(uint8_t item);
|
||||
void push(const uint8_t *data, size_t len);
|
||||
uint8_t pop();
|
||||
size_t pop(uint8_t *data, size_t len);
|
||||
void clear() { this->read_pos_ = this->insert_pos_ = 0; }
|
||||
|
||||
protected:
|
||||
uint16_t insert_pos_ = 0;
|
||||
uint16_t read_pos_ = 0;
|
||||
uint16_t buffer_size_;
|
||||
uint8_t *buffer_;
|
||||
};
|
||||
|
||||
class USBUartChannel : public uart::UARTComponent, public Parented<USBUartComponent> {
|
||||
friend class USBUartComponent;
|
||||
friend class USBUartTypeCdcAcm;
|
||||
friend class USBUartTypeCP210X;
|
||||
friend class USBUartTypeCH34X;
|
||||
|
||||
public:
|
||||
USBUartChannel(uint8_t index, uint16_t buffer_size)
|
||||
: index_(index), input_buffer_(RingBuffer(buffer_size)), output_buffer_(RingBuffer(buffer_size)) {}
|
||||
void write_array(const uint8_t *data, size_t len) override;
|
||||
;
|
||||
bool peek_byte(uint8_t *data) override;
|
||||
;
|
||||
bool read_array(uint8_t *data, size_t len) override;
|
||||
int available() override { return static_cast<int>(this->input_buffer_.get_available()); }
|
||||
void flush() override {}
|
||||
void check_logger_conflict() override {}
|
||||
void set_parity(UARTParityOptions parity) { this->parity_ = parity; }
|
||||
void set_debug(bool debug) { this->debug_ = debug; }
|
||||
void set_dummy_receiver(bool dummy_receiver) { this->dummy_receiver_ = dummy_receiver; }
|
||||
|
||||
protected:
|
||||
const uint8_t index_;
|
||||
RingBuffer input_buffer_;
|
||||
RingBuffer output_buffer_;
|
||||
UARTParityOptions parity_{UART_CONFIG_PARITY_NONE};
|
||||
bool input_started_{true};
|
||||
bool output_started_{true};
|
||||
CdcEps cdc_dev_{};
|
||||
bool debug_{};
|
||||
bool dummy_receiver_{};
|
||||
bool initialised_{};
|
||||
};
|
||||
|
||||
class USBUartComponent : public usb_host::USBClient {
|
||||
public:
|
||||
USBUartComponent(uint16_t vid, uint16_t pid) : usb_host::USBClient(vid, pid) {}
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
std::vector<USBUartChannel *> get_channels() { return this->channels_; }
|
||||
|
||||
void add_channel(USBUartChannel *channel) { this->channels_.push_back(channel); }
|
||||
|
||||
void start_input(USBUartChannel *channel);
|
||||
void start_output(USBUartChannel *channel);
|
||||
|
||||
protected:
|
||||
std::vector<USBUartChannel *> channels_{};
|
||||
};
|
||||
|
||||
class USBUartTypeCdcAcm : public USBUartComponent {
|
||||
public:
|
||||
USBUartTypeCdcAcm(uint16_t vid, uint16_t pid) : USBUartComponent(vid, pid) {}
|
||||
|
||||
protected:
|
||||
virtual std::vector<CdcEps> parse_descriptors_(usb_device_handle_t dev_hdl);
|
||||
void on_connected() override;
|
||||
virtual void enable_channels();
|
||||
void on_disconnected() override;
|
||||
};
|
||||
|
||||
class USBUartTypeCP210X : public USBUartTypeCdcAcm {
|
||||
public:
|
||||
USBUartTypeCP210X(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
|
||||
|
||||
protected:
|
||||
std::vector<CdcEps> parse_descriptors_(usb_device_handle_t dev_hdl) override;
|
||||
void enable_channels() override;
|
||||
};
|
||||
class USBUartTypeCH34X : public USBUartTypeCdcAcm {
|
||||
public:
|
||||
USBUartTypeCH34X(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
|
||||
|
||||
protected:
|
||||
void enable_channels() override;
|
||||
};
|
||||
|
||||
} // namespace usb_uart
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
5
tests/components/usb_host/test.esp32-s3-idf.yaml
Normal file
5
tests/components/usb_host/test.esp32-s3-idf.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
usb_host:
|
||||
devices:
|
||||
- id: device_1
|
||||
vid: 0x1234
|
||||
pid: 0x1234
|
33
tests/components/usb_uart/common.yaml
Normal file
33
tests/components/usb_uart/common.yaml
Normal file
@ -0,0 +1,33 @@
|
||||
usb_uart:
|
||||
- id: uart_0
|
||||
type: cdc_acm
|
||||
vid: 0x1234
|
||||
pid: 0x5678
|
||||
channels:
|
||||
- id: channel_0_1
|
||||
- id: uart_1
|
||||
type: cp210x
|
||||
channels:
|
||||
- id: channel_1_1
|
||||
baud_rate: 115200
|
||||
stop_bits: 2
|
||||
data_bits: 7
|
||||
parity: even
|
||||
- id: uart_2
|
||||
type: ch34x
|
||||
channels:
|
||||
- id: channel_2_1
|
||||
baud_rate: 115200
|
||||
- id: channel_2_2
|
||||
baud_rate: 9600
|
||||
- id: uart_3
|
||||
type: ch340
|
||||
channels:
|
||||
- id: channel_3_1
|
||||
baud_rate: 57600
|
||||
- id: uart_4
|
||||
type: esp_jtag
|
||||
channels:
|
||||
- id: channel_4_1
|
||||
debug: true
|
||||
dummy_receiver: true
|
1
tests/components/usb_uart/test.esp32-s3-idf.yaml
Normal file
1
tests/components/usb_uart/test.esp32-s3-idf.yaml
Normal file
@ -0,0 +1 @@
|
||||
!include common.yaml
|
Loading…
x
Reference in New Issue
Block a user