diff --git a/pm3py/core/transport.py b/pm3py/core/transport.py index 8f7eeb4..694488b 100644 --- a/pm3py/core/transport.py +++ b/pm3py/core/transport.py @@ -163,6 +163,10 @@ class PM3Transport: self._reader: asyncio.StreamReader | None = None self._writer: asyncio.StreamWriter | None = None self._lock = asyncio.Lock() + # set when a read times out: the command's response may still be in flight and would be + # misread as the *next* command's reply (the "04 BB = stale UID" desync). The next send + # drains it first. Cheap: only fires after a timeout, which is rare on a healthy link. + self._resync_needed = False async def connect(self) -> None: import serial_asyncio @@ -192,6 +196,9 @@ class PM3Transport: async def send_frame(self, frame: bytes) -> None: if not self._writer: raise PM3Error("Not connected") + if self._resync_needed: # a prior command timed out; clear its late response first + await self._drain_input() + self._resync_needed = False self._writer.write(frame) await self._writer.drain() @@ -208,6 +215,7 @@ class PM3Transport: try: raw = await asyncio.wait_for(self._read_response_frame(), timeout=timeout) except asyncio.TimeoutError: + self._resync_needed = True # late response would desync the next read — drain it raise PM3Error("Response timeout", status=-4) try: resp = PM3Response.from_dict(decode_response_frame(raw)) @@ -360,6 +368,7 @@ class PM3Transport: try: raw = await asyncio.wait_for(self._read_any_frame(), timeout=timeout) except asyncio.TimeoutError: + self._resync_needed = True # late response would desync the next read — drain it raise PM3Error("Response timeout", status=-4) magic = struct.unpack_from("