439 lines
18 KiB
JavaScript
439 lines
18 KiB
JavaScript
const fetchInterval = 500;
|
|
|
|
// Configuration constants
|
|
const MEAN = 0;
|
|
const WARNING_LIMIT = 2000;
|
|
const ERROR_LIMIT = 3000;
|
|
const TARGET_VALUE = 0;
|
|
|
|
// State
|
|
let chart;
|
|
let isAcquiring = false;
|
|
let currentMode = 'live';
|
|
let activeSessionData = [];
|
|
let history = [];
|
|
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: 500, title: { display: true, text: 'Position (mm)' } },
|
|
y: { min: -3, max: 3 }
|
|
},
|
|
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();
|
|
|
|
if (data) {
|
|
// 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 (data.db100 && data.db100.duration !== undefined) {
|
|
latencyList.textContent = `Latency: ${data.db100.duration.toFixed(2)}ms`;
|
|
}
|
|
|
|
// Update acquisition status text
|
|
if (data.acq_status !== undefined) {
|
|
acquisitionStatusList.textContent = `• Mode: ${data.acq_status}`;
|
|
if (data.acq_status === "Acquisition") {
|
|
acquisitionStatusList.parentElement.classList.replace('text-gray-600', 'text-blue-400');
|
|
} else {
|
|
acquisitionStatusList.parentElement.classList.replace('text-blue-400', 'text-gray-600');
|
|
}
|
|
}
|
|
|
|
// Handle Live Chart Update
|
|
if (data.db901 && data.db901.params) {
|
|
const acq = data.db901.params.acquisizione;
|
|
const fine = data.db901.params.fine_acq;
|
|
const pronti = data.db901.params.pronti;
|
|
|
|
// Prendi i limiti dinamici (ucl, lcl, target) dal JSON della risposta API
|
|
const limits = data.limits || { ucl: 212, lcl: 208, target: 0 };
|
|
|
|
if (acq && !fine) {
|
|
acquisitionStatusList.textContent = `• Mode: Acquisition`;
|
|
acquisitionStatusList.parentElement.classList.replace('text-gray-600', 'text-blue-400');
|
|
|
|
if (data.db901.params.x_val !== undefined && data.db901.params.y_val !== undefined) {
|
|
const x = data.db901.params.x_val;
|
|
const y = data.db901.params.y_val;
|
|
|
|
chart.data.datasets[0].data.push({ x, y });
|
|
chart.data.datasets[1].data.push({ x, y: limits.ucl });
|
|
chart.data.datasets[2].data.push({ x, y: limits.lcl });
|
|
chart.data.datasets[3].data.push({ x, y: limits.ucl });
|
|
chart.data.datasets[4].data.push({ x, y: limits.lcl });
|
|
chart.data.datasets[5].data.push({ x, y: limits.target });
|
|
|
|
if (chart.data.datasets[0].data.length > 100) {
|
|
chart.data.datasets.forEach(ds => ds.data.shift());
|
|
}
|
|
chart.update('none');
|
|
}
|
|
} else if (fine && pronti) {
|
|
acquisitionStatusList.textContent = `• Mode: Finished`;
|
|
acquisitionStatusList.parentElement.classList.replace('text-blue-400', 'text-green-600');
|
|
|
|
if (data.db900 && data.db900.misure && data.db900.misure.length > 0) {
|
|
activeSessionData = data.db900.misure.map(m => ({
|
|
x: m.x,
|
|
y: m.y,
|
|
ts: m.ts || new Date()
|
|
}));
|
|
|
|
chart.data.datasets.forEach(ds => ds.data = []);
|
|
activeSessionData.forEach(p => {
|
|
chart.data.datasets[0].data.push({ x: p.x, y: p.y });
|
|
chart.data.datasets[1].data.push({ x: p.x, y: limits.ucl });
|
|
chart.data.datasets[2].data.push({ x: p.x, y: limits.lcl });
|
|
chart.data.datasets[3].data.push({ x: p.x, y: limits.ucl });
|
|
chart.data.datasets[4].data.push({ x: p.x, y: limits.lcl });
|
|
chart.data.datasets[5].data.push({ x: p.x, y: limits.target });
|
|
});
|
|
|
|
chart.update();
|
|
// Mostra dashboard dettagli...
|
|
}
|
|
} else {
|
|
acquisitionStatusList.textContent = `• Mode: Polling (5Hz)`;
|
|
acquisitionStatusList.parentElement.classList.replace('text-blue-400', 'text-gray-600');
|
|
}
|
|
}
|
|
// if (data.db901 && data.db901.params) {
|
|
// const x = data.db901.params.x;
|
|
// const y = data.db901.params.y;
|
|
|
|
// if (data.acq_status === "Acquisition") {
|
|
// if (x !== undefined && y !== undefined) {
|
|
// activeSessionData.push({x, y, ts: new Date()});
|
|
|
|
// chart.data.datasets[0].data.push({x, y});
|
|
// chart.data.datasets[1].data.push({x, y: MEAN + WARNING_LIMIT});
|
|
// chart.data.datasets[2].data.push({x, y: MEAN - WARNING_LIMIT});
|
|
// chart.data.datasets[3].data.push({x, y: MEAN + ERROR_LIMIT});
|
|
// chart.data.datasets[4].data.push({x, y: MEAN - ERROR_LIMIT});
|
|
// chart.data.datasets[5].data.push({x, y: TARGET_VALUE});
|
|
|
|
// if (chart.data.datasets[0].data.length > 100) {
|
|
// chart.data.datasets.forEach(ds => ds.data.shift());
|
|
// }
|
|
// chart.update();
|
|
// }
|
|
// } else if (data.acq_status === "Finished") {
|
|
// if (data.measurements && data.measurements.length > 0) {
|
|
// activeSessionData = data.measurements.map(m => ({
|
|
// x: m.x,
|
|
// y: m.y,
|
|
// ts: m.ts || new Date()
|
|
// }));
|
|
|
|
// // Clear chart and load all measurements
|
|
// chart.data.datasets.forEach(ds => ds.data = []);
|
|
// activeSessionData.forEach(p => {
|
|
// chart.data.datasets[0].data.push({x: p.x, y: p.y});
|
|
// chart.data.datasets[1].data.push({x: p.x, y: MEAN + WARNING_LIMIT});
|
|
// chart.data.datasets[2].data.push({x: p.x, y: MEAN - WARNING_LIMIT});
|
|
// chart.data.datasets[3].data.push({x: p.x, y: MEAN + ERROR_LIMIT});
|
|
// chart.data.datasets[4].data.push({x: p.x, y: MEAN - ERROR_LIMIT});
|
|
// chart.data.datasets[5].data.push({x: p.x, y: TARGET_VALUE});
|
|
// });
|
|
// chart.update();
|
|
|
|
// liveDataContainer.classList.remove('hidden');
|
|
// historyDetailContainer.classList.add('hidden');
|
|
// detailPlaceholder.classList.add('hidden');
|
|
// detailContent.classList.remove('hidden');
|
|
// renderFullSessionTable();
|
|
// }
|
|
// }
|
|
// }
|
|
}
|
|
} catch (err) {
|
|
console.error("Error updating data:", err);
|
|
}
|
|
}
|
|
|
|
// Fetch history
|
|
async function fetchHistory() {
|
|
try {
|
|
const response = await fetch('/history');
|
|
if (response.ok) {
|
|
history = await response.json();
|
|
renderHistoryTable();
|
|
}
|
|
} catch (err) {
|
|
console.error("Error fetching history:", err);
|
|
}
|
|
}
|
|
|
|
function renderHistoryTable() {
|
|
historyTableBody.innerHTML = '';
|
|
if (history.length === 0) {
|
|
historyTableBody.innerHTML = '<tr><td colspan="4" class="p-2 text-center text-gray-400">No sessions recorded</td></tr>';
|
|
return;
|
|
}
|
|
|
|
const start = (currentPage - 1) * 10;
|
|
const end = start + 10;
|
|
const pageData = history.slice(start, end);
|
|
|
|
pageData.forEach((item, i) => {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td class="p-2 border-b">${item.filename}</td>
|
|
<td class="p-2 border-b">${item.articolo} / ${item.cod_produzione}</td>
|
|
<td class="p-2 border-b">${new Date().toLocaleDateString()}</td>
|
|
<td class="p-2 border-b">
|
|
<button class="bg-blue-600 text-white px-2 py-1 rounded text-[10px] hover:bg-blue-700">View</button>
|
|
</td>
|
|
`;
|
|
row.onclick = () => loadHistoryFile(item.file_path);
|
|
historyTableBody.appendChild(row);
|
|
});
|
|
|
|
pageInfo.innerText = `Page ${currentPage} of ${Math.ceil(history.length / 10)}`;
|
|
prevBtn.disabled = currentPage === 1;
|
|
nextBtn.disabled = currentPage * 10 >= history.length;
|
|
}
|
|
|
|
async function loadHistoryFile(filePath) {
|
|
try {
|
|
const response = await fetch(`/history/${filePath}`);
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
activeSessionData = data.misure.map(m => ({
|
|
x: m.x,
|
|
y: m.y,
|
|
ts: m.ts || new Date()
|
|
}));
|
|
|
|
// Update chart
|
|
chart.data.datasets.forEach(ds => ds.data = []);
|
|
activeSessionData.forEach(p => {
|
|
chart.data.datasets[0].data.push({x: p.x, y: p.y});
|
|
chart.data.datasets[1].data.push({x: p.x, y: MEAN + WARNING_LIMIT});
|
|
chart.data.datasets[2].data.push({x: p.x, y: MEAN - WARNING_LIMIT});
|
|
chart.data.datasets[3].data.push({x: p.x, y: MEAN + ERROR_LIMIT});
|
|
chart.data.datasets[4].data.push({x: p.x, y: MEAN - ERROR_LIMIT});
|
|
chart.data.datasets[5].data.push({x: p.x, y: TARGET_VALUE});
|
|
});
|
|
chart.update();
|
|
|
|
liveDataContainer.classList.remove('hidden');
|
|
historyDetailContainer.classList.remove('hidden');
|
|
detailPlaceholder.classList.add('hidden');
|
|
detailContent.classList.remove('hidden');
|
|
|
|
renderFullSessionTable();
|
|
|
|
selectedData.innerText = `Session Loaded: ${data.articolo} | ${data.cod_produzione} | ${data.num_certificato}`;
|
|
}
|
|
} catch (err) {
|
|
console.error("Error loading history file:", err);
|
|
}
|
|
}
|
|
|
|
function renderFullSessionTable() {
|
|
fullDataTableBody.innerHTML = '';
|
|
if (activeSessionData.length === 0) return;
|
|
|
|
const start = (fullDataPage - 1) * 50;
|
|
const end = start + 50;
|
|
const pageData = activeSessionData.slice(start, end);
|
|
|
|
pageData.forEach((p, i) => {
|
|
const row = document.createElement('tr');
|
|
const diff = Math.abs(p.y - 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.y.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 / 50)}`;
|
|
fullPrevBtn.disabled = fullDataPage === 1;
|
|
fullNextBtn.disabled = fullDataPage * 50 >= activeSessionData.length;
|
|
}
|
|
|
|
// Event Listeners
|
|
startBtn.onclick = () => {
|
|
if (isAcquiring) return;
|
|
isAcquiring = true;
|
|
startBtn.disabled = true;
|
|
startBtn.classList.replace('bg-green-600', 'bg-gray-400');
|
|
progressContainer.classList.remove('hidden');
|
|
|
|
activeSessionData = [];
|
|
currentMode = 'live';
|
|
toggleDetailVisibility();
|
|
|
|
let currentStep = 0;
|
|
let timerInterval = setInterval(() => {
|
|
const point = {
|
|
ts: new Date(),
|
|
val: generateNormal(MEAN, 1),
|
|
x: (currentStep / 500) * 500
|
|
};
|
|
activeSessionData.push(point);
|
|
|
|
// 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 / 500) * 100;
|
|
progressBar.style.width = `${progress}%`;
|
|
timerDisplay.innerText = `Time Remaining: ${Math.max(0, Math.ceil((500 - currentStep) / 5))}s`;
|
|
|
|
if (currentStep >= 500) {
|
|
clearInterval(timerInterval);
|
|
completeAcquisition();
|
|
}
|
|
}, 1000 / 5);
|
|
};
|
|
|
|
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');
|
|
detailPlaceholder.classList.add('hidden');
|
|
detailContent.classList.remove('hidden');
|
|
renderFullSessionTable();
|
|
}
|
|
|
|
function updateTable() {
|
|
if (tableBody.innerHTML === '') {
|
|
tableBody.innerHTML = '<tr><td colspan="4" class="p-2 text-center text-gray-400">Initializing...</td></tr>';
|
|
}
|
|
// This is a simplified update for the simulation mode
|
|
}
|
|
|
|
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');
|
|
}
|
|
}
|
|
|
|
// Helper functions
|
|
function generateNormal(mean, stdDev) {
|
|
let u = 0, v = 0;
|
|
while (u === 0) u = Math.random();
|
|
while (v === 0) v = Math.random();
|
|
return mean + stdDev * (Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v));
|
|
}
|
|
|
|
const TOTAL_SAMPLES = 500;
|
|
const SAMPLE_RATE = 5;
|
|
const itemsPerPage = 50;
|
|
|
|
// Initialization
|
|
initChart();
|
|
fetchHistory();
|
|
setInterval(updateData, fetchInterval);
|
|
updateData();
|
|
|
|
// Pagination Listeners
|
|
prevBtn.onclick = () => { if (currentPage > 1) { currentPage--; renderHistoryTable(); } };
|
|
nextBtn.onclick = () => { if (currentPage * 10 < history.length) { currentPage++; renderHistoryTable(); } };
|
|
fullPrevBtn.onclick = () => { if (fullDataPage > 1) { fullDataPage--; renderFullSessionTable(); } };
|
|
fullNextBtn.onclick = () => { if (fullDataPage * itemsPerPage < activeSessionData.length) { fullDataPage++; renderFullSessionTable(); } };
|