Skip to content

Commit 66a38bb

Browse files
committed
mpremote: Add automatic reconnect feature.
This adds a `reconnect` command that enables automatic reconnection when a device disconnects. The reconnect feature: - For direct port connections: reconnects to the exact same port - For `id:` connections: searches for the device by serial number on any port - Shows clear status messages during reconnection - Preserves resume state through reconnections - Handles Windows timing issues with retry logic Usage: mpremote connect auto reconnect repl mpremote connect id:1234567890 reconnect repl The reconnect command behaves differently based on connection type: - For `auto` and direct ports: only reconnects to the exact same port - For `id:` connections: finds the device on any available port This prevents accidental connections to different devices when using auto mode, while allowing id-based connections to find the device wherever it reappears. Signed-off-by: Andrew Leech <[email protected]>
1 parent ad0f34e commit 66a38bb

File tree

3 files changed

+111
-7
lines changed

3 files changed

+111
-7
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: 70 additions & 1 deletion
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"):
@@ -51,17 +54,20 @@ def do_connect(state, args=None):
5154
elif dev.startswith("id:"):
5255
# Search for a device with the given serial number.
5356
serial_number = dev[len("id:") :]
54-
dev = None
5557
for p in serial.tools.list_ports.comports():
5658
if p.serial_number == serial_number:
5759
state.transport = SerialTransport(p.device, baudrate=115200)
60+
# For id: mode, store the original id: string for reconnect
61+
state.connect_device = dev
5862
return
5963
raise TransportError("no device with serial number {}".format(serial_number))
6064
else:
6165
# Connect to the given device.
6266
if dev.startswith("port:"):
6367
dev = dev[len("port:") :]
6468
state.transport = SerialTransport(dev, baudrate=115200)
69+
# Store the actual device path for reconnect
70+
state.connect_device = dev
6571
return
6672
except TransportError as er:
6773
msg = er.args[0]
@@ -70,6 +76,61 @@ def do_connect(state, args=None):
7076
raise CommandError(msg)
7177

7278

79+
def do_reconnect(state):
80+
"""Attempt to reconnect to the same device after disconnect."""
81+
if not state.reconnect_enabled or not state.connect_device:
82+
return False
83+
84+
# Show the waiting message with the exact connection identifier used
85+
device_name = state.connect_device
86+
print(f"Waiting for reconnection on {device_name}...")
87+
88+
# Add a small initial delay to let the device fully disconnect
89+
time.sleep(1)
90+
91+
while True:
92+
try:
93+
dev = state.connect_device
94+
95+
if dev.startswith("id:"):
96+
# For id: mode, search for the device by serial number
97+
serial_number = dev[len("id:"):]
98+
for p in serial.tools.list_ports.comports():
99+
if p.serial_number == serial_number:
100+
try:
101+
state.transport = SerialTransport(p.device, baudrate=115200)
102+
# Restore resume state if it was set
103+
if state.was_resumed:
104+
state._auto_soft_reset = False
105+
# Give the device a moment to stabilize
106+
time.sleep(0.5)
107+
return True
108+
except TransportError:
109+
pass
110+
else:
111+
# For direct port connections, try the specific port
112+
for attempt in range(3):
113+
try:
114+
state.transport = SerialTransport(dev, baudrate=115200)
115+
# Restore resume state if it was set
116+
if state.was_resumed:
117+
state._auto_soft_reset = False
118+
# Give the device a moment to stabilize
119+
time.sleep(0.5)
120+
return True
121+
except TransportError:
122+
if attempt < 2: # Not the last attempt
123+
time.sleep(0.5) # Wait a bit before retry
124+
else:
125+
pass # Last attempt failed, continue to wait
126+
127+
except Exception:
128+
pass
129+
130+
# Wait before retrying
131+
time.sleep(1)
132+
133+
73134
def do_disconnect(state, _args=None):
74135
if not state.transport:
75136
return
@@ -484,6 +545,14 @@ def do_umount(state, path):
484545

485546
def do_resume(state, _args=None):
486547
state._auto_soft_reset = False
548+
state.was_resumed = True
549+
550+
551+
def do_reconnect_cmd(state, _args=None):
552+
"""Enable automatic reconnection on disconnect."""
553+
state.reconnect_enabled = True
554+
# The connect_device should already be set by do_connect with the appropriate value
555+
# (either the exact port or the id: string)
487556

488557

489558
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)