266 lines
10 KiB
JavaScript
266 lines
10 KiB
JavaScript
const fetchInterval = 500;
|
|
|
|
// Configuration constants from DemoPage.html
|
|
const MEAN = 210;
|
|
const WARNING_LIMIT = 2;
|
|
const ERROR_LIMIT = 3;
|
|
const TARGET_VALUE = 210;
|
|
const ACQUISITION_LEN = 500;
|
|
|
|
// State
|
|
let dataPoints = [];
|
|
let chart;
|
|
let timerInterval;
|
|
let isAcquiring = false;
|
|
let currentMode = 'live';
|
|
let activeSessionData = [];
|
|
let currentPage = 1;
|
|
let fullDataPage = 1;
|
|
|
|
// DOM Elements
|
|
const startBtn = document.getElementById('startBtn');
|
|
const progressBar = document.getElementById('progressBar');
|
|
const progressContainer = document.getElementById('progressContainer');
|
|
const timerDisplay = document.getElementById('timerDisplay');
|
|
const tableBody = document.getElementById('measurementsTable');
|
|
const detailPlaceholder = document.getElementById('detailPlaceholder');
|
|
const detailContent = document.getElementById('detailContent');
|
|
const fullDataTableBody = document.getElementById('fullDataTableBody');
|
|
const historyTableBody = document.getElementById('historyTableBody');
|
|
const pageInfo = document.getElementById('pageInfo');
|
|
const prevBtn = document.getElementById('prevPage');
|
|
const nextBtn = document.getElementById('nextPage');
|
|
const fullPrevBtn = document.getElementById('fullPrevPage');
|
|
const fullNextBtn = document.getElementById('fullNextPage');
|
|
const liveDataContainer = document.getElementById('liveDataContainer');
|
|
const historyDetailContainer = document.getElementById('historyDetailContainer');
|
|
const selectedData = document.getElementById('selectedData');
|
|
const plcIpDisplay = document.getElementById('plcIpDisplay');
|
|
const connStatusDot = document.getElementById('connStatusDot');
|
|
const latencyList = document.getElementById('latencyList');
|
|
const acquisitionStatusList = document.getElementById('acquisitionStatusList');
|
|
|
|
// Initialize Chart.js
|
|
function initChart() {
|
|
const ctx = document.getElementById('liveChart').getContext('2d');
|
|
chart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
datasets: [
|
|
{ label: 'Value (mm)', data: [], borderColor: '#3b82f6', backgroundColor: 'rgba(59, 130, 246, 0.1)', fill: true, pointRadius: 0, borderWidth: 2, tension: 0 },
|
|
{ label: 'Warning UCL', data: [], borderColor: '#facc15', borderWidth: 1, pointRadius: 0, fill: false, borderDash: [5, 5] },
|
|
{ label: 'Warning LCL', data: [], borderColor: '#facc15', borderWidth: 1, pointRadius: 0, fill: false, borderDash: [5, 5] },
|
|
{ label: 'Error UCL', data: [], borderColor: '#f87171', borderWidth: 1, pointRadius: 0, fill: false, borderDash: [5, 5] },
|
|
{ label: 'Error LCL', data: [], borderColor: '#f87171', borderWidth: 1, pointRadius: 0, fill: false, borderDash: [5, 5] },
|
|
{ label: 'Target Value', data: [], borderColor: '#22c55e', borderWidth: 1.5, pointRadius: 0, fill: false, tension: 0 }
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
x: { type: 'linear', min: 0, max: ACQUISITION_LEN, title: { display: true, text: 'Position (mm)' } },
|
|
y: { min: 200, max: 220 }
|
|
},
|
|
animation: false
|
|
}
|
|
});
|
|
}
|
|
|
|
// Fetch data from Flask and update UI
|
|
async function updateData() {
|
|
try {
|
|
const response = await fetch('/data');
|
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
|
const data = await response.json();
|
|
console.log("Data received:", data);
|
|
|
|
if (data) {
|
|
const db100 = data.db100;
|
|
|
|
// Update Connectivity Status
|
|
if (data.connected !== undefined) {
|
|
connStatusDot.className = data.connected
|
|
? 'w-2 h-2 bg-green-400 rounded-full mr-2 animate-pulse'
|
|
: 'w-2 h-2 bg-red-600 rounded-full mr-2';
|
|
plcIpDisplay.textContent = data.plc_ip || "Disconnected";
|
|
}
|
|
|
|
if (db100) {
|
|
// Update Latencies
|
|
if (db100.duration !== undefined) {
|
|
latencyList.textContent = `Latency: ${db100.duration.toFixed(2)}ms`;
|
|
}
|
|
|
|
// Update Live Values (if needed)
|
|
if (db100.params && db100.params.x_live !== undefined) {
|
|
// Could update a specific UI element for X_Live here
|
|
}
|
|
}
|
|
|
|
// Update Chart data if acquiring
|
|
if (isAcquiring && data.db101 && data.db101.values) {
|
|
chart.data.datasets[0].data = data.db101.values.map((v, i) => ({ x: i * (ACQUISITION_LEN / 40), y: v }));
|
|
chart.update();
|
|
}
|
|
|
|
// Update acquisition status text
|
|
if (isAcquiring) {
|
|
acquisitionStatusList.textContent = `• Mode: Active Acquisition`;
|
|
acquisitionStatusList.parentElement.classList.replace('text-gray-600', 'text-blue-400');
|
|
} else {
|
|
acquisitionStatusList.textContent = `• Mode: Polling (5Hz)`;
|
|
acquisitionStatusList.parentElement.classList.replace('text-blue-400', 'text-gray-600');
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error("Error updating data:", err);
|
|
}
|
|
}
|
|
|
|
// Start acquisition (Mocked logic for now to match DemoPage.html)
|
|
startBtn.addEventListener('click', () => {
|
|
if (isAcquiring) return;
|
|
|
|
dataPoints = [];
|
|
activeSessionData = [];
|
|
chart.data.datasets.forEach(ds => ds.data = []);
|
|
chart.update();
|
|
|
|
isAcquiring = true;
|
|
startBtn.disabled = true;
|
|
startBtn.classList.replace('bg-green-600', 'bg-gray-400');
|
|
progressContainer.classList.remove('hidden');
|
|
|
|
currentMode = 'live';
|
|
toggleDetailVisibility();
|
|
|
|
let currentStep = 0;
|
|
|
|
let timerInterval = setInterval(() => {
|
|
const point = {
|
|
ts: new Date(),
|
|
val: generateNormal(MEAN, 1),
|
|
x: (currentStep / (TOTAL_SAMPLES - 1)) * ACQUISITION_LEN
|
|
};
|
|
dataPoints.push(point);
|
|
activeSessionData = [...dataPoints];
|
|
|
|
// Update Chart data
|
|
chart.data.datasets[0].data.push({x: point.x, y: point.val});
|
|
chart.data.datasets[1].data.push({x: point.x, y: MEAN + WARNING_LIMIT});
|
|
chart.data.datasets[2].data.push({x: point.x, y: MEAN - WARNING_LIMIT});
|
|
chart.data.datasets[3].data.push({x: point.x, y: MEAN + ERROR_LIMIT});
|
|
chart.data.datasets[4].data.push({x: point.x, y: MEAN - ERROR_LIMIT});
|
|
chart.data.datasets[5].data.push({x: point.x, y: TARGET_VALUE});
|
|
|
|
if (chart.data.datasets[0].data.length > 100) {
|
|
chart.data.datasets.forEach(ds => ds.data.shift());
|
|
}
|
|
chart.update();
|
|
|
|
if (currentStep % 3 === 0) {
|
|
updateTable();
|
|
}
|
|
|
|
currentStep++;
|
|
const progress = (currentStep / TOTAL_SAMPLES) * 100;
|
|
progressBar.style.width = `${progress}%`;
|
|
timerDisplay.innerText = `Time Remaining: ${Math.max(0, Math.ceil((TOTAL_SAMPLES - currentStep) / SAMPLE_RATE))}s`;
|
|
|
|
if (currentStep >= TOTAL_SAMPLES) {
|
|
clearInterval(timerInterval);
|
|
completeAcquisition();
|
|
}
|
|
}, 1000 / SAMPLE_RATE);
|
|
});
|
|
|
|
function completeAcquisition() {
|
|
isAcquiring = false;
|
|
startBtn.disabled = false;
|
|
startBtn.classList.replace('bg-gray-400', 'bg-green-600');
|
|
timerDisplay.innerText = "Acquisition Complete";
|
|
timerDisplay.classList.replace('text-blue-600', 'text-green-600');
|
|
|
|
activeSessionData = [...dataPoints].sort((a, b) => b.ts - a.ts);
|
|
|
|
liveDataContainer.classList.remove('hidden');
|
|
historyDetailContainer.classList.add('hidden');
|
|
renderFullSessionTable();
|
|
|
|
detailPlaceholder.classList.add('hidden');
|
|
detailContent.classList.remove('hidden');
|
|
selectedData.innerText = `Session completed. Captured ${dataPoints.length} points. Mean: ${dataPoints.reduce((a,b)=>a+b.val,0)/dataPoints.length}`;
|
|
}
|
|
|
|
function renderFullSessionTable() {
|
|
fullDataTableBody.innerHTML = '';
|
|
const start = (fullDataPage - 1) * itemsPerPage;
|
|
const end = start + itemsPerPage;
|
|
const pageData = activeSessionData.slice(start, end);
|
|
|
|
pageData.forEach((p, i) => {
|
|
const row = document.createElement('tr');
|
|
const diff = Math.abs(p.val - MEAN);
|
|
let status = 'OK';
|
|
let colorClass = 'text-green-600 border-green-600';
|
|
|
|
if (diff > ERROR_LIMIT) {
|
|
status = 'ERROR';
|
|
colorClass = 'text-red-600 border-red-600';
|
|
} else if (diff > WARNING_LIMIT) {
|
|
status = 'WARNING';
|
|
colorClass = 'text-yellow-600 border-yellow-600';
|
|
}
|
|
|
|
row.innerHTML = `
|
|
<td class="p-2 border-b">${start + i + 1}</td>
|
|
<td class="p-2 border-b">${p.ts.toLocaleTimeString()}</td>
|
|
<td class="p-2 border-b font-mono">${p.val.toFixed(2)}</td>
|
|
<td class="p-2 border-b">
|
|
<span class="px-1 py-0.5 bg-white rounded-full text-[9px] font-bold border ${colorClass}">${status}</span>
|
|
</td>
|
|
`;
|
|
fullDataTableBody.appendChild(row);
|
|
});
|
|
|
|
fullPageInfo.innerText = `Page ${fullDataPage} of ${Math.ceil(activeSessionData.length / itemsPerPage)}`;
|
|
fullPrevBtn.disabled = fullDataPage === 1;
|
|
fullNextBtn.disabled = fullDataPage * itemsPerPage >= activeSessionData.length;
|
|
}
|
|
|
|
function toggleDetailVisibility() {
|
|
if (isAcquiring || currentMode === 'history') {
|
|
liveDataContainer.classList.remove('hidden');
|
|
historyDetailContainer.classList.add('hidden');
|
|
if (isAcquiring) {
|
|
activeSessionData = [...dataPoints];
|
|
renderFullSessionTable();
|
|
}
|
|
} else {
|
|
liveDataContainer.classList.add('hidden');
|
|
historyDetailContainer.classList.add('hidden');
|
|
detailPlaceholder.classList.remove('hidden');
|
|
}
|
|
}
|
|
|
|
// Initialization
|
|
initChart();
|
|
setInterval(updateData, fetchInterval);
|
|
updateData();
|
|
|
|
// Pagination
|
|
fullPrevBtn.onclick = () => {
|
|
if (fullDataPage > 1) {
|
|
fullDataPage--;
|
|
renderFullSessionTable();
|
|
}
|
|
};
|
|
|
|
fullNextBtn.onclick = () => {
|
|
if (fullDataPage * itemsPerPage < activeSessionData.length) {
|
|
fullDataPage++;
|
|
renderFullSessionTable();
|
|
}
|
|
};
|