Files
DD3-Lora-Bridge-Raspi-Debugger/src/webapp.py

94 lines
3.4 KiB
Python

import json
import queue
from typing import Any, Dict
from flask import Flask, Response, jsonify, render_template, request, stream_with_context
from waitress import serve
from app_state import AppState
from network_manager import NetworkManager
from serial_bridge import SerialBroadcaster
class WebPortal:
def __init__(
self,
state: AppState,
network_manager: NetworkManager,
broadcaster: SerialBroadcaster,
template_folder: str = "../templates",
static_folder: str = "../static",
) -> None:
self.state = state
self.network_manager = network_manager
self.broadcaster = broadcaster
self.app = Flask(__name__, template_folder=template_folder, static_folder=static_folder)
self._register_routes()
def _register_routes(self) -> None:
@self.app.route("/")
def index() -> str:
return render_template("index.html")
@self.app.route("/serial")
def serial_page() -> str:
return render_template("serial.html")
@self.app.route("/api/status", methods=["GET"])
def status() -> Response:
try:
self.network_manager.refresh_state()
return jsonify(self.state.snapshot())
except Exception as exc:
self.state.update_status("Status update failed", str(exc))
return jsonify(self.state.snapshot()), 503
@self.app.route("/api/scan", methods=["POST", "GET"])
def scan() -> Response:
try:
ssids = self.network_manager.scan_networks()
return jsonify({"ok": True, "ssids": ssids})
except Exception as exc:
self.state.update_status("Scan failed", str(exc))
return jsonify({"ok": False, "ssids": [], "message": str(exc)}), 503
@self.app.route("/api/connect", methods=["POST"])
def connect() -> Response:
payload: Dict[str, Any] = request.get_json(silent=True) or {}
ssid = (payload.get("ssid") or "").strip()
password = payload.get("password") or ""
ok, message = self.network_manager.connect_to_wifi(ssid, password)
if not ok:
self.state.update_status("Connect failed", message)
try:
self.network_manager.start_ap()
except Exception:
pass
return jsonify({"ok": False, "message": message}), 400
self.network_manager.refresh_state()
return jsonify({"ok": True, "message": message})
@self.app.route("/events/serial")
def serial_events() -> Response:
@stream_with_context
def generate():
q = self.broadcaster.subscribe()
try:
yield "retry: 2000\n\n"
while True:
try:
line = q.get(timeout=15)
data = json.dumps({"line": line})
yield f"data: {data}\n\n"
except queue.Empty:
yield ": keepalive\n\n"
finally:
self.broadcaster.unsubscribe(q)
return Response(generate(), mimetype="text/event-stream")
def run(self, host: str = "0.0.0.0", port: int = 80) -> None:
serve(self.app, host=host, port=port, threads=6)