Reformatting

This commit is contained in:
Etienne Chassaing 2025-08-06 11:13:29 +02:00
parent 400fe40bcd
commit 9ad18a17c8
2 changed files with 67 additions and 39 deletions

98
main.py
View File

@ -50,11 +50,11 @@ latest_data: Dict[str, Any] = {
"PU_1": None, "PU_1": None,
"PU_2": None, "PU_2": None,
"PU_3": None, "PU_3": None,
"DS" : None, "DS": None,
"PatientSkid": {"QSkid": 0.0}, "PatientSkid": {"QSkid": 0.0},
} }
active_PUs : list[int] = [] active_PUs: list[int] = []
DEFAULT_FEED_VALVE = 0.0 DEFAULT_FEED_VALVE = 0.0
@ -78,7 +78,8 @@ def format_PU_data(data):
"Qdilute": np.round(data.get("FM1", 0.0), 1), "Qdilute": np.round(data.get("FM1", 0.0), 1),
"Qdrain": np.round(data.get("FM4", 0.0), 1), "Qdrain": np.round(data.get("FM4", 0.0), 1),
"Qrecirc": np.round(data.get("FM3", 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), "Pro": np.round(data.get("PS2", 0.0), 2),
"Pdilute": np.round(data.get("PS3", 0.0), 2), "Pdilute": np.round(data.get("PS3", 0.0), 2),
"Pretentate": np.round(data.get("PS1", 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), "TankLevel": np.round(data.get("TankLevel", 0.0), 2),
"Qinlet": np.round(data.get("Inlet_flow", 0.0), 1), "Qinlet": np.round(data.get("Inlet_flow", 0.0), 1),
"Qoutlet": np.round(data.get("Outlet_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), "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 ======== # ======== 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) @app.get("/control", response_class=HTMLResponse)
def control_page(request: Request): def control_page(request: Request):
can_backend.connect() can_backend.connect()
@ -177,12 +182,14 @@ def monitor_page(request: Request):
with open("static/monitor_DS.html") as f: with open("static/monitor_DS.html") as f:
return HTMLResponse(f.read()) return HTMLResponse(f.read())
@app.get("/monitor-page", response_class=HTMLResponse)
@app.get("/monitor-PU", response_class=HTMLResponse)
def monitor_page(request: Request): def monitor_page(request: Request):
with open("static/monitor_PU.html") as f: with open("static/monitor_PU.html") as f:
return HTMLResponse(f.read()) 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): def monitor_page(request: Request):
with open("static/multi_pu_dashboard.html") as f: with open("static/multi_pu_dashboard.html") as f:
return HTMLResponse(f.read()) return HTMLResponse(f.read())
@ -190,6 +197,7 @@ def monitor_page(request: Request):
# ======== CAN + BACKEND ROUTES ======== # ======== CAN + BACKEND ROUTES ========
@app.post("/connect_toggle") @app.post("/connect_toggle")
def connect_toggle(): def connect_toggle():
logging.info(f"Toggling CAN connection, CAN is {can_backend.connected}") 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.") raise HTTPException(status_code=500, detail="Connection failed.")
return {"connected": can_backend.connected} return {"connected": can_backend.connected}
@app.get("/is_connected") @app.get("/is_connected")
def is_connected(): def is_connected():
return {"connected": can_backend.connected} return {"connected": can_backend.connected}
# PU CONTROL
@app.post("/command/{state}/pu/{pu_number}") @app.post("/command/{state}/pu/{pu_number}")
def send_command(state: str, pu_number: int, ploop_setpoint: float = Query(...)): def send_command(state: str, pu_number: int, ploop_setpoint: float = Query(...)):
global DEFAULT_FEED_VALVE 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)) raise HTTPException(status_code=500, detail=str(e))
## MONITORING
@app.get("/api/pu_status") @app.get("/api/pu_status")
def get_pu_status(): def get_pu_status():
global active_PUs global active_PUs
@ -253,7 +268,11 @@ def get_pu_status():
} }
logging.info(f"[PU STATUS] {states}") 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}") logging.info(f"[ACTIVE PU] {active_PUs}")
return JSONResponse(content=states) return JSONResponse(content=states)
@ -262,7 +281,7 @@ def get_pu_status():
async def update_latest_data(): async def update_latest_data():
global active_PUs global active_PUs
while True: while True:
#DS # DS
data = can_backend.get_latest_data(pu_number=0) data = can_backend.get_latest_data(pu_number=0)
latest_data[f"DS"] = format_DS_data(data) 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) data = can_backend.get_latest_data(pu_number=pu)
latest_data[f"PU_{pu}"] = format_PU_data(data) latest_data[f"PU_{pu}"] = format_PU_data(data)
logging.debug(f"[MONITOR DS BUFFER] latest_data: {latest_data}") logging.debug(f"[MONITOR DS BUFFER] latest_data: {latest_data}")
await asyncio.sleep(0.05) await asyncio.sleep(0.05)
@ -281,18 +299,6 @@ async def get_monitor_data():
return JSONResponse(content=latest_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 # LOCAL RECORDER
@app.post("/start_recording") @app.post("/start_recording")
async def start_recording(): async def start_recording():
@ -344,11 +350,7 @@ async def record_data_loop():
timestamp = datetime.datetime.now().isoformat() timestamp = datetime.datetime.now().isoformat()
for pu, data in latest_data.items(): for pu, data in latest_data.items():
if data: if data:
row = { row = {"timestamp": timestamp, "pu": pu, **data}
"timestamp": timestamp,
"pu": pu,
**data
}
recording_writer.writerow(row) recording_writer.writerow(row)
# Flush every flush_interval seconds # Flush every flush_interval seconds
@ -360,18 +362,26 @@ async def record_data_loop():
await asyncio.sleep(0.05) # 10 Hz await asyncio.sleep(0.05) # 10 Hz
## AUTOMATIC TESTING ## 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) await asyncio.sleep(delay_s)
logging.info(f"[AUTO TEST] Sending {state} to PU{pu} after {delay_s}s") logging.info(f"[AUTO TEST] Sending {state} to PU{pu} after {delay_s}s")
can_backend.send_state_command(state, pu, ploop_setpoint) can_backend.send_state_command(state, pu, ploop_setpoint)
async def set_patients_with_delay(count: int, delay_s: int): async def set_patients_with_delay(count: int, delay_s: int):
await asyncio.sleep(delay_s) 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) set_patient_skid_users(count)
@router.post("/test/auto/1") @router.post("/test/auto/1")
async def auto_test_pu1(ploop_setpoint: float = Query(0.0)): async def auto_test_pu1(ploop_setpoint: float = Query(0.0)):
pu = 1 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)) asyncio.create_task(run_auto_test_pu1(pu, ploop_setpoint))
return {"status": "started", "pu": pu} return {"status": "started", "pu": pu}
@router.post("/test/auto/2") @router.post("/test/auto/2")
async def auto_test_pu2(ploop_setpoint: float = Query(0.0)): async def auto_test_pu2(ploop_setpoint: float = Query(0.0)):
logging.info("[AUTO TEST] Starting automatic test for 2 PUs") logging.info("[AUTO TEST] Starting automatic test for 2 PUs")
asyncio.create_task(run_auto_test_pu2(ploop_setpoint)) asyncio.create_task(run_auto_test_pu2(ploop_setpoint))
return {"status": "started", "pu": [1, 2]} return {"status": "started", "pu": [1, 2]}
async def run_auto_test_pu1(pu: int, ploop_setpoint: float): 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(
await send_command_with_delay("PRODUCTION", pu, delay_s=180, ploop_setpoint=ploop_setpoint) "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(5, delay_s=60)
await set_patients_with_delay(10, 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) await send_command_with_delay("IDLE", pu, delay_s=60, ploop_setpoint=ploop_setpoint)
logging.info("[AUTO TEST] Finished PU1 test") logging.info("[AUTO TEST] Finished PU1 test")
async def run_auto_test_pu2(ploop_setpoint: float): async def run_auto_test_pu2(ploop_setpoint: float):
# Step 1: Run PU1 test # Step 1: Run PU1 test
await run_auto_test_pu1(1, ploop_setpoint) await run_auto_test_pu1(1, ploop_setpoint)
# Step 2: PU2 sequence # Step 2: PU2 sequence
await send_command_with_delay("PRE-PRODUCTION", 2, delay_s=0, ploop_setpoint=ploop_setpoint) await send_command_with_delay(
await send_command_with_delay("PRODUCTION", 2, delay_s=180, ploop_setpoint=ploop_setpoint) "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(15, delay_s=60)
await set_patients_with_delay(0, 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", 2, delay_s=60, ploop_setpoint=ploop_setpoint)
await send_command_with_delay("IDLE", 1, 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") logging.info("[AUTO TEST] Finished PU1 + PU2 test")
@router.post("/test/auto/3") @router.post("/test/auto/3")
async def auto_test_pu3(): async def auto_test_pu3():
# Call the function for PU3 auto test # Call the function for PU3 auto test
logging.info("Start auto test of 3 PU") logging.info("Start auto test of 3 PU")
return {"status": "started", "pu": 3} return {"status": "started", "pu": 3}
# PATIENT SKID HELPERS # PATIENT SKID HELPERS
async def update_latest_flow(): async def update_latest_flow():
global active_PUs global active_PUs
@ -431,6 +454,7 @@ async def update_latest_flow():
logging.error(f"Error fetching flow: {e}") logging.error(f"Error fetching flow: {e}")
await asyncio.sleep(1.0) await asyncio.sleep(1.0)
def set_patient_skid_users(count: int = 1): def set_patient_skid_users(count: int = 1):
try: try:
url = f"http://192.168.1.28:8000/set_users/{count}" 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: if response.status_code == 200:
return {"status": "success", "detail": response.json()} return {"status": "success", "detail": response.json()}
else: 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: 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) app.include_router(router)

View File

@ -241,16 +241,16 @@
<h1>Hydraulic Machine Control</h1> <h1>Hydraulic Machine Control</h1>
<div class="monitor-pu-buttons"> <div class="monitor-pu-buttons">
<!-- New multi-monitor button --> <!-- New multi-monitor button -->
<a href="/multi-monitor-page" target="_blank" class="monitor-link"> <a href="/multi-monitor-PU" target="_blank" class="monitor-link">
<i class="fas fa-chart-bar"></i> Monitor All PUs <i class="fas fa-chart-bar"></i> Monitor All PUs
</a> </a>
<a href="/monitor-page?pu_number=1" target="_blank" class="monitor-link"> <a href="/monitor-PU?pu_number=1" target="_blank" class="monitor-link">
<i class="fas fa-chart-line"></i> Monitor PU 1 <i class="fas fa-chart-line"></i> Monitor PU 1
</a> </a>
<a href="/monitor-page?pu_number=2" target="_blank" class="monitor-link"> <a href="/monitor-PU?pu_number=2" target="_blank" class="monitor-link">
<i class="fas fa-chart-line"></i> Monitor PU 2 <i class="fas fa-chart-line"></i> Monitor PU 2
</a> </a>
<a href="/monitor-page?pu_number=3" target="_blank" class="monitor-link"> <a href="/monitor-PU?pu_number=3" target="_blank" class="monitor-link">
<i class="fas fa-chart-line"></i> Monitor PU 3 <i class="fas fa-chart-line"></i> Monitor PU 3
</a> </a>
<a href="/monitor-DS" target="_blank" class="monitor-link"> <a href="/monitor-DS" target="_blank" class="monitor-link">