mirror of
https://github.com/esphome/esphome.git
synced 2026-09-17 10:08:40 +00:00
[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:
co-authored by
Claude Opus 4.8
Keith Burzinski
parent
e7933a5387
commit
ce46895270
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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};
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user