Adds setpoints to the dashboard
This commit is contained in:
parent
d68b170ccb
commit
c7c850129c
33
main.py
33
main.py
|
|
@ -46,13 +46,19 @@ app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
## BUFFER CREATION AND HELPER
|
## BUFFER CREATION AND HELPER
|
||||||
# Global object to store the latest data
|
# Global object to store the latest data
|
||||||
latest_data: Dict[str, Any] = {
|
latest_data: Dict[str, Any] = {
|
||||||
"PU_1": None,
|
"PU_1": {"Ploop_sp": 0.0, "Qperm_sp": 0.0},
|
||||||
"PU_2": None,
|
"PU_2": {"Ploop_sp": 0.0, "Qperm_sp": 0.0},
|
||||||
"PU_3": None,
|
"PU_3": {"Ploop_sp": 0.0, "Qperm_sp": 0.0},
|
||||||
"DS": None,
|
"DS": None,
|
||||||
"PatientSkid": {"QSkid": 0.0},
|
"PatientSkid": {"QSkid": 0.0},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
latest_setpoints: Dict[str, Any] = {
|
||||||
|
"PU_1": {"Ploop_sp": 2.5, "Qperm_sp": 1200},
|
||||||
|
"PU_2": {"Ploop_sp": 2.5, "Qperm_sp": 1200},
|
||||||
|
"PU_3": {"Ploop_sp": 2.5, "Qperm_sp": 1200},
|
||||||
|
}
|
||||||
|
|
||||||
active_PUs: list[int] = []
|
active_PUs: list[int] = []
|
||||||
|
|
||||||
# RECORDER
|
# RECORDER
|
||||||
|
|
@ -110,6 +116,20 @@ def format_DS_data(data):
|
||||||
"Qoutlet": np.round(data.get("Outlet_flow", 0.0), 1),
|
"Qoutlet": np.round(data.get("Outlet_flow", 0.0), 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
## Fetch setpoints
|
||||||
|
def update_setpoints(p_loop_setpoint : float, q_perm_setpoint : float, pu : int):
|
||||||
|
global latest_setpoints
|
||||||
|
pu_key = "PU_"+str(pu)
|
||||||
|
latest_setpoints[pu_key]["Ploop_sp"] = p_loop_setpoint
|
||||||
|
latest_setpoints[pu_key]["Qperm_sp"] = q_perm_setpoint
|
||||||
|
|
||||||
|
def format_setpoints(pu: int): # THis is a bit convoluted to pass from an object to another but it works
|
||||||
|
global latest_setpoints, latest_data
|
||||||
|
pu_key = "PU_" + str(pu)
|
||||||
|
latest_data[pu_key]["Ploop_sp"] = latest_setpoints[pu_key]["Ploop_sp"]
|
||||||
|
latest_data[pu_key]["Qperm_sp"] = latest_setpoints[pu_key]["Qperm_sp"]
|
||||||
|
|
||||||
|
|
||||||
# CREDENTIALS
|
# CREDENTIALS
|
||||||
CREDENTIAL_PATH = Path("credentials.json")
|
CREDENTIAL_PATH = Path("credentials.json")
|
||||||
if CREDENTIAL_PATH.exists():
|
if CREDENTIAL_PATH.exists():
|
||||||
|
|
@ -255,6 +275,7 @@ def send_command(state: str, pu_number: int, ploop_setpoint: float = Query(...),
|
||||||
raise HTTPException(status_code=400, detail=f"Invalid state '{state}'")
|
raise HTTPException(status_code=400, detail=f"Invalid state '{state}'")
|
||||||
|
|
||||||
logging.info(f"Sending state '{state}' to PU {pu_number}")
|
logging.info(f"Sending state '{state}' to PU {pu_number}")
|
||||||
|
update_setpoints(ploop_setpoint, qperm_setpoint, pu_number)
|
||||||
|
|
||||||
pu_number = [pu_number] if pu_number !=3 else [1,2] # Temporary way of starting two pus
|
pu_number = [pu_number] if pu_number !=3 else [1,2] # Temporary way of starting two pus
|
||||||
|
|
||||||
|
|
@ -296,14 +317,14 @@ def get_pu_status():
|
||||||
"PU2": can_backend.read_current_state(2),
|
"PU2": can_backend.read_current_state(2),
|
||||||
"PU3": can_backend.read_current_state(3),
|
"PU3": can_backend.read_current_state(3),
|
||||||
}
|
}
|
||||||
logging.info(f"[PU STATUS] {states}")
|
logging.debug(f"[PU STATUS] {states}")
|
||||||
|
|
||||||
active_PUs = [
|
active_PUs = [
|
||||||
index + 1
|
index + 1
|
||||||
for index, (pu, status) in enumerate(states.items())
|
for index, (pu, status) in enumerate(states.items())
|
||||||
if status != "Offline"
|
if status != "Offline"
|
||||||
]
|
]
|
||||||
logging.info(f"[ACTIVE PU] {active_PUs}")
|
logging.debug(f"[ACTIVE PU] {active_PUs}")
|
||||||
|
|
||||||
return JSONResponse(content=states)
|
return JSONResponse(content=states)
|
||||||
|
|
||||||
|
|
@ -318,6 +339,7 @@ async def update_latest_data():
|
||||||
for pu in active_PUs:
|
for pu in active_PUs:
|
||||||
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)
|
||||||
|
format_setpoints(pu)
|
||||||
|
|
||||||
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)
|
||||||
|
|
@ -325,7 +347,6 @@ async def update_latest_data():
|
||||||
@app.get("/monitor")
|
@app.get("/monitor")
|
||||||
async def get_monitor_data():
|
async def get_monitor_data():
|
||||||
return latest_data
|
return latest_data
|
||||||
# return JSONResponse(content=latest_data)
|
|
||||||
|
|
||||||
# LOCAL RECORDER
|
# LOCAL RECORDER
|
||||||
@app.post("/start_recording")
|
@app.post("/start_recording")
|
||||||
|
|
|
||||||
|
|
@ -83,20 +83,22 @@
|
||||||
const DSData = allData[`DS`];
|
const DSData = allData[`DS`];
|
||||||
const t = new Date(puData.timestamp);
|
const t = new Date(puData.timestamp);
|
||||||
|
|
||||||
Plotly.extendTraces('flow-plot-1', {
|
Plotly.extendTraces('flow-plot-1',
|
||||||
x: [[t], [t]],
|
{ x: [[t], [t], [t]], y: [[puData.Qperm], [puData.Qdilute], [puData.Qperm_sp]] },
|
||||||
y: [[puData.Qperm], [puData.Qdilute]]
|
[0, 1, 2],
|
||||||
}, [0, 1], maxPoints);
|
maxPoints
|
||||||
|
);
|
||||||
|
|
||||||
Plotly.extendTraces('flow-plot-2', {
|
Plotly.extendTraces('flow-plot-2', {
|
||||||
x: [[t], [t], [t], [t], [t], [t]],
|
x: [[t], [t], [t], [t], [t], [t]],
|
||||||
y: [[puData.Qdrain], [puData.Qrecirc], [SkidData.QSkid], [puData.QdrainEDI], [DSData.Qconso], [puData.Qdrain_sp]]
|
y: [[puData.Qdrain], [puData.Qrecirc], [SkidData.QSkid], [puData.QdrainEDI], [DSData.Qconso], [puData.Qdrain_sp]]
|
||||||
}, [0, 1, 2, 3, 4, 5], maxPoints);
|
}, [0, 1, 2, 3, 4, 5], maxPoints);
|
||||||
|
|
||||||
Plotly.extendTraces('pressure-plot-1', {
|
Plotly.extendTraces('pressure-plot-1',
|
||||||
x: [[t], [t]],
|
{ x: [[t], [t], [t]], y: [[puData.Pro], [puData.Pretentate], [puData.Ploop_sp]] },
|
||||||
y: [[puData.Pro], [puData.Pretentate]]
|
[0, 1, 2],
|
||||||
}, [0, 1], maxPoints);
|
maxPoints
|
||||||
|
);
|
||||||
|
|
||||||
Plotly.extendTraces('pressure-plot-2', {
|
Plotly.extendTraces('pressure-plot-2', {
|
||||||
x: [[t]],
|
x: [[t]],
|
||||||
|
|
@ -167,12 +169,15 @@
|
||||||
|
|
||||||
Plotly.newPlot('flow-plot-1', [
|
Plotly.newPlot('flow-plot-1', [
|
||||||
{ x: time0, y: [0], name: 'Qperm', mode: 'lines' },
|
{ x: time0, y: [0], name: 'Qperm', mode: 'lines' },
|
||||||
{ x: time0, y: [0], name: 'Qdilute', mode: 'lines' }
|
{ x: time0, y: [0], name: 'Qdilute', mode: 'lines' },
|
||||||
|
{ x: time0, y: [0], name: 'Qperm_sp', mode: 'lines', line: { dash: 'dash', color: 'red' } }
|
||||||
], {
|
], {
|
||||||
title: 'Qperm and Qdilute',
|
title: 'Qperm and Qdilute',
|
||||||
xaxis: { type: 'date' }, yaxis: { title: 'Flow (L/h)' }
|
xaxis: { type: 'date' },
|
||||||
|
yaxis: { title: 'Flow (L/h)' }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Plotly.newPlot('flow-plot-2', [
|
Plotly.newPlot('flow-plot-2', [
|
||||||
{ x: time0, y: [0], name: 'Qdrain', mode: 'lines' },
|
{ x: time0, y: [0], name: 'Qdrain', mode: 'lines' },
|
||||||
{ x: time0, y: [0], name: 'Qrecirc', mode: 'lines' },
|
{ x: time0, y: [0], name: 'Qrecirc', mode: 'lines' },
|
||||||
|
|
@ -186,9 +191,12 @@
|
||||||
|
|
||||||
Plotly.newPlot('pressure-plot-1', [
|
Plotly.newPlot('pressure-plot-1', [
|
||||||
{ x: time0, y: [0], name: 'Pro', mode: 'lines' },
|
{ x: time0, y: [0], name: 'Pro', mode: 'lines' },
|
||||||
{ x: time0, y: [0], name: 'Pretentate', mode: 'lines' }
|
{ x: time0, y: [0], name: 'Pretentate', mode: 'lines' },
|
||||||
|
{ x: time0, y: [0], name: 'Ploop_sp', mode: 'lines', line: { dash: 'dash', color: 'red' } }
|
||||||
], {
|
], {
|
||||||
title: 'Pro and Pretentate ', xaxis: { type: 'date' }, yaxis: { title: 'Pressure (bar)' }
|
title: 'Pro and Pretentate',
|
||||||
|
xaxis: { type: 'date' },
|
||||||
|
yaxis: { title: 'Pressure (bar)' }
|
||||||
});
|
});
|
||||||
|
|
||||||
Plotly.newPlot('pressure-plot-2', [
|
Plotly.newPlot('pressure-plot-2', [
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,27 @@
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.status-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.status-box {
|
||||||
|
padding: 8px 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
font-weight: bold;
|
||||||
|
min-width: 100px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
.plot-container {
|
.plot-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 5px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.plot {
|
.plot {
|
||||||
|
|
@ -28,6 +45,14 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Multi-PU Monitoring Dashboard</h1>
|
<h1>Multi-PU Monitoring Dashboard</h1>
|
||||||
|
|
||||||
|
<!-- Statuses for each PU -->
|
||||||
|
<div class="status-container" id="statusContainer">
|
||||||
|
<div id="PU1-status" class="status-box">PU1: Loading...</div>
|
||||||
|
<div id="PU2-status" class="status-box">PU2: Loading...</div>
|
||||||
|
<div id="PU3-status" class="status-box">PU3: Loading...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="plot-container">
|
<div class="plot-container">
|
||||||
<div id="Qperm-plot" class="plot"></div>
|
<div id="Qperm-plot" class="plot"></div>
|
||||||
<div id="Pdilute-plot" class="plot"></div>
|
<div id="Pdilute-plot" class="plot"></div>
|
||||||
|
|
@ -36,31 +61,23 @@
|
||||||
<div id="Qdrain-plot" class="plot"></div>
|
<div id="Qdrain-plot" class="plot"></div>
|
||||||
<div id="Cdilute-plot" class="plot"></div>
|
<div id="Cdilute-plot" class="plot"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const time0 = [new Date()];
|
const time0 = [new Date()];
|
||||||
const zero = [0];
|
const zero = [0];
|
||||||
const maxPoints = 200;
|
const maxPoints = 200;
|
||||||
const puList = ['PU_1', 'PU_2', 'PU_3'];
|
const puList = ['PU_1', 'PU_2', 'PU_3'];
|
||||||
|
const windowMs = 30 * 1000; // 30 seconds
|
||||||
|
|
||||||
const plots = [
|
const plots = [
|
||||||
{ id: 'Qperm-plot', quantity: 'Qperm', title: 'Qperm per PU', ref: 1200 },
|
{ id: 'Qperm-plot', quantity: 'Qperm', title: 'Qperm per PU', refKey: 'Qperm_sp' },
|
||||||
{ id: 'Qdilute-plot', quantity: 'Qdilute', title: 'Qdilute per PU' },
|
{ id: 'Qdilute-plot', quantity: 'Qdilute', title: 'Qdilute per PU' },
|
||||||
{ id: 'Qdrain-plot', quantity: 'Qdrain', title: 'Qdrain per PU' },
|
{ id: 'Qdrain-plot', quantity: 'Qdrain', title: 'Qdrain per PU' },
|
||||||
{ id: 'Pro-plot', quantity: 'Pro', title: 'Pro per PU' },
|
{ id: 'Pro-plot', quantity: 'Pro', title: 'Pro per PU', refKey: 'Ploop_sp' },
|
||||||
{ id: 'Pdilute-plot', quantity: 'Pdilute', title: 'Pdilute per PU', ref: 2.5 },
|
{ id: 'Pdilute-plot', quantity: 'Pdilute', title: 'Pdilute per PU' },
|
||||||
{ id: 'Cdilute-plot', quantity: 'Cdilute', title: 'Cdilute per PU' },
|
{ id: 'Cdilute-plot', quantity: 'Cdilute', title: 'Cdilute per PU' },
|
||||||
];
|
];
|
||||||
|
|
||||||
function makeTraces(quantity) {
|
|
||||||
return puList.map((pu, i) => ({
|
|
||||||
x: time0.slice(),
|
|
||||||
y: zero.slice(),
|
|
||||||
name: pu,
|
|
||||||
mode: 'lines',
|
|
||||||
line: { width: 2 },
|
|
||||||
legendgroup: pu
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
function initAllPlots() {
|
function initAllPlots() {
|
||||||
plots.forEach(plot => {
|
plots.forEach(plot => {
|
||||||
const data = makeTraces(plot.quantity);
|
const data = makeTraces(plot.quantity);
|
||||||
|
|
@ -70,19 +87,19 @@ function initAllPlots() {
|
||||||
yaxis: { title: plot.id.includes('P') ? 'Pressure (bar)' : 'Flow (L/h)' },
|
yaxis: { title: plot.id.includes('P') ? 'Pressure (bar)' : 'Flow (L/h)' },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add ref line if present
|
// Add dynamic reference line placeholder (flat 0 initially)
|
||||||
if (plot.ref !== undefined) {
|
if (plot.refKey) {
|
||||||
data.push({
|
data.push({
|
||||||
x: [time0[0], time0[0]],
|
x: time0.slice(),
|
||||||
y: [plot.ref, plot.ref],
|
y: [0],
|
||||||
mode: 'lines',
|
mode: 'lines',
|
||||||
line: { dash: 'dash', color: 'red' },
|
line: { dash: 'dash', color: 'red' },
|
||||||
name: `Ref ${plot.ref}`,
|
name: `${plot.refKey} (PU3)`,
|
||||||
showlegend: true
|
showlegend: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add QSkid trace only for Qperm plot
|
// Extra traces for Qperm plot
|
||||||
if (plot.id === 'Qperm-plot') {
|
if (plot.id === 'Qperm-plot') {
|
||||||
data.push({
|
data.push({
|
||||||
x: time0.slice(),
|
x: time0.slice(),
|
||||||
|
|
@ -92,8 +109,17 @@ function initAllPlots() {
|
||||||
line: { color: 'black', width: 2, dash: 'dot' },
|
line: { color: 'black', width: 2, dash: 'dot' },
|
||||||
legendgroup: 'PatientSkid'
|
legendgroup: 'PatientSkid'
|
||||||
});
|
});
|
||||||
|
data.push({
|
||||||
|
x: time0.slice(),
|
||||||
|
y: zero.slice(),
|
||||||
|
name: 'Qconso',
|
||||||
|
mode: 'lines',
|
||||||
|
line: { color: 'green', width: 2, dash: 'dot' },
|
||||||
|
legendgroup: 'Consumption'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extra trace for Qdrain plot
|
||||||
if (plot.id === 'Qdrain-plot') {
|
if (plot.id === 'Qdrain-plot') {
|
||||||
data.push({
|
data.push({
|
||||||
x: time0.slice(),
|
x: time0.slice(),
|
||||||
|
|
@ -116,15 +142,15 @@ async function updateAllPlots() {
|
||||||
const allData = await res.json();
|
const allData = await res.json();
|
||||||
const timestamp = new Date();
|
const timestamp = new Date();
|
||||||
|
|
||||||
// SkidData is only fetched once
|
|
||||||
const SkidData = allData["PatientSkid"] || {};
|
const SkidData = allData["PatientSkid"] || {};
|
||||||
const DSData = allData["DS"];
|
const DSData = allData["DS"] || {};
|
||||||
|
const pu3Data = allData["PU_3"] || {}; // <--- take ref values from PU_3
|
||||||
|
|
||||||
plots.forEach(plot => {
|
plots.forEach(plot => {
|
||||||
const xUpdates = [];
|
const xUpdates = [];
|
||||||
const yUpdates = [];
|
const yUpdates = [];
|
||||||
|
|
||||||
|
// Extend PU1, PU2, PU3 values
|
||||||
puList.forEach(pu => {
|
puList.forEach(pu => {
|
||||||
const puData = allData[pu] || {};
|
const puData = allData[pu] || {};
|
||||||
const value = puData[plot.quantity];
|
const value = puData[plot.quantity];
|
||||||
|
|
@ -134,38 +160,74 @@ async function updateAllPlots() {
|
||||||
|
|
||||||
Plotly.extendTraces(plot.id, { x: xUpdates, y: yUpdates }, puList.map((_, i) => i), maxPoints);
|
Plotly.extendTraces(plot.id, { x: xUpdates, y: yUpdates }, puList.map((_, i) => i), maxPoints);
|
||||||
|
|
||||||
if (plot.ref !== undefined) {
|
// Update PU3 reference line dynamically
|
||||||
Plotly.extendTraces(plot.id, {
|
if (plot.refKey) {
|
||||||
x: [[timestamp]],
|
const refVal = pu3Data[plot.refKey];
|
||||||
y: [[plot.ref]]
|
const refIndex = puList.length; // because ref trace was pushed last
|
||||||
}, [puList.length], maxPoints); // the ref line is always the last trace
|
Plotly.extendTraces(plot.id,
|
||||||
|
{ x: [[timestamp]], y: [[refVal ?? null]] },
|
||||||
|
[refIndex],
|
||||||
|
maxPoints
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extend PatientSkid.QSkid only for Qperm plot
|
// Extend QSkid + Qconso for Qperm
|
||||||
if (plot.id === 'Qperm-plot') {
|
if (plot.id === 'Qperm-plot') {
|
||||||
const qSkid = SkidData["QSkid"];
|
const qSkid = SkidData["QSkid"];
|
||||||
const skidX = [[timestamp]];
|
const qConso = DSData["Qconso"];
|
||||||
const skidY = [[qSkid !== undefined ? qSkid : null]];
|
const baseIndex = puList.length + (plot.refKey ? 1 : 0);
|
||||||
const qSkidTraceIndex = puList.length + (plot.ref !== undefined ? 1 : 0); // last trace index
|
Plotly.extendTraces(plot.id, { x: [[timestamp]], y: [[qSkid ?? null]] }, [baseIndex], maxPoints);
|
||||||
Plotly.extendTraces(plot.id, { x: skidX, y: skidY }, [qSkidTraceIndex], maxPoints);
|
Plotly.extendTraces(plot.id, { x: [[timestamp]], y: [[qConso ?? null]] }, [baseIndex + 1], maxPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extend Qconso for Qdrain
|
||||||
if (plot.id === 'Qdrain-plot') {
|
if (plot.id === 'Qdrain-plot') {
|
||||||
const Qconso = DSData["Qconso"];
|
const qConso = DSData["Qconso"];
|
||||||
const consoX = [[timestamp]];
|
const consoIndex = puList.length + (plot.refKey ? 1 : 0);
|
||||||
const consoY = [[Qconso !== undefined ? Qconso : null]];
|
Plotly.extendTraces(plot.id, { x: [[timestamp]], y: [[qConso ?? null]] }, [consoIndex], maxPoints);
|
||||||
const QconsoTraceIndex = puList.length + (plot.ref !== undefined ? 1 : 0); // last trace index
|
|
||||||
Plotly.extendTraces(plot.id, { x: consoX, y: consoY }, [QconsoTraceIndex], maxPoints);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sliding window (30s)
|
||||||
|
const layoutUpdate = {
|
||||||
|
'xaxis.range': [new Date(timestamp - windowMs), timestamp]
|
||||||
|
};
|
||||||
|
Plotly.relayout(plot.id, layoutUpdate);
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to update plots:", err);
|
console.error("Failed to update plots:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function makeTraces(quantity) {
|
||||||
|
return puList.map((pu) => ({
|
||||||
|
x: time0.slice(),
|
||||||
|
y: zero.slice(),
|
||||||
|
name: pu,
|
||||||
|
mode: 'lines',
|
||||||
|
line: { width: 2 },
|
||||||
|
legendgroup: pu
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function updateStatuses() {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/pu_status");
|
||||||
|
const statuses = await res.json();
|
||||||
|
puList.forEach((pu, i) => {
|
||||||
|
const el = document.getElementById(`PU${i+1}-status`);
|
||||||
|
el.textContent = `${pu}: ${statuses[`PU${i+1}`] || "Unknown"}`;
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching PU status:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
initAllPlots();
|
initAllPlots();
|
||||||
setInterval(updateAllPlots, 1000);
|
setInterval(updateAllPlots, 1000);
|
||||||
|
setInterval(updateStatuses, 1000);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user