diff --git a/firmware/data/bottom.js b/firmware/data/bottom.js new file mode 100644 index 0000000..e997670 --- /dev/null +++ b/firmware/data/bottom.js @@ -0,0 +1,299 @@ +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"); +}); +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(); +}); +} catch (error) { +console.log("Fehler in 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']); +channel2.push(data['tcdata'][i]['ch2']); +channel3.push(data['tcdata'][i]['ch3']); +channel4.push(data['tcdata'][i]['ch4']); +} +currenttime.push(data['currenttime']['hour']); +currenttime.push(data['currenttime']['min']); +console.log(currenttime); +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++) { +console.log(time[i] + ' <= ' + currentTimeStr + ' >= ' + time[i + 1]); +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'); +console.log(start.format('YYYY-MM-DD HH:mm') + ' <= ' + curr.format('YYYY-MM-DD HH:mm') + ' >= ' + end.format('YYYY-MM-DD HH:mm')); +console.log(curr.isBetween(start, end)); +if (curr.isBetween(start, end)) { +lowerIndex = i; +upperIndex = i + 1; +break; +} +} +console.log('lowerIndex=' + lowerIndex); +console.log('upperIndex=' + upperIndex); +if (lowerIndex === -1 || upperIndex === -1) { +console.log("Error: Current time not found in time array and not between two elements in time array."); +lowerIndex = 0; +upperIndex = 1; +var tmp1 = time[0].split(':'); +console.log('tmp1 = ' + tmp1); +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)); +console.log("Index (float): " + indexFloat); +} else { +console.log("Index (integer): " + index); +console.log("Index (float): " + index); +} +if (indexFloat > index) { +index = indexFloat; +} +console.log("index in graph >>>" + index); +var trace1 = { +x: time, +y: channel1, +name: 'Channel 1', +type: 'scatter', +mode: 'lines+markers', +}; +var trace2 = { +x: time, +y: channel2, +name: 'Channel 2', +type: 'scatter', +mode: 'lines+markers', +}; +var trace3 = { +x: time, +y: channel3, +name: 'Channel 3', +type: 'scatter', +mode: 'lines+markers', +}; +var trace4 = { +x: time, +y: channel4, +name: 'Channel 4', +type: 'scatter', +mode: 'lines+markers', +}; +var layout = { +title: 'Timing Control Data Blocks', +xaxis: { +title: 'Time', +tickangle: -45, +}, +yaxis: { +title: 'Brightness', +range: [0, 255], +}, +shapes: [{ +type: 'line', +x0: index, +y0: 0, +x1: index, +y1: 255, +line: { +color: 'lightgrey', +width: 3, +dash: 'dot' +} +}] +}; +Plotly.newPlot('plot_chart', [trace1, trace2, trace3, trace4], layout); +}); +} +setInterval(loadGraphData, 10000); +loadGraphData(); +function updateLightState() { +console.log('----> setting bri and power switch <----'); +for (let i = 1; i <= {{LIGHT_COUNT}}; i++) { +const lightURL = `http://{{IP_ADDRESS}}/state?light=${i}`; +fetch(lightURL).then(response => response.json()).then(data => { +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`); +briSlider.value = data.bri; +briSliderVal.innerHTML = (Math.round((data.bri * 100.0 / 255.0) * 100) / 100).toFixed(2); +console.log('data.on ' + i + ' = ' + data.on); +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'); +} +}).catch(error => console.error(error)); +} +} +setInterval(updateLightState, 10000); +updateLightState(); +function updatePWMValues() { +console.log('----> setting pwm data <----'); +for (let i = 0; i < {{LIGHT_COUNT}}; i++) { +const lightID = i + 1; +const pwmElement = document.getElementById(`light${i}_pwm`); +const pwmElementTxt = document.getElementById(`light${i}_pwm_txt`); +if (pwmElement) { +const url = `http://{{IP_ADDRESS}}/state?light=${lightID}`; +fetch(url).then(response => response.json()).then(data => { +const pwmValue = ((Math.round((data.curpwm - ((data.curpwm >= {{PWM_MIN}}) ? {{PWM_MIN}} : 0)) / {{PWM_MAX}} * 10000) / 100).toFixed(2)); +console.log('curpwm[' + i + '] = ' + data.curpwm + ' = ' + pwmValue); +pwmElement.innerText = pwmValue.toString(); +pwmElement.value = pwmValue; +pwmElementTxt.innerText = pwmValue.toString(); +}).catch(error => console.error(error)); +} +} +} +updatePWMValues(); +setInterval(updatePWMValues, 5000); +var links = document.querySelectorAll('[id^="on"][id$="_off"]'); +links.forEach(function(link) { +link.addEventListener('click', function(event) { +event.preventDefault(); +var id = this.id.replace('on', '').replace('_off', ''); +var xhr = new XMLHttpRequest(); +xhr.open('GET', 'http://{{IP_ADDRESS}}/?on' + id + '=false&transition=' + document.getElementById('transition').value, true); +xhr.send(); +updateLightState(); +this.classList.add('pure-button-primary'); +document.getElementById('on'+id+'_on').classList.remove('pure-button-primary'); +}); +}); +var links = document.querySelectorAll('[id^="on"][id$="_on"]'); +links.forEach(function(link) { +link.addEventListener('click', function(event) { +event.preventDefault(); +var id = this.id.replace('on', '').replace('_on', ''); +var xhr = new XMLHttpRequest(); +xhr.open('GET', 'http://{{IP_ADDRESS}}/?on' + id + '=true&transition=' + document.getElementById('transition').value, true); +xhr.send(); +updateLightState(); +this.classList.add('pure-button-primary'); +document.getElementById('on'+id+'_off').classList.remove('pure-button-primary'); +}); +}); +function createTable() { +var table = document.createElement("table"); +var headerRow = document.createElement("tr"); +var headers = ["Stunde", "Minute", "ch1", "ch2", "ch3", "ch4"]; +for (var i = 0; i < headers.length; i++) { +var header = document.createElement("th"); +header.innerHTML = headers[i]; +headerRow.appendChild(header); +} +table.appendChild(headerRow); +for (var row = 0; row < 10; row++) { +var contentRow = document.createElement("tr"); +var hourCell = document.createElement("td"); +var hourInput = document.createElement("input"); +hourInput.type = "number"; +hourInput.min = 0; +hourInput.max = 23; +hourCell.appendChild(hourInput); +contentRow.appendChild(hourCell); +var minuteCell = document.createElement("td"); +var minuteSelect = document.createElement("select"); +for (var minute = 0; minute <= 50; minute += 10) { +var option = document.createElement("option"); +option.value = minute; +option.text = minute.toString().padStart(2, "0"); +minuteSelect.appendChild(option); +} +minuteCell.appendChild(minuteSelect); +contentRow.appendChild(minuteCell); +for (var channel = 1; channel <= 4; channel++) { +var channelCell = document.createElement("td"); +var channelInput = document.createElement("input"); +channelInput.type = "number"; +channelInput.min = 0; +channelInput.max = 100; +channelCell.appendChild(channelInput); +contentRow.appendChild(channelCell); +} +table.appendChild(contentRow); +} +var button = document.createElement("button"); +button.innerHTML = "Export als JSON"; +button.onclick = function() { +var data = []; +var rows = table.getElementsByTagName("tr"); +for (var row = 1; row < rows.length; row++) { +var cells = rows[row].getElementsByTagName("td"); +var hour = cells[0].querySelector("input").value; +var minute = cells[1].querySelector("select").value; +var ch1 = cells[2].querySelector("input").value; +var ch2 = cells[3].querySelector("input").value; +var ch3 = cells[4].querySelector("input").value; +var ch4 = cells[5].querySelector("input").value; +var rowObject = {"hour": hour, "min": minute, "ch1": ch1, "ch2": ch2, "ch3": ch3, "ch4": ch4}; +data.push(rowObject); +} +var json = JSON.stringify(data); +console.log(json); +var xhr = new XMLHttpRequest(); +xhr.open("POST", "http://{{IP_ADDRESS}}/tc_data_save?data=" + encodeURIComponent(json), true); +xhr.send(); +}; +var container = document.getElementById("table-container"); +container.innerHTML = ""; +container.appendChild(table); +container.appendChild(button); +} \ No newline at end of file diff --git a/firmware/data/index_template_bottom.html b/firmware/data/index_template_bottom.html index 7bec1c0..990ac03 100644 --- a/firmware/data/index_template_bottom.html +++ b/firmware/data/index_template_bottom.html @@ -1,249 +1,14 @@
-
+
+
+ +
+
- diff --git a/firmware/data/index_template_top.html b/firmware/data/index_template_top.html index fc985f7..d0cadaf 100644 --- a/firmware/data/index_template_top.html +++ b/firmware/data/index_template_top.html @@ -57,48 +57,7 @@ display: block;
-
diff --git a/firmware/data/style.css b/firmware/data/style.css new file mode 100644 index 0000000..244096a --- /dev/null +++ b/firmware/data/style.css @@ -0,0 +1,52 @@ + #tab-lights, #tab-config, #tab-tde { + display: none; + background-color: #ffffff; + color: black; + font-weight: bold; + } + #tab-lights.visible { + display: block; + } + #tab-config.visible { + display: block; + } + #tab-tde.visible { + display: block; + } + /* CSS-Regeln für die Tabellenzellen */ + .pure-table td { + padding: 4px; + } + + /* CSS-Regeln für die Eingabefelder */ + .pure-form input[type="number"] { + width: 60px; + height: 20px; + border-radius: 3px; + border: 1px solid #ccc; + } + + /* CSS-Regeln für die Auswahllisten */ + .pure-form select { + width: 80px; + height: 26px; + border-radius: 3px; + border: 1px solid #ccc; + background-color: #fff; + } + + /* CSS-Regeln für den Export-Button */ + .pure-button { + background-color: #5a5a5a; + color: #fff; + border-radius: 3px; + border: none; + padding: 8px 12px; + font-size: 14px; + cursor: pointer; + } + + .pure-button:hover { + background-color: #333; + } + diff --git a/firmware/data/tc_data_edit.html b/firmware/data/tc_data_edit.html deleted file mode 100644 index e358441..0000000 --- a/firmware/data/tc_data_edit.html +++ /dev/null @@ -1,184 +0,0 @@ -
-
-Daten senden - \ No newline at end of file diff --git a/firmware/data/top.js b/firmware/data/top.js new file mode 100644 index 0000000..42eebbd --- /dev/null +++ b/firmware/data/top.js @@ -0,0 +1,41 @@ +var links = document.querySelectorAll('[id^="tc_on"]'); +links.forEach(function(link) { +link.addEventListener('click', function(event) { +event.preventDefault(); +var xhr = new XMLHttpRequest(); +xhr.open('GET', 'http://{{IP_ADDRESS}}/?tc=true', true); +xhr.send(); +console.log('tc=true call'); +document.getElementById('tc_on').classList.add('pure-button-primary'); +document.getElementById('tc_off').classList.remove('pure-button-primary'); +}); +}); +var links = document.querySelectorAll('[id^="tc_off"]'); +links.forEach(function(link) { +link.addEventListener('click', function(event) { +event.preventDefault(); +var xhr = new XMLHttpRequest(); +xhr.open('GET', 'http://{{IP_ADDRESS}}/?tc=false', true); +xhr.send(); +console.log('tc=false call'); +document.getElementById('tc_off').classList.add('pure-button-primary'); +document.getElementById('tc_on').classList.remove('pure-button-primary'); +}); +}); +let timeoutId; +function sendSliderValue(x) { +x = x - 1; +clearTimeout(timeoutId); +timeoutId = setTimeout(() => { +var value = document.getElementById(`bri${x}`).value; +var url = `http://{{IP_ADDRESS}}/?bri${x}=${value}`; +fetch(url).then(response => { +if (!response.ok) { +throw new Error(`HTTP error! status: ${response.status}`); +} +console.log(`Sent slider value ${value} to ${url}`); +}).catch(error => { +console.error(`Error sending slider value to ${url}: ${error}`); +}); +}, 500); +} \ No newline at end of file diff --git a/firmware/firmware.ino b/firmware/firmware.ino index 538203b..4b483da 100644 --- a/firmware/firmware.ino +++ b/firmware/firmware.ino @@ -489,9 +489,19 @@ void init_webserver() } }); - server.on("/tc_data_edit", []() + server.on("/js_top", []() { - server.send(200, "text/html", genTCEditHTML()); + server.send(200, "text/html", replacePlaceholder(loadSPIFFSFile("/top.js"))); + }); + + server.on("/js_bottom", []() + { + server.send(200, "text/html", replacePlaceholder(loadSPIFFSFile("/bottom.js"))); + }); + + server.on("/css", []() + { + server.send(200, "text/html", loadSPIFFSFile("/style.css")); }); server.on("/", []() diff --git a/firmware/html/bottom.js b/firmware/html/bottom.js new file mode 100644 index 0000000..a01ff19 --- /dev/null +++ b/firmware/html/bottom.js @@ -0,0 +1,344 @@ +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"); + }); + + 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(); + }); + } catch (error) { + console.log("Fehler in 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']); + channel2.push(data['tcdata'][i]['ch2']); + channel3.push(data['tcdata'][i]['ch3']); + channel4.push(data['tcdata'][i]['ch4']); + } + currenttime.push(data['currenttime']['hour']); + currenttime.push(data['currenttime']['min']); + console.log(currenttime); + 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++) { + console.log(time[i] + ' <= ' + currentTimeStr + ' >= ' + time[i + 1]); + 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'); + console.log(start.format('YYYY-MM-DD HH:mm') + ' <= ' + curr.format('YYYY-MM-DD HH:mm') + ' >= ' + end.format('YYYY-MM-DD HH:mm')); + console.log(curr.isBetween(start, end)); + if (curr.isBetween(start, end)) { + lowerIndex = i; + upperIndex = i + 1; + break; + } + } + console.log('lowerIndex=' + lowerIndex); + console.log('upperIndex=' + upperIndex); + if (lowerIndex === -1 || upperIndex === -1) { + console.log("Error: Current time not found in time array and not between two elements in time array."); + lowerIndex = 0; + upperIndex = 1; + var tmp1 = time[0].split(':'); + console.log('tmp1 = ' + tmp1); + 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)); + console.log("Index (float): " + indexFloat); + } else { + console.log("Index (integer): " + index); + console.log("Index (float): " + index); + } + if (indexFloat > index) { + index = indexFloat; + } + console.log("index in graph >>>" + index); + var trace1 = { + x: time, + y: channel1, + name: 'Channel 1', + type: 'scatter', + mode: 'lines+markers', + }; + var trace2 = { + x: time, + y: channel2, + name: 'Channel 2', + type: 'scatter', + mode: 'lines+markers', + }; + var trace3 = { + x: time, + y: channel3, + name: 'Channel 3', + type: 'scatter', + mode: 'lines+markers', + }; + var trace4 = { + x: time, + y: channel4, + name: 'Channel 4', + type: 'scatter', + mode: 'lines+markers', + }; + var layout = { + title: 'Timing Control Data Blocks', + xaxis: { + title: 'Time', + tickangle: -45, + }, + yaxis: { + title: 'Brightness', + range: [0, 255], + }, + shapes: [{ + type: 'line', + x0: index, + y0: 0, + x1: index, + y1: 255, + line: { + color: 'lightgrey', + width: 3, + dash: 'dot' + } + }] + }; + Plotly.newPlot('plot_chart', [trace1, trace2, trace3, trace4], layout); + }); +} +setInterval(loadGraphData, 10000); +loadGraphData(); + +function updateLightState() { + console.log('----> setting bri and power switch <----'); + for (let i = 1; i <= {{LIGHT_COUNT}}; i++) { + const lightURL = `http://{{IP_ADDRESS}}/state?light=${i}`; + fetch(lightURL).then(response => response.json()).then(data => { + 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`); + briSlider.value = data.bri; + briSliderVal.innerHTML = (Math.round((data.bri * 100.0 / 255.0) * 100) / 100).toFixed(2); + console.log('data.on ' + i + ' = ' + data.on); + 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'); + } + }).catch(error => console.error(error)); + } +} +setInterval(updateLightState, 10000); +updateLightState(); + +function updatePWMValues() { + console.log('----> setting pwm data <----'); + for (let i = 0; i < {{LIGHT_COUNT}}; i++) { + const lightID = i + 1; + const pwmElement = document.getElementById(`light${i}_pwm`); + const pwmElementTxt = document.getElementById(`light${i}_pwm_txt`); + if (pwmElement) { + const url = `http://{{IP_ADDRESS}}/state?light=${lightID}`; + fetch(url).then(response => response.json()).then(data => { + const pwmValue = ((Math.round((data.curpwm - ((data.curpwm >= {{PWM_MIN}}) ? {{PWM_MIN}} : 0)) / {{PWM_MAX}} * 10000) / 100).toFixed(2)); + console.log('curpwm[' + i + '] = ' + data.curpwm + ' = ' + pwmValue); + pwmElement.innerText = pwmValue.toString(); + pwmElement.value = pwmValue; + pwmElementTxt.innerText = pwmValue.toString(); + }).catch(error => console.error(error)); + } + } +} +updatePWMValues(); +setInterval(updatePWMValues, 5000); + +// Suche nach allen Links auf der Seite mit IDs von on0_off bis on3_off +var links = document.querySelectorAll('[id^="on"][id$="_off"]'); +// Füge einen Klick-Listener zu jedem Link hinzu +links.forEach(function(link) { + link.addEventListener('click', function(event) { + // Verhindere, dass der Link die Seite neu lädt + event.preventDefault(); + // Extrahiere die Zahl aus der ID des Links + var id = this.id.replace('on', '').replace('_off', ''); + // Erstelle eine neue Anfrage an die entsprechende URL + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'http://{{IP_ADDRESS}}/?on' + id + '=false&transition=' + document.getElementById('transition').value, true); + // Sende die Anfrage im Hintergrund + xhr.send(); + updateLightState(); + this.classList.add('pure-button-primary'); + document.getElementById('on'+id+'_on').classList.remove('pure-button-primary'); + }); +}); +// Suche nach allen Links auf der Seite mit IDs von on0_off bis on3_off +var links = document.querySelectorAll('[id^="on"][id$="_on"]'); +// Füge einen Klick-Listener zu jedem Link hinzu +links.forEach(function(link) { + link.addEventListener('click', function(event) { + // Verhindere, dass der Link die Seite neu lädt + event.preventDefault(); + // Extrahiere die Zahl aus der ID des Links + var id = this.id.replace('on', '').replace('_on', ''); + // Erstelle eine neue Anfrage an die entsprechende URL + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'http://{{IP_ADDRESS}}/?on' + id + '=true&transition=' + document.getElementById('transition').value, true); + // Sende die Anfrage im Hintergrund + xhr.send(); + updateLightState(); + this.classList.add('pure-button-primary'); + document.getElementById('on'+id+'_off').classList.remove('pure-button-primary'); + }); +}); + +function createTable() { +var table = document.createElement("table"); + +// Headerzeile +var headerRow = document.createElement("tr"); +var headers = ["Stunde", "Minute", "ch1", "ch2", "ch3", "ch4"]; +for (var i = 0; i < headers.length; i++) { + var header = document.createElement("th"); + header.innerHTML = headers[i]; + headerRow.appendChild(header); +} +table.appendChild(headerRow); + +// Inhaltszeilen +for (var row = 0; row < 10; row++) { + var contentRow = document.createElement("tr"); + + // Spalte "Stunde" + var hourCell = document.createElement("td"); + var hourInput = document.createElement("input"); + hourInput.type = "number"; + hourInput.min = 0; + hourInput.max = 23; + hourCell.appendChild(hourInput); + contentRow.appendChild(hourCell); + + // Spalte "Minute" + var minuteCell = document.createElement("td"); + var minuteSelect = document.createElement("select"); + for (var minute = 0; minute <= 50; minute += 10) { + var option = document.createElement("option"); + option.value = minute; + option.text = minute.toString().padStart(2, "0"); + minuteSelect.appendChild(option); + } + minuteCell.appendChild(minuteSelect); + contentRow.appendChild(minuteCell); + + // Spalten "ch1" bis "ch4" + for (var channel = 1; channel <= 4; channel++) { + var channelCell = document.createElement("td"); + var channelInput = document.createElement("input"); + channelInput.type = "number"; + channelInput.min = 0; + channelInput.max = 100; + channelCell.appendChild(channelInput); + contentRow.appendChild(channelCell); + } + + table.appendChild(contentRow); +} + +// Button +var button = document.createElement("button"); +button.innerHTML = "Export als JSON"; +button.onclick = function() { + var data = []; + + var rows = table.getElementsByTagName("tr"); + for (var row = 1; row < rows.length; row++) { + var cells = rows[row].getElementsByTagName("td"); + + var hour = cells[0].querySelector("input").value; + var minute = cells[1].querySelector("select").value; + var ch1 = cells[2].querySelector("input").value; + var ch2 = cells[3].querySelector("input").value; + var ch3 = cells[4].querySelector("input").value; + var ch4 = cells[5].querySelector("input").value; + + var rowObject = {"hour": hour, "min": minute, "ch1": ch1, "ch2": ch2, "ch3": ch3, "ch4": ch4}; + data.push(rowObject); + } + + var json = JSON.stringify(data); + console.log(json); + var xhr = new XMLHttpRequest(); + xhr.open("POST", "http://{{IP_ADDRESS}}/tc_data_save?data=" + encodeURIComponent(json), true); + xhr.send(); + +}; + +var container = document.getElementById("table-container"); +container.innerHTML = ""; +container.appendChild(table); +container.appendChild(button); +} \ No newline at end of file diff --git a/firmware/html/index_template_bottom.html b/firmware/html/index_template_bottom.html index 8ab0126..0d0142e 100644 --- a/firmware/html/index_template_bottom.html +++ b/firmware/html/index_template_bottom.html @@ -1,283 +1,15 @@
-
+
+
+ +
+
- diff --git a/firmware/html/index_template_top.html b/firmware/html/index_template_top.html index d70d8b7..6818214 100644 --- a/firmware/html/index_template_top.html +++ b/firmware/html/index_template_top.html @@ -1,108 +1,51 @@ - - - - - - Light setup - {{LIGHT_NAME}} - - - - - - - - -
-

{{LIGHT_NAME}}

-
- -
-
- Lights control - Config - Timing data edit -
-
-
-
-
- - ON - OFF -
- - -
-
- -
-
- -
- + + + + + + Light setup - {{LIGHT_NAME}} + + + + + + + + +
+

{{LIGHT_NAME}}

+
+ +
+ +
+
+
+
+ + ON + OFF +
+ + +
+
+ +
+ + +
+ diff --git a/firmware/html/style.css b/firmware/html/style.css new file mode 100644 index 0000000..244096a --- /dev/null +++ b/firmware/html/style.css @@ -0,0 +1,52 @@ + #tab-lights, #tab-config, #tab-tde { + display: none; + background-color: #ffffff; + color: black; + font-weight: bold; + } + #tab-lights.visible { + display: block; + } + #tab-config.visible { + display: block; + } + #tab-tde.visible { + display: block; + } + /* CSS-Regeln für die Tabellenzellen */ + .pure-table td { + padding: 4px; + } + + /* CSS-Regeln für die Eingabefelder */ + .pure-form input[type="number"] { + width: 60px; + height: 20px; + border-radius: 3px; + border: 1px solid #ccc; + } + + /* CSS-Regeln für die Auswahllisten */ + .pure-form select { + width: 80px; + height: 26px; + border-radius: 3px; + border: 1px solid #ccc; + background-color: #fff; + } + + /* CSS-Regeln für den Export-Button */ + .pure-button { + background-color: #5a5a5a; + color: #fff; + border-radius: 3px; + border: none; + padding: 8px 12px; + font-size: 14px; + cursor: pointer; + } + + .pure-button:hover { + background-color: #333; + } + diff --git a/firmware/html/tc_data_edit.html b/firmware/html/tc_data_edit.html deleted file mode 100644 index 52139ad..0000000 --- a/firmware/html/tc_data_edit.html +++ /dev/null @@ -1,227 +0,0 @@ -
-
-Daten senden - - \ No newline at end of file diff --git a/firmware/html/top.js b/firmware/html/top.js new file mode 100644 index 0000000..73dd43b --- /dev/null +++ b/firmware/html/top.js @@ -0,0 +1,42 @@ +var links = document.querySelectorAll('[id^="tc_on"]'); +links.forEach(function(link) { + link.addEventListener('click', function(event) { + event.preventDefault(); + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'http://{{IP_ADDRESS}}/?tc=true', true); + xhr.send(); + //console.log('tc=true call'); + document.getElementById('tc_on').classList.add('pure-button-primary'); + document.getElementById('tc_off').classList.remove('pure-button-primary'); + }); +}); +var links = document.querySelectorAll('[id^="tc_off"]'); +links.forEach(function(link) { + link.addEventListener('click', function(event) { + event.preventDefault(); + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'http://{{IP_ADDRESS}}/?tc=false', true); + xhr.send(); + //console.log('tc=false call'); + document.getElementById('tc_off').classList.add('pure-button-primary'); + document.getElementById('tc_on').classList.remove('pure-button-primary'); + }); +}); + +let timeoutId; +function sendSliderValue(x) { + x = x - 1; + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + var value = document.getElementById(`bri${x}`).value; + var url = `http://{{IP_ADDRESS}}/?bri${x}=${value}`; + fetch(url).then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + //console.log(`Sent slider value ${value} to ${url}`); + }).catch(error => { + console.error(`Error sending slider value to ${url}: ${error}`); + }); + }, 500); +} \ No newline at end of file diff --git a/firmware/timing_control.ino b/firmware/timing_control.ino index c465e0f..b63721b 100644 --- a/firmware/timing_control.ino +++ b/firmware/timing_control.ino @@ -180,6 +180,12 @@ void tc_update_main() if (target_data_block >= NUMBER_OF_TIMER_DATA_BLOCKS) { + // we are not between two valid data points, do nothing + // TODO check if setting all lights to false is correct here + for (uint8_t i = 0; i < LIGHTS_COUNT; i++) + { + light_state[i] = false; + } target_data_block = 255; current_target_data_block = 255; return; diff --git a/tools/html_gen_files.sh b/tools/html_gen_files.sh index b43ffb4..61222d8 100755 --- a/tools/html_gen_files.sh +++ b/tools/html_gen_files.sh @@ -5,4 +5,6 @@ bash ../../tools/html2string.sh ../html/index_template_middle.html > index_templ bash ../../tools/html2string.sh ../html/index_template_bottom.html > index_template_bottom.html bash ../../tools/html2string.sh ../html/config_template.html > config_template.html bash ../../tools/html2string.sh ../html/light_control_template.html > light_control_template.html -bash ../../tools/html2string.sh ../html/tc_data_edit.html > tc_data_edit.html +bash ../../tools/html2string.sh ../html/top.js > top.js +bash ../../tools/html2string.sh ../html/bottom.js > bottom.js +cp -av ../html/style.css .