2#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
3 defined(USE_ESP32_VARIANT_ESP32S31) || defined(USE_ESP32_VARIANT_ESP32H4)
20static optional<CdcEps> get_cdc(
const usb_config_desc_t *config_desc, uint8_t intf_idx) {
21 int conf_offset, ep_offset;
25 const auto *intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx++, 0, &conf_offset);
27 ESP_LOGE(TAG,
"usb_parse_interface_descriptor failed");
30 ESP_LOGD(TAG,
"intf_desc: bInterfaceClass=%02X, bInterfaceSubClass=%02X, bInterfaceProtocol=%02X, bNumEndpoints=%d",
31 intf_desc->bInterfaceClass, intf_desc->bInterfaceSubClass, intf_desc->bInterfaceProtocol,
32 intf_desc->bNumEndpoints);
33 for (uint8_t i = 0; i != intf_desc->bNumEndpoints; i++) {
34 ep_offset = conf_offset;
35 const auto *ep = usb_parse_endpoint_descriptor_by_index(intf_desc, i, config_desc->wTotalLength, &ep_offset);
37 ESP_LOGE(TAG,
"Ran out of interfaces at %d before finding all endpoints", i);
40 ESP_LOGD(TAG,
"ep: bEndpointAddress=%02X, bmAttributes=%02X", ep->bEndpointAddress, ep->bmAttributes);
41 if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_INT) {
43 eps.interrupt_interface_number = intf_desc->bInterfaceNumber;
44 eps.interrupt_interface_class = intf_desc->bInterfaceClass;
45 }
else if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_BULK && ep->bEndpointAddress & usb_host::USB_DIR_IN &&
46 (eps.bulk_interface_number == 0xFF || eps.bulk_interface_number == intf_desc->bInterfaceNumber)) {
48 eps.bulk_interface_number = intf_desc->bInterfaceNumber;
49 eps.bulk_interface_class = intf_desc->bInterfaceClass;
50 }
else if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_BULK && !(ep->bEndpointAddress & usb_host::USB_DIR_IN) &&
51 (eps.bulk_interface_number == 0xFF || eps.bulk_interface_number == intf_desc->bInterfaceNumber)) {
53 eps.bulk_interface_number = intf_desc->bInterfaceNumber;
54 eps.bulk_interface_class = intf_desc->bInterfaceClass;
56 ESP_LOGE(TAG,
"Unexpected endpoint attributes: %02X", ep->bmAttributes);
60 if (eps.in_ep !=
nullptr && eps.out_ep !=
nullptr && eps.notify_ep !=
nullptr)
66 const usb_config_desc_t *config_desc;
67 const usb_device_desc_t *device_desc;
69 std::vector<CdcEps> cdc_devs{};
72 if (usb_host_get_device_descriptor(dev_hdl, &device_desc) != ESP_OK) {
73 ESP_LOGE(TAG,
"get_device_descriptor failed");
76 if (usb_host_get_active_config_descriptor(dev_hdl, &config_desc) != ESP_OK) {
77 ESP_LOGE(TAG,
"get_active_config_descriptor failed");
80 if (device_desc->bDeviceClass == USB_CLASS_COMM || device_desc->bDeviceClass == USB_CLASS_VENDOR_SPEC) {
82 if (
auto eps = get_cdc(config_desc, 0)) {
83 ESP_LOGV(TAG,
"Found CDC-ACM device");
84 cdc_devs.push_back(*eps);
88 if (((device_desc->bDeviceClass == USB_CLASS_MISC) && (device_desc->bDeviceSubClass == USB_SUBCLASS_COMMON) &&
89 (device_desc->bDeviceProtocol == USB_DEVICE_PROTOCOL_IAD)) ||
90 ((device_desc->bDeviceClass == USB_CLASS_PER_INTERFACE) && (device_desc->bDeviceSubClass == USB_SUBCLASS_NULL) &&
91 (device_desc->bDeviceProtocol == USB_PROTOCOL_NULL))) {
93 const auto *this_desc =
reinterpret_cast<const usb_standard_desc_t *
>(config_desc);
95 this_desc = usb_parse_next_descriptor_of_type(this_desc, config_desc->wTotalLength,
96 USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION, &desc_offset);
99 const auto *iad_desc =
reinterpret_cast<const usb_iad_desc_t *
>(this_desc);
101 if (iad_desc->bFunctionClass == USB_CLASS_COMM && iad_desc->bFunctionSubClass == USB_CDC_SUBCLASS_ACM) {
102 ESP_LOGV(TAG,
"Found CDC-ACM device in composite device");
103 if (
auto eps = get_cdc(config_desc, iad_desc->bFirstInterface))
104 cdc_devs.push_back(*eps);
121 for (
size_t i = 0; i !=
len; i++) {
134 for (
size_t i = 0; i !=
len; i++) {
142 ESP_LOGD(TAG,
"Channel not initialised - write ignored");
145#ifdef USE_UART_DEBUGGER
147 constexpr size_t batch = 16;
149 for (
size_t off = 0; off <
len; off += batch) {
150 size_t n = std::min(
len - off, batch);
158 if (chunk ==
nullptr) {
159 ESP_LOGE(TAG,
"Output pool full - lost %zu bytes",
len);
163 memcpy(chunk->
data, data, chunk_len);
164 chunk->
length = chunk_len;
171 this->
parent_->start_output(
this);
182 this->
parent_->start_output(
this);
185 if (!this->
output_queue_.empty() || this->output_started_.load())
199 ESP_LOGV(TAG,
"Channel not initialised - read ignored");
205 ESP_LOGV(TAG,
"underflow: requested %zu but returned %d, bytes",
len,
available);
209 for (
size_t i = 0; i !=
len; i++) {
212 this->
parent_->start_input(
this);
224 auto *channel = chunk->
channel;
226#ifdef USE_UART_DEBUGGER
227 if (channel->debug_) {
230 ESP_LOGD(TAG,
"%s<<< %s", channel->debug_prefix_.c_str(), buf);
236 if (channel->input_buffer_.get_free_space() < chunk->
length) {
248 if (channel->rx_callback_) {
249 channel->rx_callback_();
254 uint16_t dropped = this->
usb_data_queue_.get_and_reset_dropped_count();
256 ESP_LOGW(TAG,
"Dropped %u USB data chunks due to buffer overflow", dropped);
265 USBClient::dump_config();
269 " Baud Rate: %" PRIu32
" baud\n"
273 " Flush Timeout: %" PRIu32
" ms\n"
275 " Dummy receiver: %s",
276 channel->index_, channel->baud_rate_, channel->data_bits_,
277 PARITY_NAMES[
static_cast<uint8_t
>(channel->line_parity_())],
278 STOP_BITS_NAMES[
static_cast<uint8_t
>(channel->line_stop_bits_())], channel->flush_timeout_ms_,
279 YESNO(channel->debug_), YESNO(channel->dummy_receiver_));
298 auto started =
false;
299 if (!channel->
input_started_.compare_exchange_strong(started,
true))
304 ESP_LOGV(TAG,
"Transfer result: length: %u; status %X",
status.data_len,
status.error_code);
306 ESP_LOGE(TAG,
"Input transfer failed, status=%s", esp_err_to_name(
status.error_code));
315 if (chunk ==
nullptr) {
345 if (!this->
transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize)) {
346 ESP_LOGE(TAG,
"IN transfer submission failed for ep=0x%02X", ep->bEndpointAddress);
359 bool expected =
false;
360 if (!channel->
output_started_.compare_exchange_strong(expected,
true, std::memory_order_acq_rel)) {
365 if (chunk ==
nullptr) {
377 ESP_LOGW(TAG,
"Output transfer failed: status %X",
status.error_code);
379 ESP_LOGV(TAG,
"Output Transfer result: length: %u; status %X",
status.data_len,
status.error_code);
395 ESP_LOGV(TAG,
"Output %u bytes started",
len);
402static void fix_mps(
const usb_ep_desc_t *ep) {
404 auto *ep_mutable =
const_cast<usb_ep_desc_t *
>(ep);
405 if (ep->wMaxPacketSize > usb_host::USB_MAX_PACKET_SIZE) {
406 ESP_LOGW(TAG,
"Corrected MPS of EP 0x%02X from %u to %u",
static_cast<uint8_t
>(ep->bEndpointAddress & 0xFF),
407 ep->wMaxPacketSize, usb_host::USB_MAX_PACKET_SIZE);
408 ep_mutable->wMaxPacketSize = usb_host::USB_MAX_PACKET_SIZE;
414 if (cdc_devs.empty()) {
419 ESP_LOGD(TAG,
"Found %zu CDC-ACM devices", cdc_devs.size());
422 if (i == cdc_devs.size()) {
423 ESP_LOGE(TAG,
"No configuration found for channel %d", channel->index_);
427 channel->cdc_dev_ = cdc_devs[i++];
428 fix_mps(channel->cdc_dev_.in_ep);
429 fix_mps(channel->cdc_dev_.out_ep);
430 channel->initialised_.store(
true);
434 if (channel->cdc_dev_.interrupt_interface_number != 0xFF &&
435 channel->cdc_dev_.interrupt_interface_number != channel->cdc_dev_.bulk_interface_number) {
437 ESP_LOGD(TAG,
"Comm interface %d left unclaimed", channel->cdc_dev_.interrupt_interface_number);
440 channel->cdc_dev_.interrupt_interface_number, 0);
441 if (err_comm != ESP_OK) {
443 ESP_LOGW(TAG,
"Could not claim comm interface %d: %s", channel->cdc_dev_.interrupt_interface_number,
444 esp_err_to_name(err_comm));
446 ESP_LOGD(TAG,
"Claimed comm interface %d", channel->cdc_dev_.interrupt_interface_number);
447 channel->cdc_dev_.interrupt_interface_claimed =
true;
452 usb_host_interface_claim(this->
handle_, this->
device_handle_, channel->cdc_dev_.bulk_interface_number, 0);
454 ESP_LOGE(TAG,
"usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_,
455 channel->cdc_dev_.bulk_interface_number);
469 if (channel->cdc_dev_.in_ep ==
nullptr)
471 usb_host_endpoint_halt(this->
device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
472 usb_host_endpoint_flush(this->
device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
473 if (channel->cdc_dev_.out_ep !=
nullptr) {
474 usb_host_endpoint_halt(this->
device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
475 usb_host_endpoint_flush(this->
device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
479 if (channel->cdc_dev_.notify_ep !=
nullptr && channel->cdc_dev_.interrupt_interface_claimed) {
480 usb_host_endpoint_halt(this->
device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
481 usb_host_endpoint_flush(this->
device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
483 if (channel->cdc_dev_.interrupt_interface_claimed) {
484 usb_host_interface_release(this->
handle_, this->
device_handle_, channel->cdc_dev_.interrupt_interface_number);
485 channel->cdc_dev_.interrupt_interface_claimed =
false;
487 usb_host_interface_release(this->
handle_, this->
device_handle_, channel->cdc_dev_.bulk_interface_number);
489 channel->input_started_.store(
true);
490 channel->output_started_.store(
true);
491 channel->input_buffer_.clear();
495 while ((chunk = channel->output_queue_.pop()) !=
nullptr) {
496 channel->output_pool_.release(chunk);
499 channel->initialised_.store(
false);
501 channel->cdc_dev_ = {};
502 channel->dtr_ =
true;
503 channel->rts_ =
true;
505 USBClient::on_disconnected();
508static constexpr uint8_t CDC_REQUEST_TYPE = usb_host::USB_TYPE_CLASS | usb_host::USB_RECIP_INTERFACE;
511 const uint8_t *response) {
512 static constexpr uint8_t CDC_SET_LINE_CODING = 0x20;
521 std::vector<uint8_t> line_coding = {
522 static_cast<uint8_t
>(baud & 0xFF),
523 static_cast<uint8_t
>((baud >> 8) & 0xFF),
524 static_cast<uint8_t
>((baud >> 16) & 0xFF),
525 static_cast<uint8_t
>((baud >> 24) & 0xFF),
530 ESP_LOGD(TAG,
"SET_LINE_CODING: baud=%u stop=%s parity=%s data=%u", (
unsigned) baud,
549 static constexpr uint8_t CDC_SET_CONTROL_LINE_STATE = 0x22;
550 static constexpr uint16_t CDC_CONTROL_LINE_DTR = 1 << 0;
551 static constexpr uint16_t CDC_CONTROL_LINE_RTS = 1 << 1;
552 const uint16_t value = (channel->
dtr_ ? CDC_CONTROL_LINE_DTR : 0) | (channel->
rts_ ? CDC_CONTROL_LINE_RTS : 0);
553 ESP_LOGD(TAG,
"SET_CONTROL_LINE_STATE: DTR=%s RTS=%s", ONOFF(channel->
dtr_), ONOFF(channel->
rts_));
562 channel->reload_pending_ =
false;
563 channel->modem_pending_ =
false;
605 const std::vector<uint8_t> &data, uint8_t *dest) {
614 type, request, value, index,
618 ESP_LOGW(TAG,
"Config control transfer failed: %s", esp_err_to_name(
status.error_code));
621 const uint8_t *payload =
status.data + usb_host::SETUP_PACKET_SIZE;
622 const size_t received =
623 status.data_len > usb_host::SETUP_PACKET_SIZE ?
status.data_len - usb_host::SETUP_PACKET_SIZE : 0;
629 ESP_LOGW(TAG,
"Config read returned %zu of %zu bytes", received, this->
cfg_read_len_);
634 this->
cfg_done_.store(1, std::memory_order_release);
642 ESP_LOGW(TAG,
"Config control transfer submit failed");
644 this->
cfg_done_.store(1, std::memory_order_release);
654 if (!this->
cfg_done_.load(std::memory_order_acquire))
664 ESP_LOGV(TAG,
"Config machine: device_phase=%d channel_idx=%d step=%d mode=%d ok=%d", this->
cfg_device_phase_,
683 if (channel !=
nullptr && channel->
initialised_.load()) {
704 if (channel !=
nullptr && init && channel->
initialised_.load()) {
716 }
else if (++this->cfg_channel_idx_ >= this->
channels_.size()) {
727 if (pending->reload_pending_) {
728 pending->reload_pending_ =
false;
733 if (pending->modem_pending_) {
734 pending->modem_pending_ =
false;
772 this->
parent_->apply_channel_settings(
this);
778 const bool changed = dtr != this->
dtr_ || rts != this->
rts_;
783 this->
parent_->apply_modem_control(
this);
BedjetMode mode
BedJet operating mode.
void wake_loop_threadsafe()
Wake the main event loop from another thread or callback.
void status_set_warning()
void status_clear_error()
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void enable_loop()
Enable this component's loop.
void disable_loop()
Disable this component's loop.
USBUartComponent * parent_
constexpr const char * c_str() const
UARTParityOptions parity_
void load_settings()
Load the UART settings.
usb_host_client_handle_t handle_
bool transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length)
Performs an output transfer operation.
virtual void disconnect()
bool process_usb_events_()
bool control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const transfer_cb_t &callback, const std::vector< uint8_t > &data={})
bool transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length)
Performs a transfer input operation.
usb_device_handle_t device_handle_
void report_connected_()
Report the claimed device to the connection callbacks.
size_t get_free_space() const
size_t get_available() const
uart::UARTFlushResult flush() override
uint32_t flush_timeout_ms_
bool read_array(uint8_t *data, size_t len) override
EventPool< UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT - 1 > output_pool_
bool peek_byte(uint8_t *data) override
LineParity line_parity_() const
LockFreeQueue< UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT > output_queue_
size_t available() override
std::atomic< bool > input_started_
std::atomic< bool > initialised_
std::atomic< bool > output_started_
void write_array(const uint8_t *data, size_t len) override
bool set_modem_control(bool dtr, bool rts)
Store DTR/RTS and send them to the device. False when not connected.
LineStopBits line_stop_bits_() const
USBUartChannelBase * cfg_single_
std::atomic< uint8_t > cfg_done_
std::vector< USBUartChannelBase * > channels_
void start_config_(ConfigMode mode)
LockFreeQueue< UsbDataChunk, USB_DATA_QUEUE_SIZE > usb_data_queue_
virtual void on_rx_overflow(USBUartChannelBase *channel)
virtual void modem_control_transfer(USBUartChannelBase *channel)=0
bool run_config_machine_()
void config_transfer_(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const std::vector< uint8_t > &data={}, uint8_t *dest=nullptr)
virtual bool config_device_step(uint8_t step, bool ok, const uint8_t *response)
void dump_config() override
virtual void start_input(USBUartChannelBase *channel)
void apply_modem_control(USBUartChannelBase *channel)
void apply_channel_settings(USBUartChannelBase *channel)
void start_output(USBUartChannelBase *channel)
EventPool< UsbDataChunk, USB_DATA_QUEUE_SIZE - 1 > chunk_pool_
virtual bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response)=0
void on_disconnected() override
bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override
bool claim_comm_interface_
void on_connected() override
virtual std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl)
void modem_control_transfer(USBUartChannelBase *channel) override
@ UART_CONFIG_PARITY_MARK
@ UART_CONFIG_PARITY_EVEN
@ UART_CONFIG_PARITY_SPACE
UARTFlushResult
Result of a flush() call.
@ UART_FLUSH_RESULT_SUCCESS
Confirmed: all bytes left the TX FIFO.
@ UART_FLUSH_RESULT_TIMEOUT
Confirmed: timed out before TX completed.
ConfigMode
What the config state machine is applying.
@ CONFIG_MODE_RELOAD
Re-apply the line settings of one already-open channel.
@ CONFIG_MODE_MODEM
Apply the DTR/RTS state of one already-open channel.
@ CONFIG_MODE_INIT
Full initialisation of every channel after the device connected.
@ UART_CONFIG_STOP_BITS_1_5
@ UART_CONFIG_STOP_BITS_2
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.
const usb_ep_desc_t * out_ep
const usb_ep_desc_t * in_ep
uint8_t interrupt_interface_number
uint8_t data[usb_host::USB_MAX_PACKET_SIZE]
USBUartChannelBase * channel
uint8_t data[MAX_CHUNK_SIZE]
static constexpr size_t MAX_CHUNK_SIZE