Skip to content

Commit a60ad33

Browse files
committed
tools/mpremote: Add new CLI utility to interact with remote device.
This has been under development since April 2017. See adafruit#3034 and adafruit#6375. Signed-off-by: Damien George <damien@micropython.org>
1 parent e4ba57c commit a60ad33

File tree

9 files changed

+1390
-0
lines changed

9 files changed

+1390
-0
lines changed

tools/mpremote/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2021 Damien P. George
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

tools/mpremote/README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# mpremote -- MicroPython remote control
2+
3+
This CLI tool provides an integrated set of utilities to remotely interact with
4+
and automate a MicroPython device over a serial connection.
5+
6+
The simplest way to use this tool is:
7+
8+
mpremote
9+
10+
This will automatically connect to the device and provide an interactive REPL.
11+
12+
The full list of supported commands are:
13+
14+
mpremote connect <device> -- connect to given device
15+
device may be: list, auto, id:x, port:x
16+
or any valid device name/path
17+
mpremote disconnect -- disconnect current device
18+
mpremote mount <local-dir> -- mount local directory on device
19+
mpremote eval <string> -- evaluate and print the string
20+
mpremote exec <string> -- execute the string
21+
mpremote run <file> -- run the given local script
22+
mpremote fs <command> <args...> -- execute filesystem commands on the device
23+
command may be: cat, ls, cp, rm, mkdir, rmdir
24+
use ":" as a prefix to specify a file on the device
25+
mpremote repl -- enter REPL
26+
options:
27+
--capture <file>
28+
--inject-code <string>
29+
--inject-file <file>
30+
31+
Multiple commands can be specified and they will be run sequentially. Connection
32+
and disconnection will be done automatically at the start and end of the execution
33+
of the tool, if such commands are not explicitly given. Automatic connection will
34+
search for the first available serial device. If no action is specified then the
35+
REPL will be entered.
36+
37+
Shortcuts can be defined using the macro system. Built-in shortcuts are:
38+
39+
- a0, a1, a2, a3: connect to `/dev/ttyACM?`
40+
- u0, u1, u2, u3: connect to `/dev/ttyUSB?`
41+
- c0, c1, c2, c3: connect to `COM?`
42+
- cat, ls, cp, rm, mkdir, rmdir, df: filesystem commands
43+
- reset: reset the device
44+
- bootloader: make the device enter its bootloader
45+
46+
Any user configuration, including user-defined shortcuts, can be placed in
47+
.config/mpremote/config.py. For example:
48+
49+
# Custom macro commands
50+
commands = {
51+
"c33": "connect id:334D335C3138",
52+
"bl": "bootloader",
53+
"double x=4": "eval x*2",
54+
}
55+
56+
Examples:
57+
58+
mpremote
59+
mpremote a1
60+
mpremote connect /dev/ttyUSB0 repl
61+
mpremote ls
62+
mpremote a1 ls
63+
mpremote exec "import micropython; micropython.mem_info()"
64+
mpremote eval 1/2 eval 3/4
65+
mpremote mount .
66+
mpremote mount . exec "import local_script"
67+
mpremote ls
68+
mpremote cat boot.py
69+
mpremote cp :main.py .
70+
mpremote cp main.py :
71+
mpremote cp -r dir/ :

tools/mpremote/mpremote.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
from mpremote import main
5+
6+
sys.exit(main.main())

tools/mpremote/mpremote/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# empty

tools/mpremote/mpremote/console.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import sys
2+
3+
try:
4+
import select, termios
5+
except ImportError:
6+
termios = None
7+
select = None
8+
import msvcrt
9+
10+
11+
class ConsolePosix:
12+
def __init__(self):
13+
self.infd = sys.stdin.fileno()
14+
self.infile = sys.stdin.buffer.raw
15+
self.outfile = sys.stdout.buffer.raw
16+
self.orig_attr = termios.tcgetattr(self.infd)
17+
18+
def enter(self):
19+
# attr is: [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]
20+
attr = termios.tcgetattr(self.infd)
21+
attr[0] &= ~(
22+
termios.BRKINT | termios.ICRNL | termios.INPCK | termios.ISTRIP | termios.IXON
23+
)
24+
attr[1] = 0
25+
attr[2] = attr[2] & ~(termios.CSIZE | termios.PARENB) | termios.CS8
26+
attr[3] = 0
27+
attr[6][termios.VMIN] = 1
28+
attr[6][termios.VTIME] = 0
29+
termios.tcsetattr(self.infd, termios.TCSANOW, attr)
30+
31+
def exit(self):
32+
termios.tcsetattr(self.infd, termios.TCSANOW, self.orig_attr)
33+
34+
def waitchar(self):
35+
# TODO pyb.serial might not have fd
36+
select.select([console_in.infd, pyb.serial.fd], [], [])
37+
38+
def readchar(self):
39+
res = select.select([self.infd], [], [], 0)
40+
if res[0]:
41+
return self.infile.read(1)
42+
else:
43+
return None
44+
45+
def write(self, buf):
46+
self.outfile.write(buf)
47+
48+
49+
class ConsoleWindows:
50+
KEY_MAP = {
51+
b"H": b"A", # UP
52+
b"P": b"B", # DOWN
53+
b"M": b"C", # RIGHT
54+
b"K": b"D", # LEFT
55+
b"G": b"H", # POS1
56+
b"O": b"F", # END
57+
b"Q": b"6~", # PGDN
58+
b"I": b"5~", # PGUP
59+
b"s": b"1;5D", # CTRL-LEFT,
60+
b"t": b"1;5C", # CTRL-RIGHT,
61+
b"\x8d": b"1;5A", # CTRL-UP,
62+
b"\x91": b"1;5B", # CTRL-DOWN,
63+
b"w": b"1;5H", # CTRL-POS1
64+
b"u": b"1;5F", # CTRL-END
65+
b"\x98": b"1;3A", # ALT-UP,
66+
b"\xa0": b"1;3B", # ALT-DOWN,
67+
b"\x9d": b"1;3C", # ALT-RIGHT,
68+
b"\x9b": b"1;3D", # ALT-LEFT,
69+
b"\x97": b"1;3H", # ALT-POS1,
70+
b"\x9f": b"1;3F", # ALT-END,
71+
b"S": b"3~", # DEL,
72+
b"\x93": b"3;5~", # CTRL-DEL
73+
b"R": b"2~", # INS
74+
b"\x92": b"2;5~", # CTRL-INS
75+
b"\x94": b"Z", # Ctrl-Tab = BACKTAB,
76+
}
77+
78+
def enter(self):
79+
pass
80+
81+
def exit(self):
82+
pass
83+
84+
def inWaiting(self):
85+
return 1 if msvcrt.kbhit() else 0
86+
87+
def waitchar(self):
88+
while not (self.inWaiting() or pyb.serial.inWaiting()):
89+
time.sleep(0.01)
90+
91+
def readchar(self):
92+
if msvcrt.kbhit():
93+
ch = msvcrt.getch()
94+
while ch in b"\x00\xe0": # arrow or function key prefix?
95+
if not msvcrt.kbhit():
96+
return None
97+
ch = msvcrt.getch() # second call returns the actual key code
98+
try:
99+
ch = b"\x1b[" + self.KEY_MAP[ch]
100+
except KeyError:
101+
return None
102+
return ch
103+
104+
def write(self, buf):
105+
buf = buf.decode() if isinstance(buf, bytes) else buf
106+
sys.stdout.write(buf)
107+
sys.stdout.flush()
108+
# for b in buf:
109+
# if isinstance(b, bytes):
110+
# msvcrt.putch(b)
111+
# else:
112+
# msvcrt.putwch(b)
113+
114+
115+
if termios:
116+
Console = ConsolePosix
117+
VT_ENABLED = True
118+
else:
119+
Console = ConsoleWindows
120+
121+
# Windows VT mode ( >= win10 only)
122+
# https://bugs.python.org/msg291732
123+
import ctypes
124+
from ctypes import wintypes
125+
126+
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
127+
128+
ERROR_INVALID_PARAMETER = 0x0057
129+
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
130+
131+
def _check_bool(result, func, args):
132+
if not result:
133+
raise ctypes.WinError(ctypes.get_last_error())
134+
return args
135+
136+
LPDWORD = ctypes.POINTER(wintypes.DWORD)
137+
kernel32.GetConsoleMode.errcheck = _check_bool
138+
kernel32.GetConsoleMode.argtypes = (wintypes.HANDLE, LPDWORD)
139+
kernel32.SetConsoleMode.errcheck = _check_bool
140+
kernel32.SetConsoleMode.argtypes = (wintypes.HANDLE, wintypes.DWORD)
141+
142+
def set_conout_mode(new_mode, mask=0xFFFFFFFF):
143+
# don't assume StandardOutput is a console.
144+
# open CONOUT$ instead
145+
fdout = os.open("CONOUT$", os.O_RDWR)
146+
try:
147+
hout = msvcrt.get_osfhandle(fdout)
148+
old_mode = wintypes.DWORD()
149+
kernel32.GetConsoleMode(hout, ctypes.byref(old_mode))
150+
mode = (new_mode & mask) | (old_mode.value & ~mask)
151+
kernel32.SetConsoleMode(hout, mode)
152+
return old_mode.value
153+
finally:
154+
os.close(fdout)
155+
156+
# def enable_vt_mode():
157+
mode = mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING
158+
try:
159+
set_conout_mode(mode, mask)
160+
VT_ENABLED = True
161+
except WindowsError as e:
162+
VT_ENABLED = False

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