Skip to content

Commit 1e09d9a

Browse files
committed
webrepl_cli.py: Add support for accessing the REPL.
With this commit, the webrepl_cli.py script can be used to access the REPL, for example: $ ./webrepl_cli.py -p x 192.168.4.1 op:repl, host:192.168.20.27, port:8266, passwd:x. Remote WebREPL version: (1, 17, 0) Use Ctrl-] to exit this shell 1/2 0.5 >>> Signed-off-by: Damien George <damien@micropython.org>
1 parent fff7b87 commit 1e09d9a

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
@@ -19,6 +19,8 @@
1919
WEBREPL_PUT_FILE = 1
2020
WEBREPL_GET_FILE = 2
2121
WEBREPL_GET_VER = 3
22+
WEBREPL_FRAME_TXT = 0x81
23+
WEBREPL_FRAME_BIN = 0x82
2224

2325

2426
def debugmsg(msg):
@@ -35,13 +37,12 @@ def __init__(self, s):
3537
self.s = s
3638
self.buf = b""
3739

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

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

117118

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

165231
def help(rc=0):
166232
exename = sys.argv[0].rsplit("/", 1)[-1]
167-
print("%s - Perform remote file operations using MicroPython WebREPL protocol" % exename)
233+
print(
234+
"%s - Access REPL, perform remote file operations via MicroPython WebREPL protocol"
235+
% exename
236+
)
168237
print("Arguments:")
238+
print(" [-p password] <host> - Access the remote REPL")
169239
print(" [-p password] <host>:<remote_file> <local_file> - Copy remote file to local file")
170240
print(" [-p password] <local_file> <host>:<remote_file> - Copy local file to remote file")
171241
print("Examples:")
242+
print(" %s 192.168.4.1" % exename)
172243
print(" %s script.py 192.168.4.1:/another_name.py" % exename)
173244
print(" %s script.py 192.168.4.1:/app/" % exename)
174245
print(" %s -p password 192.168.4.1:/app/script.py ." % exename)
@@ -212,26 +283,30 @@ def client_handshake(sock):
212283

213284

214285
def main():
215-
if len(sys.argv) not in (3, 5):
216-
help(1)
217-
218286
passwd = None
219287
for i in range(len(sys.argv)):
220288
if sys.argv[i] == '-p':
221289
sys.argv.pop(i)
222290
passwd = sys.argv.pop(i)
223291
break
224292

225-
if not passwd:
293+
if len(sys.argv) not in (2, 3):
294+
help(1)
295+
296+
if passwd is None:
226297
import getpass
227298
passwd = getpass.getpass()
228299

229-
if ":" in sys.argv[1] and ":" in sys.argv[2]:
230-
error("Operations on 2 remote files are not supported")
231-
if ":" not in sys.argv[1] and ":" not in sys.argv[2]:
232-
error("One remote file is required")
300+
if len(sys.argv) > 2:
301+
if ":" in sys.argv[1] and ":" in sys.argv[2]:
302+
error("Operations on 2 remote files are not supported")
303+
if ":" not in sys.argv[1] and ":" not in sys.argv[2]:
304+
error("One remote file is required")
233305

234-
if ":" in sys.argv[1]:
306+
if len(sys.argv) == 2:
307+
op = "repl"
308+
host, port, _ = parse_remote(sys.argv[1] + ":")
309+
elif ":" in sys.argv[1]:
235310
op = "get"
236311
host, port, src_file = parse_remote(sys.argv[1])
237312
dst_file = sys.argv[2]
@@ -248,7 +323,8 @@ def main():
248323

249324
if True:
250325
print("op:%s, host:%s, port:%d, passwd:%s." % (op, host, port, passwd))
251-
print(src_file, "->", dst_file)
326+
if op in ("get", "put"):
327+
print(src_file, "->", dst_file)
252328

253329
s = socket.socket()
254330

@@ -267,7 +343,9 @@ def main():
267343
# Set websocket to send data marked as "binary"
268344
ws.ioctl(9, 2)
269345

270-
if op == "get":
346+
if op == "repl":
347+
do_repl(ws)
348+
elif op == "get":
271349
get_file(ws, dst_file, src_file)
272350
elif op == "put":
273351
put_file(ws, src_file, dst_file)

0 commit comments

Comments
 (0)