function addTabListener() { try { var tabMain = document.getElementById("tab-lights"); var tabConfig = document.getElementById("tab-config"); var tabTDE = document.getElementById("tab-tde"); var amain = document.getElementById("tab-a-lights"); var acfg = document.getElementById("tab-a-config"); var atde = document.getElementById("tab-a-tde"); amain.addEventListener("click", function() { tabMain.classList.add("visible"); tabConfig.classList.remove("visible"); tabTDE.classList.remove("visible"); amain.classList.add("pure-button-primary"); acfg.classList.remove("pure-button-primary"); atde.classList.remove("pure-button-primary"); loadGraphData(); }); acfg.addEventListener("click", function() { tabMain.classList.remove("visible"); tabConfig.classList.add("visible"); tabTDE.classList.remove("visible"); amain.classList.remove("pure-button-primary"); acfg.classList.add("pure-button-primary"); atde.classList.remove("pure-button-primary"); }); atde.addEventListener("click", function() { tabMain.classList.remove("visible"); tabConfig.classList.remove("visible"); tabTDE.classList.add("visible"); amain.classList.remove("pure-button-primary"); acfg.classList.remove("pure-button-primary"); atde.classList.add("pure-button-primary"); createTable(); fillTableFromJson(); }); } catch (error) { console.log("Error: load listener of the tab action listener management: " + error.message); } } window.addEventListener('load', function() { addTabListener(); }); function loadGraphData() { console.log('----> generate graph <----'); $.getJSON('/tc_data_blocks_read', function(data) { var currenttime = []; var time = []; var channel1 = []; var channel2 = []; var channel3 = []; var channel4 = []; for (var i = 0; i < data['tcdata'].length; i++) { time.push(data['tcdata'][i]['hour'] + ':' + (data['tcdata'][i]['min'] < 10 ? '0' : '') + data['tcdata'][i]['min']); channel1.push(data['tcdata'][i]['ch1'] * 100 / 255); channel2.push(data['tcdata'][i]['ch2'] * 100 / 255); channel3.push(data['tcdata'][i]['ch3'] * 100 / 255); channel4.push(data['tcdata'][i]['ch4'] * 100 / 255); } currenttime.push(data['currenttime']['hour']); currenttime.push(data['currenttime']['min']); var currentTimeStr = currenttime[0] + ':' + (currenttime[1] < 10 ? '0' : '') + currenttime[1]; var index = time.indexOf(currentTimeStr); if (index === -1) { var lowerIndex = -1; var upperIndex = -1; for (var i = 0; i < time.length - 1; i++) { const currentDate = new Date(); const year = currentDate.getFullYear(); const month = (currentDate.getMonth() + 1).toString().padStart(2, '0'); const day = currentDate.getDate().toString().padStart(2, '0'); const dateString = `${year}-${month}-${day}`; const start = moment(dateString + ' ' + time[i], 'YYYY-MM-DD HH:mm'); const curr = moment(dateString + ' ' + currentTimeStr, 'YYYY-MM-DD HH:mm'); const end = moment(dateString + ' ' + time[i + 1], 'YYYY-MM-DD HH:mm'); if (curr.isBetween(start, end)) { lowerIndex = i; upperIndex = i + 1; break; } } if (lowerIndex === -1 || upperIndex === -1) { console.log("Error: Current time not found in time array and not between two elements in time array. Fixing it..."); lowerIndex = 0; upperIndex = 1; var tmp1 = time[0].split(':'); currenttime[0] = tmp1[0]; currenttime[1] = tmp1[1]; } var lowerTime = time[lowerIndex].split(":"); var upperTime = time[upperIndex].split(":"); var timeDiff = (currenttime[0] - lowerTime[0]) + ((currenttime[1] - lowerTime[1]) / 60); var indexFloat = lowerIndex + timeDiff / ((upperTime[0] - lowerTime[0]) + ((upperTime[1] - lowerTime[1]) / 60)); } else { } if (indexFloat > index) { index = indexFloat; } var traces = []; for (var i = 1; i <= 4; i++) { var trace = { x: time, y: eval("channel" + i), name: 'Channel ' + i, type: 'scatter', mode: 'lines+markers', }; traces.push(trace); } var layout = { title: 'Timing Control Data Blocks', xaxis: { title: 'Time', tickangle: -45, }, yaxis: { title: 'Brightness', range: [0, 100], }, shapes: [{ type: 'line', x0: index, y0: 0, x1: index, y1: 255, line: { color: 'lightgrey', width: 3, dash: 'dot' } }] }; Plotly.newPlot('plot_chart', traces, layout); }); } setInterval(loadGraphData, 10000); loadGraphData(); function loadTCGraphData() { console.log('----> generate tc data graph <----'); var data = JSON.parse(createJsonFromTable()); var currenttime = []; var time = []; var channel1 = []; var channel2 = []; var channel3 = []; var channel4 = []; for (var i = 0; i < data['tcdata'].length; i++) { time.push(data['tcdata'][i]['hour'] + ':' + (data['tcdata'][i]['min'] < 10 ? '0' : '') + data['tcdata'][i]['min']); channel1.push(data['tcdata'][i]['ch1'] * 100 / 255); channel2.push(data['tcdata'][i]['ch2'] * 100 / 255); channel3.push(data['tcdata'][i]['ch3'] * 100 / 255); channel4.push(data['tcdata'][i]['ch4'] * 100 / 255); } currenttime.push(data['currenttime']['hour']); currenttime.push(data['currenttime']['min']); var currentTimeStr = currenttime[0] + ':' + (currenttime[1] < 10 ? '0' : '') + currenttime[1]; var index = time.indexOf(currentTimeStr); if (index === -1) { var lowerIndex = -1; var upperIndex = -1; for (var i = 0; i < time.length - 1; i++) { const currentDate = new Date(); const year = currentDate.getFullYear(); const month = (currentDate.getMonth() + 1).toString().padStart(2, '0'); const day = currentDate.getDate().toString().padStart(2, '0'); const dateString = `${year}-${month}-${day}`; const start = moment(dateString + ' ' + time[i], 'YYYY-MM-DD HH:mm'); const curr = moment(dateString + ' ' + currentTimeStr, 'YYYY-MM-DD HH:mm'); const end = moment(dateString + ' ' + time[i + 1], 'YYYY-MM-DD HH:mm'); if (curr.isBetween(start, end)) { lowerIndex = i; upperIndex = i + 1; break; } } if (lowerIndex === -1 || upperIndex === -1) { console.log("Warning: Current time not found in time array and not between two elements in time array. Fixing it..."); lowerIndex = 0; upperIndex = 1; var tmp1 = time[0].split(':'); currenttime[0] = tmp1[0]; currenttime[1] = tmp1[1]; } var lowerTime = time[lowerIndex].split(":"); var upperTime = time[upperIndex].split(":"); var timeDiff = (currenttime[0] - lowerTime[0]) + ((currenttime[1] - lowerTime[1]) / 60); var indexFloat = lowerIndex + timeDiff / ((upperTime[0] - lowerTime[0]) + ((upperTime[1] - lowerTime[1]) / 60)); } else { } if (indexFloat > index) { index = indexFloat; } var traces = []; for (var i = 1; i <= 4; i++) { var trace = { x: time, y: eval("channel" + i), name: 'Channel ' + i, type: 'scatter', mode: 'lines+markers', }; traces.push(trace); } var layout = { title: 'Timing Control Data Blocks', xaxis: { title: 'Time', tickangle: -45, }, yaxis: { title: 'Brightness', range: [0, 100], }, shapes: [{ type: 'line', x0: index, y0: 0, x1: index, y1: 255, line: { color: 'lightgrey', width: 3, dash: 'dot' } }] }; Plotly.newPlot('tc_plot_chart', traces, layout); var plot = document.getElementById('tc_plot_chart'); } function rgbaToHex(rgba) { var hex = rgba.map(function (value) { var hexValue = Math.round(value * 255).toString(16); return hexValue.length === 1 ? '0' + hexValue : hexValue; }); return '#' + hex.join('').toUpperCase(); } setInterval(function() { if (document.getElementById('tab-tde').classList.contains('visible')) { loadTCGraphData(); } }, 2000); function updateLightStateAndPWMValues() { console.log('----> setting bri, power switch, and pwm data <----'); const promises = []; for (let i = 1; i <= {{LIGHT_COUNT}}; i++) { const lightURL = `http://{{IP_ADDRESS}}/state?light=${i}`; promises.push(fetch(lightURL).then(response => response.json())); } Promise.all(promises) .then(dataArray => { for (let i = 1; i <= {{LIGHT_COUNT}}; i++) { const data = dataArray[i - 1]; const briSlider = document.getElementById(`bri${i - 1}`); const briSliderVal = document.getElementById(`bri${i - 1}_val`); const onLinkOn = document.getElementById(`on${i - 1}_on`); const onLinkOff = document.getElementById(`on${i - 1}_off`); const pwmElement = document.getElementById(`light${i - 1}_pwm`); const pwmElementTxt = document.getElementById(`light${i - 1}_pwm_txt`); briSlider.value = data.bri; briSliderVal.innerHTML = (Math.round((data.bri * 100.0 / 255.0) * 100) / 100).toFixed(2); if (data.on == true) { onLinkOn.classList.add('pure-button-primary'); onLinkOff.classList.remove('pure-button-primary'); } else { onLinkOn.classList.remove('pure-button-primary'); onLinkOff.classList.add('pure-button-primary'); } if (pwmElement) { const pwmValue = ((Math.round((data.curpwm - ((data.curpwm >= {{PWM_MIN}}) ? {{PWM_MIN}} : 0)) / {{PWM_MAX}} * 10000) / 100).toFixed(2)); pwmElement.innerText = pwmValue.toString(); pwmElement.value = pwmValue; pwmElementTxt.innerText = pwmValue.toString(); } } }) .catch(error => console.error(error)); } updateLightStateAndPWMValues(); setInterval(updateLightStateAndPWMValues, 5000); function handleOnRequest(id_full, id, value) { var button = document.getElementById(id_full); var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://{{IP_ADDRESS}}/?on' + id + '=' + value + '&transition=' + document.getElementById('transition').value, true); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { button.classList.remove("pure-button-primary"); button.classList.add("success"); button.innerHTML = value ? "Enabled!" : "Disabled!"; button.style.backgroundColor = "green"; setTimeout(function () { button.classList.remove("success"); button.classList.add("pure-button-primary"); button.innerHTML = value ? "ON" : "OFF"; button.style.backgroundColor = ""; }, 2000); console.log('Data successfully sent to server!'); } else { button.classList.remove("pure-button-primary"); button.classList.add("error"); button.innerHTML = "Error!"; button.style.backgroundColor = "red"; setTimeout(function () { button.classList.remove("error"); button.classList.add("pure-button-primary"); button.innerHTML = value ? "ON" : "OFF"; button.style.backgroundColor = ""; }, 2000); showToast('Error while sending data to server.', 'error'); console.log('Error while sending data to server.'); } } }; xhr.send(); updateLightStateAndPWMValues(); document.getElementById(id_full).classList.add('pure-button-primary'); document.getElementById('on' + id + (value ? '_off' : '_on')).classList.remove('pure-button-primary'); } function addOnButtonClickListener(id_full, id, value) { var link = document.getElementById(id_full); link.addEventListener('click', function (event) { event.preventDefault(); handleOnRequest(id_full, id, value); }); } var offLinks = document.querySelectorAll('[id^="on"][id$="_off"]'); offLinks.forEach(function (link) { var id_full = link.id; var id = link.id.replace('on', '').replace('_off', ''); addOnButtonClickListener(id_full, id, false); }); var onLinks = document.querySelectorAll('[id^="on"][id$="_on"]'); onLinks.forEach(function (link) { var id_full = link.id; var id = link.id.replace('on', '').replace('_on', ''); addOnButtonClickListener(id_full, id, true); }); function createTable() { var table = document.createElement("table"); table.border = "1"; var headerRow = document.createElement("tr"); var headers = ["Hour", "Minute", "Ch1", "Ch2", "Ch3", "Ch4"]; headers.forEach(header => { var th = document.createElement("th"); th.innerHTML = header; headerRow.appendChild(th); }); table.appendChild(headerRow); for (var i = 1; i <= 10; i++) { var tr = document.createElement("tr"); var tdHour = createSelectCell(23, 0, 1); tdHour.id = "hour" + i; tr.appendChild(tdHour); var tdMinute = createSelectCell(59, 0, 10); tdMinute.id = "minute" + i; tr.appendChild(tdMinute); for (var j = 1; j <=4; j++) { var tdCh = createSlider("ch" + j + "_" + i, 100, 0, 1); tr.appendChild(tdCh); } table.appendChild(tr); } var container = document.getElementById("table-container"); container.innerHTML = ""; container.classList.add("pure-form"); container.appendChild(table); } function createSlider(id, max, value, step) { var input = document.createElement("input"); input.type = "range"; input.min = 0; input.max = max; input.step = step; input.value = value; input.style = "1px solod darkgray"; input.classList.add("pure-slider-range"); if (id.startsWith("ch1_")) { input.style.background = "linear-gradient(to left, " + rgb2Hex(31, 119, 180) + ", white)"; } else if (id.startsWith("ch2_")) { input.style.background = "linear-gradient(to left, " + rgb2Hex(255, 127, 14) + ", white)"; } else if (id.startsWith("ch3_")) { input.style.background = "linear-gradient(to left, " + rgb2Hex(44, 160, 44) + ", white)"; } else if (id.startsWith("ch4_")) { input.style.background = "linear-gradient(to left, " + rgb2Hex(214, 39, 40) + ", white)"; } div = document.createElement("div"); div.appendChild(input); var span = document.createElement("span"); span.innerHTML = " " + value + " %"; div.appendChild(span); input.oninput = function() { span.innerHTML = " " + this.value + " %"; }; var td = document.createElement("td"); td.id = id; td.appendChild(div); return td; } function createSelectCell(max, value, step) { var select = document.createElement("select"); for (var i = 0; i <= max; i += step) { var option = document.createElement("option"); option.value = i; option.text = i; select.appendChild(option); } select.value = value; var row = document.createElement("td"); row.appendChild(select); return row; } function fillTableFromJson() { fetch('http://{{IP_ADDRESS}}/tc_data_blocks_read') .then(response => response.json()) .then(data => { var tcdata = data.tcdata; for (var i = 0; i < tcdata.length; i++) { var row = document.getElementById("hour" + (i+1)).parentNode; row.cells[0].childNodes[0].value = tcdata[i].hour; row.cells[1].childNodes[0].value = tcdata[i].min; row.cells[2].childNodes[0].childNodes[0].value = parseInt(Math.round(tcdata[i].ch1 * 100 / 255)); row.cells[2].childNodes[0].childNodes[1].innerHTML = " " + row.cells[2].childNodes[0].childNodes[0].value + " %"; row.cells[3].childNodes[0].childNodes[0].value = parseInt(Math.round(tcdata[i].ch2 * 100 / 255)); row.cells[3].childNodes[0].childNodes[1].innerHTML = " " + row.cells[3].childNodes[0].childNodes[0].value + " %"; row.cells[4].childNodes[0].childNodes[0].value = parseInt(Math.round(tcdata[i].ch3 * 100 / 255)); row.cells[4].childNodes[0].childNodes[1].innerHTML = " " + row.cells[4].childNodes[0].childNodes[0].value + " %"; row.cells[5].childNodes[0].childNodes[0].value = parseInt(Math.round(tcdata[i].ch4 * 100 / 255)); row.cells[5].childNodes[0].childNodes[1].innerHTML = " " + row.cells[5].childNodes[0].childNodes[0].value + " %"; } loadTCGraphData(); }); } function createJsonFromTable() { var tableRows = document.querySelectorAll("table tr"); var tcdata = []; var timeArr = []; for (var i = 1; i <= 10; i++) { var row = document.getElementById("hour" + i).parentNode; var hour = parseInt(row.cells[0].childNodes[0].value); var min = parseInt(row.cells[1].childNodes[0].value); timeArr.push(hour * 60 + min); var ch1 = Math.round(parseInt(row.cells[2].childNodes[0].childNodes[0].value) * 2.55); var ch2 = Math.round(parseInt(row.cells[3].childNodes[0].childNodes[0].value) * 2.55); var ch3 = Math.round(parseInt(row.cells[4].childNodes[0].childNodes[0].value) * 2.55); var ch4 = Math.round(parseInt(row.cells[5].childNodes[0].childNodes[0].value) * 2.55); tcdata.push({hour: hour, min: min, ch1: ch1, ch2: ch2, ch3: ch3, ch4: ch4}); } for (var i = 0; i < timeArr.length - 1; i++) { if (timeArr[i] >= timeArr[i + 1]) { showToast('Error while verifying timing control data. The timestamps are not incrementing.', 'error'); return null; } } if (tcdata[0].ch1 > 0 || tcdata[0].ch2 > 0 || tcdata[0].ch3 > 0 || tcdata[0].ch4 > 0 || tcdata[9].ch1 > 0 || tcdata[9].ch2 > 0 || tcdata[9].ch3 > 0 || tcdata[9].ch4 > 0) { showToast('Error: Values for ch1 to ch4 must be 0 for the first and last entry.', 'error'); return null; } var currentTime = {hour: new Date().getHours(), min: new Date().getMinutes()}; var jsonData = {tcdata: tcdata, currenttime: currentTime}; return JSON.stringify(jsonData); } function sendDataToServer() { var jsonData = createJsonFromTable(); if (!jsonData) { button.classList.remove("pure-button-primary"); button.classList.add("error"); button.innerHTML = "Error!"; button.style.backgroundColor = "red"; setTimeout(function () { button.classList.remove("error"); button.classList.add("pure-button-primary"); button.style.backgroundColor = ""; button.innerHTML = "save"; }, 2000); console.log('Error while generating timing control data.'); return; } var urlEncodedData = encodeURIComponent(jsonData); var url = 'http://{{IP_ADDRESS}}/tc_data_blocks_store?data=' + urlEncodedData; var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { var button = document.getElementById("save-button"); if (xhr.status === 200) { button.classList.remove("pure-button-primary"); button.classList.add("success"); button.innerHTML = "Saved!"; button.style.backgroundColor = "green"; setTimeout(function () { button.classList.remove("success"); button.classList.add("pure-button-primary"); button.innerHTML = "save"; button.style.backgroundColor = ""; }, 2000); console.log('Data successfully sent to server!'); } else { button.classList.remove("pure-button-primary"); button.classList.add("error"); button.innerHTML = "Error!"; button.style.backgroundColor = "red"; setTimeout(function () { button.classList.remove("error"); button.classList.add("pure-button-primary"); button.innerHTML = "save"; button.style.backgroundColor = ""; }, 2000); showToast('Error while sending timing control data to server.', 'error'); console.log('Error while sending data to server.'); } } }; xhr.send(); loadTCGraphData(); } function showToast(message, type) { const toast = document.querySelector('.toast'); if (toast) { toast.textContent = message; toast.classList.remove('success', 'error'); if (type === 'success') { toast.classList.add('success'); } else if (type === 'error') { toast.classList.add('error'); } toast.style.opacity = 1; setTimeout(() => { toast.style.opacity = 0; }, 5000); } } function sendPWMTestRequest(event) { event.preventDefault(); var button = event.target; var xhr = new XMLHttpRequest(); xhr.open("GET", 'http://{{IP_ADDRESS}}/test_pwm', true); xhr.onload = function () { if (xhr.status == 200 && xhr.responseText === "OK") { button.innerHTML = "PWM test started"; button.style.backgroundColor = "green"; } else { button.innerHTML = "Error!"; button.style.backgroundColor = "red"; } setTimeout(function () { button.innerHTML = "PWM Test"; button.style.backgroundColor = ""; }, 2000); }; xhr.onerror = function () { button.innerHTML = "Error!"; button.style.backgroundColor = "red"; setTimeout(function () { button.innerHTML = "PWM Test"; button.style.backgroundColor = ""; }, 2000); }; xhr.send(); }