From ce468952708d24ea2094758ac9640f1be499922d Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 9 Jul 2026 03:30:49 +1000 Subject: [PATCH] [uart][usb_uart] Implement runtime settings update (#16990) Co-authored-by: Claude Opus 4.8 Co-authored-by: Keith Burzinski --- esphome/components/uart/uart_component.h | 4 +- .../components/uart/uart_component_esp8266.h | 2 +- .../components/uart/uart_component_esp_idf.h | 2 +- esphome/components/usb_cdc_acm/usb_cdc_acm.h | 6 + esphome/components/usb_uart/ch34x.cpp | 184 +++++++------- esphome/components/usb_uart/cp210x.cpp | 46 ++-- esphome/components/usb_uart/ft23xx.cpp | 236 ++++++------------ esphome/components/usb_uart/pl2303.cpp | 184 +++++++------- esphome/components/usb_uart/usb_uart.cpp | 208 +++++++++++---- esphome/components/usb_uart/usb_uart.h | 66 +++-- esphome/components/weikai/weikai.h | 9 + tests/components/mitsubishi_cn105/common.h | 3 + tests/components/uart/common.h | 3 + 13 files changed, 534 insertions(+), 419 deletions(-) diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h index afd3ad5777..3e52531791 100644 --- a/esphome/components/uart/uart_component.h +++ b/esphome/components/uart/uart_component.h @@ -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 diff --git a/esphome/components/uart/uart_component_esp8266.h b/esphome/components/uart/uart_component_esp8266.h index ee3be3cd3a..469885b6b6 100644 --- a/esphome/components/uart/uart_component_esp8266.h +++ b/esphome/components/uart/uart_component_esp8266.h @@ -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; diff --git a/esphome/components/uart/uart_component_esp_idf.h b/esphome/components/uart/uart_component_esp_idf.h index 3b86368797..649dd3aa46 100644 --- a/esphome/components/uart/uart_component_esp_idf.h +++ b/esphome/components/uart/uart_component_esp_idf.h @@ -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; diff --git a/esphome/components/usb_cdc_acm/usb_cdc_acm.h b/esphome/components/usb_cdc_acm/usb_cdc_acm.h index 2251c600e7..8e71fc61b2 100644 --- a/esphome/components/usb_cdc_acm/usb_cdc_acm.h +++ b/esphome/components/usb_cdc_acm/usb_cdc_acm.h @@ -84,6 +84,12 @@ class USBCDCACMInstance final : public uart::UARTComponent, public Parenteddefer([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(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(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 USBUartTypeCH34X::parse_descriptors(usb_device_handle_t dev_hdl) { diff --git a/esphome/components/usb_uart/cp210x.cpp b/esphome/components/usb_uart/cp210x.cpp index c4edaed038..2722ec8555 100644 --- a/esphome/components/usb_uart/cp210x.cpp +++ b/esphome/components/usb_uart/cp210x.cpp @@ -97,29 +97,31 @@ std::vector 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(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(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 diff --git a/esphome/components/usb_uart/ft23xx.cpp b/esphome/components/usb_uart/ft23xx.cpp index 25e4cc524f..79aa107d72 100644 --- a/esphome/components/usb_uart/ft23xx.cpp +++ b/esphome/components/usb_uart/ft23xx.cpp @@ -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 get_uart(const usb_config_desc_t *config_desc, uint8_t intf_idx) { @@ -264,138 +270,6 @@ std::vector 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; } } diff --git a/esphome/components/usb_uart/pl2303.cpp b/esphome/components/usb_uart/pl2303.cpp index 134c51198d..3c7ecd9a83 100644 --- a/esphome/components/usb_uart/pl2303.cpp +++ b/esphome/components/usb_uart/pl2303.cpp @@ -200,100 +200,114 @@ std::vector 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{0} : std::vector{}); + 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 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 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 diff --git a/esphome/components/usb_uart/usb_uart.cpp b/esphome/components/usb_uart/usb_uart.cpp index a995e93e15..482b209a3f 100644 --- a/esphome/components/usb_uart/usb_uart.cpp +++ b/esphome/components/usb_uart/usb_uart.cpp @@ -6,6 +6,7 @@ #include "esphome/core/application.h" #include +#include 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 line_coding = { - static_cast(baud & 0xFF), static_cast((baud >> 8) & 0xFF), - static_cast((baud >> 16) & 0xFF), static_cast((baud >> 24) & 0xFF), - static_cast(channel->stop_bits_), // bCharFormat: 0=1stop, 1=1.5stop, 2=2stop - static_cast(channel->parity_), // bParityType: 0=None, 1=Odd, 2=Even, 3=Mark, 4=Space - static_cast(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 line_coding = { + static_cast(baud & 0xFF), static_cast((baud >> 8) & 0xFF), + static_cast((baud >> 16) & 0xFF), static_cast((baud >> 24) & 0xFF), + static_cast(channel->stop_bits_), // bCharFormat: 0=1stop, 1=1.5stop, 2=2stop + static_cast(channel->parity_), // bParityType: 0=None, 1=Odd, 2=Even, 3=Mark, 4=Space + static_cast(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 &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(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 diff --git a/esphome/components/usb_uart/usb_uart.h b/esphome/components/usb_uart/usb_uart.h index 6d60809b38..5bb4c97796 100644 --- a/esphome/components/usb_uart/usb_uart.h +++ b/esphome/components/usb_uart/usb_uart.h @@ -146,7 +146,9 @@ class USBUartChannel final : public uart::UARTComponent, public Parentedinput_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 cb) { this->rx_callback_ = std::move(cb); } protected: + void check_logger_conflict() override {} // Larger structures first (8+ bytes) RingBuffer input_buffer_; LockFreeQueue 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 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 &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 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 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 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 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 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 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 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}; }; diff --git a/esphome/components/weikai/weikai.h b/esphome/components/weikai/weikai.h index 6f38f58318..02a39d3c84 100644 --- a/esphome/components/weikai/weikai.h +++ b/esphome/components/weikai/weikai.h @@ -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; diff --git a/tests/components/mitsubishi_cn105/common.h b/tests/components/mitsubishi_cn105/common.h index 798f7283f6..45f7b65289 100644 --- a/tests/components/mitsubishi_cn105/common.h +++ b/tests/components/mitsubishi_cn105/common.h @@ -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 { diff --git a/tests/components/uart/common.h b/tests/components/uart/common.h index de3ea3029e..5c4ba1130e 100644 --- a/tests/components/uart/common.h +++ b/tests/components/uart/common.h @@ -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