diff options
author | Jim Mussared <jim.mussared@gmail.com> | 2019-10-21 15:55:18 +1100 |
---|---|---|
committer | Damien George <damien.p.george@gmail.com> | 2019-12-20 12:59:13 +1100 |
commit | 7ce1e0b1dc466e48606164aad223c81c93a9cea2 (patch) | |
tree | 7ee755742cb06393c13b420610525b85b77f10aa /extmod/webrepl | |
parent | 7f235cbee924305e2d8a8aa86876770af66d7d82 (diff) |
extmod/webrepl: Move webrepl scripts to common place and use manifest.
Move webrepl support code from ports/esp8266/modules into extmod/webrepl
(to be alongside extmod/modwebrepl.c), and use frozen manifests to include
it in the build on esp8266 and esp32.
A small modification is made to webrepl.py to make it work on non-ESP
ports, i.e. don't call dupterm_notify if not available.
Diffstat (limited to 'extmod/webrepl')
-rw-r--r-- | extmod/webrepl/manifest.py | 1 | ||||
-rw-r--r-- | extmod/webrepl/webrepl.py | 80 | ||||
-rw-r--r-- | extmod/webrepl/webrepl_setup.py | 102 | ||||
-rw-r--r-- | extmod/webrepl/websocket_helper.py | 74 |
4 files changed, 257 insertions, 0 deletions
diff --git a/extmod/webrepl/manifest.py b/extmod/webrepl/manifest.py new file mode 100644 index 000000000..0f2b44005 --- /dev/null +++ b/extmod/webrepl/manifest.py @@ -0,0 +1 @@ +freeze('.', ('webrepl.py', 'webrepl_setup.py', 'websocket_helper.py',)) diff --git a/extmod/webrepl/webrepl.py b/extmod/webrepl/webrepl.py new file mode 100644 index 000000000..24c63299d --- /dev/null +++ b/extmod/webrepl/webrepl.py @@ -0,0 +1,80 @@ +# This module should be imported from REPL, not run from command line. +import socket +import uos +import network +import uwebsocket +import websocket_helper +import _webrepl + +listen_s = None +client_s = None + +def setup_conn(port, accept_handler): + global listen_s + listen_s = socket.socket() + listen_s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + ai = socket.getaddrinfo("0.0.0.0", port) + addr = ai[0][4] + + listen_s.bind(addr) + listen_s.listen(1) + if accept_handler: + listen_s.setsockopt(socket.SOL_SOCKET, 20, accept_handler) + for i in (network.AP_IF, network.STA_IF): + iface = network.WLAN(i) + if iface.active(): + print("WebREPL daemon started on ws://%s:%d" % (iface.ifconfig()[0], port)) + return listen_s + + +def accept_conn(listen_sock): + global client_s + cl, remote_addr = listen_sock.accept() + prev = uos.dupterm(None) + uos.dupterm(prev) + if prev: + print("\nConcurrent WebREPL connection from", remote_addr, "rejected") + cl.close() + return + print("\nWebREPL connection from:", remote_addr) + client_s = cl + websocket_helper.server_handshake(cl) + ws = uwebsocket.websocket(cl, True) + ws = _webrepl._webrepl(ws) + cl.setblocking(False) + # notify REPL on socket incoming data (ESP32/ESP8266-only) + if hasattr(uos, 'dupterm_notify'): + cl.setsockopt(socket.SOL_SOCKET, 20, uos.dupterm_notify) + uos.dupterm(ws) + + +def stop(): + global listen_s, client_s + uos.dupterm(None) + if client_s: + client_s.close() + if listen_s: + listen_s.close() + + +def start(port=8266, password=None): + stop() + if password is None: + try: + import webrepl_cfg + _webrepl.password(webrepl_cfg.PASS) + setup_conn(port, accept_conn) + print("Started webrepl in normal mode") + except: + print("WebREPL is not configured, run 'import webrepl_setup'") + else: + _webrepl.password(password) + setup_conn(port, accept_conn) + print("Started webrepl in manual override mode") + + +def start_foreground(port=8266): + stop() + s = setup_conn(port, None) + accept_conn(s) diff --git a/extmod/webrepl/webrepl_setup.py b/extmod/webrepl/webrepl_setup.py new file mode 100644 index 000000000..129313a21 --- /dev/null +++ b/extmod/webrepl/webrepl_setup.py @@ -0,0 +1,102 @@ +import sys +#import uos as os +import os +import machine + +RC = "./boot.py" +CONFIG = "./webrepl_cfg.py" + +def input_choice(prompt, choices): + while 1: + resp = input(prompt) + if resp in choices: + return resp + +def getpass(prompt): + return input(prompt) + +def input_pass(): + while 1: + passwd1 = getpass("New password (4-9 chars): ") + if len(passwd1) < 4 or len(passwd1) > 9: + print("Invalid password length") + continue + passwd2 = getpass("Confirm password: ") + if passwd1 == passwd2: + return passwd1 + print("Passwords do not match") + + +def exists(fname): + try: + with open(fname): + pass + return True + except OSError: + return False + + +def get_daemon_status(): + with open(RC) as f: + for l in f: + if "webrepl" in l: + if l.startswith("#"): + return False + return True + return None + + +def change_daemon(action): + LINES = ("import webrepl", "webrepl.start()") + with open(RC) as old_f, open(RC + ".tmp", "w") as new_f: + found = False + for l in old_f: + for patt in LINES: + if patt in l: + found = True + if action and l.startswith("#"): + l = l[1:] + elif not action and not l.startswith("#"): + l = "#" + l + new_f.write(l) + if not found: + new_f.write("import webrepl\nwebrepl.start()\n") + # FatFs rename() is not POSIX compliant, will raise OSError if + # dest file exists. + os.remove(RC) + os.rename(RC + ".tmp", RC) + + +def main(): + status = get_daemon_status() + + print("WebREPL daemon auto-start status:", "enabled" if status else "disabled") + print("\nWould you like to (E)nable or (D)isable it running on boot?") + print("(Empty line to quit)") + resp = input("> ").upper() + + if resp == "E": + if exists(CONFIG): + resp2 = input_choice("Would you like to change WebREPL password? (y/n) ", ("y", "n", "")) + else: + print("To enable WebREPL, you must set password for it") + resp2 = "y" + + if resp2 == "y": + passwd = input_pass() + with open(CONFIG, "w") as f: + f.write("PASS = %r\n" % passwd) + + + if resp not in ("D", "E") or (resp == "D" and not status) or (resp == "E" and status): + print("No further action required") + sys.exit() + + change_daemon(resp == "E") + + print("Changes will be activated after reboot") + resp = input_choice("Would you like to reboot now? (y/n) ", ("y", "n", "")) + if resp == "y": + machine.reset() + +main() diff --git a/extmod/webrepl/websocket_helper.py b/extmod/webrepl/websocket_helper.py new file mode 100644 index 000000000..9c06db502 --- /dev/null +++ b/extmod/webrepl/websocket_helper.py @@ -0,0 +1,74 @@ +import sys +try: + import ubinascii as binascii +except: + import binascii +try: + import uhashlib as hashlib +except: + import hashlib + +DEBUG = 0 + +def server_handshake(sock): + clr = sock.makefile("rwb", 0) + l = clr.readline() + #sys.stdout.write(repr(l)) + + webkey = None + + while 1: + l = clr.readline() + if not l: + raise OSError("EOF in headers") + if l == b"\r\n": + break + # sys.stdout.write(l) + h, v = [x.strip() for x in l.split(b":", 1)] + if DEBUG: + print((h, v)) + if h == b'Sec-WebSocket-Key': + webkey = v + + if not webkey: + raise OSError("Not a websocket request") + + if DEBUG: + print("Sec-WebSocket-Key:", webkey, len(webkey)) + + d = hashlib.sha1(webkey) + d.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11") + respkey = d.digest() + respkey = binascii.b2a_base64(respkey)[:-1] + if DEBUG: + print("respkey:", respkey) + + sock.send(b"""\ +HTTP/1.1 101 Switching Protocols\r +Upgrade: websocket\r +Connection: Upgrade\r +Sec-WebSocket-Accept: """) + sock.send(respkey) + sock.send("\r\n\r\n") + + +# Very simplified client handshake, works for MicroPython's +# websocket server implementation, but probably not for other +# servers. +def client_handshake(sock): + cl = sock.makefile("rwb", 0) + cl.write(b"""\ +GET / HTTP/1.1\r +Host: echo.websocket.org\r +Connection: Upgrade\r +Upgrade: websocket\r +Sec-WebSocket-Key: foo\r +\r +""") + l = cl.readline() +# print(l) + while 1: + l = cl.readline() + if l == b"\r\n": + break +# sys.stdout.write(l) |