Replaced html page generation by cleaner version.

This commit is contained in:
Kai Lauterbach 2023-05-01 11:18:15 +02:00
parent 747f7dd985
commit 382486000c
5 changed files with 463 additions and 441 deletions

View file

@ -21,4 +21,3 @@
#define MY_NTP_SERVER "de.pool.ntp.org"
//#define DISABLE_WEB_CONTROL

62
firmware/data/config.html Normal file
View file

@ -0,0 +1,62 @@
<form class="pure-form pure-form-aligned" action="/" method="post">
<h3>Config</h3>
<div class="pure-control-group">
<label for="startup">
<strong>Startup</strong>
</label>
<select onchange="this.form.submit()" id="startup" name="startup">
<option {{STARTUP_SELECTED_LS_0}} value="0">Last state</option>
<option {{STARTUP_SELECTED_ON_1}} value="1">On</option>
<option {{STARTUP_SELECTED_OFF_2}} value="2">Off</option>
</select>
</div>
<div class="pure-control-group">
<label for="scene">
<strong>Scene</strong>
</label>
<select onchange="this.form.submit()" id="scene" name="scene">
<option {{SCENE_SELECTED_RELAX_0}} value="0">Relax</option>
<option {{SCENE_SELECTED_BRIGHT_1}} value="1">Bright</option>
<option {{SCENE_SELECTED_NIGHT_2}} value="2">Night</option>
</select>
</div>
<br>
<h3>Wifi</h3>
<div class="pure-control-group">
<label for="ip">SSID</label>
<input id="ssid" name="ssid" type="text" value="{{WIFI_SSID}}">
</div>
<div class="pure-control-group">
<label for="wpw">Passphrase</label>
<input id="wpw" name="wpw" type="text" placeholder="1234password">
</div>
<br>
<h3>Network</h3>
<div class="pure-control-group">
<label for="dip">
<strong>Dynamic-IP</strong>
</label>
<a class="pure-button {{DIP_LINK_ON_PRIMARY}}" href="/?dip=true">ON</a>
<a class="pure-button {{DIP_LINK_OFF_PRIMARY}}" href="/?dip=false">OFF</a>
</div>
<div class="pure-control-group">
<label for="ip">IP</label>
<input id="ip" name="ip" type="text" value="{{WIFI_IP}}">
</div>
<div class="pure-control-group">
<label for="gwip">Gateway IP</label>
<input id="gwip" name="gwip" type="text" value="{{WIFI_GW}}">
</div>
<div class="pure-control-group">
<label for="ip">Netmask</label>
<input id="netmask" name="netmas" type="text" value="{{WIFI_NM}}">
</div>
<div class="pure-control-group">
<label for="ip">DNS</label>
<input id="netmask" name="dns" type="text" value="{{WIFI_DNS}}">
</div>
<div class="pure-controls">
<button type="submit" class="pure-button pure-button-primary">Save</button>
</div>
</form>

275
firmware/data/index.html Normal file
View file

@ -0,0 +1,275 @@
<!doctype html>
<html>
<head>
<style></style>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Light Setup</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/purecss@0.6.2/build/pure-min.css">
</head>
<body>
<fieldset>
<h3>{{LIGHT_NAME}}</h3>
<div class="pure-form pure-form-aligned">
<div class="pure-controls">
<span class="pure-form-message">
<a href="/?alert=1">alert</a>
&nbsp;
<a href="/?reset=1">reset</a>
&nbsp;
<a href="/?resettc">reset timing control data</a>
&nbsp;
<a href="/update">update</a>
</span>
<label for="cb" class="pure-checkbox"></label>
</div>
<br>
<div class="pure-control-group">
<label for="tc_on">
<strong>Timing control</strong>
</label>
<a id="tc_on" class="pure-button {{TC_LINK_PRIMARY_ON}}" href="#">ON</a>
<a id="tc_off" class="pure-button {{TC_LINK_PRIMARY_OFF" href="#">OFF</a>
</div>
<script>
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');
});
});
</script>
<br>
<div class="pure-control-group">
<label for="transition">Transition time (s)</label>
<input id="transition" name="transition" type="text" placeholder="10" value="{{TRANSITION_TIME}}">
</div>
<br>
<script>
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);
}
</script>
<br>
<table border=0>
<tr>
<td>
{{LIGHTS_CONTROL}}
</td>
<td>
<div id="plot_chart"></div>
</td>
</tr>
</table>
{{CONFIG_PAGE}}
<script>
function loadGraphData() {
console.log('----> generate graph <----');
$.getJSON('/tc_data_blocks', 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_NUM}}; 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_NUM}}; 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);
</script>
</div>
</fieldset>
</body>
</html>

