[uart][usb_uart] Implement runtime settings update (#16990)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
This commit is contained in:
Clyde Stubbs
2026-07-08 13:30:49 -04:00
committed by GitHub
co-authored by Claude Opus 4.8 Keith Burzinski
parent e7933a5387
commit ce46895270
13 changed files with 534 additions and 419 deletions
+2 -2
View File
@@ -178,7 +178,7 @@ class UARTComponent {
*
* This will load the current UART interface with the latest settings (baud_rate, parity, etc).
*/
virtual void load_settings(bool dump_config){};
virtual void load_settings(bool dump_config) = 0;
/**
* Load the UART settings.
@@ -190,7 +190,7 @@ class UARTComponent {
*
* This will load the current UART interface with the latest settings (baud_rate, parity, etc).
*/
virtual void load_settings(){};
void load_settings() { this->load_settings(true); }
#endif // USE_ESP8266 || USE_ESP32
#ifdef USE_UART_DEBUGGER
@@ -75,7 +75,7 @@ class ESP8266UartComponent final : public UARTComponent, public Component {
* This will load the current UART interface with the latest settings (baud_rate, parity, etc).
*/
void load_settings(bool dump_config) override;
void load_settings() override { this->load_settings(true); }
using UARTComponent::load_settings; // also bring in the no-arg overload for convenience
protected:
void check_logger_conflict() override;
@@ -50,7 +50,7 @@ class IDFUARTComponent final : public UARTComponent, public Component {
* This will load the current UART interface with the latest settings (baud_rate, parity, etc).
*/
void load_settings(bool dump_config) override;
void load_settings() override { this->load_settings(true); }
using UARTComponent::load_settings; // also bring in the no-arg overload for convenience
protected:
void check_logger_conflict() override;
@@ -84,6 +84,12 @@ class USBCDCACMInstance final : public uart::UARTComponent, public Parented<USBC
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
uart::UARTFlushResult flush() override;
#if defined(USE_ESP8266) || defined(USE_ESP32)
// No-op: in CDC ACM device mode the host dictates the line coding, so there are no
// local UART settings to (re)apply.
void load_settings(bool dump_config) override {}
using UARTComponent::load_settings; // also bring in the no-arg overload for convenience
#endif
protected:
void check_logger_conflict() override;
+89 -95
View File
@@ -50,47 +50,44 @@ static const CH34xEntry CH34X_TABLE[] = {
{"CH346C_M2", 0x55EC, 1, 0xFF, 0xFF, CHIP_CH346C_M2, 2},
};
void USBUartTypeCH34X::enable_channels() {
usb_host::transfer_cb_t cb = [this](const usb_host::TransferStatus &status) {
if (!status.success) {
this->defer([this, error_code = status.error_code]() {
ESP_LOGE(TAG, "CH34x chip detection failed: %s", esp_err_to_name(error_code));
this->apply_line_settings_();
});
return;
}
CH34xChipType chiptype = CHIP_UNKNOWN;
uint8_t num_ports = 1;
for (const auto &e : CH34X_TABLE) {
if (e.pid != this->pid_)
continue;
if (e.match != 0xFF && (status.data[e.byte_idx] & e.mask) != e.match)
continue;
chiptype = e.chiptype;
num_ports = e.num_ports;
bool USBUartTypeCH34X::config_device_step(uint8_t step, bool ok, const uint8_t *response) {
if (step == 0) {
// Vendor-specific GET_CHIP_VERSION request (bRequest=0x5F): returns chip ID bytes
// used to distinguish CH34x variants sharing the same PID.
this->config_transfer_(USB_VENDOR_DEV | usb_host::USB_DIR_IN, 0x5F, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0});
return true;
}
// step 1: parse the chip-version response (falling back to "unknown" on failure).
if (!ok) {
ESP_LOGE(TAG, "CH34x chip detection failed");
return false;
}
CH34xChipType chiptype = CHIP_UNKNOWN;
uint8_t num_ports = 1;
for (const auto &e : CH34X_TABLE) {
if (e.pid != this->pid_)
continue;
if (e.match != 0xFF && (response[e.byte_idx] & e.mask) != e.match)
continue;
chiptype = e.chiptype;
num_ports = e.num_ports;
break;
}
// CH344L vs CH344L_V2 requires chipver (data[0]) in addition to chiptype (data[1])
if (chiptype == CHIP_CH344L && (response[0] & 0xF0) != 0x40)
chiptype = CHIP_CH344L_V2;
const char *name = "unknown";
for (const auto &e : CH34X_TABLE) {
if (e.chiptype == chiptype) {
name = e.name;
break;
}
// CH344L vs CH344L_V2 requires chipver (data[0]) in addition to chiptype (data[1])
if (chiptype == CHIP_CH344L && (status.data[0] & 0xF0) != 0x40)
chiptype = CHIP_CH344L_V2;
const char *name = "unknown";
for (const auto &e : CH34X_TABLE) {
if (e.chiptype == chiptype) {
name = e.name;
break;
}
}
this->defer([this, chiptype, num_ports, name]() {
this->chiptype_ = chiptype;
this->chip_name_ = name;
this->num_ports_ = num_ports;
ESP_LOGD(TAG, "CH34x chip: %s, ports: %u", name, this->num_ports_);
this->apply_line_settings_();
});
};
// Vendor-specific GET_CHIP_VERSION request (bRequest=0x5F): returns chip ID bytes
// used to distinguish CH34x variants sharing the same PID.
this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_IN, 0x5F, 0, 0, cb, {0, 0, 0, 0, 0, 0, 0, 0});
}
this->chiptype_ = chiptype;
this->chip_name_ = name;
this->num_ports_ = num_ports;
ESP_LOGD(TAG, "CH34x chip: %s, ports: %u", name, this->num_ports_);
return false;
}
void USBUartTypeCH34X::dump_config() {
@@ -98,67 +95,64 @@ void USBUartTypeCH34X::dump_config() {
ESP_LOGCONFIG(TAG, " CH34x chip: %s", this->chip_name_);
}
void USBUartTypeCH34X::apply_line_settings_() {
for (auto *channel : this->channels_) {
if (!channel->initialised_.load())
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_.store(false);
bool USBUartTypeCH34X::config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok,
const uint8_t *response) {
uint8_t cmd = 0xA1 + channel->index_;
if (channel->index_ >= 2)
cmd += 0xE;
switch (step) {
case 0: {
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;
}
}
};
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);
return false;
}
}
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_.store(false);
continue;
}
if ((clk / factor - baud_rate) > (baud_rate - clk / (factor + 1)))
factor++;
factor = 256 - factor;
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;
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;
this->config_transfer_(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, cmd, value, (factor << 8) | divisor);
return true;
}
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);
this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, cmd + 3, 0x80, 0, callback);
case 1:
this->config_transfer_(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, cmd + 3, 0x80, 0);
return true;
default:
return false;
}
this->start_channels_();
}
std::vector<CdcEps> USBUartTypeCH34X::parse_descriptors(usb_device_handle_t dev_hdl) {
+24 -22
View File
@@ -97,29 +97,31 @@ std::vector<CdcEps> USBUartTypeCP210X::parse_descriptors(usb_device_handle_t dev
return cdc_devs;
}
void USBUartTypeCP210X::enable_channels() {
// enable the channels
for (auto *channel : this->channels_) {
if (!channel->initialised_.load())
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_.store(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());
bool USBUartTypeCP210X::config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok,
const uint8_t *response) {
// On reload, skip the one-time IFC_ENABLE step (the interface is already enabled).
if (reload)
step++;
switch (step) {
case 0:
this->config_transfer_(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, IFC_ENABLE, 1, channel->index_);
return true;
case 1: {
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->config_transfer_(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, SET_LINE_CTL, line_control, channel->index_);
return true;
}
case 2: {
auto baud = ByteBuffer::wrap(channel->baud_rate_, LITTLE);
this->config_transfer_(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, SET_BAUDRATE, 0, channel->index_, baud.get_data());
return true;
}
default:
return false;
}
this->start_channels_();
}
} // namespace esphome::usb_uart
+81 -155
View File
@@ -112,40 +112,46 @@ static int ftdi_to_clkbits(int baudrate, unsigned int clk, int clk_div, uint32_t
return best_baud;
}
static int ftdi_convert_baudrate(int baudrate, uint8_t chip_type, uint8_t channel_index, uint16_t *value,
uint16_t *index) {
struct FtdiConfig {
uint16_t value;
uint16_t ftdi_index;
int best_baud;
};
static FtdiConfig ftdi_convert_baudrate(int baudrate, uint8_t chip_type, uint8_t channel_index) {
uint32_t encoded_divisor;
FtdiConfig config{};
if (baudrate <= 0) {
return -1;
return config;
}
static constexpr uint32_t H_CLK = 120000000;
static constexpr uint32_t C_CLK = 48000000;
if ((chip_type == TYPE_2232H) || (chip_type == TYPE_4232H) || (chip_type == TYPE_232H)) {
if (baudrate * 10 > H_CLK / 0x3fff) {
best_baud = ftdi_to_clkbits(baudrate, H_CLK, 10, &encoded_divisor);
config.best_baud = ftdi_to_clkbits(baudrate, H_CLK, 10, &encoded_divisor);
encoded_divisor |= 0x20000; /* switch on CLK/10*/
} else {
best_baud = ftdi_to_clkbits(baudrate, C_CLK, 16, &encoded_divisor);
config.best_baud = ftdi_to_clkbits(baudrate, C_CLK, 16, &encoded_divisor);
}
} else if ((chip_type == TYPE_BM) || (chip_type == TYPE_2232C) || (chip_type == TYPE_R) || (chip_type == TYPE_230X)) {
best_baud = ftdi_to_clkbits(baudrate, C_CLK, 16, &encoded_divisor);
config.best_baud = ftdi_to_clkbits(baudrate, C_CLK, 16, &encoded_divisor);
} else {
best_baud = ftdi_to_clkbits_am(baudrate, &encoded_divisor);
config.best_baud = ftdi_to_clkbits_am(baudrate, &encoded_divisor);
}
*value = (uint16_t) (encoded_divisor & 0xFFFF);
config.value = (uint16_t) (encoded_divisor & 0xFFFF);
if (chip_type == TYPE_2232H || chip_type == TYPE_4232H || chip_type == TYPE_232H) {
*index = (uint16_t) (encoded_divisor >> 8);
*index &= 0xFF00;
*index |= (channel_index + 1);
config.ftdi_index = (uint16_t) (encoded_divisor >> 8);
config.ftdi_index &= 0xFF00;
config.ftdi_index |= (channel_index + 1);
} else {
*index = (uint16_t) (encoded_divisor >> 16);
config.ftdi_index = (uint16_t) (encoded_divisor >> 16);
}
return best_baud;
return config;
}
static optional<CdcEps> get_uart(const usb_config_desc_t *config_desc, uint8_t intf_idx) {
@@ -264,138 +270,6 @@ std::vector<CdcEps> USBUartTypeFT23XX::parse_descriptors(usb_device_handle_t dev
return cdc_devs;
}
int USBUartTypeFT23XX::reset_(USBUartChannel *channel) {
usb_host::transfer_cb_t callback = [channel, this](const usb_host::TransferStatus &status) {
if (!status.success) {
ESP_LOGE(TAG, "Reset failed, status=%s", esp_err_to_name(status.error_code));
channel->initialised_.store(false);
} else {
ESP_LOGD(TAG, "Reset successful, setting baudrate...");
this->set_baudrate_(channel);
}
};
bool ok = this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, 0x00, 0x00,
channel->cdc_dev_.bulk_interface_number + 1, callback);
if (!ok) {
ESP_LOGE(TAG, "Reset control_transfer submit failed");
channel->initialised_.store(false);
return -1;
}
return 0;
}
int USBUartTypeFT23XX::set_baudrate_(USBUartChannel *channel, uint32_t baudrate) {
usb_host::transfer_cb_t callback = [channel, this](const usb_host::TransferStatus &status) {
if (!status.success) {
ESP_LOGE(TAG, "Set baudrate failed, status=%s", esp_err_to_name(status.error_code));
channel->initialised_.store(false);
} else {
ESP_LOGD(TAG, "Baudrate %" PRIu32 " set, setting line properties...", channel->baud_rate_);
this->set_line_properties_(channel);
}
};
if (baudrate == 0) {
baudrate = channel->baud_rate_;
}
uint16_t value = 0, ftdi_index = 0;
ftdi_convert_baudrate(baudrate, this->chip_type_, channel->index_, &value, &ftdi_index);
ESP_LOGD(TAG, "Baudrate: %" PRIu32 ", value=0x%04X, ftdi_index=0x%04X", baudrate, value, ftdi_index);
uint16_t usb_index = (ftdi_index & 0xFF00) | (channel->cdc_dev_.bulk_interface_number + 1);
bool ok = this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, 0x03, value, usb_index, callback);
if (!ok) {
ESP_LOGE(TAG, "Set baudrate control_transfer submit failed");
channel->initialised_.store(false);
return -1;
}
return 0;
}
int USBUartTypeFT23XX::set_line_properties_(USBUartChannel *channel) {
usb_host::transfer_cb_t callback = [channel, this](const usb_host::TransferStatus &status) {
if (!status.success) {
ESP_LOGE(TAG, "Set line properties failed, status=%s", esp_err_to_name(status.error_code));
channel->initialised_.store(false);
return;
}
ESP_LOGD(TAG, "Line properties set, setting modem control...");
this->set_dtr_rts_(channel);
};
uint16_t value = channel->data_bits_;
switch (channel->parity_) {
case UART_CONFIG_PARITY_NONE:
value |= (0x00 << 8);
break;
case UART_CONFIG_PARITY_ODD:
value |= (0x01 << 8);
break;
case UART_CONFIG_PARITY_EVEN:
value |= (0x02 << 8);
break;
case UART_CONFIG_PARITY_MARK:
value |= (0x03 << 8);
break;
case UART_CONFIG_PARITY_SPACE:
value |= (0x04 << 8);
break;
}
switch (channel->stop_bits_) {
case UART_CONFIG_STOP_BITS_1:
value |= (0x00 << 11);
break;
case UART_CONFIG_STOP_BITS_1_5:
value |= (0x01 << 11);
break;
case UART_CONFIG_STOP_BITS_2:
value |= (0x02 << 11);
break;
}
value |= (0x00 << 14);
bool ok = this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, 0x04, value,
channel->cdc_dev_.bulk_interface_number + 1, callback);
if (!ok) {
ESP_LOGE(TAG, "Set line properties control_transfer submit failed");
channel->initialised_.store(false);
return -1;
}
return 0;
}
int USBUartTypeFT23XX::set_dtr_rts_(USBUartChannel *channel) {
usb_host::transfer_cb_t callback = [channel, this](const usb_host::TransferStatus &status) {
if (!status.success) {
ESP_LOGE(TAG, "Set modem control failed, status=%s", esp_err_to_name(status.error_code));
channel->initialised_.store(false);
return;
}
ESP_LOGD(TAG, "Modem control set for channel %d, starting input...", channel->index_);
channel->initialised_.store(true);
this->start_input(channel);
uint8_t next_index = channel->index_ + 1;
if (next_index < this->channels_.size()) {
USBUartChannel *next_channel = this->channels_[next_index];
ESP_LOGD(TAG, "Configuring next channel %d", next_channel->index_);
this->reset_(next_channel);
return;
} else {
ESP_LOGI(TAG, "All channels configured");
}
};
bool ok = this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, 0x01, 0x0000,
channel->cdc_dev_.bulk_interface_number + 1, callback);
if (!ok) {
ESP_LOGE(TAG, "Set modem control control_transfer submit failed");
channel->initialised_.store(false);
return -1;
}
return 0;
}
void USBUartTypeFT23XX::start_input(USBUartChannel *channel) {
if (!channel->initialised_.load())
return;
@@ -467,16 +341,68 @@ void USBUartTypeFT23XX::on_rx_overflow(USBUartChannel *channel) {
channel->input_buffer_.clear();
}
void USBUartTypeFT23XX::enable_channels() {
if (!this->channels_.empty() && this->channels_[0]->initialised_.load()) {
this->reset_(this->channels_[0]);
}
for (auto *channel : this->channels_) {
if (!channel->initialised_.load())
continue;
channel->input_started_.store(false);
channel->output_started_.store(false);
bool USBUartTypeFT23XX::config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok,
const uint8_t *response) {
// On reload (settings change on an open channel) skip the SIO reset; the FTDI set_termios
// path only re-applies baud + line properties and does not re-assert DTR/RTS.
if (reload)
step++;
switch (step) {
case 0: // SIO reset (init only)
this->config_transfer_(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, 0x00, 0x00,
channel->cdc_dev_.bulk_interface_number + 1);
return true;
case 1: { // set baudrate
auto config = ftdi_convert_baudrate(channel->baud_rate_, this->chip_type_, channel->index_);
uint16_t usb_index = (config.ftdi_index & 0xFF00) | (channel->cdc_dev_.bulk_interface_number + 1);
ESP_LOGD(TAG, "Baudrate: %u, value=0x%04X, ftdi_index=0x%04X", (unsigned) channel->baud_rate_, config.value,
config.ftdi_index);
this->config_transfer_(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, 0x03, config.value, usb_index);
return true;
}
case 2: { // set line properties (data bits / parity / stop bits)
uint16_t value = channel->data_bits_;
switch (channel->parity_) {
case UART_CONFIG_PARITY_NONE:
value |= (0x00 << 8);
break;
case UART_CONFIG_PARITY_ODD:
value |= (0x01 << 8);
break;
case UART_CONFIG_PARITY_EVEN:
value |= (0x02 << 8);
break;
case UART_CONFIG_PARITY_MARK:
value |= (0x03 << 8);
break;
case UART_CONFIG_PARITY_SPACE:
value |= (0x04 << 8);
break;
}
switch (channel->stop_bits_) {
default: // 1 bit
value |= (0x00 << 11);
break;
case UART_CONFIG_STOP_BITS_1_5:
value |= (0x01 << 11);
break;
case UART_CONFIG_STOP_BITS_2:
value |= (0x02 << 11);
break;
}
value |= (0x00 << 14);
this->config_transfer_(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, 0x04, value,
channel->cdc_dev_.bulk_interface_number + 1);
return true;
}
case 3: // set modem control DTR+RTS (init only)
if (reload)
return false;
this->config_transfer_(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, 0x01, 0x0000,
channel->cdc_dev_.bulk_interface_number + 1);
return true;
default:
return false;
}
}
+99 -85
View File
@@ -200,100 +200,114 @@ std::vector<CdcEps> USBUartTypePL2303::parse_descriptors(usb_device_handle_t dev
return cdc_devs;
}
void USBUartTypePL2303::enable_channels() {
if (this->channels_.empty())
return;
// Vendor init sequence for non-HXN chips (mirrors pl2303_startup in the Linux driver):
// read 0x8484, write 0x0404=0, read 0x8484, read 0x8383, read 0x8484, write 0x0404=1,
// read 0x8484, read 0x8383, write 0=1, write 1=0, write 2=0x24 (legacy) or 0x44 (HX+).
// The final entry's wIndex is patched at runtime depending on the chip type.
struct Pl2303InitStep {
uint8_t type;
uint8_t request;
uint16_t value;
uint16_t index;
bool read; // reads need a 1-byte buffer to set wLength=1 so the IN data stage runs
};
static const Pl2303InitStep PL2303_INIT[] = {
{VENDOR_READ_REQUEST_TYPE, VENDOR_READ_REQUEST, 0x8484, 0, true},
{VENDOR_WRITE_REQUEST_TYPE, VENDOR_WRITE_REQUEST, 0x0404, 0, false},
{VENDOR_READ_REQUEST_TYPE, VENDOR_READ_REQUEST, 0x8484, 0, true},
{VENDOR_READ_REQUEST_TYPE, VENDOR_READ_REQUEST, 0x8383, 0, true},
{VENDOR_READ_REQUEST_TYPE, VENDOR_READ_REQUEST, 0x8484, 0, true},
{VENDOR_WRITE_REQUEST_TYPE, VENDOR_WRITE_REQUEST, 0x0404, 1, false},
{VENDOR_READ_REQUEST_TYPE, VENDOR_READ_REQUEST, 0x8484, 0, true},
{VENDOR_READ_REQUEST_TYPE, VENDOR_READ_REQUEST, 0x8383, 0, true},
{VENDOR_WRITE_REQUEST_TYPE, VENDOR_WRITE_REQUEST, 0, 1, false},
{VENDOR_WRITE_REQUEST_TYPE, VENDOR_WRITE_REQUEST, 1, 0, false},
{VENDOR_WRITE_REQUEST_TYPE, VENDOR_WRITE_REQUEST, 2, 0, false},
};
static constexpr uint8_t PL2303_INIT_COUNT = sizeof(PL2303_INIT) / sizeof(PL2303_INIT[0]);
auto *channel = this->channels_[0];
bool USBUartTypePL2303::config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok,
const uint8_t *response) {
bool is_legacy = (this->chip_type_ == PL2303_TYPE_H);
bool is_hxn = (this->chip_type_ == PL2303_TYPE_HXN);
usb_host::transfer_cb_t nop_cb = [](const usb_host::TransferStatus &status) {
if (!status.success)
ESP_LOGW(TAG, "PL2303: vendor init transfer failed");
};
// Init sequence for non-HXN chips (mirrors pl2303_startup in Linux driver):
// Read 0x8484, write 0x0404=0, read 0x8484, read 0x8383, read 0x8484,
// write 0x0404=1, read 0x8484, read 0x8383,
// write 0=1, write 1=0, write 2=0x24 (legacy) or 0x44 (HX+)
if (!is_hxn) {
uint8_t req = VENDOR_READ_REQUEST;
uint8_t wreq = VENDOR_WRITE_REQUEST;
// Fire-and-forget vendor reads: result discarded, chip requires this sequence.
// Pass a 1-byte buffer to set wLength=1 so the IN data stage is performed.
this->control_transfer(VENDOR_READ_REQUEST_TYPE, req, 0x8484, 0, nop_cb, {0});
this->control_transfer(VENDOR_WRITE_REQUEST_TYPE, wreq, 0x0404, 0, nop_cb);
this->control_transfer(VENDOR_READ_REQUEST_TYPE, req, 0x8484, 0, nop_cb, {0});
this->control_transfer(VENDOR_READ_REQUEST_TYPE, req, 0x8383, 0, nop_cb, {0});
this->control_transfer(VENDOR_READ_REQUEST_TYPE, req, 0x8484, 0, nop_cb, {0});
this->control_transfer(VENDOR_WRITE_REQUEST_TYPE, wreq, 0x0404, 1, nop_cb);
this->control_transfer(VENDOR_READ_REQUEST_TYPE, req, 0x8484, 0, nop_cb, {0});
this->control_transfer(VENDOR_READ_REQUEST_TYPE, req, 0x8383, 0, nop_cb, {0});
this->control_transfer(VENDOR_WRITE_REQUEST_TYPE, wreq, 0, 1, nop_cb);
this->control_transfer(VENDOR_WRITE_REQUEST_TYPE, wreq, 1, 0, nop_cb);
this->control_transfer(VENDOR_WRITE_REQUEST_TYPE, wreq, 2, is_legacy ? 0x24 : 0x44, nop_cb);
// Vendor init burst runs only on full init for non-HXN chips.
uint8_t init_count = (!reload && !is_hxn) ? PL2303_INIT_COUNT : 0;
if (step < init_count) {
const auto &e = PL2303_INIT[step];
uint16_t index = (step == PL2303_INIT_COUNT - 1) ? (is_legacy ? 0x24 : 0x44) : e.index;
this->config_transfer_(e.type, e.request, e.value, index,
e.read ? std::vector<uint8_t>{0} : std::vector<uint8_t>{});
return true;
}
step -= init_count;
// Build 7-byte line coding structure:
// [0-3] baud rate (LE32), [4] stop bits, [5] parity, [6] data bits
uint8_t line_coding[7] = {};
uint32_t baud = channel->get_baud_rate();
// Choose baud encoding based on chip type
uint32_t nearest = nearest_supported_baud(baud);
if (baud == nearest || this->chip_type_ == PL2303_TYPE_HXN) {
encode_baud_direct(line_coding, baud);
} else if (this->chip_type_ == PL2303_TYPE_TA || this->chip_type_ == PL2303_TYPE_TB) {
encode_baud_divisor_alt(line_coding, baud);
} else {
encode_baud_divisor(line_coding, baud);
}
// Stop bits: 0=1, 1=1.5, 2=2
switch (channel->get_stop_bits()) {
case 2:
line_coding[4] = 2;
break;
default:
line_coding[4] = 0;
break;
}
// Parity: 0=none, 1=odd, 2=even, 3=mark, 4=space
switch (channel->parity_) {
case UART_CONFIG_PARITY_ODD:
line_coding[5] = 1;
break;
case UART_CONFIG_PARITY_EVEN:
line_coding[5] = 2;
break;
case UART_CONFIG_PARITY_MARK:
line_coding[5] = 3;
break;
case UART_CONFIG_PARITY_SPACE:
line_coding[5] = 4;
break;
default:
line_coding[5] = 0;
break;
}
// Data bits
line_coding[6] = channel->get_data_bits();
ESP_LOGD(TAG, "PL2303: SET_LINE_REQUEST baud=%" PRIu32 " stop=%u parity=%u data=%u", baud, line_coding[4],
line_coding[5], line_coding[6]);
std::vector<uint8_t> lc_vec(line_coding, line_coding + 7);
uint16_t iface = channel->cdc_dev_.bulk_interface_number;
this->control_transfer(SET_LINE_REQUEST_TYPE, SET_LINE_REQUEST, 0, iface, nop_cb, lc_vec);
switch (step) {
case 0: {
// Build 7-byte line coding structure:
// [0-3] baud rate (LE32), [4] stop bits, [5] parity, [6] data bits
uint8_t line_coding[7] = {};
uint32_t baud = channel->get_baud_rate();
// Assert DTR + RTS
this->control_transfer(SET_CONTROL_REQUEST_TYPE, SET_CONTROL_REQUEST, CONTROL_DTR | CONTROL_RTS, iface, nop_cb);
// Choose baud encoding based on chip type
uint32_t nearest = nearest_supported_baud(baud);
if (baud == nearest || this->chip_type_ == PL2303_TYPE_HXN) {
encode_baud_direct(line_coding, baud);
} else if (this->chip_type_ == PL2303_TYPE_TA || this->chip_type_ == PL2303_TYPE_TB) {
encode_baud_divisor_alt(line_coding, baud);
} else {
encode_baud_divisor(line_coding, baud);
}
this->start_channels_();
// Stop bits: 0=1, 1=1.5, 2=2
switch (channel->get_stop_bits()) {
case 2:
line_coding[4] = 2;
break;
default:
line_coding[4] = 0;
break;
}
// Parity: 0=none, 1=odd, 2=even, 3=mark, 4=space
switch (channel->parity_) {
case UART_CONFIG_PARITY_ODD:
line_coding[5] = 1;
break;
case UART_CONFIG_PARITY_EVEN:
line_coding[5] = 2;
break;
case UART_CONFIG_PARITY_MARK:
line_coding[5] = 3;
break;
case UART_CONFIG_PARITY_SPACE:
line_coding[5] = 4;
break;
default:
line_coding[5] = 0;
break;
}
// Data bits
line_coding[6] = channel->get_data_bits();
ESP_LOGD(TAG, "PL2303: SET_LINE_REQUEST baud=%u stop=%u parity=%u data=%u", baud, line_coding[4], line_coding[5],
line_coding[6]);
std::vector<uint8_t> lc_vec(line_coding, line_coding + 7);
this->config_transfer_(SET_LINE_REQUEST_TYPE, SET_LINE_REQUEST, 0, iface, lc_vec);
return true;
}
case 1:
// Assert DTR + RTS (init only)
if (reload)
return false;
this->config_transfer_(SET_CONTROL_REQUEST_TYPE, SET_CONTROL_REQUEST, CONTROL_DTR | CONTROL_RTS, iface);
return true;
default:
return false;
}
}
} // namespace esphome::usb_uart
+166 -42
View File
@@ -6,6 +6,7 @@
#include "esphome/core/application.h"
#include <cinttypes>
#include <cstring>
namespace esphome::usb_uart {
@@ -213,6 +214,7 @@ bool USBUartChannel::read_array(uint8_t *data, size_t len) {
void USBUartComponent::setup() { USBClient::setup(); }
void USBUartComponent::loop() {
bool had_work = this->process_usb_events_();
had_work |= this->run_config_machine_();
// Process USB data from the lock-free queue
UsbDataChunk *chunk;
@@ -489,60 +491,182 @@ void USBUartTypeCdcAcm::on_disconnected() {
USBClient::on_disconnected();
}
void USBUartTypeCdcAcm::enable_channels() {
bool USBUartTypeCdcAcm::config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok,
const uint8_t *response) {
static constexpr uint8_t CDC_REQUEST_TYPE = usb_host::USB_TYPE_CLASS | usb_host::USB_RECIP_INTERFACE;
static constexpr uint8_t CDC_SET_LINE_CODING = 0x20;
static constexpr uint8_t CDC_SET_CONTROL_LINE_STATE = 0x22;
static constexpr uint16_t CDC_DTR_RTS = 0x0003; // D0=DTR, D1=RTS
for (auto *channel : this->channels_) {
if (!channel->initialised_.load())
continue;
// Configure the bridge's UART parameters. A USB-UART bridge will not forward data
// at the correct speed until SET_LINE_CODING is sent; without it the UART may run
// at an indeterminate default rate so the NCP receives garbled bytes and never
// sends RSTACK.
uint32_t baud = channel->baud_rate_;
std::vector<uint8_t> line_coding = {
static_cast<uint8_t>(baud & 0xFF), static_cast<uint8_t>((baud >> 8) & 0xFF),
static_cast<uint8_t>((baud >> 16) & 0xFF), static_cast<uint8_t>((baud >> 24) & 0xFF),
static_cast<uint8_t>(channel->stop_bits_), // bCharFormat: 0=1stop, 1=1.5stop, 2=2stop
static_cast<uint8_t>(channel->parity_), // bParityType: 0=None, 1=Odd, 2=Even, 3=Mark, 4=Space
static_cast<uint8_t>(channel->data_bits_), // bDataBits
};
ESP_LOGD(TAG, "SET_LINE_CODING: baud=%u stop=%u parity=%u data=%u", (unsigned) baud, channel->stop_bits_,
(unsigned) channel->parity_, channel->data_bits_);
this->control_transfer(
CDC_REQUEST_TYPE, CDC_SET_LINE_CODING, 0, channel->cdc_dev_.interrupt_interface_number,
[](const usb_host::TransferStatus &status) {
if (!status.success) {
ESP_LOGW(TAG, "SET_LINE_CODING failed: %X", status.error_code);
} else {
ESP_LOGD(TAG, "SET_LINE_CODING OK");
}
},
line_coding);
// Assert DTR+RTS to signal DTE is present.
this->control_transfer(CDC_REQUEST_TYPE, CDC_SET_CONTROL_LINE_STATE, CDC_DTR_RTS,
channel->cdc_dev_.interrupt_interface_number, [](const usb_host::TransferStatus &status) {
if (!status.success) {
ESP_LOGW(TAG, "SET_CONTROL_LINE_STATE failed: %X", status.error_code);
} else {
ESP_LOGD(TAG, "SET_CONTROL_LINE_STATE (DTR+RTS) OK");
}
});
switch (step) {
case 0: {
// Configure the bridge's UART parameters. A USB-UART bridge will not forward data
// at the correct speed until SET_LINE_CODING is sent; without it the UART may run
// at an indeterminate default rate so the NCP receives garbled bytes and never
// sends RSTACK.
uint32_t baud = channel->baud_rate_;
std::vector<uint8_t> line_coding = {
static_cast<uint8_t>(baud & 0xFF), static_cast<uint8_t>((baud >> 8) & 0xFF),
static_cast<uint8_t>((baud >> 16) & 0xFF), static_cast<uint8_t>((baud >> 24) & 0xFF),
static_cast<uint8_t>(channel->stop_bits_), // bCharFormat: 0=1stop, 1=1.5stop, 2=2stop
static_cast<uint8_t>(channel->parity_), // bParityType: 0=None, 1=Odd, 2=Even, 3=Mark, 4=Space
static_cast<uint8_t>(channel->data_bits_), // bDataBits
};
ESP_LOGD(TAG, "SET_LINE_CODING: baud=%u stop=%u parity=%u data=%u", (unsigned) baud, channel->stop_bits_,
(unsigned) channel->parity_, channel->data_bits_);
this->config_transfer_(CDC_REQUEST_TYPE, CDC_SET_LINE_CODING, 0, channel->cdc_dev_.interrupt_interface_number,
line_coding);
return true;
}
case 1:
// Assert DTR+RTS to signal DTE is present (init only).
if (reload)
return false;
this->config_transfer_(CDC_REQUEST_TYPE, CDC_SET_CONTROL_LINE_STATE, CDC_DTR_RTS,
channel->cdc_dev_.interrupt_interface_number);
return true;
default:
return false;
}
this->start_channels_();
}
void USBUartTypeCdcAcm::start_channels_() {
for (auto *channel : this->channels_) {
if (!channel->initialised_.load())
continue;
void USBUartComponent::enable_channels() {
this->cfg_single_ = nullptr;
this->cfg_pending_reload_ = nullptr;
this->cfg_channel_idx_ = 0;
this->start_config_(false);
}
void USBUartComponent::apply_channel_settings(USBUartChannel *channel) {
if (this->cfg_active_) {
// A config sequence is already running. Defer this reload until it finishes to preserve
// the one-control-transfer-at-a-time guarantee (restarting mid-flight would let an
// in-flight callback complete against fresh state). The pending slot coalesces multiple
// requests; the channel's live settings are read when the reload eventually runs.
// Note: multiple channel reloads are not queued; only one pending reload is supported at a time.
this->cfg_pending_reload_ = channel;
return;
}
this->cfg_single_ = channel;
this->start_config_(true);
}
void USBUartComponent::start_config_(bool reload) {
this->cfg_reload_ = reload;
this->cfg_device_phase_ = !reload;
this->cfg_step_ = 0;
this->cfg_ok_ = true;
this->cfg_in_flight_ = false;
this->cfg_done_.store(false);
this->cfg_active_ = true;
this->enable_loop();
}
void USBUartComponent::config_transfer_(uint8_t type, uint8_t request, uint16_t value, uint16_t index,
const std::vector<uint8_t> &data) {
this->cfg_done_.store(false);
// The completion callback runs in the USB-task context: it only records the result and
// wakes the loop. The next transfer is issued from run_config_machine_() on the loop thread.
bool submitted = this->control_transfer(
type, request, value, index,
[this](const usb_host::TransferStatus &status) {
this->cfg_ok_ = status.success;
if (!status.success) {
ESP_LOGW(TAG, "Config control transfer failed: %s", esp_err_to_name(status.error_code));
} else if (status.data_len > 0) {
memcpy(this->cfg_response_, status.data, std::min<size_t>(status.data_len, sizeof(this->cfg_response_)));
}
// Release: publishes cfg_ok_/cfg_response_ before the loop observes cfg_done_.
this->cfg_done_.store(true, std::memory_order_release);
this->enable_loop_soon_any_context();
App.wake_loop_threadsafe();
},
data);
if (!submitted) {
// Submission failed (e.g. no free transfer request). No callback will fire, so synthesize
// a failed completion here so the state machine advances/aborts instead of hanging.
ESP_LOGW(TAG, "Config control transfer submit failed");
this->cfg_ok_ = false;
this->cfg_done_.store(true, std::memory_order_release);
}
}
bool USBUartComponent::run_config_machine_() {
if (!this->cfg_active_)
return false;
if (this->cfg_in_flight_) {
// Acquire: pairs with the release in config_transfer_'s callback.
if (!this->cfg_done_.load(std::memory_order_acquire))
return false; // still waiting; the callback will re-wake the loop (no busy spin)
this->cfg_in_flight_ = false;
this->cfg_done_.store(false);
this->cfg_step_++;
}
// cfg_ok_ is now synchronized (we only get here on the initial entry or after observing
// cfg_done_ with acquire ordering), so it is safe to read.
ESP_LOGV(TAG, "Config machine: device_phase=%d channel_idx=%d step=%d reload=%d ok=%d", this->cfg_device_phase_,
this->cfg_channel_idx_, this->cfg_step_, this->cfg_reload_, this->cfg_ok_);
// One-time device-level phase (init only). config_device_step() inspects cfg_ok_ itself.
if (this->cfg_device_phase_) {
if (this->config_device_step(this->cfg_step_, this->cfg_ok_, this->cfg_response_)) {
this->cfg_in_flight_ = true;
return true;
}
this->cfg_device_phase_ = false;
this->cfg_step_ = 0;
this->cfg_ok_ = true;
}
USBUartChannel *channel =
this->cfg_single_ != nullptr
? this->cfg_single_
: (this->cfg_channel_idx_ < this->channels_.size() ? this->channels_[this->cfg_channel_idx_] : nullptr);
if (channel != nullptr && channel->initialised_.load()) {
if (!this->cfg_ok_) {
// A previous step in this channel's sequence failed. Abort the rest. On a full init,
// mark the channel uninitialised so data flow isn't started on a misconfigured channel;
// on a reload, leave the already-working channel as it was.
if (!this->cfg_reload_)
channel->initialised_.store(false);
} else if (this->config_step(channel, this->cfg_step_, this->cfg_reload_, this->cfg_ok_, this->cfg_response_)) {
this->cfg_in_flight_ = true;
return true;
}
}
// Channel finished (or aborted). On full init, kick off data flow if still initialised.
if (channel != nullptr && !this->cfg_reload_ && channel->initialised_.load()) {
channel->input_started_.store(false);
channel->output_started_.store(false);
this->start_input(channel);
}
// Advance to the next channel (or finish).
this->cfg_step_ = 0;
this->cfg_ok_ = true;
if (this->cfg_single_ != nullptr) {
this->cfg_active_ = false;
this->cfg_single_ = nullptr;
} else if (++this->cfg_channel_idx_ >= this->channels_.size()) {
this->cfg_active_ = false;
}
// If the machine just went idle and a reload was requested while it was busy, start it now.
if (!this->cfg_active_ && this->cfg_pending_reload_ != nullptr) {
this->cfg_single_ = this->cfg_pending_reload_;
this->cfg_pending_reload_ = nullptr;
this->start_config_(true);
}
return true;
}
void USBUartChannel::load_settings(bool /*dump_config*/) {
// The per-channel control transfers already log their values at debug level.
this->parent_->apply_channel_settings(this);
}
} // namespace esphome::usb_uart
+50 -16
View File
@@ -146,7 +146,9 @@ class USBUartChannel final : public uart::UARTComponent, public Parented<USBUart
size_t available() override { return this->input_buffer_.get_available(); }
bool is_connected() override { return this->initialised_.load(); }
uart::UARTFlushResult flush() override;
void check_logger_conflict() override {}
// Re-apply the current line settings (baud, parity, etc) to this already-open channel.
void load_settings(bool dump_config) override;
using UARTComponent::load_settings; // also bring in the no-arg overload for convenience
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; }
@@ -160,6 +162,7 @@ class USBUartChannel final : public uart::UARTComponent, public Parented<USBUart
void set_rx_callback(std::function<void()> cb) { this->rx_callback_ = std::move(cb); }
protected:
void check_logger_conflict() override {}
// Larger structures first (8+ bytes)
RingBuffer input_buffer_;
LockFreeQueue<UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT> output_queue_;
@@ -195,6 +198,12 @@ class USBUartComponent : public usb_host::USBClient {
virtual void start_input(USBUartChannel *channel);
void start_output(USBUartChannel *channel);
// Begin configuring all channels (full initialisation). Called from on_connected().
void enable_channels();
// Re-apply line settings to a single, already-open channel (used by
// USBUartChannel::load_settings()).
void apply_channel_settings(USBUartChannel *channel);
// Called from loop() when input_buffer_ has insufficient space for the incoming chunk.
// Default is a no-op; override in device-specific subclasses that need resync on overflow.
virtual void on_rx_overflow(USBUartChannel *channel) {}
@@ -206,7 +215,41 @@ class USBUartComponent : public usb_host::USBClient {
EventPool<UsbDataChunk, USB_DATA_QUEUE_SIZE - 1> chunk_pool_;
protected:
// Issue one control transfer as part of the setup state machine. The completion
// callback (USB-task context) records the result/IN data, marks the step done and
// wakes the loop so run_config_machine_() advances on the loop thread. Call exactly
// once from config_step_()/config_device_step_() when issuing a step.
void config_transfer_(uint8_t type, uint8_t request, uint16_t value, uint16_t index,
const std::vector<uint8_t> &data = {});
// (Re)start the config state machine. reload=false runs full init over all channels;
// reload=true re-applies settings to cfg_single_ only.
void start_config_(bool reload);
// Advance the config state machine; called from loop(). Returns true if it did work.
bool run_config_machine_();
// Per-subclass per-channel settings sequence. For the given zero-based step, issue the
// next control transfer via config_transfer_() and return true, or return false when the
// channel has no more steps. reload=true ⇒ apply only baud/parity/stop/data (skip
// enable/reset/DTR-RTS). ok/response carry the previous step's result and IN data.
virtual bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) = 0;
// Optional one-time device-level setup run before the per-channel phase on init only
// (e.g. CH34x chip detection). Same contract as config_step_(). Default: no steps.
virtual bool config_device_step(uint8_t step, bool ok, const uint8_t *response) { return false; }
std::vector<USBUartChannel *> channels_{};
// Config state machine
USBUartChannel *cfg_single_{nullptr}; // non-null: reload of a single channel
USBUartChannel *cfg_pending_reload_{nullptr}; // reload requested while the machine was busy
std::atomic<bool> cfg_done_{false}; // synchronizes cfg_ok_/cfg_response_ across threads
uint8_t cfg_response_[8]{}; // last IN transfer payload (for detection reads)
uint8_t cfg_channel_idx_{0};
uint8_t cfg_step_{0};
bool cfg_active_{false};
bool cfg_reload_{false};
bool cfg_device_phase_{false};
bool cfg_in_flight_{false};
bool cfg_ok_{true};
};
class USBUartTypeCdcAcm : public USBUartComponent {
@@ -217,11 +260,7 @@ class USBUartTypeCdcAcm : public USBUartComponent {
virtual std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl);
void on_connected() override;
void on_disconnected() override;
virtual void enable_channels();
/// Resets per-channel transfer flags and posts the first bulk IN transfer.
/// Called by enable_channels() and by vendor-specific subclass overrides that
/// handle their own line-coding setup before starting data flow.
void start_channels_();
bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
};
class USBUartTypeCP210X : public USBUartTypeCdcAcm {
@@ -230,7 +269,7 @@ class USBUartTypeCP210X : public USBUartTypeCdcAcm {
protected:
std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
void enable_channels() override;
bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
};
class USBUartTypeCH34X : public USBUartTypeCdcAcm {
public:
@@ -238,11 +277,11 @@ class USBUartTypeCH34X : public USBUartTypeCdcAcm {
void dump_config() override;
protected:
void enable_channels() override;
bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
bool config_device_step(uint8_t step, bool ok, const uint8_t *response) override;
std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
private:
void apply_line_settings_();
CH34xChipType chiptype_{CHIP_UNKNOWN};
const char *chip_name_{"unknown"};
uint8_t num_ports_{1};
@@ -257,12 +296,7 @@ class USBUartTypeFT23XX : public USBUartTypeCdcAcm {
protected:
std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
void enable_channels() override;
int reset_(USBUartChannel *channel);
int set_baudrate_(USBUartChannel *channel, uint32_t baudrate = 0);
int set_line_properties_(USBUartChannel *channel);
int set_dtr_rts_(USBUartChannel *channel);
bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
uint8_t chip_type_{255};
};
@@ -285,7 +319,7 @@ class USBUartTypePL2303 : public USBUartTypeCdcAcm {
protected:
std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
void enable_channels() override;
bool config_step(USBUartChannel *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override;
Pl2303ChipType chip_type_{PL2303_TYPE_UNKNOWN};
};
+9
View File
@@ -381,6 +381,15 @@ class WeikaiChannel : public uart::UARTComponent {
/// we wait until all bytes are gone with a timeout of 100 ms
uart::UARTFlushResult flush() override;
#if defined(USE_ESP8266) || defined(USE_ESP32)
/// @brief Re-apply the current line settings (baud, parity, etc) to the channel.
void load_settings(bool dump_config) override {
this->set_line_param_();
this->set_baudrate_();
}
using UARTComponent::load_settings; // also bring in the no-arg overload for convenience
#endif
protected:
friend class WeikaiComponent;
@@ -37,6 +37,9 @@ class MockUARTComponent : public uart::UARTComponent {
MOCK_METHOD(bool, peek_byte, (uint8_t * data), (override));
MOCK_METHOD(uart::UARTFlushResult, flush, (), (override));
MOCK_METHOD(void, check_logger_conflict, (), (override));
#if defined(USE_ESP8266) || defined(USE_ESP32)
void load_settings(bool dump_config) override {}
#endif // defined(USE_ESP8266) || defined(USE_ESP32)
};
class TestableMitsubishiCN105 : public MitsubishiCN105 {
+3
View File
@@ -32,6 +32,9 @@ class MockUARTComponent : public UARTComponent {
MOCK_METHOD(size_t, available, (), (override));
MOCK_METHOD(UARTFlushResult, flush, (), (override));
MOCK_METHOD(void, check_logger_conflict, (), (override));
#if defined(USE_ESP8266) || defined(USE_ESP32)
MOCK_METHOD(void, load_settings, (bool dump_config), (override));
#endif
};
} // namespace esphome::uart::testing