From cd350825c72a43078d556a49d3cf2920df5b2d5e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 19 Aug 2026 14:14:28 -0500 Subject: [PATCH] Only re-wake the loop once the RX buffer is drained, reset the frame state in decoder setup, test reconfiguration --- .../uart/software_serial_rx_decoder.h | 4 ++ .../uart/uart_component_esp8266.cpp | 6 ++- .../uart/test_software_serial_rx_decoder.cpp | 53 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/esphome/components/uart/software_serial_rx_decoder.h b/esphome/components/uart/software_serial_rx_decoder.h index 29c31b3c36..6c09258183 100644 --- a/esphome/components/uart/software_serial_rx_decoder.h +++ b/esphome/components/uart/software_serial_rx_decoder.h @@ -22,6 +22,8 @@ class SoftwareSerialRxDecoder { public: static constexpr uint8_t RX_IDLE = 0xFF; + /// Configure the framing and buffer. Drops buffered bytes and any partial frame and + /// assumes an idle high line; call reset() afterwards with the real line level. void setup(uint32_t bit_cycles, uint8_t data_bits, bool parity, uint8_t stop_bits, uint8_t *buffer, size_t buffer_size) { this->bit_cycles_ = bit_cycles; @@ -33,11 +35,13 @@ class SoftwareSerialRxDecoder { this->buffer_size_ = buffer_size; this->in_pos_ = 0; this->out_pos_ = 0; + this->reset(0, true); } /// Forget any partial frame; `level` is the current line level. void reset(uint32_t now, bool level) { this->bit_ = RX_IDLE; + this->cur_byte_ = 0; this->last_level_ = level; this->last_edge_ = now; } diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 0eab780114..514510a6cf 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -315,8 +315,10 @@ void IRAM_ATTR ESP8266SoftwareSerial::gpio_intr_edge(ESP8266SoftwareSerial *arg) void ESP8266SoftwareSerial::rx_finalize_pending_() { if (!this->rx_.finalize_due(arch_get_cpu_cycle_count())) { #ifdef USE_UART_WAKE_LOOP_ON_RX - // Not old enough yet: run the loop again right away instead of after a full loop_interval_. - wake_loop_threadsafe(); + // Not old enough yet: once the caller has drained what is there, run the loop + // again right away instead of after a full loop_interval_. + if (this->rx_.available() == 0) + wake_loop_threadsafe(); #endif return; } diff --git a/tests/components/uart/test_software_serial_rx_decoder.cpp b/tests/components/uart/test_software_serial_rx_decoder.cpp index c8961fc3ad..89e65f759f 100644 --- a/tests/components/uart/test_software_serial_rx_decoder.cpp +++ b/tests/components/uart/test_software_serial_rx_decoder.cpp @@ -219,6 +219,59 @@ TEST(SoftwareSerialRxDecoder, DropsBytesWhenBufferIsFullAndKeepsOldest) { EXPECT_EQ(sim.received()[n], n); } +TEST(SoftwareSerialRxDecoder, SetupAgainDropsStaleStateAndUsesNewBufferAndFraming) { + // Mirrors load_settings(): bytes buffered and a frame left open under 8N1 in a + // 64 byte buffer, then setup() again with 5E2 in a 4 byte buffer. + LineSim sim(9600, 8, false, false, 1); + uint32_t t = 5000; + for (int n = 0; n < 10; n++) + t = sim.send(static_cast(0x40 + n), t); + sim.edge(t + 8 * sim.bit_cycles(), false); // open a frame, never closed + SoftwareSerialRxDecoder &dec = sim.decoder(); + ASSERT_GE(dec.available(), 9u); + + std::vector small(4, 0xEE); + const uint32_t bit = CPU_HZ / 2400; + dec.setup(bit, 5, true, 2, small.data(), small.size()); + EXPECT_EQ(dec.available(), 0u); + EXPECT_FALSE(dec.pending()); + EXPECT_EQ(dec.read_byte(), 0); + + // 5E2 frames fed straight into the reconfigured decoder: capacity is 3, the + // rest are dropped and nothing is written past the end of the new buffer. + auto send_5e2 = [&](uint8_t value, uint32_t start) { + bool line = true; + uint32_t at = start; + auto put = [&](bool b) { + if (b != line) { + dec.on_edge(at, b); + line = b; + } + at += bit; + }; + put(false); + int ones = 0; + for (int i = 0; i < 5; i++) { + bool b = (value >> i) & 1; + ones += b; + put(b); + } + put(ones & 1); + put(true); + put(true); + return at; + }; + uint32_t t2 = 5000; + for (int n = 1; n <= 6; n++) + t2 = send_5e2(static_cast(n), t2); + dec.finalize(t2 + 20 * bit); + ASSERT_EQ(dec.available(), 3u); + EXPECT_EQ(dec.read_byte(), 1); + EXPECT_EQ(dec.read_byte(), 2); + EXPECT_EQ(dec.read_byte(), 3); + EXPECT_EQ(small[3], 0xEE); // capacity slot is never written +} + TEST(SoftwareSerialRxDecoder, ResetDiscardsPartialFrame) { LineSim sim(9600, 8, false, false, 1); const uint32_t bit = sim.bit_cycles();