Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
173 lines
5.8 KiB
C++
173 lines
5.8 KiB
C++
#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
|
#include "usb_cdc_acm.h"
|
|
#include "esphome/core/application.h"
|
|
#include "esphome/core/helpers.h"
|
|
#include "esphome/core/log.h"
|
|
namespace esphome::usb_cdc_acm {
|
|
|
|
static const char *const TAG = "usb_cdc_acm";
|
|
|
|
// Global component instance for managing USB device
|
|
USBCDCACMComponent *global_usb_cdc_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
|
|
|
//==============================================================================
|
|
// USBCDCACMInstance Implementation
|
|
//==============================================================================
|
|
|
|
void USBCDCACMInstance::queue_line_state_event(bool dtr, bool rts) {
|
|
// Allocate event from pool
|
|
CDCEvent *event = this->event_pool_.allocate();
|
|
if (event == nullptr) {
|
|
ESP_LOGW(TAG, "Event pool exhausted, line state event dropped (itf=%d)", this->itf_);
|
|
return;
|
|
}
|
|
|
|
event->type = CDC_EVENT_LINE_STATE_CHANGED;
|
|
event->data.line_state.dtr = dtr;
|
|
event->data.line_state.rts = rts;
|
|
|
|
// Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
|
|
// allocate() returned non-null, the queue cannot be full.
|
|
this->event_queue_.push(event);
|
|
App.wake_loop_threadsafe();
|
|
}
|
|
|
|
void USBCDCACMInstance::queue_line_coding_event(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity,
|
|
uint8_t data_bits) {
|
|
// Allocate event from pool
|
|
CDCEvent *event = this->event_pool_.allocate();
|
|
if (event == nullptr) {
|
|
ESP_LOGW(TAG, "Event pool exhausted, line coding event dropped (itf=%d)", this->itf_);
|
|
return;
|
|
}
|
|
|
|
event->type = CDC_EVENT_LINE_CODING_CHANGED;
|
|
event->data.line_coding.bit_rate = bit_rate;
|
|
event->data.line_coding.stop_bits = stop_bits;
|
|
event->data.line_coding.parity = parity;
|
|
event->data.line_coding.data_bits = data_bits;
|
|
|
|
// Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
|
|
// allocate() returned non-null, the queue cannot be full.
|
|
this->event_queue_.push(event);
|
|
App.wake_loop_threadsafe();
|
|
}
|
|
|
|
void USBCDCACMInstance::process_events_() {
|
|
// Process all pending events from the queue
|
|
CDCEvent *event;
|
|
while ((event = this->event_queue_.pop()) != nullptr) {
|
|
switch (event->type) {
|
|
case CDC_EVENT_LINE_STATE_CHANGED: {
|
|
bool dtr = event->data.line_state.dtr;
|
|
bool rts = event->data.line_state.rts;
|
|
|
|
// Invoke user callback in main loop context
|
|
if (this->line_state_callback_ != nullptr) {
|
|
this->line_state_callback_(dtr, rts);
|
|
}
|
|
break;
|
|
}
|
|
case CDC_EVENT_LINE_CODING_CHANGED: {
|
|
uint32_t bit_rate = event->data.line_coding.bit_rate;
|
|
uint8_t stop_bits = event->data.line_coding.stop_bits;
|
|
uint8_t parity = event->data.line_coding.parity;
|
|
uint8_t data_bits = event->data.line_coding.data_bits;
|
|
|
|
// Update UART configuration based on CDC line coding
|
|
this->baud_rate_ = bit_rate;
|
|
this->data_bits_ = data_bits;
|
|
|
|
// Convert CDC stop bits to UART stop bits format
|
|
// CDC: 0=1 stop bit, 1=1.5 stop bits, 2=2 stop bits
|
|
this->stop_bits_ = (stop_bits == 0) ? 1 : (stop_bits == 1) ? 1 : 2;
|
|
|
|
// Convert CDC parity to UART parity format
|
|
// CDC: 0=None, 1=Odd, 2=Even, 3=Mark, 4=Space
|
|
switch (parity) {
|
|
case 0:
|
|
this->parity_ = uart::UART_CONFIG_PARITY_NONE;
|
|
break;
|
|
case 1:
|
|
this->parity_ = uart::UART_CONFIG_PARITY_ODD;
|
|
break;
|
|
case 2:
|
|
this->parity_ = uart::UART_CONFIG_PARITY_EVEN;
|
|
break;
|
|
default:
|
|
// Mark and Space parity are not commonly supported, default to None
|
|
this->parity_ = uart::UART_CONFIG_PARITY_NONE;
|
|
break;
|
|
}
|
|
|
|
// Invoke user callback in main loop context
|
|
if (this->line_coding_callback_ != nullptr) {
|
|
this->line_coding_callback_(bit_rate, stop_bits, parity, data_bits);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
// Return event to pool for reuse
|
|
this->event_pool_.release(event);
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
// USBCDCACMComponent Implementation
|
|
//==============================================================================
|
|
|
|
USBCDCACMComponent::USBCDCACMComponent() { global_usb_cdc_component = this; }
|
|
|
|
void USBCDCACMComponent::setup() {
|
|
// Setup all registered interfaces
|
|
for (auto *interface : this->interfaces_) {
|
|
if (interface != nullptr) {
|
|
interface->setup();
|
|
}
|
|
}
|
|
}
|
|
|
|
void USBCDCACMComponent::loop() {
|
|
// Call loop() on all registered interfaces to process events
|
|
for (auto *interface : this->interfaces_) {
|
|
if (interface != nullptr) {
|
|
interface->loop();
|
|
}
|
|
}
|
|
}
|
|
|
|
void USBCDCACMComponent::dump_config() {
|
|
ESP_LOGCONFIG(TAG,
|
|
"USB CDC-ACM:\n"
|
|
" Number of Interfaces: %d",
|
|
ESPHOME_MAX_USB_CDC_INSTANCES);
|
|
for (uint8_t i = 0; i < ESPHOME_MAX_USB_CDC_INSTANCES; ++i) {
|
|
if (this->interfaces_[i] != nullptr) {
|
|
this->interfaces_[i]->dump_config();
|
|
} else {
|
|
ESP_LOGCONFIG(TAG, " Interface %u is disabled", i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void USBCDCACMComponent::add_interface(USBCDCACMInstance *interface) {
|
|
uint8_t itf_num = static_cast<uint8_t>(interface->get_itf());
|
|
if (itf_num < ESPHOME_MAX_USB_CDC_INSTANCES) {
|
|
this->interfaces_[itf_num] = interface;
|
|
} else {
|
|
ESP_LOGE(TAG, "Interface number must be less than %u", ESPHOME_MAX_USB_CDC_INSTANCES);
|
|
}
|
|
}
|
|
|
|
USBCDCACMInstance *USBCDCACMComponent::get_interface_by_number(uint8_t itf) {
|
|
for (auto *interface : this->interfaces_) {
|
|
if ((interface != nullptr) && (interface->get_itf() == itf)) {
|
|
return interface;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
} // namespace esphome::usb_cdc_acm
|
|
#endif
|