Separates into 3 monitor pages
This commit is contained in:
parent
f3cd47432c
commit
656ae95f19
25
MockCAN.py
25
MockCAN.py
|
|
@ -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
15
main.py
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user