diff --git a/src/meshcore/serial_cx.py b/src/meshcore/serial_cx.py index 3f4150c..61163bd 100644 --- a/src/meshcore/serial_cx.py +++ b/src/meshcore/serial_cx.py @@ -79,6 +79,10 @@ class SerialConnection: idx = data.find(b"\x3e") if idx < 0: # no start of frame return + # Discard any leading junk bytes before the actual frame marker. + # Some radios interleave console/debug text on the same UART, so + # valid companion frames may begin at an offset inside the chunk. + data = data[idx:] self.header = data[0:1] data = data[1:] diff --git a/tests/unit/test_serial_connection.py b/tests/unit/test_serial_connection.py new file mode 100644 index 0000000..0db7f1b --- /dev/null +++ b/tests/unit/test_serial_connection.py @@ -0,0 +1,31 @@ +import asyncio + +import pytest + +from meshcore.serial_cx import SerialConnection + + +class RecordingReader: + def __init__(self): + self.frames = [] + + async def handle_rx(self, data): + self.frames.append(bytes(data)) + + +@pytest.mark.asyncio +async def test_handle_rx_discards_leading_junk_before_frame_start(): + conn = SerialConnection("/dev/null", 115200) + reader = RecordingReader() + conn.set_reader(reader) + + payload = b"\x00\x01\x02\x53" + frame = b"\x3e" + len(payload).to_bytes(2, "little") + payload + + conn.handle_rx(b"junk bytes\r\n" + frame) + await asyncio.sleep(0) + + assert reader.frames == [payload] + assert conn.header == b"" + assert conn.inframe == b"" + assert conn.frame_expected_size == 0