Adds feed valve button + reformatting
This commit is contained in:
parent
e21bfa26f6
commit
1545a01350
|
|
@ -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),
|
||||
|
|
|
|||
59
main.py
59
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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -230,6 +241,11 @@
|
|||
<div class="pu-item"><span>PU 2: </span><span id="pu2-status">Offline</span></div>
|
||||
<div class="pu-item"><span>PU 3: </span><span id="pu3-status">Offline</span></div>
|
||||
</div>
|
||||
<div class="feed-valve-buttons">
|
||||
<button onclick="setFeedValve(0, this)">Feed Valve 0%</button>
|
||||
<button onclick="setFeedValve(50, this)">Feed Valve 50%</button>
|
||||
<button onclick="setFeedValve(100, this)">Feed Valve 100%</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
<div class="monitor-block">
|
||||
|
|
@ -266,48 +282,39 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updatePloopSetpoint(value) {
|
||||
document.getElementById('currentValue').textContent = value;
|
||||
}
|
||||
|
||||
async function toggleConnection() {
|
||||
const response = await fetch('/connect_toggle', { method: 'POST' });
|
||||
const data = await response.json();
|
||||
const connectButton = document.getElementById('connectButton');
|
||||
connectButton.classList.toggle('connected', data.connected);
|
||||
connectButton.innerHTML = `<i class="fas fa-power-off"></i> ${data.connected ? 'Disconnect' : 'Connect'}`;
|
||||
}
|
||||
async function toggleConnection() {
|
||||
const response = await fetch('/connect_toggle', { method: 'POST' });
|
||||
const data = await response.json();
|
||||
const connectButton = document.getElementById('connectButton');
|
||||
connectButton.classList.toggle('connected', data.connected);
|
||||
connectButton.innerHTML = `<i class="fas fa-power-off"></i> ${data.connected ? 'Disconnect' : 'Connect'}`;
|
||||
}
|
||||
|
||||
async function sendCommand(state, puNumber, buttonEl) {
|
||||
async function sendCommand(state, puNumber, buttonEl) {
|
||||
const ploopSetpoint = document.getElementById('ploopSetpoint').value;
|
||||
|
||||
// Send command to backend
|
||||
await fetch(`/command/${state}/pu/${puNumber}?ploop_setpoint=${ploopSetpoint}`, { method: 'POST' });
|
||||
|
||||
// Reset button styles
|
||||
document.querySelectorAll('button').forEach(btn => {
|
||||
btn.classList.remove('in-progress', 'ready', 'production');
|
||||
});
|
||||
|
||||
// Handle PRE-PRODUCTION sequence
|
||||
if (state === 'PRE-PRODUCTION') {
|
||||
buttonEl.classList.add('in-progress');
|
||||
buttonEl.textContent = `Waiting... PU ${puNumber}`;
|
||||
buttonEl.disabled = true;
|
||||
|
||||
const checkReady = async () => {
|
||||
const res = await fetch(`/api/pu_status`);
|
||||
const states = await res.json();
|
||||
const currentState = states[`PU${puNumber}`];
|
||||
|
||||
if (currentState === 'SYSTEM_MODE_READY') {
|
||||
buttonEl.classList.remove('in-progress');
|
||||
buttonEl.classList.add('ready');
|
||||
buttonEl.textContent = `START PRODUCTION PU ${puNumber}`;
|
||||
buttonEl.disabled = false;
|
||||
|
||||
buttonEl.onclick = async () => {
|
||||
await sendCommand("PRODUCTION", puNumber, buttonEl);
|
||||
buttonEl.classList.remove('ready');
|
||||
|
|
@ -319,19 +326,15 @@ function updatePloopSetpoint(value) {
|
|||
setTimeout(checkReady, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
checkReady();
|
||||
|
||||
} else if (state === 'PRODUCTION') {
|
||||
buttonEl.classList.add('production');
|
||||
buttonEl.textContent = `PRODUCTION ON PU ${puNumber}`;
|
||||
|
||||
} else if (state === 'IDLE' || state === 'FIRST_START') {
|
||||
buttonEl.classList.remove('in-progress', 'ready', 'production');
|
||||
buttonEl.classList.add('production');
|
||||
buttonEl.textContent = `${state.replace('_', ' ')} PU ${puNumber}`;
|
||||
|
||||
// === Reset PRE-PROD button ===
|
||||
const preProdBtn = document.querySelector(`button[data-action="PRE-PRODUCTION"][data-pu="${puNumber}"]`);
|
||||
if (preProdBtn) {
|
||||
preProdBtn.classList.remove('in-progress', 'ready', 'production');
|
||||
|
|
@ -340,7 +343,6 @@ function updatePloopSetpoint(value) {
|
|||
preProdBtn.onclick = () => sendCommand("PRE-PRODUCTION", puNumber, preProdBtn);
|
||||
}
|
||||
|
||||
// === Reset IDLE button ===
|
||||
const idleBtn = document.querySelector(`button[data-action="IDLE"][data-pu="${puNumber}"]`);
|
||||
if (idleBtn && idleBtn !== buttonEl) {
|
||||
idleBtn.classList.remove('in-progress', 'ready', 'production');
|
||||
|
|
@ -349,7 +351,6 @@ function updatePloopSetpoint(value) {
|
|||
idleBtn.onclick = () => sendCommand("IDLE", puNumber, idleBtn);
|
||||
}
|
||||
|
||||
// === Reset FIRST START button ===
|
||||
const firstStartBtn = document.querySelector(`button[data-action="FIRST_START"][data-pu="${puNumber}"]`);
|
||||
if (firstStartBtn && firstStartBtn !== buttonEl) {
|
||||
firstStartBtn.classList.remove('in-progress', 'ready', 'production');
|
||||
|
|
@ -360,105 +361,98 @@ function updatePloopSetpoint(value) {
|
|||
}
|
||||
}
|
||||
|
||||
async function fetchPUStatus() {
|
||||
const response = await fetch("/api/pu_status");
|
||||
const data = await response.json();
|
||||
document.getElementById("pu1-status").textContent = data.PU1 || "Unknown";
|
||||
document.getElementById("pu2-status").textContent = data.PU2 || "Unknown";
|
||||
document.getElementById("pu3-status").textContent = data.PU3 || "Unknown";
|
||||
async function setFeedValve(opening, buttonEl) {
|
||||
await fetch(`/command/feed_valve?MV01_opening=${opening}`, { method: 'POST' });
|
||||
document.querySelectorAll('.feed-valve-buttons button').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
buttonEl.classList.add('active');
|
||||
}
|
||||
|
||||
async function fetchPUStatus() {
|
||||
const response = await fetch("/api/pu_status");
|
||||
const data = await response.json();
|
||||
document.getElementById("pu1-status").textContent = data.PU1 || "Unknown";
|
||||
document.getElementById("pu2-status").textContent = data.PU2 || "Unknown";
|
||||
document.getElementById("pu3-status").textContent = data.PU3 || "Unknown";
|
||||
}
|
||||
|
||||
fetchPUStatus();
|
||||
setInterval(fetchPUStatus, 5000);
|
||||
|
||||
async function updateMonitorData() {
|
||||
const response = await fetch('/monitor');
|
||||
const data = await response.json();
|
||||
for (const [puId, puData] of Object.entries(data)) {
|
||||
const container = document.getElementById(puId);
|
||||
if (!container) continue;
|
||||
container.innerHTML = `
|
||||
<h3>${puId}</h3>
|
||||
<div class="monitor-value">Q_perm<br>${puData.Qperm.toFixed(1)} L/h</div>
|
||||
<div class="monitor-value">Q_dilute<br>${puData.Qdilute.toFixed(1)} L/h</div>
|
||||
<div class="monitor-value">Q_drain<br>${puData.Qdrain.toFixed(1)} L/h</div>
|
||||
<div class="monitor-value">Q_recirc<br>${puData.Qrecirc.toFixed(1)} L/h</div>
|
||||
<div class="monitor-value">P_ro<br>${puData.Pro.toFixed(1)} bar</div>
|
||||
<div class="monitor-value">P_dilute<br>${puData.Pdilute.toFixed(1)} bar</div>
|
||||
<div class="monitor-value">P_rentate<br>${puData.Prentate.toFixed(1)} bar</div>
|
||||
<div class="monitor-value">Conductivity<br>${puData.Conductivity.toFixed(1)} µS/cm</div>
|
||||
<div class="monitor-value">MV02<br>${puData.MV02.toFixed(1)} % (sp: ${puData.MV02_sp.toFixed(1)})</div>
|
||||
<div class="monitor-value">MV03<br>${puData.MV03.toFixed(1)} % (sp: ${puData.MV03_sp.toFixed(1)})</div>
|
||||
<div class="monitor-value">MV05<br>${puData.MV05.toFixed(1)} % (sp: ${puData.MV05_sp.toFixed(1)})</div>
|
||||
<div class="monitor-value">MV06<br>${puData.MV06.toFixed(1)} % (sp: ${puData.MV06_sp.toFixed(1)})</div>
|
||||
<div class="monitor-value">MV07<br>${puData.MV07.toFixed(1)} % (sp: ${puData.MV07_sp.toFixed(1)})</div>
|
||||
<div class="monitor-value">MV08<br>${puData.MV08.toFixed(1)} % (sp: ${puData.MV08_sp.toFixed(1)})</div>
|
||||
`;
|
||||
}
|
||||
fetchPUStatus();
|
||||
setInterval(fetchPUStatus, 5000);
|
||||
}
|
||||
|
||||
async function updateMonitorData() {
|
||||
const response = await fetch('/monitor');
|
||||
const data = await response.json(); // data = { PU_1: {...}, PU_2: {...}, PU_3: {...} }
|
||||
function updateMonitorValues(id, values, unit) {
|
||||
const container = document.getElementById(id);
|
||||
const valueElements = container.querySelectorAll('.monitor-value');
|
||||
valueElements.forEach((element, index) => {
|
||||
if (index < values.length) {
|
||||
element.innerHTML = `#${index + 1}<br>${values[index]} ${unit}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const [puId, puData] of Object.entries(data)) {
|
||||
const container = document.getElementById(puId);
|
||||
setInterval(updateMonitorData, 1000);
|
||||
|
||||
async function fetchMonitorData() {
|
||||
try {
|
||||
const puMap = {
|
||||
"PU_1": 1,
|
||||
"PU_2": 2,
|
||||
"PU_3": 3
|
||||
};
|
||||
const fields = {
|
||||
"Qperm": "L/h",
|
||||
"Pdilute": "bar",
|
||||
"Conductivity": "µS/cm",
|
||||
"Pro": "bar"
|
||||
};
|
||||
const dataResponse = await fetch('/monitor');
|
||||
const allData = await dataResponse.json();
|
||||
for (const [fieldId, unit] of Object.entries(fields)) {
|
||||
const container = document.getElementById(fieldId);
|
||||
if (!container) continue;
|
||||
|
||||
container.innerHTML = `
|
||||
<h3>${puId}</h3>
|
||||
<div class="monitor-value">Q_perm<br>${puData.Qperm.toFixed(1)} L/h</div>
|
||||
<div class="monitor-value">Q_dilute<br>${puData.Qdilute.toFixed(1)} L/h</div>
|
||||
<div class="monitor-value">Q_drain<br>${puData.Qdrain.toFixed(1)} L/h</div>
|
||||
<div class="monitor-value">Q_recirc<br>${puData.Qrecirc.toFixed(1)} L/h</div>
|
||||
|
||||
<div class="monitor-value">P_ro<br>${puData.Pro.toFixed(1)} bar</div>
|
||||
<div class="monitor-value">P_dilute<br>${puData.Pdilute.toFixed(1)} bar</div>
|
||||
<div class="monitor-value">P_rentate<br>${puData.Prentate.toFixed(1)} bar</div>
|
||||
|
||||
<div class="monitor-value">Conductivity<br>${puData.Conductivity.toFixed(1)} µS/cm</div>
|
||||
|
||||
<div class="monitor-value">MV02<br>${puData.MV02.toFixed(1)} % (sp: ${puData.MV02_sp.toFixed(1)})</div>
|
||||
<div class="monitor-value">MV03<br>${puData.MV03.toFixed(1)} % (sp: ${puData.MV03_sp.toFixed(1)})</div>
|
||||
<div class="monitor-value">MV05<br>${puData.MV05.toFixed(1)} % (sp: ${puData.MV05_sp.toFixed(1)})</div>
|
||||
<div class="monitor-value">MV06<br>${puData.MV06.toFixed(1)} % (sp: ${puData.MV06_sp.toFixed(1)})</div>
|
||||
<div class="monitor-value">MV07<br>${puData.MV07.toFixed(1)} % (sp: ${puData.MV07_sp.toFixed(1)})</div>
|
||||
<div class="monitor-value">MV08<br>${puData.MV08.toFixed(1)} % (sp: ${puData.MV08_sp.toFixed(1)})</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateMonitorValues(id, values, unit) {
|
||||
const container = document.getElementById(id);
|
||||
const valueElements = container.querySelectorAll('.monitor-value');
|
||||
valueElements.forEach((element, index) => {
|
||||
if (index < values.length) {
|
||||
element.innerHTML = `#${index + 1}<br>${values[index]} ${unit}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update monitor data every second
|
||||
setInterval(updateMonitorData, 1000);
|
||||
</script>
|
||||
|
||||
<script>
|
||||
async function fetchMonitorData() {
|
||||
try {
|
||||
const puMap = {
|
||||
"PU_1": 1,
|
||||
"PU_2": 2,
|
||||
"PU_3": 3
|
||||
};
|
||||
|
||||
const fields = {
|
||||
"Qperm": "L/h",
|
||||
"Pdilute": "bar",
|
||||
"Conductivity": "µS/cm",
|
||||
"Pro": "bar"
|
||||
};
|
||||
|
||||
const dataResponse = await fetch('/monitor');
|
||||
const allData = await dataResponse.json(); // Returns {PU_1: {...}, PU_2: {...}, PU_3: {...}}
|
||||
|
||||
for (const [fieldId, unit] of Object.entries(fields)) {
|
||||
const container = document.getElementById(fieldId);
|
||||
if (!container) continue;
|
||||
|
||||
const valueElements = container.querySelectorAll('.monitor-value');
|
||||
|
||||
let index = 0;
|
||||
for (const [puLabel, puData] of Object.entries(allData)) {
|
||||
const value = puData[fieldId] ?? 0.0;
|
||||
|
||||
// Reuse DOM element if it exists
|
||||
if (valueElements[index]) {
|
||||
valueElements[index].innerHTML = `#${index + 1}<br>${value.toFixed(1)} ${unit}`;
|
||||
}
|
||||
index++;
|
||||
let index = 0;
|
||||
for (const [puLabel, puData] of Object.entries(allData)) {
|
||||
const value = puData[fieldId] ?? 0.0;
|
||||
if (valueElements[index]) {
|
||||
valueElements[index].innerHTML = `#${index + 1}<br>${value.toFixed(1)} ${unit}`;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching monitor data:', error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching monitor data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(fetchMonitorData, 1000);
|
||||
fetchMonitorData(); // initial load
|
||||
</script>
|
||||
setInterval(fetchMonitorData, 1000);
|
||||
fetchMonitorData();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user