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
+
+
+
+
+
+
+
+
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