View file

@ -0,0 +1,29 @@
<h4>Light {{LIGHT_NUMBER}}</h4>
<div class="pure-control-group">
<label for="power">
<strong>Power</strong>
</label>
<a id="on{{LIGHT_NUMBER_DEC}}_on" class="pure-button" href="#">ON</a>
<a id="on{{LIGHT_NUMBER_DEC}}_off" class="pure-button" href="#">OFF</a>
</div>
<div class="pure-control-group">
<label for="bri{{LIGHT_NUMBER_DEC}}">Bri</label>
<input id="bri{{LIGHT_NUMBER_DEC}}" onchange="sendSliderValue({{LIGHT_NUMBER}})" name="bri{{LIGHT_NUMBER_DEC}}" type="range" min="0" max="255" value="25">
&nbsp;
<span id="bri{{LIGHT_NUMBER_DEC}}_val" name="bri{{LIGHT_NUMBER_DEC}}">9</span>
%
<br>
<label for="light{{LIGHT_NUMBER_DEC}}_pwm">PWM-Value</label>
<input type="range" min="0" max="100" value="0" id="light{{LIGHT_NUMBER_DEC}}_pwm" disabled>
&nbsp;
<span id="light{{LIGHT_NUMBER_DEC}}_pwm_txt"></span>
%
<script>
var slider{{LIGHT_NUMBER_DEC}} = document.getElementById("bri{{LIGHT_NUMBER_DEC}}");
var output{{LIGHT_NUMBER_DEC}} = document.getElementById("bri{{LIGHT_NUMBER_DEC}}_val");
output{{LIGHT_NUMBER_DEC}}.innerHTML = (Math.round((slider{{LIGHT_NUMBER_DEC}}.value * 100.0 / 255.0) * 100) / 100).toFixed(2);
slider{{LIGHT_NUMBER_DEC}}.oninput = function() {
output{{LIGHT_NUMBER_DEC}}.innerHTML = (Math.round((this.value * 100.0 / 255.0) * 100) / 100).toFixed(2);
}
</script>
</div>

View file

