diff --git a/main.py b/main.py index f5772f6..abe2edc 100644 --- a/main.py +++ b/main.py @@ -50,11 +50,11 @@ latest_data: Dict[str, Any] = { "PU_1": None, "PU_2": None, "PU_3": None, - "DS" : None, + "DS": None, "PatientSkid": {"QSkid": 0.0}, } -active_PUs : list[int] = [] +active_PUs: list[int] = [] DEFAULT_FEED_VALVE = 0.0 @@ -78,7 +78,8 @@ def format_PU_data(data): "Qdilute": np.round(data.get("FM1", 0.0), 1), "Qdrain": np.round(data.get("FM4", 0.0), 1), "Qrecirc": np.round(data.get("FM3", 0.0), 1), - "QdrainEDI": np.round(data.get("FM2", 0.0), 1)- np.round(data.get("FM1", 0.0), 1), + "QdrainEDI": np.round(data.get("FM2", 0.0), 1) + - np.round(data.get("FM1", 0.0), 1), "Pro": np.round(data.get("PS2", 0.0), 2), "Pdilute": np.round(data.get("PS3", 0.0), 2), "Pretentate": np.round(data.get("PS1", 0.0), 2), @@ -109,11 +110,9 @@ def format_DS_data(data): "TankLevel": np.round(data.get("TankLevel", 0.0), 2), "Qinlet": np.round(data.get("Inlet_flow", 0.0), 1), "Qoutlet": np.round(data.get("Outlet_flow", 0.0), 1), - "Q_conso_filt": np.round(data.get("Qdrain_sp", 0.0), 1), "Q_conso_filt": np.round(data.get("Qdrain_sp", 0.0), 1), "Q_conso_filt": np.round(data.get("Qdrain_sp", 0.0), 1), - } @@ -164,6 +163,12 @@ def logout(request: Request): # ======== PROTECTED INTERFACE ======== +@app.on_event("startup") +async def startup_event(): + asyncio.create_task(update_latest_data()) + asyncio.create_task(update_latest_flow()) + + @app.get("/control", response_class=HTMLResponse) def control_page(request: Request): can_backend.connect() @@ -177,12 +182,14 @@ def monitor_page(request: Request): with open("static/monitor_DS.html") as f: return HTMLResponse(f.read()) -@app.get("/monitor-page", response_class=HTMLResponse) + +@app.get("/monitor-PU", response_class=HTMLResponse) def monitor_page(request: Request): with open("static/monitor_PU.html") as f: return HTMLResponse(f.read()) -@app.get("/multi-monitor-page", response_class=HTMLResponse) + +@app.get("/multi-monitor-PU", response_class=HTMLResponse) def monitor_page(request: Request): with open("static/multi_pu_dashboard.html") as f: return HTMLResponse(f.read()) @@ -190,6 +197,7 @@ def monitor_page(request: Request): # ======== CAN + BACKEND ROUTES ======== + @app.post("/connect_toggle") def connect_toggle(): logging.info(f"Toggling CAN connection, CAN is {can_backend.connected}") @@ -204,11 +212,15 @@ def connect_toggle(): raise HTTPException(status_code=500, detail="Connection failed.") return {"connected": can_backend.connected} + @app.get("/is_connected") def is_connected(): return {"connected": can_backend.connected} +# PU CONTROL + + @app.post("/command/{state}/pu/{pu_number}") def send_command(state: str, pu_number: int, ploop_setpoint: float = Query(...)): global DEFAULT_FEED_VALVE @@ -243,6 +255,9 @@ def send_command(state: str, pu_number: int, ploop_setpoint: float = Query(...)) raise HTTPException(status_code=500, detail=str(e)) +## MONITORING + + @app.get("/api/pu_status") def get_pu_status(): global active_PUs @@ -253,7 +268,11 @@ def get_pu_status(): } logging.info(f"[PU STATUS] {states}") - active_PUs = [index + 1 for index, (pu, status) in enumerate(states.items()) if status != 'Offline'] + active_PUs = [ + index + 1 + for index, (pu, status) in enumerate(states.items()) + if status != "Offline" + ] logging.info(f"[ACTIVE PU] {active_PUs}") return JSONResponse(content=states) @@ -262,7 +281,7 @@ def get_pu_status(): async def update_latest_data(): global active_PUs while True: - #DS + # DS data = can_backend.get_latest_data(pu_number=0) latest_data[f"DS"] = format_DS_data(data) @@ -271,7 +290,6 @@ async def update_latest_data(): data = can_backend.get_latest_data(pu_number=pu) latest_data[f"PU_{pu}"] = format_PU_data(data) - logging.debug(f"[MONITOR DS BUFFER] latest_data: {latest_data}") await asyncio.sleep(0.05) @@ -281,18 +299,6 @@ async def get_monitor_data(): return JSONResponse(content=latest_data) -@app.on_event("startup") -async def startup_event(): - asyncio.create_task(update_latest_data()) - asyncio.create_task(update_latest_flow()) - - -@app.get("/can_status") -def can_status(): - """Return current CAN connection status.""" - return {"connected": can_backend.connected} - - # LOCAL RECORDER @app.post("/start_recording") async def start_recording(): @@ -344,11 +350,7 @@ async def record_data_loop(): timestamp = datetime.datetime.now().isoformat() for pu, data in latest_data.items(): if data: - row = { - "timestamp": timestamp, - "pu": pu, - **data - } + row = {"timestamp": timestamp, "pu": pu, **data} recording_writer.writerow(row) # Flush every flush_interval seconds @@ -360,18 +362,26 @@ async def record_data_loop(): await asyncio.sleep(0.05) # 10 Hz + ## AUTOMATIC TESTING -async def send_command_with_delay(state: str, pu: int, delay_s: int = 0, ploop_setpoint: float = 0.0): + +async def send_command_with_delay( + state: str, pu: int, delay_s: int = 0, ploop_setpoint: float = 0.0 +): await asyncio.sleep(delay_s) logging.info(f"[AUTO TEST] Sending {state} to PU{pu} after {delay_s}s") can_backend.send_state_command(state, pu, ploop_setpoint) + async def set_patients_with_delay(count: int, delay_s: int): await asyncio.sleep(delay_s) - logging.info(f"[AUTO TEST] Sending {count} patients to patient skid after {delay_s}s") + logging.info( + f"[AUTO TEST] Sending {count} patients to patient skid after {delay_s}s" + ) set_patient_skid_users(count) + @router.post("/test/auto/1") async def auto_test_pu1(ploop_setpoint: float = Query(0.0)): pu = 1 @@ -379,39 +389,52 @@ async def auto_test_pu1(ploop_setpoint: float = Query(0.0)): asyncio.create_task(run_auto_test_pu1(pu, ploop_setpoint)) return {"status": "started", "pu": pu} + @router.post("/test/auto/2") async def auto_test_pu2(ploop_setpoint: float = Query(0.0)): logging.info("[AUTO TEST] Starting automatic test for 2 PUs") asyncio.create_task(run_auto_test_pu2(ploop_setpoint)) return {"status": "started", "pu": [1, 2]} + async def run_auto_test_pu1(pu: int, ploop_setpoint: float): - await send_command_with_delay("PRE-PRODUCTION", pu, delay_s=0, ploop_setpoint=ploop_setpoint) - await send_command_with_delay("PRODUCTION", pu, delay_s=180, ploop_setpoint=ploop_setpoint) + await send_command_with_delay( + "PRE-PRODUCTION", pu, delay_s=0, ploop_setpoint=ploop_setpoint + ) + await send_command_with_delay( + "PRODUCTION", pu, delay_s=180, ploop_setpoint=ploop_setpoint + ) await set_patients_with_delay(5, delay_s=60) await set_patients_with_delay(10, delay_s=60) await send_command_with_delay("IDLE", pu, delay_s=60, ploop_setpoint=ploop_setpoint) logging.info("[AUTO TEST] Finished PU1 test") + async def run_auto_test_pu2(ploop_setpoint: float): # Step 1: Run PU1 test await run_auto_test_pu1(1, ploop_setpoint) # Step 2: PU2 sequence - await send_command_with_delay("PRE-PRODUCTION", 2, delay_s=0, ploop_setpoint=ploop_setpoint) - await send_command_with_delay("PRODUCTION", 2, delay_s=180, ploop_setpoint=ploop_setpoint) + await send_command_with_delay( + "PRE-PRODUCTION", 2, delay_s=0, ploop_setpoint=ploop_setpoint + ) + await send_command_with_delay( + "PRODUCTION", 2, delay_s=180, ploop_setpoint=ploop_setpoint + ) await set_patients_with_delay(15, delay_s=60) await set_patients_with_delay(0, delay_s=60) await send_command_with_delay("IDLE", 2, delay_s=60, ploop_setpoint=ploop_setpoint) await send_command_with_delay("IDLE", 1, delay_s=60, ploop_setpoint=ploop_setpoint) logging.info("[AUTO TEST] Finished PU1 + PU2 test") + @router.post("/test/auto/3") async def auto_test_pu3(): # Call the function for PU3 auto test logging.info("Start auto test of 3 PU") return {"status": "started", "pu": 3} + # PATIENT SKID HELPERS async def update_latest_flow(): global active_PUs @@ -431,6 +454,7 @@ async def update_latest_flow(): logging.error(f"Error fetching flow: {e}") await asyncio.sleep(1.0) + def set_patient_skid_users(count: int = 1): try: url = f"http://192.168.1.28:8000/set_users/{count}" @@ -439,9 +463,13 @@ def set_patient_skid_users(count: int = 1): if response.status_code == 200: return {"status": "success", "detail": response.json()} else: - raise HTTPException(status_code=502, detail=f"Remote server error: {response.text}") + raise HTTPException( + status_code=502, detail=f"Remote server error: {response.text}" + ) except httpx.RequestError as e: - raise HTTPException(status_code=500, detail=f"Request to external server failed: {str(e)}") + raise HTTPException( + status_code=500, detail=f"Request to external server failed: {str(e)}" + ) app.include_router(router) diff --git a/templates/control.html b/templates/control.html index 94f825d..b1df70a 100644 --- a/templates/control.html +++ b/templates/control.html @@ -241,16 +241,16 @@