From 1545a013509aefa57c1aa7583fd3e1511ddfdc42 Mon Sep 17 00:00:00 2001 From: Etienne Chassaing <60154720+cetiennec@users.noreply.github.com> Date: Tue, 15 Jul 2025 13:51:05 +0200 Subject: [PATCH] Adds feed valve button + reformatting --- MockCAN.py | 2 +- main.py | 59 ++++++---- templates/control.html | 238 ++++++++++++++++++++--------------------- 3 files changed, 154 insertions(+), 145 deletions(-) diff --git a/MockCAN.py b/MockCAN.py index d432340..9ab93cb 100644 --- a/MockCAN.py +++ b/MockCAN.py @@ -28,7 +28,7 @@ class CANBackend: # Placeholder for sending mode command PUs_states[pu_number-1] = {"PU_MODE": state, "ploop_setpoint":ploop_setpoint} - def get_latest_data(self): + def get_latest_data(self, pu_number=1, data={}): # Simulate getting the latest data with random values return { "FM2": round(1000 * np.random.random(), 1), diff --git a/main.py b/main.py index c9efe81..8e5b2a0 100644 --- a/main.py +++ b/main.py @@ -5,19 +5,22 @@ import logging import os from fastapi import Request, APIRouter import platform -from fastapi.templating import Jinja2Templates # pip install fastapi uvicorn jinja2 python-multipart passlib +from fastapi.templating import ( + Jinja2Templates, +) # pip install fastapi uvicorn jinja2 python-multipart passlib from starlette.middleware.sessions import SessionMiddleware from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.status import HTTP_302_FOUND import json from pathlib import Path +from typing import Optional +from fastapi import Query - -if platform.system() in ['Darwin']: # macOS or Windows +if platform.system() in ["Darwin"]: # macOS or Windows from MockCAN import CANBackend -else : +else: from classCAN import CANBackend # Your real backend - + app = FastAPI() app.add_middleware(SessionMiddleware, secret_key="your_super_secret_key") router = APIRouter() @@ -53,12 +56,15 @@ PASSWORD = CREDENTIALS["password"] # ======== LOGIN & SESSION HANDLING ======== + def require_login(request: Request): user = request.session.get("user") if user != USERNAME: # raise 302 to trigger redirection manually (FastAPI doesn't support redirects from Depends directly) raise StarletteHTTPException(status_code=302, detail="Redirect to login") return user + + @app.get("/", response_class=HTMLResponse) def login_form(request: Request): return templates.TemplateResponse("login.html", {"request": request}) @@ -69,7 +75,9 @@ def login(request: Request, username: str = Form(...), password: str = Form(...) if username == USERNAME and password == PASSWORD: request.session["user"] = username return RedirectResponse("/control", status_code=HTTP_302_FOUND) - return templates.TemplateResponse("login.html", {"request": request, "error": "Invalid credentials.json"}) + return templates.TemplateResponse( + "login.html", {"request": request, "error": "Invalid credentials.json"} + ) @app.get("/logout") @@ -80,19 +88,23 @@ def logout(request: Request): # ======== PROTECTED INTERFACE ======== + @app.get("/control", response_class=HTMLResponse) def control_page(request: Request): if request.session.get("user") != USERNAME: return RedirectResponse("/", status_code=HTTP_302_FOUND) return templates.TemplateResponse("control.html", {"request": request}) + @app.get("/monitor-page", response_class=HTMLResponse) def monitor_page(request: Request): with open("static/monitor.html") as f: return HTMLResponse(f.read()) + # ======== CAN + BACKEND ROUTES ======== + @app.post("/connect_toggle") def connect_toggle(): logging.info("Toggling CAN connection...") @@ -109,8 +121,13 @@ def connect_toggle(): @app.post("/command/{state}/pu/{pu_number}") def send_command(state: str, pu_number: int, ploop_setpoint: float = Query(...)): VALID_STATES = { - "IDLE", "PRE-PRODUCTION", "PRODUCTION", "FIRST_START", - "THERMALLOOPCLEANING", "DISINFECTION", "SLEEP" + "IDLE", + "PRE-PRODUCTION", + "PRODUCTION", + "FIRST_START", + "THERMALLOOPCLEANING", + "DISINFECTION", + "SLEEP", } state = state.upper() @@ -128,7 +145,7 @@ def send_command(state: str, pu_number: int, ploop_setpoint: float = Query(...)) "command": state, "pu": pu_number, "ploop_setpoint": ploop_setpoint, - "current_state": current_state + "current_state": current_state, } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @@ -145,9 +162,6 @@ def get_pu_status(): return JSONResponse(content=states) -from typing import Optional -from fastapi import Query - @app.get("/monitor") def get_monitor_data(pu_number: Optional[int] = Query(None)): def format_data(data): @@ -156,30 +170,22 @@ def get_monitor_data(pu_number: Optional[int] = Query(None)): "Qdilute": data.get("FM2", 0.0), "Qdrain": data.get("FM3", 0.0), "Qrecirc": data.get("FM4", 0.0), - "Pro": data.get("PS1", 0.0), "Pdilute": data.get("PS2", 0.0), "Prentate": data.get("PS3", 0.0), - "Conductivity": data.get("Cond", 0.0), - "MV02": data.get("MV02", 0.0), "MV02_sp": data.get("MV02_sp", 0.0), - "MV03": data.get("MV03", 0.0), "MV03_sp": data.get("MV03_sp", 0.0), - "MV05": data.get("MV05", 0.0), "MV05_sp": data.get("MV05_sp", 0.0), - "MV06": data.get("MV06", 0.0), "MV06_sp": data.get("MV06_sp", 0.0), - "MV07": data.get("MV07", 0.0), "MV07_sp": data.get("MV07_sp", 0.0), - "MV08": data.get("MV08", 0.0), - "MV08_sp": data.get("MV08_sp", 0.0) + "MV08_sp": data.get("MV08_sp", 0.0), } if pu_number is not None: @@ -193,7 +199,7 @@ def get_monitor_data(pu_number: Optional[int] = Query(None)): print(f"[MONITOR] PU{pu}: {data}") all_data[f"PU_{pu}"] = format_data(data) return all_data - + @app.get("/can_status") def can_status(): @@ -201,10 +207,19 @@ def can_status(): return {"connected": can_backend.connected} +@app.post("/command/feed_valve") +def can_status(MV01_opening: int = Query(...)): + """Control MV01 feed valve""" + # can_backend.send_command(MV01_opening) # TODO: TODO + logging.info(f"Feed valve opening to {MV01_opening}") + return {"status": "ok"} + + app.include_router(router) if __name__ == "__main__": import uvicorn + uvicorn.run( "main:app", host="127.0.0.1", diff --git a/templates/control.html b/templates/control.html index b04eb61..3f05f7d 100644 --- a/templates/control.html +++ b/templates/control.html @@ -10,14 +10,13 @@ font-family: Arial, sans-serif; margin: 0; padding: 0; - overflow-x: hidden; /* ✅ prevent horizontal scroll */ + overflow-x: hidden; height: 100vh; background-color: #121212; color: white; display: flex; flex-direction: column; } - .header { background-color: #1e1e1e; padding: 10px 20px; @@ -45,7 +44,6 @@ overflow-x: hidden; box-sizing: border-box; } - .left-panel, .right-panel { flex: 1; padding: 20px; @@ -77,8 +75,8 @@ transition: background-color 0.3s; flex: 1; } - .mode-block button:hover { - background-color: #3367d6; + .mode-block button:hover { + background-color: #3367d6; } .mode-block button.active { background-color: #00C851; } .mode-block button.in-progress { background-color: #ffcc00; color: #000; } @@ -88,17 +86,14 @@ background-color: yellow !important; color: black !important; } - .ready { background-color: orange !important; color: black !important; } - .production { background-color: green !important; color: white !important; } - .pu-status { margin-top: 20px; } .pu-item { background-color: #333; @@ -148,7 +143,6 @@ width: 100%; overflow: hidden; } - .slider-values span#currentValue { font-weight: bold; color: #00bfff; @@ -178,6 +172,23 @@ font-weight: bold; } .monitor-link:hover { background-color: #0056b3; } + .feed-valve-buttons { + display: flex; + gap: 10px; + margin-top: 10px; + } + .feed-valve-buttons button { + flex: 1; + padding: 10px; + border: none; + border-radius: 5px; + cursor: pointer; + background-color: #444; + color: white; + } + .feed-valve-buttons button.active { + background-color: #00C851; + } @@ -230,6 +241,11 @@
PU 2: Offline
PU 3: Offline
+
+ + + +
@@ -266,48 +282,39 @@
- - - +setInterval(fetchMonitorData, 1000); +fetchMonitorData(); +