Skip to content

Commit 67428d3

Browse files
committed
mpremote: Add automatic reconnect feature.
This adds a `reconnect` command that enables automatic reconnection when a device disconnects. The reconnect feature: - Always reconnects to the exact same port, even when using `auto` - Shows clear status messages during reconnection - Preserves resume state through reconnections - Handles Windows timing issues with retry logic Usage: mpremote connect auto reconnect repl The reconnect command remembers the actual device path that was connected (e.g., /dev/ttyACM2) and only reconnects to that specific device, preventing accidental connections to different boards. Signed-off-by: Andrew Leech <[email protected]>
1 parent ad0f34e commit 67428d3

File tree

3 files changed

+97
-6
lines changed

3 files changed

+97
-6
lines changed

docs/reference/mpremote.rst

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ The full list of supported commands are:
9090

9191
.. code-block:: bash
9292
93-
$ mpremote connect <device>
93+
$ mpremote connect [--options] <device>
9494
9595
``<device>`` may be one of:
9696

@@ -135,6 +135,19 @@ The full list of supported commands are:
135135
This disables :ref:`auto-soft-reset <mpremote_reset>`. This is useful if you
136136
want to run a subsequent command on a board without first soft-resetting it.
137137

138+
.. _mpremote_command_reconnect:
139+
140+
- **reconnect** -- enable automatic reconnection on disconnect:
141+
142+
.. code-block:: bash
143+
144+
$ mpremote reconnect
145+
146+
This enables automatic reconnection to the same device if it disconnects.
147+
When the device disconnects, mpremote will wait for it to reconnect instead
148+
of exiting. This is useful for development where devices may be unplugged
149+
and replugged frequently.
150+
138151
.. _mpremote_command_soft_reset:
139152

140153
- **soft-reset** -- perform a soft-reset of the device:

tools/mpremote/mpremote/commands.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import sys
66
import tempfile
77
import zlib
8+
import time
89

910
import serial.tools.list_ports
1011

@@ -43,6 +44,8 @@ def do_connect(state, args=None):
4344
if p.vid is not None and p.pid is not None:
4445
try:
4546
state.transport = SerialTransport(p.device, baudrate=115200)
47+
# Store the actual device path for reconnect
48+
state.connect_device = p.device
4649
return
4750
except TransportError as er:
4851
if not er.args[0].startswith("failed to access"):
@@ -55,13 +58,17 @@ def do_connect(state, args=None):
5558
for p in serial.tools.list_ports.comports():
5659
if p.serial_number == serial_number:
5760
state.transport = SerialTransport(p.device, baudrate=115200)
61+
# Store the actual device path for reconnect
62+
state.connect_device = p.device
5863
return
5964
raise TransportError("no device with serial number {}".format(serial_number))
6065
else:
6166
# Connect to the given device.
6267
if dev.startswith("port:"):
6368
dev = dev[len("port:") :]
6469
state.transport = SerialTransport(dev, baudrate=115200)
70+
# Store the actual device path for reconnect
71+
state.connect_device = dev
6572
return
6673
except TransportError as er:
6774
msg = er.args[0]
@@ -70,6 +77,46 @@ def do_connect(state, args=None):
7077
raise CommandError(msg)
7178

7279

80+
def do_reconnect(state):
81+
"""Attempt to reconnect to the same device after disconnect."""
82+
if not state.reconnect_enabled or not state.connect_device:
83+
return False
84+
85+
# Always show the specific device we're waiting for
86+
device_name = state.connect_device
87+
print(f"Waiting for reconnection on {device_name}...")
88+
89+
# Add a small initial delay to let the device fully disconnect
90+
time.sleep(1)
91+
92+
while True:
93+
try:
94+
# Always try to reconnect to the specific device path that was connected
95+
dev = state.connect_device
96+
97+
# Try multiple times for the specific port
98+
for attempt in range(3):
99+
try:
100+
state.transport = SerialTransport(dev, baudrate=115200)
101+
# Restore resume state if it was set
102+
if state.was_resumed:
103+
state._auto_soft_reset = False
104+
# Give the device a moment to stabilize
105+
time.sleep(0.5)
106+
return True
107+
except TransportError:
108+
if attempt < 2: # Not the last attempt
109+
time.sleep(0.5) # Wait a bit before retry
110+
else:
111+
pass # Last attempt failed, continue to wait
112+
113+
except Exception:
114+
pass
115+
116+
# Wait before retrying
117+
time.sleep(1)
118+
119+
73120
def do_disconnect(state, _args=None):
74121
if not state.transport:
75122
return
@@ -484,6 +531,15 @@ def do_umount(state, path):
484531

485532
def do_resume(state, _args=None):
486533
state._auto_soft_reset = False
534+
state.was_resumed = True
535+
536+
537+
def do_reconnect_cmd(state, _args=None):
538+
"""Enable automatic reconnection on disconnect."""
539+
state.reconnect_enabled = True
540+
# If we're already connected, store the current device
541+
if state.transport:
542+
state.connect_device = state.transport.device_name
487543

488544

489545
def do_soft_reset(state, _args=None):

tools/mpremote/mpremote/main.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
CommandError,
2727
do_connect,
2828
do_disconnect,
29+
do_reconnect,
30+
do_reconnect_cmd,
2931
do_edit,
3032
do_filesystem,
3133
do_mount,
@@ -281,6 +283,10 @@ def argparse_none(description):
281283
do_resume,
282284
argparse_none("resume a previous mpremote session (will not auto soft-reset)"),
283285
),
286+
"reconnect": (
287+
do_reconnect_cmd,
288+
argparse_none("enable automatic reconnection on disconnect"),
289+
),
284290
"soft-reset": (
285291
do_soft_reset,
286292
argparse_none("perform a soft-reset of the device"),
@@ -493,6 +499,9 @@ def __init__(self):
493499
self.transport = None
494500
self._did_action = False
495501
self._auto_soft_reset = True
502+
self.reconnect_enabled = False
503+
self.connect_device = None
504+
self.was_resumed = False
496505

497506
def did_action(self):
498507
self._did_action = True
@@ -574,11 +583,24 @@ def main():
574583
# If no commands were "actions" then implicitly finish with the REPL
575584
# using default args.
576585
if state.run_repl_on_completion():
577-
disconnected = do_repl(state, argparse_repl().parse_args([]))
578-
579-
# Handle disconnection message
580-
if disconnected:
581-
print("\ndevice disconnected")
586+
while True:
587+
disconnected = do_repl(state, argparse_repl().parse_args([]))
588+
589+
# Handle disconnection message
590+
if disconnected:
591+
print("\nDevice disconnected")
592+
593+
# Try to reconnect if enabled
594+
if state.reconnect_enabled:
595+
do_disconnect(state) # Clean up current connection state
596+
if do_reconnect(state):
597+
# Successfully reconnected, continue the loop
598+
# Reset action state for the new connection
599+
state._did_action = False
600+
continue
601+
602+
# If not reconnecting or reconnect failed, exit
603+
break
582604

583605
return 0
584606
except CommandError as e:

0 commit comments

Comments
 (0)