const scriptPath = document.currentScript ? document.currentScript.src : window.location.href; const API_BASE = scriptPath.substring(0, scriptPath.lastIndexOf('/') + 1) + 'api/'; // Store device info globally to simplify reload let filteredDevices = []; // Grouped: { "ControllerName": [ {dev info...}, {dev info...} ] } let groupedDevices = {}; let currentRange = 'day'; let chartInstances = {}; async function init() { setupControls(); await loadDevices(); } function setupControls() { // Range Buttons document.querySelectorAll('.range-btn').forEach(btn => { btn.addEventListener('click', (e) => { document.querySelectorAll('.range-btn').forEach(b => b.classList.remove('active')); e.target.classList.add('active'); currentRange = e.target.dataset.range; loadData(); }); }); } /** * Fetch and setup device containers (Grouped by Controller) */ async function loadDevices() { try { const res = await fetch(`${API_BASE}devices`); const rawDevices = await res.json(); const container = document.getElementById('devicesContainer'); container.innerHTML = ''; if (rawDevices.length === 0) { container.innerHTML = '

No devices found.

'; return; } // Group by Controller Name groupedDevices = rawDevices.reduce((acc, dev) => { if (!acc[dev.dev_name]) acc[dev.dev_name] = []; acc[dev.dev_name].push(dev); return acc; }, {}); // Create Section per Controller for (const [controllerName, ports] of Object.entries(groupedDevices)) { const safeControllerName = controllerName.replace(/\s+/g, '_'); const section = document.createElement('div'); section.className = 'controller-section'; // Generate Ports HTML let portsHtml = ''; ports.forEach(port => { const safePortId = `${controllerName}_${port.port}`.replace(/\s+/g, '_'); const isLight = port.port_name && port.port_name.toLowerCase().includes('light'); const levelTitle = isLight ? 'Brightness' : 'Fan Speed'; portsHtml += `

${port.port_name || 'Port ' + port.port}

`; }); section.innerHTML = `

${controllerName}

Environment (Temp / Humidity)

${portsHtml}
`; container.appendChild(section); } // Trigger initial data load loadData(); } catch (err) { console.error("Failed to load devices", err); } } /** * Fetch data and render */ async function loadData() { for (const [controllerName, ports] of Object.entries(groupedDevices)) { const safeControllerName = controllerName.replace(/\s+/g, '_'); // 1. Fetch Environment Data (Use first port as representative source) if (ports.length > 0) { const firstPort = ports[0]; try { const res = await fetch(`${API_BASE}history?devName=${encodeURIComponent(controllerName)}&port=${firstPort.port}&range=${currentRange}`); const data = await res.json(); renderEnvChart(safeControllerName, data); } catch (err) { console.error(`Failed to load env data for ${controllerName}`, err); } } // 2. Fetch Level Data for EACH port for (const port of ports) { const safePortId = `${controllerName}_${port.port}`.replace(/\s+/g, '_'); try { const res = await fetch(`${API_BASE}history?devName=${encodeURIComponent(controllerName)}&port=${port.port}&range=${currentRange}`); const data = await res.json(); const isLight = port.port_name && port.port_name.toLowerCase().includes('light'); renderLevelChart(safePortId, data, isLight); } catch (err) { console.error(`Failed to load level data for ${controllerName}:${port.port}`, err); } } } } function renderEnvChart(safeName, data) { const labels = data.map(d => new Date(d.timestamp).toLocaleString()); const ctx = document.getElementById(`env_${safeName}`).getContext('2d'); updateChart(`env_${safeName}`, ctx, labels, [ { label: 'Temperature (°C)', data: data.map(d => d.temp_c), borderColor: '#ff6384', yAxisID: 'y' }, { label: 'Humidity (%)', data: data.map(d => d.humidity), borderColor: '#36a2eb', yAxisID: 'y1' } ], { scales: { y: { type: 'linear', display: true, position: 'left', suggestedMin: 15, title: { display: true, text: 'Temp (°C)' } }, y1: { type: 'linear', display: true, position: 'right', grid: { drawOnChartArea: false }, suggestedMin: 30, suggestedMax: 80, title: { display: true, text: 'Humidity (%)' } } } }); } function renderLevelChart(safeId, data, isLight) { const labels = data.map(d => new Date(d.timestamp).toLocaleString()); const ctx = document.getElementById(`level_${safeId}`).getContext('2d'); const levelLabel = isLight ? 'Brightness' : 'Fan Speed'; const levelColor = isLight ? '#ffcd56' : '#9966ff'; updateChart(`level_${safeId}`, ctx, labels, [{ label: levelLabel, data: data.map(d => d.fan_speed), borderColor: levelColor, backgroundColor: levelColor, stepped: true }], { scales: { y: { suggestedMin: 0, suggestedMax: 10, ticks: { stepSize: 1 } } } }); } function updateChart(id, ctx, labels, datasets, extraOptions = {}) { if (chartInstances[id]) { chartInstances[id].destroy(); } chartInstances[id] = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: datasets.map(ds => ({ ...ds, borderWidth: 2, tension: 0.3, pointRadius: 1 })) }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false, }, plugins: { legend: { position: 'top' } }, ...extraOptions } }); } document.addEventListener('DOMContentLoaded', init);