summaryrefslogtreecommitdiffstatshomepage
path: root/tools/mpremote
diff options
context:
space:
mode:
Diffstat (limited to 'tools/mpremote')
-rw-r--r--tools/mpremote/mpremote/main.py6
-rw-r--r--tools/mpremote/mpremote/repl.py107
2 files changed, 69 insertions, 44 deletions
diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py
index d122e93c0f..0441857fab 100644
--- a/tools/mpremote/mpremote/main.py
+++ b/tools/mpremote/mpremote/main.py
@@ -620,7 +620,11 @@ def main():
# If no commands were "actions" then implicitly finish with the REPL
# using default args.
if state.run_repl_on_completion():
- do_repl(state, argparse_repl().parse_args([]))
+ disconnected = do_repl(state, argparse_repl().parse_args([]))
+
+ # Handle disconnection message
+ if disconnected:
+ print("\ndevice disconnected")
return 0
except CommandError as e:
diff --git a/tools/mpremote/mpremote/repl.py b/tools/mpremote/mpremote/repl.py
index d24a7774ac..4fda04a2e2 100644
--- a/tools/mpremote/mpremote/repl.py
+++ b/tools/mpremote/mpremote/repl.py
@@ -7,51 +7,53 @@ def do_repl_main_loop(
state, console_in, console_out_write, *, escape_non_printable, code_to_inject, file_to_inject
):
while True:
- console_in.waitchar(state.transport.serial)
- c = console_in.readchar()
- if c:
- if c in (b"\x1d", b"\x18"): # ctrl-] or ctrl-x, quit
- break
- elif c == b"\x04": # ctrl-D
- # special handling needed for ctrl-D if filesystem is mounted
- state.transport.write_ctrl_d(console_out_write)
- elif c == b"\x0a" and code_to_inject is not None: # ctrl-j, inject code
- state.transport.serial.write(code_to_inject)
- elif c == b"\x0b" and file_to_inject is not None: # ctrl-k, inject script
- console_out_write(bytes("Injecting %s\r\n" % file_to_inject, "utf8"))
- state.transport.enter_raw_repl(soft_reset=False)
- with open(file_to_inject, "rb") as f:
- pyfile = f.read()
- try:
- state.transport.exec_raw_no_follow(pyfile)
- except TransportError as er:
- console_out_write(b"Error:\r\n")
- console_out_write(er)
- state.transport.exit_raw_repl()
- else:
- state.transport.serial.write(c)
-
try:
+ console_in.waitchar(state.transport.serial)
+ c = console_in.readchar()
+ if c:
+ if c in (b"\x1d", b"\x18"): # ctrl-] or ctrl-x, quit
+ break
+ elif c == b"\x04": # ctrl-D
+ # special handling needed for ctrl-D if filesystem is mounted
+ state.transport.write_ctrl_d(console_out_write)
+ elif c == b"\x0a" and code_to_inject is not None: # ctrl-j, inject code
+ state.transport.serial.write(code_to_inject)
+ elif c == b"\x0b" and file_to_inject is not None: # ctrl-k, inject script
+ console_out_write(bytes("Injecting %s\r\n" % file_to_inject, "utf8"))
+ state.transport.enter_raw_repl(soft_reset=False)
+ with open(file_to_inject, "rb") as f:
+ pyfile = f.read()
+ try:
+ state.transport.exec_raw_no_follow(pyfile)
+ except TransportError as er:
+ console_out_write(b"Error:\r\n")
+ console_out_write(er)
+ state.transport.exit_raw_repl()
+ else:
+ state.transport.serial.write(c)
+
n = state.transport.serial.inWaiting()
- except OSError as er:
- if er.args[0] == 5: # IO error, device disappeared
- print("device disconnected")
- break
+ if n > 0:
+ dev_data_in = state.transport.serial.read(n)
+ if dev_data_in is not None:
+ if escape_non_printable:
+ # Pass data through to the console, with escaping of non-printables.
+ console_data_out = bytearray()
+ for c in dev_data_in:
+ if c in (8, 9, 10, 13, 27) or 32 <= c <= 126:
+ console_data_out.append(c)
+ else:
+ console_data_out.extend(b"[%02x]" % c)
+ else:
+ console_data_out = dev_data_in
+ console_out_write(console_data_out)
- if n > 0:
- dev_data_in = state.transport.serial.read(n)
- if dev_data_in is not None:
- if escape_non_printable:
- # Pass data through to the console, with escaping of non-printables.
- console_data_out = bytearray()
- for c in dev_data_in:
- if c in (8, 9, 10, 13, 27) or 32 <= c <= 126:
- console_data_out.append(c)
- else:
- console_data_out.extend(b"[%02x]" % c)
- else:
- console_data_out = dev_data_in
- console_out_write(console_data_out)
+ except OSError as er:
+ if _is_disconnect_exception(er):
+ return True
+ else:
+ raise
+ return False
def do_repl(state, args):
@@ -86,7 +88,7 @@ def do_repl(state, args):
capture_file.flush()
try:
- do_repl_main_loop(
+ return do_repl_main_loop(
state,
console,
console_out_write,
@@ -98,3 +100,22 @@ def do_repl(state, args):
console.exit()
if capture_file is not None:
capture_file.close()
+
+
+def _is_disconnect_exception(exception):
+ """
+ Check if an exception indicates device disconnect.
+
+ Returns True if the exception indicates the device has disconnected,
+ False otherwise.
+ """
+ if isinstance(exception, OSError):
+ if hasattr(exception, 'args') and len(exception.args) > 0:
+ # IO error, device disappeared
+ if exception.args[0] == 5:
+ return True
+ # Check for common disconnect messages in the exception string
+ exception_str = str(exception)
+ disconnect_indicators = ["Write timeout", "Device disconnected", "ClearCommError failed"]
+ return any(indicator in exception_str for indicator in disconnect_indicators)
+ return False