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 = `
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);