From 8fe9ecbc1bdc6ea1652508c45e76ddcc49b95afe Mon Sep 17 00:00:00 2001 From: Bruno Rocci Date: Fri, 14 Aug 2026 19:51:37 +0200 Subject: [PATCH] Consume poll body to ETX before replying to avoid RS-485 bus contention --- src/CMRI.cpp | 12 +++++---- src/CMRI.h | 2 -- test/test_cmri/test_main.cpp | 50 ++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/CMRI.cpp b/src/CMRI.cpp index e359b1e..58a29b2 100644 --- a/src/CMRI.cpp +++ b/src/CMRI.cpp @@ -202,7 +202,9 @@ uint8_t CMRI::_decode(uint8_t c) else if (c == INIT) _mode = DECODE_DATA; else if (c == POLL) - goto POSTAMBLE_POLL; + // Consume poll body via IGNORE_DATA until ETX (DLE-aware), + // then reply from POSTAMBLE_IGNORE. + _mode = IGNORE_DATA; else _mode = POSTAMBLE_OTHER; break; @@ -255,11 +257,11 @@ uint8_t CMRI::_decode(uint8_t c) _rx_index = 0; return _rx_packet_type; -POSTAMBLE_POLL: - _mode = PREAMBLE_1; - return POLL; - POSTAMBLE_IGNORE: _mode = PREAMBLE_1; + // POLL frames consume body to ETX above; reply after ETX + // to avoid RS-485 bus contention (host still transmitting). + if (_rx_packet_type == POLL) + return POLL; return NOOP; } diff --git a/src/CMRI.h b/src/CMRI.h index 52b453a..fac017c 100644 --- a/src/CMRI.h +++ b/src/CMRI.h @@ -72,8 +72,6 @@ class CMRI IGNORE_CMD, IGNORE_DATA, IGNORE_ESC_DATA, - POSTAMBLE_SET, - POSTAMBLE_POLL, POSTAMBLE_OTHER }; diff --git a/test/test_cmri/test_main.cpp b/test/test_cmri/test_main.cpp index e4b60aa..5ca2f5b 100644 --- a/test/test_cmri/test_main.cpp +++ b/test/test_cmri/test_main.cpp @@ -176,6 +176,53 @@ void test_transmit_escapes_control_bytes(void) TEST_ASSERT_EQUAL_UINT8(CMRI::ETX, s.tx[10]); } +// A well-formed POLL waits for ETX before replying. +void test_poll_waits_for_etx(void) +{ + Stream s; + CMRI cmri(0, 24, 48, s); + + cmri.set_byte(0, 0x55); + + feed_packet(s, 0, CMRI::POLL, nullptr, 0); + TEST_ASSERT_TRUE(cmri.process()); + TEST_ASSERT_EQUAL_UINT8(CMRI::GET, s.tx[4]); + TEST_ASSERT_EQUAL_UINT8(0x55, s.tx[5]); +} + +// A POLL without ETX produces no reply. +void test_poll_truncated_no_reply(void) +{ + Stream s; + CMRI cmri(0, 24, 48, s); + + cmri.set_byte(0, 0x55); + + s.feed(0xFF); + s.feed(0xFF); + s.feed(CMRI::STX); + s.feed('A' + 0); + s.feed(CMRI::POLL); + + TEST_ASSERT_FALSE(cmri.process()); + TEST_ASSERT_EQUAL_UINT(0u, s.tx.size()); +} + +// A POLL with body bytes waits for ETX before replying. +void test_poll_with_body_waits_for_etx(void) +{ + Stream s; + CMRI cmri(0, 24, 48, s); + + cmri.set_byte(0, 0x77); + + uint8_t body[2] = {0x01, 0x02}; + feed_packet(s, 0, CMRI::POLL, body, 2); + TEST_ASSERT_TRUE(cmri.process()); + TEST_ASSERT_EQUAL_UINT8(CMRI::GET, s.tx[4]); + TEST_ASSERT_EQUAL_UINT8(0x77, s.tx[5]); +} + // Garbage before a valid packet is resynced away by the preamble state machine. void test_preamble_resync_after_garbage(void) { @@ -205,6 +252,9 @@ int main(int, char **) RUN_TEST(test_set_packet_updates_outputs); RUN_TEST(test_address_filtering); RUN_TEST(test_transmit_escapes_control_bytes); + RUN_TEST(test_poll_waits_for_etx); + RUN_TEST(test_poll_truncated_no_reply); + RUN_TEST(test_poll_with_body_waits_for_etx); RUN_TEST(test_preamble_resync_after_garbage); return UNITY_END(); }