mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 03:16:37 +00:00
[uart] Add apply_settings_live() for in-place ESP-IDF reconfiguration (#19087)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
f66ef23256
commit
380938177c
@@ -160,6 +160,7 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->last_good_framing_ = this->framing_();
|
||||
|
||||
int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1;
|
||||
int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1;
|
||||
@@ -189,18 +190,9 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
||||
setup_pin_if_needed(this->tx_pin_);
|
||||
}
|
||||
|
||||
uint32_t invert = 0;
|
||||
if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) {
|
||||
invert |= UART_SIGNAL_TXD_INV;
|
||||
}
|
||||
if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) {
|
||||
invert |= UART_SIGNAL_RXD_INV;
|
||||
}
|
||||
if (this->flow_control_pin_ != nullptr && this->flow_control_pin_->is_inverted()) {
|
||||
invert |= UART_SIGNAL_RTS_INV;
|
||||
}
|
||||
|
||||
err = uart_set_line_inverse(this->uart_num_, invert);
|
||||
// Must precede uart_set_pin() so an inverted TX line never shows the wrong idle
|
||||
// level; apply_line_settings_() repeats it later for the reset registers.
|
||||
err = uart_set_line_inverse(this->uart_num_, this->line_inversion_mask_());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_set_line_inverse failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
@@ -214,25 +206,7 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
||||
return;
|
||||
}
|
||||
|
||||
err = uart_set_rx_full_threshold(this->uart_num_, this->rx_full_threshold_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
err = uart_set_rx_timeout(this->uart_num_, this->rx_timeout_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_set_rx_timeout failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Per ESP-IDF docs, uart_set_mode() must be called only after uart_driver_install().
|
||||
auto mode = this->flow_control_pin_ != nullptr ? UART_MODE_RS485_HALF_DUPLEX : UART_MODE_UART;
|
||||
err = uart_set_mode(this->uart_num_, mode);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err));
|
||||
if (this->apply_line_settings_() != ESP_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -250,6 +224,99 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t IDFUARTComponent::line_inversion_mask_() {
|
||||
uint32_t invert = 0;
|
||||
if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) {
|
||||
invert |= UART_SIGNAL_TXD_INV;
|
||||
}
|
||||
if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) {
|
||||
invert |= UART_SIGNAL_RXD_INV;
|
||||
}
|
||||
if (this->flow_control_pin_ != nullptr && this->flow_control_pin_->is_inverted()) {
|
||||
invert |= UART_SIGNAL_RTS_INV;
|
||||
}
|
||||
return invert;
|
||||
}
|
||||
|
||||
esp_err_t IDFUARTComponent::apply_line_settings_() {
|
||||
// uart_param_config() resets these; call after every use of it.
|
||||
esp_err_t err = uart_set_line_inverse(this->uart_num_, this->line_inversion_mask_());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_set_line_inverse failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
err = uart_set_rx_full_threshold(this->uart_num_, this->rx_full_threshold_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
err = uart_set_rx_timeout(this->uart_num_, this->rx_timeout_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_set_rx_timeout failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// Per ESP-IDF docs, uart_set_mode() must be called only after uart_driver_install().
|
||||
auto mode = this->flow_control_pin_ != nullptr ? UART_MODE_RS485_HALF_DUPLEX : UART_MODE_UART;
|
||||
err = uart_set_mode(this->uart_num_, mode);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void IDFUARTComponent::set_framing_(const Framing &framing) {
|
||||
this->baud_rate_ = framing.baud_rate;
|
||||
this->data_bits_ = framing.data_bits;
|
||||
this->stop_bits_ = framing.stop_bits;
|
||||
this->parity_ = framing.parity;
|
||||
this->rx_full_threshold_ = framing.rx_full_threshold;
|
||||
}
|
||||
|
||||
esp_err_t IDFUARTComponent::apply_settings_live() {
|
||||
if (this->is_failed()) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
// No driver yet: nothing to reconfigure in place.
|
||||
if (!uart_is_driver_installed(this->uart_num_)) {
|
||||
this->load_settings(false);
|
||||
return this->is_failed() ? ESP_FAIL : ESP_OK;
|
||||
}
|
||||
// Keeps the driver ring buffers; flushes both hardware FIFOs (in-flight bytes lost).
|
||||
uart_config_t uart_config = this->get_config_();
|
||||
esp_err_t err = uart_param_config(this->uart_num_, &uart_config);
|
||||
if (err != ESP_OK) {
|
||||
// Failure leaves the registers reset; put back the last accepted framing so the
|
||||
// getters still describe the hardware.
|
||||
if (this->last_good_framing_.baud_rate == 0) {
|
||||
ESP_LOGE(TAG, "uart_param_config (live) failed: %s; no previous framing to restore", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return err;
|
||||
}
|
||||
ESP_LOGW(TAG, "uart_param_config (live) failed: %s; restoring %" PRIu32 " baud", esp_err_to_name(err),
|
||||
this->last_good_framing_.baud_rate);
|
||||
this->set_framing_(this->last_good_framing_);
|
||||
uart_config = this->get_config_();
|
||||
esp_err_t restore_err = uart_param_config(this->uart_num_, &uart_config);
|
||||
if (restore_err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "UART left unconfigured after failed live reconfigure: %s", esp_err_to_name(restore_err));
|
||||
this->mark_failed();
|
||||
return err;
|
||||
}
|
||||
// Previous framing is live again; report the refusal (line-setting errors log).
|
||||
this->apply_line_settings_();
|
||||
return err;
|
||||
}
|
||||
this->last_good_framing_ = this->framing_();
|
||||
// The new framing is live; a line-setting failure here only logs.
|
||||
this->apply_line_settings_();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void IDFUARTComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_);
|
||||
LOG_PIN(" TX Pin: ", this->tx_pin_);
|
||||
|
||||
@@ -52,13 +52,49 @@ class IDFUARTComponent final : public UARTComponent, public Component {
|
||||
void load_settings(bool dump_config) override;
|
||||
using UARTComponent::load_settings; // also bring in the no-arg overload for convenience
|
||||
|
||||
/**
|
||||
* Apply the current framing (baud rate, parity, data/stop bits) to the installed
|
||||
* driver in place, without the delete/reinstall of load_settings(). Tasks blocked in
|
||||
* the driver survive and the ring buffers are kept, but both hardware FIFOs are
|
||||
* flushed: a frame in flight reaches the peer truncated and bytes not yet out of the
|
||||
* RX FIFO are dropped. No lock is taken: quiesce writers first if that matters.
|
||||
* rx_full_threshold is not rescaled (call set_rx_full_threshold_ms() first if it
|
||||
* should follow the baud rate); a rollback restores the value from the last accepted
|
||||
* configuration, undoing a standalone set_rx_full_threshold() made since. Without an
|
||||
* installed driver this is a full load_settings(false) instead.
|
||||
*
|
||||
* @return ESP_OK once the new framing is live (a line-setting error after that only
|
||||
* logs). On rejection (unreachable baud rate) the previous framing is restored and
|
||||
* the driver's error returned; if the restore fails too the component is marked
|
||||
* failed. ESP_ERR_INVALID_STATE if already failed; ESP_FAIL if the fallback
|
||||
* load_settings() fails.
|
||||
*/
|
||||
esp_err_t apply_settings_live();
|
||||
|
||||
void on_shutdown() override;
|
||||
|
||||
protected:
|
||||
void check_logger_conflict() override;
|
||||
uint32_t line_inversion_mask_();
|
||||
// Re-applies what uart_param_config() resets: inversion, RX threshold/timeout, mode.
|
||||
esp_err_t apply_line_settings_();
|
||||
uart_port_t uart_num_{UART_NUM_MAX};
|
||||
uart_config_t get_config_();
|
||||
|
||||
struct Framing {
|
||||
uint32_t baud_rate;
|
||||
uint8_t data_bits;
|
||||
uint8_t stop_bits;
|
||||
UARTParityOptions parity;
|
||||
size_t rx_full_threshold; // sized for the baud rate, so rolled back with it
|
||||
};
|
||||
Framing framing_() const {
|
||||
return {this->baud_rate_, this->data_bits_, this->stop_bits_, this->parity_, this->rx_full_threshold_};
|
||||
}
|
||||
void set_framing_(const Framing &framing);
|
||||
// Last framing the driver accepted; baud_rate 0 means none yet.
|
||||
Framing last_good_framing_{};
|
||||
|
||||
bool has_peek_{false};
|
||||
uint8_t peek_byte_;
|
||||
uint32_t flush_timeout_ms_{0}; ///< 0 means wait indefinitely (portMAX_DELAY).
|
||||
|
||||
Reference in New Issue
Block a user