mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Add WebServer settings app
This commit is contained in:
@@ -19,6 +19,7 @@ from .build_info import BuildInfo
|
||||
|
||||
# Battery manager (imported early for UI dependencies)
|
||||
from .battery_manager import BatteryManager
|
||||
from .webserver.webserver import WebServer
|
||||
|
||||
# Common activities
|
||||
from .app.activities.chooser import ChooserActivity
|
||||
@@ -67,7 +68,7 @@ __all__ = [
|
||||
"Activity",
|
||||
"SharedPreferences",
|
||||
"ConnectivityManager", "DownloadManager", "WifiService", "AudioManager", "Intent",
|
||||
"ActivityNavigator", "AppManager", "TaskManager", "CameraManager", "BatteryManager",
|
||||
"ActivityNavigator", "AppManager", "TaskManager", "CameraManager", "BatteryManager", "WebServer",
|
||||
# Device and build info
|
||||
"DeviceInfo", "BuildInfo",
|
||||
# Common activities
|
||||
|
||||
@@ -229,11 +229,10 @@ async def asyncio_repl():
|
||||
TaskManager.create_task(asyncio_repl()) # only gets started after TaskManager.start()
|
||||
|
||||
try:
|
||||
import webrepl
|
||||
from mpos.webserver import accept_handler as webrepl_accept_handler
|
||||
webrepl.start(port=7890, password="MPOSweb26", accept_handler=webrepl_accept_handler) # password is max 9 characters
|
||||
from mpos import WebServer
|
||||
WebServer.auto_start()
|
||||
except Exception as e:
|
||||
print(f"Could not start webrepl - this is normal on desktop systems: {e}")
|
||||
print(f"Could not start webserver - this is normal on desktop systems: {e}")
|
||||
|
||||
async def ota_rollback_cancel():
|
||||
try:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Web server helpers for MicroPythonOS."""
|
||||
|
||||
from .webrepl_http import accept_handler
|
||||
from .webserver import WebServer
|
||||
|
||||
__all__ = ["accept_handler"]
|
||||
__all__ = ["accept_handler", "WebServer"]
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# This module should be imported from REPL, not run from command line.
|
||||
import binascii
|
||||
import hashlib
|
||||
from micropython import const
|
||||
try:
|
||||
import network
|
||||
except ImportError:
|
||||
network = None
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import websocket
|
||||
import _webrepl
|
||||
|
||||
listen_s = None
|
||||
client_s = None
|
||||
|
||||
DEBUG = 0
|
||||
|
||||
_DEFAULT_STATIC_HOST = const("https://micropython.org/webrepl/")
|
||||
static_host = _DEFAULT_STATIC_HOST
|
||||
|
||||
|
||||
def server_handshake(cl):
|
||||
req = cl.makefile("rwb", 0)
|
||||
# Skip HTTP GET line.
|
||||
l = req.readline()
|
||||
if DEBUG:
|
||||
sys.stdout.write(repr(l))
|
||||
|
||||
webkey = None
|
||||
upgrade = False
|
||||
websocket = False
|
||||
|
||||
while True:
|
||||
l = req.readline()
|
||||
if not l:
|
||||
# EOF in headers.
|
||||
return False
|
||||
if l == b"\r\n":
|
||||
break
|
||||
if DEBUG:
|
||||
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
|
||||
elif h == b"Connection" and b"Upgrade" in v:
|
||||
upgrade = True
|
||||
elif h == b"Upgrade" and v == b"websocket":
|
||||
websocket = True
|
||||
|
||||
if not (upgrade and websocket and webkey):
|
||||
return False
|
||||
|
||||
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)
|
||||
|
||||
cl.send(
|
||||
b"""\
|
||||
HTTP/1.1 101 Switching Protocols\r
|
||||
Upgrade: websocket\r
|
||||
Connection: Upgrade\r
|
||||
Sec-WebSocket-Accept: """
|
||||
)
|
||||
cl.send(respkey)
|
||||
cl.send("\r\n\r\n")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def send_html(cl):
|
||||
cl.send(
|
||||
b"""\
|
||||
HTTP/1.0 200 OK\r
|
||||
\r
|
||||
<base href=\""""
|
||||
)
|
||||
cl.send(static_host)
|
||||
cl.send(
|
||||
b"""\"></base>\r
|
||||
<script src="webrepl_content.js"></script>\r
|
||||
"""
|
||||
)
|
||||
cl.close()
|
||||
|
||||
|
||||
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)
|
||||
if network:
|
||||
for i in (network.WLAN.IF_AP, network.WLAN.IF_STA):
|
||||
iface = network.WLAN(i)
|
||||
if iface.active():
|
||||
print("WebREPL server started on http://%s:%d/" % (iface.ifconfig()[0], port))
|
||||
return listen_s
|
||||
|
||||
|
||||
def accept_conn(listen_sock):
|
||||
global client_s
|
||||
cl, remote_addr = listen_sock.accept()
|
||||
|
||||
if not server_handshake(cl):
|
||||
send_html(cl)
|
||||
return False
|
||||
|
||||
prev = os.dupterm(None)
|
||||
os.dupterm(prev)
|
||||
if prev:
|
||||
print("\nConcurrent WebREPL connection from", remote_addr, "rejected")
|
||||
cl.close()
|
||||
return False
|
||||
print("\nWebREPL connection from:", remote_addr)
|
||||
client_s = cl
|
||||
|
||||
ws = websocket.websocket(cl, True)
|
||||
ws = _webrepl._webrepl(ws)
|
||||
cl.setblocking(False)
|
||||
# notify REPL on socket incoming data (ESP32/ESP8266-only)
|
||||
if hasattr(os, "dupterm_notify"):
|
||||
cl.setsockopt(socket.SOL_SOCKET, 20, os.dupterm_notify)
|
||||
os.dupterm(ws)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def stop():
|
||||
global listen_s, client_s
|
||||
os.dupterm(None)
|
||||
if client_s:
|
||||
client_s.close()
|
||||
if listen_s:
|
||||
listen_s.close()
|
||||
|
||||
|
||||
def start(port=8266, password=None, accept_handler=accept_conn):
|
||||
global static_host
|
||||
stop()
|
||||
webrepl_pass = password
|
||||
if webrepl_pass is None:
|
||||
try:
|
||||
import webrepl_cfg
|
||||
|
||||
webrepl_pass = webrepl_cfg.PASS
|
||||
if hasattr(webrepl_cfg, "BASE"):
|
||||
static_host = webrepl_cfg.BASE
|
||||
except:
|
||||
print("WebREPL is not configured, run 'import webrepl_setup'")
|
||||
|
||||
_webrepl.password(webrepl_pass)
|
||||
s = setup_conn(port, accept_handler)
|
||||
|
||||
if accept_handler is None:
|
||||
print("Starting webrepl in foreground mode")
|
||||
# Run accept_conn to serve HTML until we get a websocket connection.
|
||||
while not accept_conn(s):
|
||||
pass
|
||||
elif password is None:
|
||||
print("Started webrepl in normal mode")
|
||||
else:
|
||||
print("Started webrepl in manual override mode")
|
||||
|
||||
|
||||
def start_foreground(port=8266, password=None):
|
||||
start(port, password, None)
|
||||
@@ -3,7 +3,7 @@ import socket
|
||||
import uio
|
||||
|
||||
import _webrepl
|
||||
import webrepl
|
||||
from . import webrepl
|
||||
import websocket
|
||||
|
||||
WEBREPL_HTML_PATH = "builtin/html/webrepl_inlined_minified.html"
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
"""WebServer control for MicroPythonOS."""
|
||||
|
||||
from ..config import SharedPreferences
|
||||
from .webrepl_http import accept_handler
|
||||
|
||||
|
||||
class WebServer:
|
||||
PREFS_NAMESPACE = "com.micropythonos.webserver"
|
||||
DEFAULTS = {
|
||||
"autostart": "False",
|
||||
"port": "7890",
|
||||
"password": "MPOSweb26",
|
||||
}
|
||||
|
||||
_started = False
|
||||
_port = None
|
||||
_password = None
|
||||
_autostart = None
|
||||
_last_error = None
|
||||
|
||||
@classmethod
|
||||
def _prefs(cls):
|
||||
return SharedPreferences(cls.PREFS_NAMESPACE, defaults=cls.DEFAULTS)
|
||||
|
||||
@classmethod
|
||||
def _parse_bool(cls, value):
|
||||
return str(value).lower() in ("true", "1", "yes", "on")
|
||||
|
||||
@classmethod
|
||||
def _parse_port(cls, value):
|
||||
try:
|
||||
return int(value)
|
||||
except Exception:
|
||||
return int(cls.DEFAULTS["port"])
|
||||
|
||||
@classmethod
|
||||
def _sanitize_password(cls, value):
|
||||
if not value:
|
||||
value = cls.DEFAULTS["password"]
|
||||
if len(value) > 9:
|
||||
value = value[:9]
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def load_settings(cls):
|
||||
prefs = cls._prefs()
|
||||
cls._autostart = cls._parse_bool(prefs.get_string("autostart", cls.DEFAULTS["autostart"]))
|
||||
cls._port = cls._parse_port(prefs.get_string("port", cls.DEFAULTS["port"]))
|
||||
cls._password = cls._sanitize_password(prefs.get_string("password", cls.DEFAULTS["password"]))
|
||||
|
||||
@classmethod
|
||||
def status(cls):
|
||||
cls.load_settings()
|
||||
return {
|
||||
"state": "started" if cls._started else "stopped",
|
||||
"started": cls._started,
|
||||
"port": cls._port,
|
||||
"password": cls._password,
|
||||
"autostart": cls._autostart,
|
||||
"last_error": cls._last_error,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def is_started(cls):
|
||||
return cls._started
|
||||
|
||||
@classmethod
|
||||
def start(cls):
|
||||
cls.load_settings()
|
||||
try:
|
||||
from . import webrepl
|
||||
|
||||
webrepl.start(port=cls._port, password=cls._password, accept_handler=accept_handler)
|
||||
cls._started = True
|
||||
cls._last_error = None
|
||||
print(f"WebServer started on port {cls._port}")
|
||||
return True
|
||||
except Exception as exc:
|
||||
cls._last_error = exc
|
||||
cls._started = False
|
||||
print(f"WebServer start failed: {exc}")
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def stop(cls):
|
||||
try:
|
||||
from . import webrepl
|
||||
|
||||
if hasattr(webrepl, "stop"):
|
||||
webrepl.stop()
|
||||
cls._started = False
|
||||
cls._last_error = None
|
||||
print("WebServer stopped")
|
||||
return True
|
||||
except Exception as exc:
|
||||
cls._last_error = exc
|
||||
print(f"WebServer stop failed: {exc}")
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def apply_settings(cls, restart_if_running=True):
|
||||
was_running = cls._started
|
||||
cls.load_settings()
|
||||
if was_running and restart_if_running:
|
||||
cls.stop()
|
||||
cls.start()
|
||||
return cls.status()
|
||||
|
||||
@classmethod
|
||||
def auto_start(cls):
|
||||
cls.load_settings()
|
||||
if cls._autostart:
|
||||
return cls.start()
|
||||
print("WebServer autostart disabled")
|
||||
return False
|
||||
Reference in New Issue
Block a user