Separates into 3 monitor pages

This commit is contained in:
Etienne Chassaing 2025-07-15 14:56:52 +02:00
parent f3cd47432c
commit 656ae95f19
4 changed files with 142 additions and 109 deletions

View File

@ -29,6 +29,31 @@ class CANBackend:
def get_latest_data(self, pu_number=1, data={}):
# Simulate getting the latest data with random values
if pu_number ==1:
return {
"FM2": 1080,
"FM3": 1080,
"FM4": 1080,
"FM5": 1080,
"PS1": 6.2,
"PS2": 6.2,
"PS3": 6.2,
"PS4": 6.2,
"Cond": 1* np.random.random(),
"MV02": round(100 * np.random.random(), 2),
"MV02_sp": round(100 * np.random.random(), 2),
"MV03": round(100 * np.random.random(), 2),
"MV03_sp": round(100 * np.random.random(), 2),
"MV05": round(100 * np.random.random(), 2),
"MV05_sp": round(100 * np.random.random(), 2),
"MV06": round(100 * np.random.random(), 2),
"MV06_sp": round(100 * np.random.random(), 2),
"MV07": round(100 * np.random.random(), 2),
"MV07_sp": round(100 * np.random.random(), 2),
"MV08": round(100 * np.random.random(), 2),
"MV08_sp": round(100 * np.random.random(), 2),
}
else :
return {
"FM2": round(1000 * np.random.random(), 1),
"FM3": round(1000 * np.random.random(), 1),

15
main.py
View File

@ -26,19 +26,9 @@ app.add_middleware(SessionMiddleware, secret_key="your_super_secret_key")
router = APIRouter()
templates = Jinja2Templates(directory="templates")
logging.basicConfig(level=logging.INFO)
can_backend = CANBackend()
# Mount static files
app.mount("/static", StaticFiles(directory="static"), name="static")
can_backend = CANBackend()
# Serve static files (HTML, JS, CSS)
app.mount("/static", StaticFiles(directory="static"), name="static")
# Jinja2 templates
templates = Jinja2Templates(directory="templates")
# CREDENTIALS
@ -164,6 +154,7 @@ def get_pu_status():
@app.get("/monitor")
def get_monitor_data(pu_number: Optional[int] = Query(None)):
logging.info(f"[MONITOR DATA] {pu_number}")
def format_data(data):
return {
"Qperm": data.get("FM2", 0.0),
@ -192,8 +183,8 @@ def get_monitor_data(pu_number: Optional[int] = Query(None)):
}
all_data = {}
for pu in [1, 2, 3]:
data = can_backend.get_latest_data(pu)
print(f"[MONITOR] PU{pu}: {data}")
data = can_backend.get_latest_data(pu_number=pu)
logging.info(f"[MONITOR] PU{pu}: {data}")
all_data[f"PU_{pu}"] = format_data(data)
return all_data

View File

@ -28,12 +28,6 @@
h1 {
text-align: center;
}
#puSelector {
display: block;
margin: 10px auto 20px auto;
font-size: 16px;
padding: 5px 10px;
}
#recordButton {
background-color: #ff4444;
color: white;
@ -47,13 +41,7 @@
</style>
</head>
<body>
<h1>Live Monitoring Dashboard</h1>
<label for="puSelector" style="text-align:center; display:block;">Select PU:</label>
<select id="puSelector">
<option value="1">PU 1</option>
<option value="2">PU 2</option>
<option value="3">PU 3</option>
</select>
<h1 id="pageTitle">Live Monitoring Dashboard</h1>
<button id="recordButton" onclick="toggleRecording()">Record</button>
<div class="plot-container">
<div id="flow-plot" class="large-plot"></div>
@ -66,6 +54,11 @@
<div id="mv08-plot" class="small-plot"></div>
</div>
<script>
// Extract PU number from URL
const urlParams = new URLSearchParams(window.location.search);
const puNumber = urlParams.get('pu_number') || '1'; // Default to PU 1 if not specified
document.getElementById('pageTitle').textContent = `Live Monitoring Dashboard - PU ${puNumber}`;
let isRecording = false;
let recordedData = [];
let recordingInterval;
@ -78,7 +71,7 @@
recordButton.style.backgroundColor = '#ff0000';
recordButton.textContent = 'Stop Recording';
recordedData = [];
csvFileName = `monitoring_data_${new Date().toISOString().replace(/[:.]/g, '-')}.csv`;
csvFileName = `monitoring_data_PU${puNumber}_${new Date().toISOString().replace(/[:.]/g, '-')}.csv`;
startRecording();
} else {
isRecording = false;
@ -90,28 +83,29 @@
function startRecording() {
recordingInterval = setInterval(async () => {
const response = await fetch(`/monitor?pu_number=${document.getElementById('puSelector').value}`);
const response = await fetch('/monitor');
if (!response.ok) {
console.error(`HTTP error! status: ${response.status}`);
return;
}
const pu = await response.json();
const allData = await response.json();
const puData = allData[`PU_${puNumber}`];
recordedData.push({
timestamp: new Date().toISOString(),
Qperm: pu.Qperm,
Qdilute: pu.Qdilute,
Qdrain: pu.Qdrain,
Qrecirc: pu.Qrecirc,
Pro: pu.Pro,
Pdilute: pu.Pdilute,
Prentate: pu.Prentate,
MV02: pu.MV02,
MV03: pu.MV03,
MV04: pu.MV04,
MV05: pu.MV05,
MV06: pu.MV06,
MV07: pu.MV07,
MV08: pu.MV08
Qperm: puData.Qperm,
Qdilute: puData.Qdilute,
Qdrain: puData.Qdrain,
Qrecirc: puData.Qrecirc,
Pro: puData.Pro,
Pdilute: puData.Pdilute,
Prentate: puData.Prentate,
MV02: puData.MV02,
MV03: puData.MV03,
MV04: puData.MV04,
MV05: puData.MV05,
MV06: puData.MV06,
MV07: puData.MV07,
MV08: puData.MV08
});
}, 100);
}
@ -124,7 +118,6 @@
recordedData.map(row =>
`${row.timestamp},${row.Qperm},${row.Qdilute},${row.Qdrain},${row.Qrecirc},${row.Pro},${row.Pdilute},${row.Prentate},${row.MV02},${row.MV03},${row.MV04},${row.MV05},${row.MV06},${row.MV07},${row.MV08}`
).join("\n");
const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
@ -140,14 +133,8 @@
}
};
// Existing plot initialization and update functions
const maxPoints = 100;
const time = () => new Date();
let selectedPU = 1;
document.getElementById("puSelector").addEventListener("change", function () {
selectedPU = parseInt(this.value);
});
function getLastMinuteRange() {
const now = new Date();
@ -157,28 +144,28 @@
async function updatePlots() {
try {
const response = await fetch(`/monitor?pu_number=${selectedPU}`);
const response = await fetch('/monitor');
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const pu = await response.json();
const allData = await response.json();
const puData = allData[`PU_${puNumber}`];
const t = time();
Plotly.extendTraces('flow-plot', {
x: [[t], [t], [t], [t]],
y: [[pu.Qperm], [pu.Qdilute], [pu.Qdrain], [pu.Qrecirc]]
y: [[puData.Qperm], [puData.Qdilute], [puData.Qdrain], [puData.Qrecirc]]
}, [0, 1, 2, 3], maxPoints);
Plotly.extendTraces('pressure-plot', {
x: [[t], [t], [t]],
y: [[pu.Pro], [pu.Pdilute], [pu.Prentate]]
y: [[puData.Pro], [puData.Pdilute], [puData.Prentate]]
}, [0, 1, 2], maxPoints);
//
// Plotly.extendTraces('mv02-plot', { x: [[t]], y: [[pu.MV02]] }, [0]);
// Plotly.extendTraces('mv03-plot', { x: [[t]], y: [[pu.MV03]] }, [0]);
// Plotly.extendTraces('mv02-plot', { x: [[t]], y: [[puData.MV02]] }, [0]);
// Plotly.extendTraces('mv03-plot', { x: [[t]], y: [[puData.MV03]] }, [0]);
// Plotly.extendTraces('mv04-05-plot', {
// x: [[t], [t]],
// y: [[pu.MV04], [pu.MV05]]
// y: [[puData.MV04], [puData.MV05]]
// }, [0, 1]);
// Plotly.extendTraces('mv06-plot', { x: [[t]], y: [[pu.MV06]] }, [0]);
// Plotly.extendTraces('mv07-plot', { x: [[t]], y: [[pu.MV07]] }, [0]);
// Plotly.extendTraces('mv08-plot', { x: [[t]], y: [[pu.MV08]] }, [0]);
// Plotly.extendTraces('mv06-plot', { x: [[t]], y: [[puData.MV06]] }, [0]);
// Plotly.extendTraces('mv07-plot', { x: [[t]], y: [[puData.MV07]] }, [0]);
// Plotly.extendTraces('mv08-plot', { x: [[t]], y: [[puData.MV08]] }, [0]);
const range = getLastMinuteRange();
const plotIds = [
@ -245,7 +232,7 @@
// }], {
// title: 'MV08 (%)', yaxis: { range: [0, 100] }, xaxis: { type: 'date' }
// });
setInterval(updatePlots, 1000);
setInterval(updatePlots, 250);
}
window.onload = initPlots;

View File

@ -36,7 +36,9 @@
align-items: center;
gap: 10px;
}
.connected { background-color: #00C851; }
.connected {
background-color: #00C851;
}
.container {
display: flex;
flex: 1;
@ -63,7 +65,10 @@
flex-direction: column;
gap: 10px;
}
.pu-buttons { display: flex; gap: 10px; }
.pu-buttons {
display: flex;
gap: 10px;
}
.mode-block button {
background-color: #4285F4;
color: white;
@ -78,10 +83,21 @@
.mode-block button:hover {
background-color: #3367d6;
}
.mode-block button.active { background-color: #00C851; }
.mode-block button.in-progress { background-color: #ffcc00; color: #000; }
.mode-block button.ready { background-color: #00C851; color: #fff; }
.mode-block button.disabled { background-color: #777; cursor: not-allowed; }
.mode-block button.active {
background-color: #00C851;
}
.mode-block button.in-progress {
background-color: #ffcc00;
color: #000;
}
.mode-block button.ready {
background-color: #00C851;
color: #fff;
}
.mode-block button.disabled {
background-color: #777;
cursor: not-allowed;
}
.in-progress {
background-color: yellow !important;
color: black !important;
@ -94,7 +110,9 @@
background-color: green !important;
color: white !important;
}
.pu-status { margin-top: 20px; }
.pu-status {
margin-top: 20px;
}
.pu-item {
background-color: #333;
padding: 10px;
@ -171,7 +189,9 @@
text-decoration: none;
font-weight: bold;
}
.monitor-link:hover { background-color: #0056b3; }
.monitor-link:hover {
background-color: #0056b3;
}
.feed-valve-buttons {
display: flex;
gap: 10px;
@ -189,14 +209,38 @@
.feed-valve-buttons button.active {
background-color: #00C851;
}
.monitor-pu-buttons {
display: flex;
gap: 10px;
margin: 10px;
}
.monitor-pu-buttons a {
color: white;
background-color: #007bff;
padding: 10px 15px;
border-radius: 5px;
text-decoration: none;
font-weight: bold;
}
.monitor-pu-buttons a:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="header">
<h1>Hydraulic Machine Control</h1>
<a href="/monitor-page" target="_blank" rel="noopener noreferrer" class="monitor-link">
<i class="fas fa-chart-line"></i> Monitor
<div class="monitor-pu-buttons">
<a href="/monitor-page?pu_number=1" target="_blank" class="monitor-link">
<i class="fas fa-chart-line"></i> Monitor PU 1
</a>
<a href="/monitor-page?pu_number=2" target="_blank" class="monitor-link">
<i class="fas fa-chart-line"></i> Monitor PU 2
</a>
<a href="/monitor-page?pu_number=3" target="_blank" class="monitor-link">
<i class="fas fa-chart-line"></i> Monitor PU 3
</a>
</div>
<button id="connectButton" class="connect-button" onclick="toggleConnection()">
<i class="fas fa-power-off"></i> Connect
</button>
@ -286,7 +330,6 @@
function updatePloopSetpoint(value) {
document.getElementById('currentValue').textContent = value;
}
async function toggleConnection() {
const response = await fetch('/connect_toggle', { method: 'POST' });
const data = await response.json();
@ -294,14 +337,12 @@ async function toggleConnection() {
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) {
const ploopSetpoint = document.getElementById('ploopSetpoint').value;
await fetch(`/command/${state}/pu/${puNumber}?ploop_setpoint=${ploopSetpoint}`, { method: 'POST' });
document.querySelectorAll('button').forEach(btn => {
btn.classList.remove('in-progress', 'ready', 'production');
});
if (state === 'PRE-PRODUCTION') {
buttonEl.classList.add('in-progress');
buttonEl.textContent = `Waiting... PU ${puNumber}`;
@ -334,7 +375,6 @@ async function sendCommand(state, puNumber, buttonEl) {
buttonEl.classList.remove('in-progress', 'ready', 'production');
buttonEl.classList.add('production');
buttonEl.textContent = `${state.replace('_', ' ')} PU ${puNumber}`;
const preProdBtn = document.querySelector(`button[data-action="PRE-PRODUCTION"][data-pu="${puNumber}"]`);
if (preProdBtn) {
preProdBtn.classList.remove('in-progress', 'ready', 'production');
@ -342,7 +382,6 @@ async function sendCommand(state, puNumber, buttonEl) {
preProdBtn.disabled = false;
preProdBtn.onclick = () => sendCommand("PRE-PRODUCTION", puNumber, preProdBtn);
}
const idleBtn = document.querySelector(`button[data-action="IDLE"][data-pu="${puNumber}"]`);
if (idleBtn && idleBtn !== buttonEl) {
idleBtn.classList.remove('in-progress', 'ready', 'production');
@ -350,7 +389,6 @@ async function sendCommand(state, puNumber, buttonEl) {
idleBtn.disabled = false;
idleBtn.onclick = () => sendCommand("IDLE", puNumber, idleBtn);
}
const firstStartBtn = document.querySelector(`button[data-action="FIRST_START"][data-pu="${puNumber}"]`);
if (firstStartBtn && firstStartBtn !== buttonEl) {
firstStartBtn.classList.remove('in-progress', 'ready', 'production');
@ -360,7 +398,6 @@ async function sendCommand(state, puNumber, buttonEl) {
}
}
}
async function setFeedValve(opening, buttonEl) {
await fetch(`/command/feed_valve?MV01_opening=${opening}`, { method: 'POST' });
document.querySelectorAll('.feed-valve-buttons button').forEach(btn => {
@ -368,7 +405,6 @@ async function setFeedValve(opening, buttonEl) {
});
buttonEl.classList.add('active');
}
async function fetchPUStatus() {
const response = await fetch("/api/pu_status");
const data = await response.json();
@ -376,10 +412,8 @@ async function fetchPUStatus() {
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();
@ -405,7 +439,6 @@ async function updateMonitorData() {
`;
}
}
function updateMonitorValues(id, values, unit) {
const container = document.getElementById(id);
const valueElements = container.querySelectorAll('.monitor-value');
@ -415,9 +448,7 @@ function updateMonitorValues(id, values, unit) {
}
});
}
setInterval(updateMonitorData, 1000);
async function fetchMonitorData() {
try {
const puMap = {
@ -450,7 +481,6 @@ async function fetchMonitorData() {
console.error('Error fetching monitor data:', error);
}
}
setInterval(fetchMonitorData, 1000);
fetchMonitorData();
</script>