From b18368d4bd918f6cb221797073bc0a641a2d8d92 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 7 Nov 2026 15:47:24 +0100 Subject: [PATCH 1/3] transport: Don't reset the receive timeout on events. A camera that keeps sending events (e.g. stdout) while never answering a command would keep resetting the timeout and hang the host forever. Signed-off-by: iabdalkader --- src/openmv/transport.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/openmv/transport.py b/src/openmv/transport.py index c0121f5..c826968 100644 --- a/src/openmv/transport.py +++ b/src/openmv/transport.py @@ -215,7 +215,6 @@ def recv_packet(self, poll_events=False): if packet['flags'] & Flags.EVENT: self.event_callback(packet['channel'], 0xFFFF if not packet['length'] else struct.unpack(' Date: Sat, 7 Nov 2026 15:48:49 +0100 Subject: [PATCH 2/3] transport: Raise on a sequence mismatch. Packets with a valid header but an unexpected sequence were dropped silently, so the host waited until the timeout for a response that would never come (e.g. the camera NAKs with sequence 0 after a soft reboot). Raise a SequenceException instead so the host resyncs. The header CRC is now checked before the sequence, so a corrupted header is skipped rather than raised on. Signed-off-by: iabdalkader --- src/openmv/transport.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/openmv/transport.py b/src/openmv/transport.py index c826968..6f72202 100644 --- a/src/openmv/transport.py +++ b/src/openmv/transport.py @@ -278,12 +278,13 @@ def _process(self): if length > self.max_payload: self.log(seq, chan, opcode, flags, length, "Rjct") self.buf.consume(1) - elif not self._check_seq(seq, self.sequence, opcode, flags): + elif not self._check_crc(crc, header[:Protocol.HEADER_SIZE - 2], 16): self.log(seq, chan, opcode, flags, length, "Rjct") self.buf.consume(1) - elif not self._check_crc(crc, header[:Protocol.HEADER_SIZE - 2], 16): + elif not self._check_seq(seq, self.sequence, opcode, flags): self.log(seq, chan, opcode, flags, length, "Rjct") self.buf.consume(1) + raise SequenceException("") else: self.state = State.PAYLOAD self.plength = Protocol.HEADER_SIZE + length From 2c9e1d4aedd3660463abce86ef8e9f9272695e85 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 7 Nov 2026 16:38:12 +0100 Subject: [PATCH 3/3] camera: Wait for the script to stop. STOP only schedules an interrupt, the script may take a while to exit (e.g. it handles KeyboardInterrupt). A script written before then is ACKed by the old context and discarded when the script exits. Poll the stdin channel until the script stops or a timeout expires, and stop a running script before exec. Signed-off-by: iabdalkader --- src/openmv/camera.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/openmv/camera.py b/src/openmv/camera.py index fd1aa20..414fb19 100644 --- a/src/openmv/camera.py +++ b/src/openmv/camera.py @@ -7,6 +7,7 @@ # This module provides the high-level Camera class that handles all # camera operations and channel communications using the OpenMV Protocol. +import time import struct import sys import serial @@ -329,15 +330,24 @@ def update_capabilities(self): self._send_cmd_wait_resp(Opcode.PROTO_SET_CAPS, 0, payload) @retry_if_failed - def stop(self): - """Stop running script""" + def stop(self, timeout=None): + """Stop running script and wait for it to exit""" # Stop running script stdin_id = self.get_channel(name="stdin") self._channel_ioctl(stdin_id, ChannelIOCTL.STDIN_STOP) + # Wait for the script to exit + timeout = self.timeout if timeout is None else timeout + start_time = time.time() + while self.read_status()["stdin"]: + if time.time() - start_time > timeout: + raise TimeoutException("Timeout waiting for the script to stop") @retry_if_failed def exec(self, script): """Write and execute a script""" + # Stop the running script first + if self.read_status()["stdin"]: + self.stop() stdin_id = self.get_channel(name="stdin") # Reset script buffer self._channel_ioctl(stdin_id, ChannelIOCTL.STDIN_RESET)