Skip to content

Commit 191a26d

Browse files
author
Paul Sokolovsky
committed
webrepl_cli.py: Command line WebREPL file transfer tool.
Also doubles as a module to build more advanced tools on.
1 parent a43045f commit 191a26d

File tree

1 file changed

+177
-0
lines changed

1 file changed

+177
-0
lines changed

webrepl_cli.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#!/usr/bin/env python
2+
import sys
3+
import os
4+
import struct
5+
try:
6+
import usocket as socket
7+
except ImportError:
8+
import socket
9+
import websocket_helper
10+
11+
# Define to 1 to use builtin "websocket" module of MicroPython
12+
USE_BUILTIN_WEBSOCKET = 0
13+
# Treat this remote directory as a root for file transfers
14+
SANDBOX = "/tmp/webrepl/"
15+
16+
17+
WEBREPL_FILE = "3sBBBHQH64s"
18+
19+
if USE_BUILTIN_WEBSOCKET:
20+
from websocket import websocket
21+
else:
22+
class websocket:
23+
24+
def __init__(self, s):
25+
self.s = s
26+
self.buf = b""
27+
28+
def write(self, data):
29+
l = len(data)
30+
if l < 126:
31+
# TODO: hardcoded "binary" type
32+
hdr = struct.pack(">BB", 0x82, l)
33+
else:
34+
hdr = struct.pack(">BBH", 0x82, 126, l)
35+
self.s.send(hdr)
36+
self.s.send(data)
37+
38+
def read(self, sz):
39+
if not self.buf:
40+
hdr = self.s.recv(2)
41+
assert len(hdr) == 2
42+
fl, sz = struct.unpack(">BB", hdr)
43+
assert fl == 0x82
44+
if sz == 126:
45+
hdr = self.s.recv(2)
46+
assert len(hdr) == 2
47+
(sz,) = struct.unpack(">H", hdr)
48+
data = self.s.recv(sz)
49+
assert len(data) == sz
50+
self.buf = data
51+
52+
d = self.buf[:sz]
53+
self.buf = self.buf[sz:]
54+
return d
55+
56+
def ioctl(self, req, val):
57+
assert req == 9 and val == 2
58+
59+
60+
def read_resp(ws):
61+
data = ws.read(4)
62+
sig, code = struct.unpack("2sH", data)
63+
assert sig == b"WB"
64+
return code
65+
66+
def put_file(ws, local_file, remote_file):
67+
sz = os.stat(local_file)[6]
68+
dest_fname = bytes(SANDBOX + remote_file, "utf-8")
69+
rec = struct.pack(WEBREPL_FILE, b"WRA", 1, 0, 0, sz, 0, len(dest_fname), dest_fname)
70+
print(rec, len(rec))
71+
ws.write(rec[:10])
72+
ws.write(rec[10:])
73+
assert read_resp(ws) == 0
74+
with open(local_file, "rb") as f:
75+
while True:
76+
buf = f.read(256)
77+
if not buf:
78+
break
79+
ws.write(buf)
80+
assert read_resp(ws) == 0
81+
82+
def get_file(ws, local_file, remote_file):
83+
src_fname = bytes(SANDBOX + remote_file, "utf-8")
84+
rec = struct.pack(WEBREPL_FILE, b"WRA", 2, 0, 0, 0, 0, len(src_fname), src_fname)
85+
print(rec, len(rec))
86+
ws.write(rec)
87+
assert read_resp(ws) == 0
88+
with open(local_file, "wb") as f:
89+
while True:
90+
(sz,) = struct.unpack("<H", ws.read(2))
91+
if sz == 0:
92+
break
93+
while sz:
94+
buf = ws.read(sz)
95+
if not buf:
96+
raise OSError()
97+
f.write(buf)
98+
sz -= len(buf)
99+
assert read_resp(ws) == 0
100+
101+
102+
def help(rc=0):
103+
exename = sys.argv[0].rsplit("/", 1)[1]
104+
print("%s - Perform remote file operations using MicroPython WebREPL protocol" % exename)
105+
print("Arguments:")
106+
print(" <host>:<remote_file> <local_file> - Copy remote file to local file")
107+
print(" <local_file> <host>:<remote_file> - Copy local file to remote file")
108+
print("Examples:")
109+
print(" %s script.py 192.168.4.1:/another_name.py" % exename)
110+
print(" %s script.py 192.168.4.1:/app/" % exename)
111+
print(" %s 192.168.4.1:/app/script.py ." % exename)
112+
sys.exit(rc)
113+
114+
def error(msg):
115+
print(msg)
116+
sys.exit(1)
117+
118+
def parse_remote(remote):
119+
host, fname = remote.rsplit(":", 1)
120+
port = 8266
121+
if ":" in host:
122+
host, port = remote.split(":")
123+
port = int(port)
124+
return (host, port, fname)
125+
126+
def main():
127+
128+
if len(sys.argv) != 3:
129+
help(1)
130+
131+
if ":" in sys.argv[1] and ":" in sys.argv[2]:
132+
error("Operations on 2 remote files are not supported")
133+
if ":" not in sys.argv[1] and ":" not in sys.argv[2]:
134+
error("One remote file is required")
135+
136+
if ":" in sys.argv[1]:
137+
op = "get"
138+
host, port, src_file = parse_remote(sys.argv[1])
139+
dst_file = sys.argv[2]
140+
if os.path.isdir(dst_file):
141+
basename = src_file.rsplit("/", 1)[-1]
142+
dst_file += "/" + basename
143+
else:
144+
op = "put"
145+
host, port, dst_file = parse_remote(sys.argv[2])
146+
src_file = sys.argv[1]
147+
if dst_file[-1] == "/":
148+
basename = src_file.rsplit("/", 1)[-1]
149+
dst_file += basename
150+
151+
if 1:
152+
print(op, host, port)
153+
print(src_file, "->", dst_file)
154+
155+
s = socket.socket()
156+
157+
ai = socket.getaddrinfo(host, port)
158+
addr = ai[0][4]
159+
160+
s.connect(addr)
161+
#s = s.makefile("rwb")
162+
websocket_helper.client_handshake(s)
163+
164+
ws = websocket(s)
165+
# Set websocket to send data marked as "binary"
166+
ws.ioctl(9, 2)
167+
168+
if op == "get":
169+
get_file(ws, dst_file, src_file)
170+
elif op == "put":
171+
put_file(ws, src_file, dst_file)
172+
173+
s.close()
174+
175+
176+
if __name__ == "__main__":
177+
main()

0 commit comments

Comments
 (0)