Skip to content

Commit

Permalink
tools/mpremote: Introduce timeout_overall for read_until().
Browse files Browse the repository at this point in the history
And use it in `enter_raw_repl()`.  This prevents waiting forever for a
serial device that does not respond to the Ctrl-C/Ctrl-D/etc commands and
is constantly outputting data.

Signed-off-by: Hans Maerki <[email protected]>
  • Loading branch information
hmaerki authored and dpgeorge committed Jan 28, 2025
1 parent 0d46e45 commit 03fe9c5
Showing 1 changed file with 17 additions and 6 deletions.
23 changes: 17 additions & 6 deletions tools/mpremote/mpremote/transport_serial.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,21 +98,25 @@ def __init__(self, device, baudrate=115200, wait=0, exclusive=True, timeout=None
def close(self):
self.serial.close()

def read_until(self, min_num_bytes, ending, timeout=10, data_consumer=None):
def read_until(
self, min_num_bytes, ending, timeout=10, data_consumer=None, timeout_overall=None
):
"""
min_num_bytes: Obsolete.
ending: Return if 'ending' matches.
timeout [s]: Return if timeout between characters. None: Infinite timeout.
timeout_overall [s]: Return not later than timeout_overall. None: Infinite timeout.
data_consumer: Use callback for incoming characters.
If data_consumer is used then data is not accumulated and the ending must be 1 byte long
It is not visible to the caller why the function returned. It could be ending or timeout.
"""
assert data_consumer is None or len(ending) == 1
assert isinstance(timeout, (type(None), int, float))
assert isinstance(timeout_overall, (type(None), int, float))

data = b""
begin_char_s = time.monotonic()
begin_overall_s = begin_char_s = time.monotonic()
while True:
if data.endswith(ending):
break
Expand All @@ -127,10 +131,15 @@ def read_until(self, min_num_bytes, ending, timeout=10, data_consumer=None):
else:
if timeout is not None and time.monotonic() >= begin_char_s + timeout:
break
if (
timeout_overall is not None
and time.monotonic() >= begin_overall_s + timeout_overall
):
break
time.sleep(0.01)
return data

def enter_raw_repl(self, soft_reset=True):
def enter_raw_repl(self, soft_reset=True, timeout_overall=10):
self.serial.write(b"\r\x03") # ctrl-C: interrupt any running program

# flush input (without relying on serial.flushInput())
Expand All @@ -142,7 +151,9 @@ def enter_raw_repl(self, soft_reset=True):
self.serial.write(b"\r\x01") # ctrl-A: enter raw REPL

if soft_reset:
data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n>")
data = self.read_until(
1, b"raw REPL; CTRL-B to exit\r\n>", timeout_overall=timeout_overall
)
if not data.endswith(b"raw REPL; CTRL-B to exit\r\n>"):
print(data)
raise TransportError("could not enter raw repl")
Expand All @@ -152,12 +163,12 @@ def enter_raw_repl(self, soft_reset=True):
# Waiting for "soft reboot" independently to "raw REPL" (done below)
# allows boot.py to print, which will show up after "soft reboot"
# and before "raw REPL".
data = self.read_until(1, b"soft reboot\r\n")
data = self.read_until(1, b"soft reboot\r\n", timeout_overall=timeout_overall)
if not data.endswith(b"soft reboot\r\n"):
print(data)
raise TransportError("could not enter raw repl")

data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n")
data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n", timeout_overall=timeout_overall)
if not data.endswith(b"raw REPL; CTRL-B to exit\r\n"):
print(data)
raise TransportError("could not enter raw repl")
Expand Down

0 comments on commit 03fe9c5

Please sign in to comment.