ESPHome 2025.6.0
Loading...
Searching...
No Matches
usb_uart.cpp
Go to the documentation of this file.
1// Should not be needed, but it's required to pass CI clang-tidy checks
2#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
3#include "usb_uart.h"
4#include "esphome/core/log.h"
6
7#include <cinttypes>
8
9namespace esphome {
10namespace usb_uart {
11
19static optional<CdcEps> get_cdc(const usb_config_desc_t *config_desc, uint8_t intf_idx) {
20 int conf_offset, ep_offset;
21 const usb_ep_desc_t *notify_ep{}, *in_ep{}, *out_ep{};
22 uint8_t interface_number = 0;
23 // look for an interface with one interrupt endpoint (notify), and an interface with two bulk endpoints (data in/out)
24 for (;;) {
25 auto intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx++, 0, &conf_offset);
26 if (!intf_desc) {
27 ESP_LOGE(TAG, "usb_parse_interface_descriptor failed");
28 return nullopt;
29 }
30 if (intf_desc->bNumEndpoints == 1) {
31 ep_offset = conf_offset;
32 notify_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 0, config_desc->wTotalLength, &ep_offset);
33 if (!notify_ep) {
34 ESP_LOGE(TAG, "notify_ep: usb_parse_endpoint_descriptor_by_index failed");
35 return nullopt;
36 }
37 if (notify_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_INT)
38 notify_ep = nullptr;
39 } else if (USB_CLASS_CDC_DATA && intf_desc->bNumEndpoints == 2) {
40 interface_number = intf_desc->bInterfaceNumber;
41 ep_offset = conf_offset;
42 out_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 0, config_desc->wTotalLength, &ep_offset);
43 if (!out_ep) {
44 ESP_LOGE(TAG, "out_ep: usb_parse_endpoint_descriptor_by_index failed");
45 return nullopt;
46 }
47 if (out_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_BULK)
48 out_ep = nullptr;
49 ep_offset = conf_offset;
50 in_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 1, config_desc->wTotalLength, &ep_offset);
51 if (!in_ep) {
52 ESP_LOGE(TAG, "in_ep: usb_parse_endpoint_descriptor_by_index failed");
53 return nullopt;
54 }
55 if (in_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_BULK)
56 in_ep = nullptr;
57 }
58 if (in_ep != nullptr && out_ep != nullptr && notify_ep != nullptr)
59 break;
60 }
61 if (in_ep->bEndpointAddress & usb_host::USB_DIR_IN)
62 return CdcEps{notify_ep, in_ep, out_ep, interface_number};
63 return CdcEps{notify_ep, out_ep, in_ep, interface_number};
64}
65
66std::vector<CdcEps> USBUartTypeCdcAcm::parse_descriptors_(usb_device_handle_t dev_hdl) {
67 const usb_config_desc_t *config_desc;
68 const usb_device_desc_t *device_desc;
69 int desc_offset = 0;
70 std::vector<CdcEps> cdc_devs{};
71
72 // Get required descriptors
73 if (usb_host_get_device_descriptor(dev_hdl, &device_desc) != ESP_OK) {
74 ESP_LOGE(TAG, "get_device_descriptor failed");
75 return {};
76 }
77 if (usb_host_get_active_config_descriptor(dev_hdl, &config_desc) != ESP_OK) {
78 ESP_LOGE(TAG, "get_active_config_descriptor failed");
79 return {};
80 }
81 if (device_desc->bDeviceClass == USB_CLASS_COMM) {
82 // single CDC-ACM device
83 if (auto eps = get_cdc(config_desc, 0)) {
84 ESP_LOGV(TAG, "Found CDC-ACM device");
85 cdc_devs.push_back(*eps);
86 }
87 return cdc_devs;
88 }
89 if (((device_desc->bDeviceClass == USB_CLASS_MISC) && (device_desc->bDeviceSubClass == USB_SUBCLASS_COMMON) &&
90 (device_desc->bDeviceProtocol == USB_DEVICE_PROTOCOL_IAD)) ||
91 ((device_desc->bDeviceClass == USB_CLASS_PER_INTERFACE) && (device_desc->bDeviceSubClass == USB_SUBCLASS_NULL) &&
92 (device_desc->bDeviceProtocol == USB_PROTOCOL_NULL))) {
93 // This is a composite device, that uses Interface Association Descriptor
94 const auto *this_desc = reinterpret_cast<const usb_standard_desc_t *>(config_desc);
95 for (;;) {
96 this_desc = usb_parse_next_descriptor_of_type(this_desc, config_desc->wTotalLength,
97 USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION, &desc_offset);
98 if (!this_desc)
99 break;
100 const auto *iad_desc = reinterpret_cast<const usb_iad_desc_t *>(this_desc);
101
102 if (iad_desc->bFunctionClass == USB_CLASS_COMM && iad_desc->bFunctionSubClass == USB_CDC_SUBCLASS_ACM) {
103 ESP_LOGV(TAG, "Found CDC-ACM device in composite device");
104 if (auto eps = get_cdc(config_desc, iad_desc->bFirstInterface))
105 cdc_devs.push_back(*eps);
106 }
107 }
108 }
109 return cdc_devs;
110}
111
112void RingBuffer::push(uint8_t item) {
113 this->buffer_[this->insert_pos_] = item;
114 this->insert_pos_ = (this->insert_pos_ + 1) % this->buffer_size_;
115}
116void RingBuffer::push(const uint8_t *data, size_t len) {
117 for (size_t i = 0; i != len; i++) {
118 this->buffer_[this->insert_pos_] = *data++;
119 this->insert_pos_ = (this->insert_pos_ + 1) % this->buffer_size_;
120 }
121}
122
124 uint8_t item = this->buffer_[this->read_pos_];
125 this->read_pos_ = (this->read_pos_ + 1) % this->buffer_size_;
126 return item;
127}
128size_t RingBuffer::pop(uint8_t *data, size_t len) {
129 len = std::min(len, this->get_available());
130 for (size_t i = 0; i != len; i++) {
131 *data++ = this->buffer_[this->read_pos_];
132 this->read_pos_ = (this->read_pos_ + 1) % this->buffer_size_;
133 }
134 return len;
135}
136void USBUartChannel::write_array(const uint8_t *data, size_t len) {
137 if (!this->initialised_) {
138 ESP_LOGV(TAG, "Channel not initialised - write ignored");
139 return;
140 }
141 while (this->output_buffer_.get_free_space() != 0 && len-- != 0) {
142 this->output_buffer_.push(*data++);
143 }
144 len++;
145 if (len > 0) {
146 ESP_LOGE(TAG, "Buffer full - failed to write %d bytes", len);
147 }
148 this->parent_->start_output(this);
149}
150
151bool USBUartChannel::peek_byte(uint8_t *data) {
152 if (this->input_buffer_.is_empty()) {
153 return false;
154 }
155 *data = this->input_buffer_.peek();
156 return true;
157}
158bool USBUartChannel::read_array(uint8_t *data, size_t len) {
159 if (!this->initialised_) {
160 ESP_LOGV(TAG, "Channel not initialised - read ignored");
161 return false;
162 }
163 auto available = this->available();
164 bool status = true;
165 if (len > available) {
166 ESP_LOGV(TAG, "underflow: requested %zu but returned %d, bytes", len, available);
167 len = available;
168 status = false;
169 }
170 for (size_t i = 0; i != len; i++) {
171 *data++ = this->input_buffer_.pop();
172 }
173 this->parent_->start_input(this);
174 return status;
175}
176void USBUartComponent::setup() { USBClient::setup(); }
177void USBUartComponent::loop() { USBClient::loop(); }
179 USBClient::dump_config();
180 for (auto &channel : this->channels_) {
181 ESP_LOGCONFIG(TAG,
182 " UART Channel %d\n"
183 " Baud Rate: %" PRIu32 " baud\n"
184 " Data Bits: %u\n"
185 " Parity: %s\n"
186 " Stop bits: %s\n"
187 " Debug: %s\n"
188 " Dummy receiver: %s",
189 channel->index_, channel->baud_rate_, channel->data_bits_, PARITY_NAMES[channel->parity_],
190 STOP_BITS_NAMES[channel->stop_bits_], YESNO(channel->debug_), YESNO(channel->dummy_receiver_));
191 }
192}
194 if (!channel->initialised_ || channel->input_started_ ||
195 channel->input_buffer_.get_free_space() < channel->cdc_dev_.in_ep->wMaxPacketSize)
196 return;
197 auto ep = channel->cdc_dev_.in_ep;
198 auto callback = [this, channel](const usb_host::TransferStatus &status) {
199 ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code);
200 if (!status.success) {
201 ESP_LOGE(TAG, "Control transfer failed, status=%s", esp_err_to_name(status.error_code));
202 return;
203 }
204#ifdef USE_UART_DEBUGGER
205 if (channel->debug_) {
207 std::vector<uint8_t>(status.data, status.data + status.data_len), ','); // NOLINT()
208 }
209#endif
210 channel->input_started_ = false;
211 if (!channel->dummy_receiver_) {
212 for (size_t i = 0; i != status.data_len; i++) {
213 channel->input_buffer_.push(status.data[i]);
214 }
215 }
216 if (channel->input_buffer_.get_free_space() >= channel->cdc_dev_.in_ep->wMaxPacketSize) {
217 this->defer([this, channel] { this->start_input(channel); });
218 }
219 };
220 channel->input_started_ = true;
221 this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize);
222}
223
225 if (channel->output_started_)
226 return;
227 if (channel->output_buffer_.is_empty()) {
228 return;
229 }
230 auto ep = channel->cdc_dev_.out_ep;
231 auto callback = [this, channel](const usb_host::TransferStatus &status) {
232 ESP_LOGV(TAG, "Output Transfer result: length: %u; status %X", status.data_len, status.error_code);
233 channel->output_started_ = false;
234 this->defer([this, channel] { this->start_output(channel); });
235 };
236 channel->output_started_ = true;
237 uint8_t data[ep->wMaxPacketSize];
238 auto len = channel->output_buffer_.pop(data, ep->wMaxPacketSize);
239 this->transfer_out(ep->bEndpointAddress, callback, data, len);
240#ifdef USE_UART_DEBUGGER
241 if (channel->debug_) {
242 uart::UARTDebug::log_hex(uart::UART_DIRECTION_TX, std::vector<uint8_t>(data, data + len), ','); // NOLINT()
243 }
244#endif
245 ESP_LOGV(TAG, "Output %d bytes started", len);
246}
247
252static void fix_mps(const usb_ep_desc_t *ep) {
253 if (ep != nullptr) {
254 auto *ep_mutable = const_cast<usb_ep_desc_t *>(ep);
255 if (ep->wMaxPacketSize > 64) {
256 ESP_LOGW(TAG, "Corrected MPS of EP %u from %u to 64", ep->bEndpointAddress, ep->wMaxPacketSize);
257 ep_mutable->wMaxPacketSize = 64;
258 }
259 }
260}
262 auto cdc_devs = this->parse_descriptors_(this->device_handle_);
263 if (cdc_devs.empty()) {
264 this->status_set_error("No CDC-ACM device found");
265 this->disconnect();
266 return;
267 }
268 ESP_LOGD(TAG, "Found %zu CDC-ACM devices", cdc_devs.size());
269 auto i = 0;
270 for (auto channel : this->channels_) {
271 if (i == cdc_devs.size()) {
272 ESP_LOGE(TAG, "No configuration found for channel %d", channel->index_);
273 this->status_set_warning("No configuration found for channel");
274 break;
275 }
276 channel->cdc_dev_ = cdc_devs[i++];
277 fix_mps(channel->cdc_dev_.in_ep);
278 fix_mps(channel->cdc_dev_.out_ep);
279 channel->initialised_ = true;
280 auto err = usb_host_interface_claim(this->handle_, this->device_handle_, channel->cdc_dev_.interface_number, 0);
281 if (err != ESP_OK) {
282 ESP_LOGE(TAG, "usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_,
283 channel->cdc_dev_.interface_number);
284 this->status_set_error("usb_host_interface_claim failed");
285 this->disconnect();
286 return;
287 }
288 }
289 this->enable_channels();
290}
291
293 for (auto channel : this->channels_) {
294 if (channel->cdc_dev_.in_ep != nullptr) {
295 usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
296 usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
297 }
298 if (channel->cdc_dev_.out_ep != nullptr) {
299 usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
300 usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
301 }
302 if (channel->cdc_dev_.notify_ep != nullptr) {
303 usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
304 usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
305 }
306 usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.interface_number);
307 channel->initialised_ = false;
308 channel->input_started_ = false;
309 channel->output_started_ = false;
310 channel->input_buffer_.clear();
311 channel->output_buffer_.clear();
312 }
313 USBClient::on_disconnected();
314}
315
317 for (auto channel : this->channels_) {
318 if (!channel->initialised_)
319 continue;
320 channel->input_started_ = false;
321 channel->output_started_ = false;
322 this->start_input(channel);
323 }
324}
325
326} // namespace usb_uart
327} // namespace esphome
328#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
uint8_t status
Definition bl0942.h:8
void status_set_warning(const char *message="unspecified")
void status_set_error(const char *message="unspecified")
void defer(const std::string &name, std::function< void()> &&f)
Defer a callback to the next loop() call.
static void log_hex(UARTDirection direction, std::vector< uint8_t > bytes, uint8_t separator)
Log the bytes as hex values, separated by the provided separator character.
usb_host_client_handle_t handle_
Definition usb_host.h:94
void transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length)
Performs a transfer input operation.
void transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length)
Performs an output transfer operation.
usb_device_handle_t device_handle_
Definition usb_host.h:95
void push(uint8_t item)
Definition usb_uart.cpp:112
size_t get_free_space() const
Definition usb_uart.h:55
size_t get_available() const
Definition usb_uart.h:52
bool peek_byte(uint8_t *data) override
Definition usb_uart.cpp:151
void write_array(const uint8_t *data, size_t len) override
Definition usb_uart.cpp:136
bool read_array(uint8_t *data, size_t len) override
Definition usb_uart.cpp:158
std::vector< USBUartChannel * > channels_
Definition usb_uart.h:118
void start_output(USBUartChannel *channel)
Definition usb_uart.cpp:224
void start_input(USBUartChannel *channel)
Definition usb_uart.cpp:193
virtual std::vector< CdcEps > parse_descriptors_(usb_device_handle_t dev_hdl)
Definition usb_uart.cpp:66
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:302
const nullopt_t nullopt((nullopt_t::init()))
const usb_ep_desc_t * out_ep
Definition usb_uart.h:27
const usb_ep_desc_t * in_ep
Definition usb_uart.h:26