Skip to content

Consume POLL body to ETX before replying to avoid RS-485 bus contention - #30

Open
brocci wants to merge 1 commit into
madleech:masterfrom
brocci:fix-poll-answered-before-etx
Open

Consume POLL body to ETX before replying to avoid RS-485 bus contention#30
brocci wants to merge 1 commit into
madleech:masterfrom
brocci:fix-poll-answered-before-etx

Conversation

@brocci

@brocci brocci commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

As reported in issue #28:

[SPEC-DEVIATION] Poll is answered without verifying the poll's ETX — now with real collision exposure

  • Status: still present in v1.7.0, consequence elevated by the v1.5.1 delay change.
  • Spec: LCS-9.10.1 p.7 §D.2 (poll format includes ETX); p.2 ("Any sequence of bits not meeting the full specification of this general message format is not ... a valid message"); p.4 §C (half-duplex network).
  • Code: src/CMRI.cpp:204-205 (goto POSTAMBLE_POLL on the P byte itself), src/CMRI.cpp:258-260, src/CMRI.cpp:147 (delayMicroseconds(50)).
  • The node commits to transmitting its R reply the instant it sees P, before the poll's ETX has been received or checked. A corrupted frame (FF FF 02 UA P <noise>) still gets a full response. In v1.5 the 50 ms delay() masked the timing race; in v1.7.0 the reply starts ~50 µs after the P byte — while the host's ETX (~1.04 ms at 9600 bps) is still on the wire. On the spec's four-wire network the pairs are separate so no electrical collision occurs, but on the common 2-wire RS-485 setups this library is explicitly marketed for (Auto485 examples, examples/rs485_rx_and_tx/rs485_rx_and_tx.ino) the node's driver can be enabled while the host is still transmitting — a genuine bus-contention window of about one character time.
  • Fix: add a POSTAMBLE_POLL_WAIT_ETX state; reply only after ETX is received.

This PR implements the required fix and adds corresponding tests.
The fix does not use a POSTAMBLE_POLL_WAIT_ETX additional state as suggested above, but reuses existing IGNORE_DATA and a small change in POSTAMBLE_IGNORE.

@brocci
brocci force-pushed the fix-poll-answered-before-etx branch 2 times, most recently from d7dedd1 to 8fe9ecb Compare August 15, 2026 21:53
brocci added a commit to brocci/ArduinoCMRI that referenced this pull request Aug 16, 2026
# Conflicts:
#	test/test_cmri/test_main.cpp
brocci added a commit to brocci/ArduinoCMRI that referenced this pull request Sep 10, 2026
PR madleech#30 consumes the POLL body to ETX before replying to avoid RS-485 bus
contention. But POSTAMBLE_IGNORE is also reached when ignoring frames
addressed to other nodes, and it decided to reply by checking
_rx_packet_type. Since that field is only refreshed in DECODE_CMD, a node
that had recently been polled (type == 'P') spuriously transmitted a GET
reply every time it finished ignoring another node's frame. On a
multi-node bus this ping-ponged between nodes, colliding with the
addressed node's response and JMRI's polls.

Fix the regression without changing PR madleech#30's design: clear _rx_packet_type
when entering IGNORE_CMD, so finishing an ignored (foreign) frame can
never look like a pending poll. Also initialize _rx_packet_type in the
constructor.
A node now consumes the whole POLL frame (DLE-aware) through to ETX before
replying, so it never transmits while the host is still on the wire.

A node must only reply to a POLL addressed to it. _rx_packet_type is
refreshed in DECODE_CMD and cleared when entering IGNORE_CMD, so finishing
an ignored (foreign) frame can never look like a pending poll (this fixes a
spurious GET reply on multi-node buses). _rx_packet_type is initialized in
the constructor.

Adds regression tests: reply waits for ETX, no reply to another node's
POLL/GET, own POLL still answered after ignoring others.
@brocci
brocci force-pushed the fix-poll-answered-before-etx branch from 62c8fc1 to 8e230de Compare September 10, 2026 21:27
brocci added a commit to brocci/ArduinoCMRI that referenced this pull request Sep 11, 2026
PR madleech#30 consumes the POLL body to ETX before replying to avoid RS-485 bus
contention. But POSTAMBLE_IGNORE is also reached when ignoring frames
addressed to other nodes, and it decided to reply by checking
_rx_packet_type. Since that field is only refreshed in DECODE_CMD, a node
that had recently been polled (type == 'P') spuriously transmitted a GET
reply every time it finished ignoring another node's frame. On a
multi-node bus this ping-ponged between nodes, colliding with the
addressed node's response and JMRI's polls.

Fix the regression without changing PR madleech#30's design: clear _rx_packet_type
when entering IGNORE_CMD, so finishing an ignored (foreign) frame can
never look like a pending poll. Also initialize _rx_packet_type in the
constructor.

Tests: regression tests for hearing another node's POLL/GET and still
replying to one's own POLL, plus tests pinning process()'s returned packet
type (PR madleech#27 semantics: POLL/SET reported, NOOP for ignored frames and
INIT).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant