-
Notifications
You must be signed in to change notification settings - Fork 299
Added support for executing remote commands through the command line. #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,13 +35,14 @@ def __init__(self, s): | |
self.s = s | ||
self.buf = b"" | ||
|
||
def write(self, data): | ||
def write(self, data, text=False): | ||
OpCode = 1 if text else 2 | ||
l = len(data) | ||
if l < 126: | ||
# TODO: hardcoded "binary" type | ||
hdr = struct.pack(">BB", 0x82, l) | ||
hdr = struct.pack(">BB", 0x80 + OpCode, l) | ||
else: | ||
hdr = struct.pack(">BBH", 0x82, 126, l) | ||
hdr = struct.pack(">BBH", 0x80 + OpCode, 126, l) | ||
self.s.send(hdr) | ||
self.s.send(data) | ||
|
||
|
@@ -89,9 +90,9 @@ def ioctl(self, req, val): | |
|
||
def login(ws, passwd): | ||
while True: | ||
c = ws.read(1, text_ok=True) | ||
c = ws.read(1, text_ok = True) | ||
if c == b":": | ||
assert ws.read(1, text_ok=True) == b" " | ||
assert ws.read(1, text_ok = True) == b" " | ||
break | ||
ws.write(passwd.encode("utf-8") + b"\r") | ||
|
||
|
@@ -163,17 +164,59 @@ def get_file(ws, local_file, remote_file): | |
|
||
|
||
def help(rc=0): | ||
exename = sys.argv[0].rsplit("/", 1)[-1] | ||
print("%s - Perform remote file operations using MicroPython WebREPL protocol" % exename) | ||
print("Arguments:") | ||
print(" [-p password] <host>:<remote_file> <local_file> - Copy remote file to local file") | ||
print(" [-p password] <local_file> <host>:<remote_file> - Copy local file to remote file") | ||
print("Examples:") | ||
print(" %s script.py 192.168.4.1:/another_name.py" % exename) | ||
print(" %s script.py 192.168.4.1:/app/" % exename) | ||
print(" %s -p password 192.168.4.1:/app/script.py ." % exename) | ||
exename = os.path.basename(sys.argv[0]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That maybe even nice (maybe not), but definitely can't be part of a commit with the description "Added support for executing remote commands through the command line.". One commit - one logical change. Each change should be grounded (i.e., there should be arguments why change is needed). |
||
|
||
print( | ||
'{exename} - Perform remote file operations using MicroPython WebREPL protocol' | ||
'\n' | ||
'Syntax:\n' | ||
' webrepl_cli.py [options] [src dst]\n' | ||
'\n' | ||
'Options:\n' | ||
' --passwd pw Set the login password\n' | ||
' --host host Set the host when using commands\n' | ||
' --cmd cmd Execute the command cmd in the server\n' | ||
' --verbose, -v Set verbose\n' | ||
|
||
'File copy:\n' | ||
|
||
' <host>:<remote_file> <local_file> - Copy remote file to local file\n' | ||
' <local_file> <host>:<remote_file> - Copy local file to remote file\n' | ||
'\n' | ||
'Examples:\n' | ||
' {exename} script.py 192.168.4.1:/another_name.py\n' | ||
' {exename} script.py 192.168.4.1:/app/\n' | ||
' {exename} 192.168.4.1:/app/script.py\n' | ||
|
||
'\n' | ||
.format(exename = exename)) | ||
|
||
sys.exit(rc) | ||
|
||
def query_response(ws, cmd, verbose=False): | ||
'''Single line query to single line response. May be used as rpc''' | ||
# ws.write((cmd+'\r').encode(), frame=WEBREPL_FRAME_TXT) | ||
ws.write((cmd+'\r').encode(), text=True) | ||
if verbose: | ||
print(cmd) | ||
|
||
if '\r' in cmd or '\n' in cmd: | ||
raise runtime_error('Currently no support for multi line queries!') | ||
|
||
Response = b'' | ||
while True: | ||
c = ws.read(1, text_ok = True) | ||
Response += c | ||
|
||
# Finish on prompt | ||
if Response[-5:]==b'\n>>> ': | ||
break | ||
|
||
# Cleanup the response and return it | ||
return (Response[Response.index(b'\n')+1:-6] # Get rid of input line | ||
.decode() # decode into python string | ||
.replace('\r','')) # Get rid of \r | ||
|
||
def error(msg): | ||
print(msg) | ||
sys.exit(1) | ||
|
@@ -210,70 +253,139 @@ def client_handshake(sock): | |
break | ||
# sys.stdout.write(l) | ||
|
||
|
||
# A class for communicating with the esp8266 | ||
class WebreplCLI: | ||
def __init__(self, | ||
host = '192.168.0.1', | ||
port = 8266, | ||
verbose = False, | ||
password = 'esp8266', | ||
dont_connect = False): | ||
self.host = host | ||
self.port = port | ||
self.verbose = verbose | ||
self.password = password | ||
if not dont_connect: | ||
self.connect() | ||
|
||
def connect(self): | ||
self.s = socket.socket() | ||
|
||
ai = socket.getaddrinfo(self.host, self.port) | ||
addr = ai[0][4] | ||
|
||
self.s.connect(addr) | ||
#s = s.makefile("rwb") | ||
client_handshake(self.s) | ||
|
||
self.ws = websocket(self.s) | ||
|
||
login(self.ws, self.password) | ||
ver = get_ver(self.ws) | ||
if self.verbose: | ||
print("Remote WebREPL version:", ver) | ||
|
||
# Set websocket to send data marked as "binary" | ||
self.ws.ioctl(9, 2) | ||
if self.verbose: | ||
print('Connected') | ||
|
||
def close(self): | ||
self.s.close() | ||
|
||
def command(self, cmd): | ||
return query_response(self.ws, cmd) | ||
|
||
def get_file(self, local_file, remote_file): | ||
return get_file(self.ws, local_file, remote_file) | ||
|
||
def put_file(self, local_file, remote_file): | ||
return put_file(self.ws, local_file, remote_file) | ||
|
||
def main(): | ||
if len(sys.argv) not in (3, 5): | ||
help(1) | ||
|
||
passwd = None | ||
for i in range(len(sys.argv)): | ||
if sys.argv[i] == '-p': | ||
sys.argv.pop(i) | ||
passwd = sys.argv.pop(i) | ||
break | ||
|
||
if not passwd: | ||
import getpass | ||
passwd = getpass.getpass() | ||
|
||
if ":" in sys.argv[1] and ":" in sys.argv[2]: | ||
error("Operations on 2 remote files are not supported") | ||
if ":" not in sys.argv[1] and ":" not in sys.argv[2]: | ||
error("One remote file is required") | ||
|
||
if ":" in sys.argv[1]: | ||
op = "get" | ||
host, port, src_file = parse_remote(sys.argv[1]) | ||
dst_file = sys.argv[2] | ||
if os.path.isdir(dst_file): | ||
basename = src_file.rsplit("/", 1)[-1] | ||
dst_file += "/" + basename | ||
cmd = None | ||
host = '192.168.0.1' | ||
port = 8266 | ||
verbose = False | ||
|
||
argp = 1 | ||
while argp < len(sys.argv) and sys.argv[argp][0] == '-': | ||
S_ = sys.argv[argp] | ||
argp+=1 | ||
|
||
if S_ == '--help': | ||
help(0) | ||
exit(0) | ||
|
||
if S_ == '--passwd' or S_=='-p': | ||
passwd = sys.argv[argp] | ||
argp+=1 | ||
continue | ||
|
||
if S_ == '--host' or S_=='-h': | ||
host = sys.argv[argp] | ||
argp+=1 | ||
continue | ||
|
||
if S_ == '--cmd': | ||
cmd = sys.argv[argp] | ||
argp+=1 | ||
continue | ||
|
||
error('Unknown option: ' + S_ + '!') | ||
|
||
if cmd is None: | ||
if len(sys.argv) < argp+2: | ||
print('Need at least two arguments for file copy!') | ||
exit(-1) | ||
|
||
if ":" in sys.argv[argp] and ":" in sys.argv[argp+1]: | ||
error("Operations on 2 remote files are not supported") | ||
if ":" not in sys.argv[argp] and ":" not in sys.argv[argp+1]: | ||
error("One remote file is required") | ||
|
||
if ":" in sys.argv[argp]: | ||
op = "get" | ||
host, port, src_file = parse_remote(sys.argv[argp]) | ||
dst_file = sys.argv[argp+1] | ||
if os.path.isdir(dst_file): | ||
basename = src_file.rsplit("/", 1)[-1] | ||
dst_file += "/" + basename | ||
else: | ||
op = "put" | ||
host, port, dst_file = parse_remote(sys.argv[argp+1]) | ||
src_file = sys.argv[argp] | ||
if dst_file[-1] == "/": | ||
basename = src_file.rsplit("/", 1)[-1] | ||
dst_file += basename | ||
|
||
if 1: | ||
print(op, host, port) | ||
print(src_file, "->", dst_file) | ||
else: | ||
op = "put" | ||
host, port, dst_file = parse_remote(sys.argv[2]) | ||
src_file = sys.argv[1] | ||
if dst_file[-1] == "/": | ||
basename = src_file.rsplit("/", 1)[-1] | ||
dst_file += basename | ||
|
||
if True: | ||
print("op:%s, host:%s, port:%d, passwd:%s." % (op, host, port, passwd)) | ||
print(src_file, "->", dst_file) | ||
|
||
s = socket.socket() | ||
|
||
ai = socket.getaddrinfo(host, port) | ||
addr = ai[0][4] | ||
|
||
s.connect(addr) | ||
#s = s.makefile("rwb") | ||
client_handshake(s) | ||
|
||
ws = websocket(s) | ||
|
||
login(ws, passwd) | ||
print("Remote WebREPL version:", get_ver(ws)) | ||
|
||
# Set websocket to send data marked as "binary" | ||
ws.ioctl(9, 2) | ||
|
||
if op == "get": | ||
get_file(ws, dst_file, src_file) | ||
if host is None: | ||
error('Must specify port for executing commands!') | ||
|
||
if passwd is None: | ||
import getpass | ||
passwd = getpass.getpass() | ||
|
||
wc = WebreplCLI(host = host, | ||
port = port, | ||
password = passwd, | ||
verbose = verbose) | ||
|
||
if cmd is not None: | ||
res = wc.command(cmd) | ||
if res: | ||
print(res) | ||
elif op == "get": | ||
wc.get_file(dst_file, src_file) | ||
elif op == "put": | ||
put_file(ws, src_file, dst_file) | ||
|
||
s.close() | ||
wc.put_file(src_file, dst_file) | ||
|
||
wc.close() | ||
|
||
if __name__ == "__main__": | ||
main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Codestyle: you violate both PEP8 and convention of this file (which of course uses standard_python_style).