Skip to content

Commit 89ca71e

Browse files
committed
webrepl_cli.py: Add support for accessing REPL.
Signed-off-by: Damien George <damien@micropython.org>
1 parent 03492fe commit 89ca71e

File tree

1 file changed

+94
-16
lines changed

1 file changed

+94
-16
lines changed

webrepl_cli.py

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
WEBREPL_PUT_FILE = 1
2121
WEBREPL_GET_FILE = 2
2222
WEBREPL_GET_VER = 3
23+
WEBREPL_FRAME_TXT = 0x81
24+
WEBREPL_FRAME_BIN = 0x82
2325

2426

2527
def debugmsg(msg):
@@ -36,13 +38,12 @@ def __init__(self, s):
3638
self.s = s
3739
self.buf = b""
3840

39-
def write(self, data):
41+
def write(self, data, frame=WEBREPL_FRAME_BIN):
4042
l = len(data)
4143
if l < 126:
42-
# TODO: hardcoded "binary" type
43-
hdr = struct.pack(">BB", 0x82, l)
44+
hdr = struct.pack(">BB", frame, l)
4445
else:
45-
hdr = struct.pack(">BBH", 0x82, 126, l)
46+
hdr = struct.pack(">BBH", frame, 126, l)
4647
self.s.send(hdr)
4748
self.s.send(data)
4849

@@ -116,6 +117,71 @@ def get_ver(ws):
116117
return d
117118

118119

120+
def do_repl(ws):
121+
import termios, select
122+
123+
class ConsolePosix:
124+
def __init__(self):
125+
self.infd = sys.stdin.fileno()
126+
self.infile = sys.stdin.buffer.raw
127+
self.outfile = sys.stdout.buffer.raw
128+
self.orig_attr = termios.tcgetattr(self.infd)
129+
130+
def enter(self):
131+
# attr is: [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]
132+
attr = termios.tcgetattr(self.infd)
133+
attr[0] &= ~(
134+
termios.BRKINT | termios.ICRNL | termios.INPCK | termios.ISTRIP | termios.IXON
135+
)
136+
attr[1] = 0
137+
attr[2] = attr[2] & ~(termios.CSIZE | termios.PARENB) | termios.CS8
138+
attr[3] = 0
139+
attr[6][termios.VMIN] = 1
140+
attr[6][termios.VTIME] = 0
141+
termios.tcsetattr(self.infd, termios.TCSANOW, attr)
142+
143+
def exit(self):
144+
termios.tcsetattr(self.infd, termios.TCSANOW, self.orig_attr)
145+
146+
def readchar(self):
147+
res = select.select([self.infd], [], [], 0)
148+
if res[0]:
149+
return self.infile.read(1)
150+
else:
151+
return None
152+
153+
def write(self, buf):
154+
self.outfile.write(buf)
155+
156+
print("Use Ctrl-] to exit this shell")
157+
console = ConsolePosix()
158+
console.enter()
159+
try:
160+
while True:
161+
sel = select.select([console.infd, ws.s], [], [])
162+
c = console.readchar()
163+
if c:
164+
if c == b"\x1d": # ctrl-], exit
165+
break
166+
else:
167+
ws.write(c, WEBREPL_FRAME_TXT)
168+
if ws.s in sel[0]:
169+
c = ws.read(1, text_ok=True)
170+
while c is not None:
171+
# pass character through to the console
172+
oc = ord(c)
173+
if oc in (8, 9, 10, 13, 27) or oc >= 32:
174+
console.write(c)
175+
else:
176+
console.write(b"[%02x]" % ord(c))
177+
if ws.buf:
178+
c = ws.read(1)
179+
else:
180+
c = None
181+
finally:
182+
console.exit()
183+
184+
119185
def put_file(ws, local_file, remote_file):
120186
sz = os.stat(local_file)[6]
121187
dest_fname = (SANDBOX + remote_file).encode("utf-8")
@@ -165,11 +231,16 @@ def get_file(ws, local_file, remote_file):
165231

166232
def help(rc=0):
167233
exename = sys.argv[0].rsplit("/", 1)[-1]
168-
print("%s - Perform remote file operations using MicroPython WebREPL protocol" % exename)
234+
print(
235+
"%s - Access REPL, perform remote file operations via MicroPython WebREPL protocol"
236+
% exename
237+
)
169238
print("Arguments:")
239+
print(" [-p password] <host> - Access the remote REPL")
170240
print(" [-p password] <host>:<remote_file> <local_file> - Copy remote file to local file")
171241
print(" [-p password] <local_file> <host>:<remote_file> - Copy local file to remote file")
172242
print("Examples:")
243+
print(" %s 192.168.4.1" % exename)
173244
print(" %s script.py 192.168.4.1:/another_name.py" % exename)
174245
print(" %s script.py 192.168.4.1:/app/" % exename)
175246
print(" %s -p password 192.168.4.1:/app/script.py ." % exename)
@@ -191,26 +262,30 @@ def parse_remote(remote):
191262

192263

193264
def main():
194-
if len(sys.argv) not in (3, 5):
195-
help(1)
196-
197265
passwd = None
198266
for i in range(len(sys.argv)):
199267
if sys.argv[i] == '-p':
200268
sys.argv.pop(i)
201269
passwd = sys.argv.pop(i)
202270
break
203271

204-
if not passwd:
272+
if len(sys.argv) not in (2, 3):
273+
help(1)
274+
275+
if passwd is None:
205276
import getpass
206277
passwd = getpass.getpass()
207278

208-
if ":" in sys.argv[1] and ":" in sys.argv[2]:
209-
error("Operations on 2 remote files are not supported")
210-
if ":" not in sys.argv[1] and ":" not in sys.argv[2]:
211-
error("One remote file is required")
279+
if len(sys.argv) > 2:
280+
if ":" in sys.argv[1] and ":" in sys.argv[2]:
281+
error("Operations on 2 remote files are not supported")
282+
if ":" not in sys.argv[1] and ":" not in sys.argv[2]:
283+
error("One remote file is required")
212284

213-
if ":" in sys.argv[1]:
285+
if len(sys.argv) == 2:
286+
op = "repl"
287+
host, port, _ = parse_remote(sys.argv[1] + ":")
288+
elif ":" in sys.argv[1]:
214289
op = "get"
215290
host, port, src_file = parse_remote(sys.argv[1])
216291
dst_file = sys.argv[2]
@@ -227,7 +302,8 @@ def main():
227302

228303
if True:
229304
print("op:%s, host:%s, port:%d, passwd:%s." % (op, host, port, passwd))
230-
print(src_file, "->", dst_file)
305+
if op in ("get", "put"):
306+
print(src_file, "->", dst_file)
231307

232308
s = socket.socket()
233309

@@ -246,7 +322,9 @@ def main():
246322
# Set websocket to send data marked as "binary"
247323
ws.ioctl(9, 2)
248324

249-
if op == "get":
325+
if op == "repl":
326+
do_repl(ws)
327+
elif op == "get":
250328
get_file(ws, dst_file, src_file)
251329
elif op == "put":
252330
put_file(ws, src_file, dst_file)

0 commit comments

Comments
 (0)