From 88adcbcb815718c3996c871d05092ab5a51860b2 Mon Sep 17 00:00:00 2001 From: serversdwn Date: Tue, 31 Mar 2026 00:10:13 -0400 Subject: [PATCH] fix: s3parser now looks for bare ETX, not DLE+ETX. --- minimateplus/framing.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/minimateplus/framing.py b/minimateplus/framing.py index 7a81a59..a05cbc4 100644 --- a/minimateplus/framing.py +++ b/minimateplus/framing.py @@ -155,8 +155,16 @@ class S3FrameParser: State machine: IDLE — scanning for DLE (0x10) SEEN_DLE — saw DLE, waiting for STX (0x02) to start a frame - IN_FRAME — collecting de-stuffed payload bytes - IN_FRAME_DLE — inside frame, saw DLE; ETX ends frame, DLE continues stuffing + IN_FRAME — collecting de-stuffed payload bytes; bare ETX ends frame + IN_FRAME_DLE — inside frame, saw DLE; DLE continues stuffing; + DLE+ETX is treated as literal data (NOT a frame end), + which lets inner-frame terminators pass through intact + + Wire format confirmed from captures: + [DLE=0x10] [STX=0x02] [stuffed payload+chk] [bare ETX=0x03] + The ETX is NOT preceded by a DLE on the wire. DLE+ETX sequences that + appear inside the payload are inner-frame terminators and must be + treated as literal data. ACK (0x41) bytes and arbitrary non-DLE bytes in IDLE state are silently discarded (covers device boot string "Operating System" and keepalive ACKs). @@ -210,6 +218,11 @@ class S3FrameParser: elif self._state == self._IN_FRAME: if b == DLE: self._state = self._IN_FRAME_DLE + elif b == ETX: + # Bare ETX = real frame terminator (confirmed from captures) + frame = self._finalise() + self._state = self._IDLE + return frame else: self._body.append(b) @@ -219,10 +232,11 @@ class S3FrameParser: self._body.append(DLE) self._state = self._IN_FRAME elif b == ETX: - # End of frame - frame = self._finalise() - self._state = self._IDLE - return frame + # DLE+ETX inside a frame is an inner-frame terminator, NOT + # the outer frame end. Treat as literal data and continue. + self._body.append(DLE) + self._body.append(ETX) + self._state = self._IN_FRAME else: # Unexpected DLE + byte — treat both as literal data and continue self._body.append(DLE)