feat(led-tool): browser settings editor with Web Serial
Add static editor, host_ports filtering, Flask /editor and REST APIs for ports and led-cli read/write; document standalone and embedded use. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
114
web.py
114
web.py
@@ -19,6 +19,7 @@ from flask import (
|
||||
redirect,
|
||||
url_for,
|
||||
flash,
|
||||
send_from_directory,
|
||||
)
|
||||
|
||||
|
||||
@@ -696,10 +697,119 @@ def handle_action():
|
||||
)
|
||||
|
||||
|
||||
def _static_dir() -> str:
|
||||
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "static")
|
||||
|
||||
|
||||
@app.route("/ports")
|
||||
def api_list_ports():
|
||||
from host_ports import filter_port_dicts
|
||||
from serial.tools import list_ports
|
||||
|
||||
ports = filter_port_dicts(
|
||||
[
|
||||
{"device": i.device, "description": i.description, "hwid": i.hwid}
|
||||
for i in list_ports.comports()
|
||||
]
|
||||
)
|
||||
cli = os.path.join(os.path.dirname(os.path.abspath(__file__)), "cli.py")
|
||||
return json.dumps({"ports": ports, "led_cli_exists": os.path.exists(cli)})
|
||||
|
||||
|
||||
@app.route("/settings", methods=["GET"])
|
||||
def api_read_settings():
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
port = (request.args.get("port") or "").strip()
|
||||
if not port:
|
||||
return json.dumps({"error": "port is required"}), 400
|
||||
cli = os.path.join(os.path.dirname(os.path.abspath(__file__)), "cli.py")
|
||||
result = subprocess.run(
|
||||
[sys.executable, cli, "--port", port, "--show"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=180,
|
||||
cwd=os.path.dirname(cli),
|
||||
)
|
||||
settings = None
|
||||
try:
|
||||
settings = json.loads((result.stdout or "").strip())
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
return json.dumps(
|
||||
{
|
||||
"ok": result.returncode == 0,
|
||||
"returncode": result.returncode,
|
||||
"stdout": result.stdout,
|
||||
"stderr": result.stderr,
|
||||
"settings": settings,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/settings", methods=["POST"])
|
||||
def api_write_settings():
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
data = request.get_json(silent=True) or {}
|
||||
port = str(data.get("port") or "").strip()
|
||||
if not port:
|
||||
return json.dumps({"error": "port is required"}), 400
|
||||
cli = os.path.join(os.path.dirname(os.path.abspath(__file__)), "cli.py")
|
||||
cmd = [sys.executable, cli, "--port", port]
|
||||
flag_map = (
|
||||
("name", "--name"),
|
||||
("led_pin", "--pin"),
|
||||
("num_leds", "--leds"),
|
||||
("brightness", "--brightness"),
|
||||
("transport", "--transport"),
|
||||
("ssid", "--ssid"),
|
||||
("password", "--wifi-password"),
|
||||
("wifi_channel", "--wifi-channel"),
|
||||
("default", "--default"),
|
||||
)
|
||||
for key, flag in flag_map:
|
||||
val = data.get(key)
|
||||
if val is None:
|
||||
continue
|
||||
s = str(val).strip()
|
||||
if s:
|
||||
cmd.extend([flag, s])
|
||||
cmd.append("--follow")
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=180,
|
||||
cwd=os.path.dirname(cli),
|
||||
)
|
||||
return json.dumps(
|
||||
{
|
||||
"ok": result.returncode == 0,
|
||||
"returncode": result.returncode,
|
||||
"stdout": result.stdout,
|
||||
"stderr": result.stderr,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/editor")
|
||||
def settings_editor():
|
||||
"""Static settings editor (Web Serial + host serial). Prefer this over the legacy form."""
|
||||
return send_from_directory(_static_dir(), "settings_editor.html")
|
||||
|
||||
|
||||
@app.route("/static/<path:filename>")
|
||||
def settings_static(filename):
|
||||
return send_from_directory(_static_dir(), filename)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
# Bind to all interfaces so you can reach it from your LAN:
|
||||
# python web_app.py
|
||||
# Then open: http://<pi-ip>:5000/
|
||||
# python web.py
|
||||
# Then open: http://<pi-ip>:5000/editor
|
||||
app.run(host="0.0.0.0", port=5000, debug=False)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user