diff --git a/orchestrateur/requirements.txt b/orchestrateur/requirements.txt index d067341..da48bfa 100644 --- a/orchestrateur/requirements.txt +++ b/orchestrateur/requirements.txt @@ -1,9 +1,12 @@ paho-mqtt>=1.6,<3 pyserial>=3.5,<4 +zeroconf>=0.131.0 +fastapi>=0.100.0,<1.0.0 +uvicorn>=0.20.0,<1.0.0 +python-multipart>=0.0.6,<1.0.0 # picamera2>=0.3.36,<4 # → Installed with apt install python3-picamera2 # OpenCV # sudo apt install -y python3-opencv # sudo apt install -y opencv-data -zeroconf>=0.131.0 # sudo apt install pigpio python3-pigpio # sudo systemctl enable pigpiod --now \ No newline at end of file diff --git a/orchestrateur/web_server.py b/orchestrateur/web_server.py new file mode 100644 index 0000000..24c88e2 --- /dev/null +++ b/orchestrateur/web_server.py @@ -0,0 +1,159 @@ +import asyncio +from fastapi import FastAPI, Form, HTTPException, Request +from fastapi.responses import HTMLResponse, JSONResponse +import uvicorn + +HTML_TEMPLATE = """ + + + + + + SmartWave Technician Portal + + + +
+
+
+

SmartWave Orchestrator System Logs

+ +
+
Loading logs...
+
+ +
+

Telemetry Control

+
+ + +
+ +
+
+
+ + + + +""" + +def create_app(get_logs_cb, telemetry_cb, is_tech_mode_cb) -> FastAPI: + """Factory creating FastAPI app with injected handler callbacks.""" + app = FastAPI(title="Technician Portal") + + # Attach callbacks to app.state + app.state.get_systemd_logs = get_logs_cb + app.state.handle_telemetry_request = telemetry_cb + app.state.is_tech_mode = is_tech_mode_cb + + # Middleware to block traffic when TECHNICIAN_MODE is False + @app.middleware("http") + async def enforce_technician_mode(request: Request, call_next): + if not request.app.state.is_tech_mode(): + return JSONResponse( + status_code=503, + content={"detail": "Technician mode is disabled."} + ) + return await call_next(request) + + @app.get("/", response_class=HTMLResponse) + async def serve_dashboard(): + return HTML_TEMPLATE + + @app.get("/api/logs") + async def api_get_logs(request: Request): + try: + logs = await request.app.state.get_systemd_logs(lines=200, service_name="smartwave") + return JSONResponse(content=logs) + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + @app.post("/api/telemetry") + async def api_trigger_telemetry(request: Request, endpoint: str = Form(...)): + try: + await request.app.state.handle_telemetry_request(endpoint=endpoint, do_timeout=True) + return {"status": "ok", "message": f"Telemetry request sent to {endpoint}"} + except Exception as e: + raise HTTPException(status_code=500, detail=f"Telemetry trigger failed: {str(e)}") + + return app + +async def start_technician_web_server(get_logs_cb, telemetry_cb, is_tech_mode_cb, host="192.168.50.1", port=8080): + """Asynchronous server launcher meant to run within main.py's event loop.""" + app = create_app(get_logs_cb, telemetry_cb, is_tech_mode_cb) + config = uvicorn.Config(app, host=host, port=port, log_level="info") + server = uvicorn.Server(config) + await server.serve() \ No newline at end of file diff --git a/shared/config.py b/shared/config.py index c202afe..82278a0 100644 --- a/shared/config.py +++ b/shared/config.py @@ -3,6 +3,10 @@ DEBUG=True DEBUG_DANGEROUS_AREA=False DEBUG_TEMPERATURE_ALERT=False +# Technitian Web Server +TECHNICIAN_WEB_SERVER_HOST = "192.168.50.1" +TECHNICIAN_WEB_SERVER_PORT = 8080 + # LoRa LORA_HEARTBEAT_INTERVAL = 30