@ -5,6 +5,7 @@
#include <WiFiManager.h>
#include <ArduinoJson.h>
#include <EEPROM.h>
#include "FS.h"
#include "config.h"
@ -323,7 +324,6 @@ void handleNotFound()
void init_webserver()
{
#ifndef DISABLE_WEB_CONTROL
server.on("/state", HTTP_PUT, []()
{ // HTTP PUT request used to set a new light state
DynamicJsonDocument root(1024);
@ -427,13 +427,9 @@ void init_webserver()
server.send(200, "application/json", output);
});
#endif // DISABLE_WEB_CONTROL
server.on("/", []()
{
#ifndef DISABLE_WEB_CONTROL
if (server.hasArg("transition"))
{
default_transitiontime = server.arg("transition").toFloat();
@ -468,7 +464,6 @@ void init_webserver()
Serial.println(EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS));
}
}
#endif // DISABLE_WEB_CONTROL
// timing controller switch handling
if (server.hasArg("tc"))
@ -510,8 +505,6 @@ void init_webserver()
}
}
#ifndef DISABLE_WEB_CONTROL
// scene switch handling
if (server.hasArg("scene")) {
scene = server.arg("scene").toInt();
@ -602,8 +595,6 @@ void init_webserver()
} // process all lights
#endif // DISABLE_WEB_CONTROL
if (server.hasArg("resettc")) { // reqrite the tc config and reboot
tc_write_default();
ESP.reset();
@ -614,465 +605,131 @@ void init_webserver()
}
// Generate HTML page
String http_content = "<!doctype html>";
http_content += "<html>";
http_content += "<head>";
http_content += "<style></style>";
http_content += "<meta charset=\"utf-8\">";
http_content += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
//http_content += "<meta http-equiv=\"refresh\" content=\"15\">"; // Reload the page every 15 seconds automatically
http_content += "<title>Light Setup</title>";
http_content += "<script src=\"https://code.jquery.com/jquery-3.6.0.min.js\"></script>";
http_content += "<script src=\"https://cdn.plot.ly/plotly-latest.min.js\"></script>";
http_content += "<script src=\"https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js\"></script>";
http_content += "<link rel=\"stylesheet\" href=\"https://unpkg.com/purecss@0.6.2/build/pure-min.css\">";
http_content += "</head>";
http_content += "<body>";
http_content += "<fieldset>";
http_content += "<h3>" + (String)light_name + "</h3>";
String http_content = "123";
// HTML-Datei öffnen und Inhalt in eine Zeichenfolge speichern
File file = SPIFFS.open("/index.html", "r");
http_content = file.readString();
file.close();
http_content += "<div class=\"pure-form pure-form-aligned\">";
http_content.replace("{{LIGHT_NAME}}", (String)light_name);
http_content += "<div class=\"pure-controls\">";
http_content += "<span class=\"pure-form-message\"><a href=\"/?alert=1\">alert</a> &nbsp; <a href=\"/?reset=1\">reset</a> &nbsp; <a href=\"/?resettc\">reset timing control data</a> &nbsp; <a href=\"/update\">update</a></span>";
http_content += "<label for=\"cb\" class=\"pure-checkbox\">";
http_content += "</label>";
http_content += "</div>";
// timing control button
http_content += "<br>";
http_content += "<div class=\"pure-control-group\">";
http_content += "<label for=\"tc_on\"><strong>Timing control</strong></label>";
int tc_val = EEPROM.read(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS);
http_content += "<a id=\"tc_on\" class=\"pure-button";
if (tc_val == TIMING_CONTROL_ENABLED) http_content += " pure-button-primary";
http_content += "\" href=\"#\">ON</a>";
http_content += "<a id=\"tc_off\" class=\"pure-button";
if (tc_val == TIMING_CONTROL_DISABLED) http_content += " pure-button-primary";
http_content += "\" href=\"#\">OFF</a>";
http_content += "</div>";
http_content += "<script>"
// Suche nach allen tc Links auf der Seite mit IDs tc_on
"var links = document.querySelectorAll('[id^=\"tc_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();"
// Erstelle eine neue Anfrage an die entsprechende URL
"var xhr = new XMLHttpRequest();"
"xhr.open('GET', 'http://192.168.0.26/?tc=true', true);"
// Sende die Anfrage im Hintergrund
"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');"
"});"
"});"
// Suche nach allen tc Links auf der Seite mit IDs tc_off
"var links = document.querySelectorAll('[id^=\"tc_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();"
// Erstelle eine neue Anfrage an die entsprechende URL
"var xhr = new XMLHttpRequest();"
"xhr.open('GET', 'http://192.168.0.26/?tc=false', true);"
// Sende die Anfrage im Hintergrund
"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');"
"});"
"});"
"</script>";
http_content += "<br>";
if (tc_val == TIMING_CONTROL_ENABLED)
{
http_content.replace("{{TC_LINK_PRIMARY_ON]]", "pure-button-primary");
} else {
http_content.replace("{{TC_LINK_PRIMARY_ON]]", "");
}
if (tc_val == TIMING_CONTROL_DISABLED)
{
http_content.replace("{{TC_LINK_PRIMARY_OFF]]", "pure-button-primary");
} else {
http_content.replace("{{TC_LINK_PRIMARY_OFF]]", "");
}
http_content += "<div class=\"pure-control-group\">";
http_content += "<label for=\"transition\">Transition time (s)</label>";
http_content += "<input id=\"transition\" name=\"transition\" type=\"text\" placeholder=\"10\" value=\"" + (String)default_transitiontime + "\">";
http_content += "</div>";
http_content += "<br>";
http_content.replace("{{TRANSITION_TIME}}", (String)default_transitiontime);
http_content += "<script>"
"let timeoutId;" // slider value change submit to server
"function sendSliderValue(x) {"
"x = x - 1;"
"clearTimeout(timeoutId);"
"timeoutId = setTimeout(() => {"
"var value = document.getElementById(`bri${x}`).value;"
"var url = `http://192.168.0.26/?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);"
"}</script>";
// Generate lights part of the HTML page
String light_content = "";
// HTML-Datei öffnen und Inhalt in eine Zeichenfolge speichern
File file2 = SPIFFS.open("/light_control.html", "r");
light_content = file2.readString();
file2.close();
#ifndef DISABLE_WEB_CONTROL
http_content += "<br><table border=0><tr><td>";
// Light control
for (uint8 light_num = 0; light_num < LIGHTS_COUNT; light_num++)
{
// on/off buttons
http_content += "<h4>Light " + (String)(light_num + 1) + "</h4>";
http_content += "<div class=\"pure-control-group\">";
http_content += "<label for=\"power\"><strong>Power</strong></label>";
http_content += "<a id=\"on" + (String)light_num + "_on\" class=\"pure-button\" href=\"#\">ON</a>";
http_content += "<a id=\"on" + (String)light_num + "_off\" class=\"pure-button\" href=\"#\">OFF</a>";
http_content += "</div>";
// slider for brightness
http_content += "<div class=\"pure-control-group\">";
http_content += "<label for=\"bri" + (String)light_num + "\">Bri</label>";
http_content += "<input id=\"bri" + (String)light_num + "\" onchange=\"sendSliderValue(" + (String)(light_num+1) + ")\" name=\"bri" + (String)light_num + "\" type=\"range\" min=\"0\" max=\"255\" value=\"" + (String)bri[light_num] + "\">";
http_content += "&nbsp;<span id=\"bri" + (String)light_num + "_val\" name=\"bri" + (String)light_num + "\">" + (String)(int)(bri[light_num] * 100.0 / 255.0) + "</span>%";
http_content += "<br><label for=\"light" + (String)light_num + "_pwm\">PWM-Value</label>";
http_content += "<input type=\"range\" min=\"0\" max=\"100\" value=\"0\" id=\"light" + (String)light_num + "_pwm\" disabled>";
http_content += "&nbsp;<span id=\"light" + (String)light_num + "_pwm_txt\"></span>%";
http_content += "<script>";
http_content += "var slider" + (String)light_num + " = document.getElementById(\"bri" + (String)light_num + "\");";
http_content += "var output" + (String)light_num + " = document.getElementById(\"bri" + (String)light_num + "\_val\");";
http_content += "output" + (String)light_num + ".innerHTML = (Math.round((slider" + (String)light_num + ".value * 100.0 / 255.0) * 100) / 100).toFixed(2);";
http_content += "slider" + (String)light_num + ".oninput = function() {";
http_content += "output" + (String)light_num + ".innerHTML = (Math.round((this.value * 100.0 / 255.0) * 100) / 100).toFixed(2);";
http_content += "}";
http_content += "</script>";
http_content += "</div>";
// on/off buttons and slider
light_content.replace("{{LIGHT_NUMBER}}", (String)(light_num + 1));
light_content.replace("{{LIGHT_NUMBER_DEC}}", (String)light_num);
}
http_content += "<script>"
// 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://192.168.0.26/?on' + id + '=false', true);"
// Sende die Anfrage im Hintergrund
"xhr.send();"
"updateLightState();"
"});"
"});"
// 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://192.168.0.26/?on' + id + '=true', true);"
// Sende die Anfrage im Hintergrund
"xhr.send();"
"updateLightState();"
"});"
"});"
"</script>";
http_content += "<form class=\"pure-form pure-form-aligned\" action=\"/\" method=\"post\">";
// add the lights code to the html output string
http_content.replace("{{LIGHTS_CONTROL}}", light_content);
// startup state and scene for all of the lights
http_content += "</td><td>";
http_content += "<div id=\"plot_chart\"></div>";
http_content += "</td></tr></table><br>";
http_content += "<h3>Config</h3>";
http_content += "<div class=\"pure-control-group\">";
http_content += "<label for=\"startup\"><strong>Startup</strong></label>";
http_content += "<select onchange=\"this.form.submit()\" id=\"startup\" name=\"startup\">";
int ls_val = EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS);
http_content += "<option ";
if (ls_val == LAST_STATE_STARTUP_LIGHT_LAST_STATE) http_content += "selected=\"selected\"";
http_content += " value=\"0\">Last state</option>";
http_content += "<option ";
if (ls_val == LAST_STATE_STARTUP_LIGHT_ON_STATE) http_content += "selected=\"selected\"";
http_content += " value=\"1\">On</option>";
http_content += "<option ";
if (ls_val == LAST_STATE_STARTUP_LIGHT_OFF_STATE) http_content += "selected=\"selected\"";
http_content += " value=\"2\">Off</option>";
http_content += "</select>";
http_content += "</div>";
if (ls_val == LAST_STATE_STARTUP_LIGHT_LAST_STATE)
{
http_content.replace("{{STARTUP_SELECTED_LS_0}}", "selected=\"selected\"");
} else {
http_content.replace("{{STARTUP_SELECTED_LS_0}}", "");
}
if (ls_val == LAST_STATE_STARTUP_LIGHT_ON_STATE)
{
http_content.replace("{{STARTUP_SELECTED_ON_1}}", "selected=\"selected\"");
} else {
http_content.replace("{{STARTUP_SELECTED_ON_1}}", "");
}
if (ls_val == LAST_STATE_STARTUP_LIGHT_OFF_STATE)
{
http_content.replace("{{STARTUP_SELECTED_OFF_2}}", "selected=\"selected\"");
} else {
http_content.replace("{{STARTUP_SELECTED_OFF_2}}", "");
}
// scene
http_content += "<div class=\"pure-control-group\">";
http_content += "<label for=\"scene\"><strong>Scene</strong></label>";
http_content += "<select onchange = \"this.form.submit()\" id=\"scene\" name=\"scene\">";
int sc_val = EEPROM.read(EEPROM_SCENE_ADDRESS);
http_content += "<option ";
if (sc_val == SCENE_RELEAX) http_content += "selected=\"selected\"";
http_content += " value=\"0\">Relax</option>";
http_content += "<option ";
if (sc_val == SCENE_BRIGHT) http_content += "selected=\"selected\"";
http_content += " value=\"1\">Bright</option>";
http_content += "<option ";
if (sc_val == SCENE_NIGHTLY) http_content += "selected=\"selected\"";
http_content += " value=\"2\">Night</option>";
http_content += "</select>";
http_content += "</div>";
if (sc_val == SCENE_RELEAX)
{
http_content.replace("SCENE_SELECTED_RELAX_0", "selected=\"selected\"");
} else {
http_content.replace("SCENE_SELECTED_RELAX_0", "");
}
if (sc_val == SCENE_BRIGHT)
{
http_content.replace("SCENE_SELECTED_BRIGHT_1", "selected=\"selected\"");
} else {
http_content.replace("SCENE_SELECTED_BRIGHT_1", "");
}
if (sc_val == SCENE_NIGHTLY)
{
http_content.replace("SCENE_SELECTED_NIGHT_2", "selected=\"selected\"");
} else {
http_content.replace("SCENE_SELECTED_NIGHT_2", "");
}
// Generate lights part of the HTML page
String config_content = "";
// HTML-Datei öffnen und Inhalt in eine Zeichenfolge speichern
File file3 = SPIFFS.open("/config.html", "r");
config_content = file3.readString();
file3.close();
// Wifi settings
http_content += "<br>";
http_content += "<h3>Wifi</h3>";
http_content += "<div class=\"pure-control-group\">";
http_content += "<label for=\"ip\">SSID</label>";
http_content += "<input id=\"ssid\" name=\"ssid\" type=\"text\" value=\"" + WiFi.SSID() + "\">";
http_content += "</div>";
http_content += "<div class=\"pure-control-group\">";
http_content += "<label for=\"wpw\">Passphrase</label>";
http_content += "<input id=\"wpw\" name=\"wpw\" type=\"text\" placeholder=\"1234password\">";
http_content += "</div>";
config_content.replace("{{WIFI_SSIF}}", WiFi.SSID());
// Network settings
uint8_t dip = EEPROM.read(EEPROM_DYNAMIC_IP_ADDRESS);
http_content += "<br>";
http_content += "<h3>Network</h3>";
http_content += "<div class=\"pure-control-group\">";
http_content += "<label for=\"dip\"><strong>Dynamic-IP</strong></label>";
http_content += "<a class=\"pure-button";
if (dip) http_content += " pure-button-primary";
http_content += "\" href=\"/?dip=true\">ON</a>";
http_content += "<a class=\"pure-button";
if (!dip) http_content += " pure-button-primary";
http_content += "\" href=\"/?dip=false\">OFF</a>";
http_content += "</div>";
// ip config
if (dip == 0) {
http_content += "<div class=\"pure-control-group\">";
http_content += "<label for=\"ip\">IP</label>";
http_content += "<input id=\"ip\" name=\"ip\" type=\"text\" value=\"" + WiFi.localIP().toString() + "\">";
http_content += "</div>";
http_content += "<div class=\"pure-control-group\">";
http_content += "<label for=\"gwip\">Gateway IP</label>";
http_content += "<input id=\"gwip\" name=\"gwip\" type=\"text\" value=\"" + WiFi.gatewayIP().toString() + "\">";
http_content += "</div>";
http_content += "<div class=\"pure-control-group\">";
http_content += "<label for=\"ip\">Netmask</label>";
http_content += "<input id=\"netmask\" name=\"netmas\" type=\"text\" value=\"" + WiFi.subnetMask().toString() + "\">";
http_content += "</div>";
if (dip)
{
config_content.replace("{{DIP_LINK_ON_PRIMARY}}", "pure-button-primary");
config_content.replace("{{DIP_LINK_OFF_PRIMARY}}", "");
} else {
config_content.replace("{{DIP_LINK_OFF_PRIMARY}}", "pure-button-primary");
config_content.replace("{{DIP_LINK_ON_PRIMARY}}", "");
}
// The save button
http_content += "<div class=\"pure-controls\">";
http_content += "<button type=\"submit\" class=\"pure-button pure-button-primary\">Save</button>";
http_content += "</div>";
// ip config
if (dip == 0)
{
config_content.replace("{{WIFI_IP}}", WiFi.localIP().toString());
config_content.replace("{{WIFI_GW}}", WiFi.gatewayIP().toString());
config_content.replace("{{WIFI_NM}}", WiFi.subnetMask().toString());
config_content.replace("{{WIFI_DNS}}", WiFi.dnsIP().toString());
}
http_content += "<script>"
"function loadData() {" // load tc data and generate graph
"console.log('----> generate graph <----');"
"$.getJSON('/tc_data_blocks', 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);"
http_content.replace("{{CONFIG_PAGE}}", config_content);
// index suche für die vertikale linie
"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}`;"
//"console.log(dateString);" // 2023-04-28
"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);"
// TODO in array dynamisch erzeugt umschreiben
"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);" // TODO array der traces dynamisch erzeugen
"});"
"}"
"setInterval(loadData, 10000);"
"loadData();"
"function updateLightState() {" // load the light data from server and set on state and brightness
"console.log('----> setting bri and power switch <----');"
"for (let i = 1; i <= 4; i++) {"
"const lightURL = `http://192.168.0.26/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) {"
//"console.log('true');"
"onLinkOn.classList.add('pure-button-primary');"
"onLinkOff.classList.remove('pure-button-primary');"
"} else {"
//"console.log('false');"
"onLinkOn.classList.remove('pure-button-primary');"
"onLinkOff.classList.add('pure-button-primary');"
"}"
"})"
".catch(error => console.error(error));"
"}"
"}"
"setInterval(updateLightState, 10000);"
"updateLightState();"
// show pwm values in webinterface
"function updatePWMValues() {"
"console.log('----> setting pwm data <----');"
"for (let i = 0; i < " + (String)LIGHTS_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://192.168.0.26/state?light=${lightID}`;"
"fetch(url)"
".then(response => response.json())"
".then(data => {"
"const pwmValue = ((Math.round((data.curpwm - ((data.curpwm >= " + (String)PWM_MIN+ ") ? " + (String)PWM_MIN + " : 0)) / " + (String)PWM_MAX + " * 10000) / 100).toFixed(2));"// pwm as % PWM_MIN to PWM_MAX
"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);"
"</script>";
#endif // DISABLE_WEB_CONTROL
http_content += "</form>";
http_content += "</div>";
http_content += "</fieldset>";
http_content += "</body>";
http_content += "</html>";
http_content.replace("{{PWM_MIN}}", (String)PWM_MIN);
http_content.replace("{{PWM_MAX}}", (String)PWM_MAX);
server.send(200, "text/html", http_content);
});
server.on("/reset", []() { // trigger manual reset
server.on("/reset", []()
{ // trigger manual reset
server.send(200, "text/html", "reset");
delay(1000);
ESP.restart();