Skip to content

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

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 185 additions & 73 deletions webrepl_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

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).

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)

Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -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])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewrite of help.

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)
Expand Down Expand Up @@ -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()