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 = ` ${start + i + 1} ${p.ts.toLocaleTimeString()} ${p.val.toFixed(2)} ${status} `; 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(); } };