Merge remote-tracking branch 'upstream/proto-byte-buffer' into integration

This commit is contained in:
J. Nick Koston
2026-03-07 19:46:15 -10:00
25 changed files with 77 additions and 30 deletions
+3 -2
View File
@@ -100,16 +100,17 @@ size_t BLENUS::available() {
#endif
}
void BLENUS::flush() {
uart::FlushResult BLENUS::flush() {
constexpr uint32_t timeout_5sec = 5000;
uint32_t start = millis();
while (atomic_get(&this->tx_status_) != TX_DISABLED && !ring_buf_is_empty(&global_ble_tx_ring_buf)) {
if (millis() - start > timeout_5sec) {
ESP_LOGW(TAG, "Flush timeout");
return;
return uart::FlushResult::TIMEOUT;
}
delay(1);
}
return uart::FlushResult::SUCCESS;
}
void BLENUS::connected(bt_conn *conn, uint8_t err) {
+1 -1
View File
@@ -26,7 +26,7 @@ class BLENUS : public uart::UARTComponent, public Component {
bool peek_byte(uint8_t *data) override;
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
void flush() override;
uart::FlushResult flush() override;
void check_logger_conflict() override {}
void set_expose_log(bool expose_log) { this->expose_log_ = expose_log; }
#ifdef USE_LOGGER
+6
View File
@@ -183,6 +183,7 @@ UART_PARITY_OPTIONS = {
"ODD": UARTParityOptions.UART_CONFIG_PARITY_ODD,
}
CONF_FLUSH_TIMEOUT = "flush_timeout"
CONF_RX_FULL_THRESHOLD = "rx_full_threshold"
CONF_RX_TIMEOUT = "rx_timeout"
@@ -266,6 +267,9 @@ CONFIG_SCHEMA = cv.All(
cv.SplitDefault(CONF_RX_TIMEOUT, esp32=2): cv.All(
cv.only_on_esp32, cv.validate_bytes, cv.int_range(min=0, max=92)
),
cv.Optional(CONF_FLUSH_TIMEOUT): cv.All(
cv.only_on_esp32, cv.positive_time_period_milliseconds
),
cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True),
cv.Optional(CONF_DATA_BITS, default=8): cv.int_range(min=5, max=8),
cv.Optional(CONF_PARITY, default="NONE"): cv.enum(
@@ -345,6 +349,8 @@ async def to_code(config):
)
cg.add(var.set_rx_full_threshold(config[CONF_RX_FULL_THRESHOLD]))
cg.add(var.set_rx_timeout(config[CONF_RX_TIMEOUT]))
if CONF_FLUSH_TIMEOUT in config:
cg.add(var.set_flush_timeout(config[CONF_FLUSH_TIMEOUT]))
cg.add(var.set_stop_bits(config[CONF_STOP_BITS]))
cg.add(var.set_data_bits(config[CONF_DATA_BITS]))
cg.add(var.set_parity(config[CONF_PARITY]))
+1 -1
View File
@@ -45,7 +45,7 @@ class UARTDevice {
size_t available() { return this->parent_->available(); }
void flush() { this->parent_->flush(); }
FlushResult flush() { return this->parent_->flush(); }
// Compat APIs
int read() {
+15 -1
View File
@@ -29,6 +29,14 @@ enum UARTDirection {
const LogString *parity_to_str(UARTParityOptions parity);
/// Result of a flush() call.
enum class FlushResult {
SUCCESS, ///< Confirmed: all bytes left the TX FIFO.
TIMEOUT, ///< Confirmed: timed out before TX completed.
FAILED, ///< Confirmed: driver or hardware error.
ASSUMED_SUCCESS, ///< Platform cannot report result; success is assumed.
};
class UARTComponent {
public:
static constexpr size_t RX_FULL_THRESHOLD_UNSET = 0;
@@ -74,7 +82,13 @@ class UARTComponent {
virtual size_t available() = 0;
// Pure virtual method to block until all bytes have been written to the UART bus.
virtual void flush() = 0;
// @return FlushResult indicating whether the flush was confirmed, timed out, failed, or assumed successful.
virtual FlushResult flush() = 0;
// Sets the maximum time to wait for TX to drain during flush().
// Only meaningful on ESP32 (IDF). Other platforms ignore this value.
// @param flush_timeout_ms Timeout in milliseconds; 0 means wait indefinitely.
virtual void set_flush_timeout(uint32_t flush_timeout_ms) {}
// Sets the TX (transmit) pin for the UART bus.
// @param tx_pin Pointer to the internal GPIO pin used for transmission.
@@ -213,13 +213,14 @@ size_t ESP8266UartComponent::available() {
return this->sw_serial_->available();
}
}
void ESP8266UartComponent::flush() {
FlushResult ESP8266UartComponent::flush() {
ESP_LOGVV(TAG, " Flushing");
if (this->hw_serial_ != nullptr) {
this->hw_serial_->flush();
} else {
this->sw_serial_->flush();
}
return FlushResult::ASSUMED_SUCCESS;
}
void ESP8266SoftwareSerial::setup(InternalGPIOPin *tx_pin, InternalGPIOPin *rx_pin, uint32_t baud_rate,
uint8_t stop_bits, uint32_t data_bits, UARTParityOptions parity,
@@ -58,7 +58,7 @@ class ESP8266UartComponent : public UARTComponent, public Component {
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
void flush() override;
FlushResult flush() override;
uint32_t get_config();
@@ -230,6 +230,9 @@ void IDFUARTComponent::dump_config() {
" RX Timeout: %u",
this->rx_buffer_size_, this->rx_full_threshold_, this->rx_timeout_);
}
if (this->flush_timeout_ms_ > 0) {
ESP_LOGCONFIG(TAG, " Flush Timeout: %" PRIu32 " ms", this->flush_timeout_ms_);
}
ESP_LOGCONFIG(TAG,
" Baud Rate: %" PRIu32 " baud\n"
" Data Bits: %u\n"
@@ -332,9 +335,15 @@ size_t IDFUARTComponent::available() {
return available;
}
void IDFUARTComponent::flush() {
FlushResult IDFUARTComponent::flush() {
ESP_LOGVV(TAG, " Flushing");
uart_wait_tx_done(this->uart_num_, portMAX_DELAY);
TickType_t ticks = this->flush_timeout_ms_ == 0 ? portMAX_DELAY : pdMS_TO_TICKS(this->flush_timeout_ms_);
esp_err_t err = uart_wait_tx_done(this->uart_num_, ticks);
if (err == ESP_OK)
return FlushResult::SUCCESS;
if (err == ESP_ERR_TIMEOUT)
return FlushResult::TIMEOUT;
return FlushResult::FAILED;
}
void IDFUARTComponent::check_logger_conflict() {}
@@ -31,7 +31,9 @@ class IDFUARTComponent : public UARTComponent, public Component {
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
void flush() override;
FlushResult flush() override;
void set_flush_timeout(uint32_t flush_timeout_ms) override { this->flush_timeout_ms_ = flush_timeout_ms; }
uint8_t get_hw_serial_number() { return this->uart_num_; }
@@ -57,6 +59,7 @@ class IDFUARTComponent : public UARTComponent, public Component {
bool has_peek_{false};
uint8_t peek_byte_;
uint32_t flush_timeout_ms_{0}; ///< 0 means wait indefinitely (portMAX_DELAY).
#ifdef USE_UART_WAKE_LOOP_ON_RX
// ISR callback for UART RX data notification — wakes the main loop directly.
@@ -274,12 +274,13 @@ size_t HostUartComponent::available() {
return result;
};
void HostUartComponent::flush() {
FlushResult HostUartComponent::flush() {
if (this->file_descriptor_ == -1) {
return;
return FlushResult::ASSUMED_SUCCESS;
}
tcflush(this->file_descriptor_, TCIOFLUSH);
ESP_LOGV(TAG, " Flushing");
return FlushResult::ASSUMED_SUCCESS;
}
void HostUartComponent::update_error_(const std::string &error) {
@@ -18,7 +18,7 @@ class HostUartComponent : public UARTComponent, public Component {
bool peek_byte(uint8_t *data) override;
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
void flush() override;
FlushResult flush() override;
void set_name(std::string port_name) { port_name_ = port_name; };
protected:
@@ -170,9 +170,10 @@ bool LibreTinyUARTComponent::read_array(uint8_t *data, size_t len) {
}
size_t LibreTinyUARTComponent::available() { return this->serial_->available(); }
void LibreTinyUARTComponent::flush() {
FlushResult LibreTinyUARTComponent::flush() {
ESP_LOGVV(TAG, " Flushing");
this->serial_->flush();
return FlushResult::ASSUMED_SUCCESS;
}
void LibreTinyUARTComponent::check_logger_conflict() {
@@ -22,7 +22,7 @@ class LibreTinyUARTComponent : public UARTComponent, public Component {
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
void flush() override;
FlushResult flush() override;
uint16_t get_config();
@@ -187,9 +187,10 @@ bool RP2040UartComponent::read_array(uint8_t *data, size_t len) {
return true;
}
size_t RP2040UartComponent::available() { return this->serial_->available(); }
void RP2040UartComponent::flush() {
FlushResult RP2040UartComponent::flush() {
ESP_LOGVV(TAG, " Flushing");
this->serial_->flush();
return FlushResult::ASSUMED_SUCCESS;
}
} // namespace esphome::uart
@@ -25,7 +25,7 @@ class RP2040UartComponent : public UARTComponent, public Component {
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
void flush() override;
FlushResult flush() override;
uint16_t get_config();
+1 -1
View File
@@ -82,7 +82,7 @@ class USBCDCACMInstance : public uart::UARTComponent, public Parented<USBCDCACMC
bool peek_byte(uint8_t *data) override;
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
void flush() override;
uart::FlushResult flush() override;
protected:
void check_logger_conflict() override;
@@ -326,10 +326,10 @@ size_t USBCDCACMInstance::available() {
return waiting + (this->has_peek_ ? 1 : 0);
}
void USBCDCACMInstance::flush() {
uart::FlushResult USBCDCACMInstance::flush() {
// Wait for TX ring buffer to be empty
if (this->usb_tx_ringbuf_ == nullptr) {
return;
return uart::FlushResult::ASSUMED_SUCCESS;
}
UBaseType_t waiting = 1;
@@ -341,7 +341,12 @@ void USBCDCACMInstance::flush() {
}
// Also wait for USB to finish transmitting
tinyusb_cdcacm_write_flush(static_cast<tinyusb_cdcacm_itf_t>(this->itf_), pdMS_TO_TICKS(100));
esp_err_t err = tinyusb_cdcacm_write_flush(static_cast<tinyusb_cdcacm_itf_t>(this->itf_), pdMS_TO_TICKS(100));
if (err == ESP_OK)
return uart::FlushResult::SUCCESS;
if (err == ESP_ERR_TIMEOUT)
return uart::FlushResult::TIMEOUT;
return uart::FlushResult::FAILED;
}
void USBCDCACMInstance::check_logger_conflict() {}
+4 -1
View File
@@ -166,7 +166,7 @@ void USBUartChannel::write_array(const uint8_t *data, size_t len) {
this->parent_->start_output(this);
}
void USBUartChannel::flush() {
uart::FlushResult USBUartChannel::flush() {
// Spin until the output queue is drained and the last USB transfer completes.
// Safe to call from the main loop only.
// The 100 ms timeout guards against a device that stops responding mid-flush;
@@ -177,6 +177,9 @@ void USBUartChannel::flush() {
this->parent_->start_output(this);
yield();
}
if (!this->output_queue_.empty() || this->output_started_.load())
return uart::FlushResult::TIMEOUT;
return uart::FlushResult::SUCCESS;
}
bool USBUartChannel::peek_byte(uint8_t *data) {
+1 -1
View File
@@ -110,7 +110,7 @@ class USBUartChannel : public uart::UARTComponent, public Parented<USBUartCompon
bool peek_byte(uint8_t *data) override;
bool read_array(uint8_t *data, size_t len) override;
size_t available() override { return this->input_buffer_.get_available(); }
void flush() override;
uart::FlushResult flush() override;
void check_logger_conflict() override {}
void set_parity(UARTParityOptions parity) { this->parity_ = parity; }
void set_debug(bool debug) { this->debug_ = debug; }
+3 -2
View File
@@ -433,15 +433,16 @@ void WeikaiChannel::write_array(const uint8_t *buffer, size_t length) {
this->reg(0).write_fifo(const_cast<uint8_t *>(buffer), length);
}
void WeikaiChannel::flush() {
uart::FlushResult WeikaiChannel::flush() {
uint32_t const start_time = millis();
while (this->tx_fifo_is_not_empty_()) { // wait until buffer empty
if (millis() - start_time > 200) {
ESP_LOGW(TAG, "WARNING flush timeout - still %d bytes not sent after 200 ms", this->tx_in_fifo_());
return;
return uart::FlushResult::TIMEOUT;
}
yield(); // reschedule our thread to avoid blocking
}
return uart::FlushResult::SUCCESS;
}
size_t WeikaiChannel::xfer_fifo_to_buffer_() {
+1 -1
View File
@@ -380,7 +380,7 @@ class WeikaiChannel : public uart::UARTComponent {
/// @details If we refer to Serial.flush() in Arduino it says: ** Waits for the transmission of outgoing serial data
/// to complete. (Prior to Arduino 1.0, this the method was removing any buffered incoming serial data.). ** Therefore
/// we wait until all bytes are gone with a timeout of 100 ms
void flush() override;
uart::FlushResult flush() override;
protected:
friend class WeikaiComponent;
+1 -1
View File
@@ -16,7 +16,7 @@ class MockUARTComponent : public uart::UARTComponent {
MOCK_METHOD(bool, read_array, (uint8_t * data, size_t len), (override));
MOCK_METHOD(bool, peek_byte, (uint8_t * data), (override));
MOCK_METHOD(size_t, available, (), (override));
MOCK_METHOD(void, flush, (), (override));
MOCK_METHOD(uart::FlushResult, flush, (), (override));
MOCK_METHOD(void, check_logger_conflict, (), (override));
};
+1 -1
View File
@@ -30,7 +30,7 @@ class MockUARTComponent : public UARTComponent {
MOCK_METHOD(bool, read_array, (uint8_t * data, size_t len), (override));
MOCK_METHOD(bool, peek_byte, (uint8_t * data), (override));
MOCK_METHOD(size_t, available, (), (override));
MOCK_METHOD(void, flush, (), (override));
MOCK_METHOD(FlushResult, flush, (), (override));
MOCK_METHOD(void, check_logger_conflict, (), (override));
};
@@ -153,8 +153,9 @@ bool MockUartComponent::read_array(uint8_t *data, size_t len) {
size_t MockUartComponent::available() { return this->rx_buffer_.size(); }
void MockUartComponent::flush() {
uart::FlushResult MockUartComponent::flush() {
// Nothing to flush in mock
return uart::FlushResult::ASSUMED_SUCCESS;
}
void MockUartComponent::set_rx_full_threshold(size_t rx_full_threshold) {
@@ -28,7 +28,7 @@ class MockUartComponent : public uart::UARTComponent, public Component {
bool peek_byte(uint8_t *data) override;
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
void flush() override;
uart::FlushResult flush() override;
void set_rx_full_threshold(size_t rx_full_threshold) override;
void set_rx_timeout(size_t rx_timeout) override;