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 <andrew.leech@planetinnovation.com.au>
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy