8000 tools/mpremote: Add new CLI utility to interact with remote device. · micropython/micropython@f4ded20 · GitHub
[go: up one dir, main page]

Skip to content

Commit f4ded20

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

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+
10000 # 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)
0