CanGrow/include/Webserver/Page_system.h

1721 lines
59 KiB
C

/*
*
* include/Webserver/Page_system.h - system settings page header file
*
*
*
*/
#include "Page_system_HTML.h"
/* global runtime variables */
/* VERY VERY DIRTY WORKAROUND
* I have the problem, that I cannot pass a parameter I receive from a http
* request to it's template processor. In my case i want to edit an output,
* the user should click an edit button on the system/output overview page.
* I am lazy so i want to reuse the output_add page, because it is quite
* kinda exactly the same. so i want to call GET /system/output/add?edit=ID
* I have searched and came to the conclusion, that at this point i see no
* other way then giving the parameter I need, the outputId, to an global
* variable, so the template processor can read it.
*/
byte tmpParam_editOutputId = 255;
byte tmpParam_editSensorId = 255;
byte tmpParam_calibrateSensorId = 255;
/* subnav processor */
const byte WEB_SYSTEM_SUBNAV_GENERAL = 1;
const byte WEB_SYSTEM_SUBNAV_SENSOR = 2;
const byte WEB_SYSTEM_SUBNAV_OUTPUT = 3;
const byte WEB_SYSTEM_SUBNAV_UPDATE = 4;
const byte WEB_SYSTEM_SUBNAV_RESTART = 5;
const byte WEB_SYSTEM_SUBNAV_WIPE = 6;
bool Test_WebPage_system_SUBNAV(const String& var) {
if(
(var == "SUBNAV") ||
(var == "ACTIVE_SUBNAV_GENERAL") ||
(var == "ACTIVE_SUBNAV_SENSOR") ||
(var == "ACTIVE_SUBNAV_OUTPUT") ||
(var == "ACTIVE_SUBNAV_UPDATE") ||
(var == "ACTIVE_SUBNAV_RESTART") ||
(var == "ACTIVE_SUBNAV_WIPE")) {
return true;
} else {
return false;
}
}
/*
* Proc_WebPage_system_SUBNAV - subnav processor for system
* this function works as same as AddHeaderFooter from Common.h
* byte activeSubnav:
* 1 - Output
* 2 - Update
* 3 - Restart
* 4 - Wipe
*/
String Proc_WebPage_system_SUBNAV(const String& var, byte activeSubnav = 0) {
String activeSubnav_ClassName = "activeNav";
if(var == "SUBNAV") {
return String(Page_system_HTML_SUBNAV);
} else if((var == "ACTIVE_SUBNAV_GENERAL") && (activeSubnav == WEB_SYSTEM_SUBNAV_GENERAL)) {
return activeSubnav_ClassName;
} else if((var == "ACTIVE_SUBNAV_SENSOR") && (activeSubnav == WEB_SYSTEM_SUBNAV_SENSOR)) {
return activeSubnav_ClassName;
} else if((var == "ACTIVE_SUBNAV_OUTPUT") && (activeSubnav == WEB_SYSTEM_SUBNAV_OUTPUT)) {
return activeSubnav_ClassName;
} else if((var == "ACTIVE_SUBNAV_UPDATE") && (activeSubnav == WEB_SYSTEM_SUBNAV_UPDATE)) {
return activeSubnav_ClassName;
} else if((var == "ACTIVE_SUBNAV_RESTART") && (activeSubnav == WEB_SYSTEM_SUBNAV_RESTART)) {
return activeSubnav_ClassName;
} else if((var == "ACTIVE_SUBNAV_WIPE") && (activeSubnav == WEB_SYSTEM_SUBNAV_WIPE)) {
return activeSubnav_ClassName;
} else {
return String();
}
}
/*******************************************************************************
* Main system page
*/
// https://techtutorialsx.com/2018/07/23/esp32-arduino-http-server-template-processing-with-multiple-placeholders/
String Proc_WebPage_system(const String& var) {
const static char LogLoc[] PROGMEM = "[Webserver:system(Proc)]";
/* This is a processor function, which returns a string.
* We check if var contains one of our placeholders from the template.
* If we hit a placeholder, we just return the String we want.
*
* TestHeaderFooter() Is kinda a processor too, but only checks for
* header specific placeholders.
*/
//Log.verbose(F("%s var: %s" CR), LogLoc, var);
if(TestHeaderFooter(var)) {
return AddHeaderFooter(var, 2);
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_GENERAL);
} else if(var == "NTPOFFSET") {
return String(config.system.ntpOffset);
} else if(var == "MAINTDUR") {
return String(config.system.maintenanceDuration);
} else if(var == "ESP32CAM") {
return String(config.system.esp32cam);
} else if(var == "HTTPLOGSERIAL") {
return Html_SelectOpt_bool(config.system.httpLogSerial);
} else if(var == "RTC_STATUS") {
/* show warn sign if rtcError is true (there was an error), otherwise green checkmark */
if(config.system.rtc > 0) {
if(rtcError == true) {
return F(" ⚠️ ");
} else {
return F(" ✅ ");
}
} else {
return String();
}
} else if(var == "RTC_AVAILABLE") {
return Html_SelectOpt_array(RTCs_total, RTCs_descr, config.system.rtc);
} else if(var == "TIME2FS") {
return Html_SelectOpt_bool(config.system.time2fs);
} else if(var == "PWMFREQ") {
return String(config.system.pwmFreq);
} else {
return String();
}
}
String Proc_WebPage_system_POST(const String& var) {
/* This is the processor for POST
* Its exactly the same, just looking for SAVE_MSG string.
* If nothing matches, it calles the main Proc_WebPage_system()
* processor function, so all the other stuff like header and so
* on get replaced
*/
if(var == "SAVE_MSG") {
return String(Common_HTML_SAVE_MSG);
} else {
return Proc_WebPage_system(var);
}
}
String Proc_WebPage_system_POST_ERR(const String& var) {
if(var == "SAVE_MSG") {
return String(Common_HTML_SAVE_MSG_ERR);
} else {
return Proc_WebPage_system(var);
}
}
/* WebPage function */
void WebPage_system(AsyncWebServerRequest *request) {
const static char LogLoc[] PROGMEM = "[Webserver:system]";
/* when changing httpLogSerial it requires a restart to take effect
* for this we keep the old val to compare it if it got changed
* to notice user for a restart */
bool old_httpLogSerial = config.system.httpLogSerial;
byte old_rtc = config.system.rtc;
short old_ntpOffset;
/* Which kind of Request */
if(request->method() == HTTP_POST) {
if(request->hasParam("ntp", true)) {
const AsyncWebParameter* param = request->getParam("ntp", true);
config.system.ntp = param->value().toInt();
}
if(request->hasParam("ntpOffset", true)) {
const AsyncWebParameter* param = request->getParam("ntpOffset", true);
//Log.verbose(F("%s POST[%s]: %s" CR), LogLoc, param->value().c_str());
old_ntpOffset = config.system.ntpOffset;
config.system.ntpOffset = param->value().toInt();
if((config.system.ntp == true) && (old_ntpOffset != config.system.ntpOffset)) {
// trigger ntp offset update
updateNtpOffset = true;
}
}
if(request->hasParam("maintenanceDuration", true)) {
const AsyncWebParameter* param = request->getParam("maintenanceDuration", true);
config.system.maintenanceDuration = param->value().toInt();
}
if(request->hasParam("esp32cam", true)) {
const AsyncWebParameter* param = request->getParam("esp32cam", true);
//config.system.esp32cam = param->value().toInt();
strlcpy(config.system.esp32cam, param->value().c_str(), sizeof(config.system.esp32cam));
}
if(request->hasParam("httpLogSerial", true)) {
const AsyncWebParameter* param = request->getParam("httpLogSerial", true);
config.system.httpLogSerial = param->value().toInt();
if( old_httpLogSerial != config.system.httpLogSerial) {
needRestart = true;
}
}
if(request->hasParam("rtc", true)) {
const AsyncWebParameter* param = request->getParam("rtc", true);
config.system.rtc = param->value().toInt();
if( old_rtc != config.system.rtc) {
needRestart = true;
if(config.system.rtc > 0)
rtcError = true;
}
}
if(request->hasParam("time2fs", true)) {
const AsyncWebParameter* param = request->getParam("time2fs", true);
config.system.time2fs = param->value().toInt();
}
if(request->hasParam("pwmFreq", true)) {
const AsyncWebParameter* param = request->getParam("pwmFreq", true);
config.system.pwmFreq = param->value().toInt();
#ifdef ESP8266
/* set pwm frequency global for ESP8266.
* ESP32 pwm frequency setting is done withing CanGrow_Output / Init */
analogWriteFreq(config.system.pwmFreq);
#endif
}
if(SaveConfig()) {
// we need a restart to apply the new settings
Log.notice(F("%s config saved" CR), LogLoc);
request->send_P(200, "text/html", Page_system_HTML, Proc_WebPage_system_POST);
} else {
Log.error(F("%s ERROR while saving config" CR), LogLoc);
request->send_P(200, TEXT_HTML, Page_system_HTML, Proc_WebPage_system_POST_ERR);
}
} else {
request->send_P(200, TEXT_HTML, Page_system_HTML, Proc_WebPage_system);
}
}
/*******************************************************************************
* Subpage restart
*/
String Proc_WebPage_system_restart(const String& var) {
if(TestHeaderFooter(var)) {
return AddHeaderFooter(var, 2);
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_RESTART);
} else if(var == "RESTART_MSG") {
return String(Page_system_restart_HTML_RESTART_MSG);
} else {
return String();
}
}
String Proc_WebPage_system_restart_POST(const String& var) {
if(var == "RESTART_MSG") {
return String(Page_system_restart_HTML_RESTART_MSG_POST);
} else {
return Proc_WebPage_system_restart(var);
}
}
void WebPage_system_restart(AsyncWebServerRequest *request) {
const static char LogLoc[] PROGMEM = "[Webserver:system:restart]";
if(request->method() == HTTP_POST) {
if(request->hasParam("confirmed", true)) {
doRestart = false;
}
//request->send_P(200, TEXT_HTML, Page_system_restart_HTML, Proc_WebPage_system_restart_POST);
/* Add custom header for redirect after timeout
* https://github.com/mathieucarbou/ESPAsyncWebServer?tab=readme-ov-file#send-large-webpage-from-progmem-containing-templates-and-extra-headers */
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", Page_system_restart_HTML, Proc_WebPage_system_restart_POST);
/* return Refresh header to redirect to root page after restart */
if(config.wifi.dhcp == true) {
response->addHeader("Refresh","20; url=http://" + WiFi.localIP().toString());
} else {
response->addHeader("Refresh","20; url=http://" + String(IP2Char(config.wifi.ip)));
}
request->send(response);
if(request->hasParam("confirmed", true)) {
Log.notice(F("%s POST[confirmed]: is set, triggering restart" CR), LogLoc);
// set global var doRestart to true causes a restart
doRestart = true;
}
} else {
request->send_P(200, TEXT_HTML, Page_system_restart_HTML, Proc_WebPage_system_restart);
}
}
/*******************************************************************************
* Subpage update
*/
// https://github.com/mathieucarbou/ESPAsyncWebServer/blob/main/docs/index.md#setting-up-the-server
void WebPage_system_update_ApplyUpdate(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
const static char LogLoc[] PROGMEM = "[Webserver:system:update:ApplyUpdate]";
if(!index){
Log.notice(F("%s Update Start: %s" CR), LogLoc, filename.c_str());
// https://github.com/me-no-dev/ESPAsyncWebServer/issues/455#issuecomment-451728099
// workaround for bug with ESP32
#ifdef ESP8266
Update.runAsync(true);
#endif
if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)){
Update.printError(Serial);
}
}
if(!Update.hasError()){
if(Update.write(data, len) != len){
Update.printError(Serial);
}
}
if(final){
if(Update.end(true)){
Log.notice(F("%s Update Success: %uB" CR), LogLoc, index+len);
} else {
Log.error(F("%s FAILED Update:" CR), LogLoc);
Update.printError(Serial);
}
}
}
String Proc_WebPage_system_update(const String& var) {
if(TestHeaderFooter(var)) {
return AddHeaderFooter(var, 2);
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_UPDATE);
} else {
return String();
}
}
/* After an update.bin file was uploaded*/
String Proc_WebPage_system_update_POST(const String& var) {
if(var == "CONFIGWIFI_IP") {
if(config.wifi.dhcp == true) {
return WiFi.localIP().toString();
} else {
return String(IP2Char(config.wifi.ip));
}
} else {
return String();
}
}
void WebPage_system_update(AsyncWebServerRequest *request) {
if(request->method() == HTTP_POST) {
doRestart = !Update.hasError();
// when doRestart is true, deliver Page_system_update_HTML_POST
// otherwise Page_system_update_HTML_POST_FAILED
AsyncWebServerResponse *response = request->beginResponse_P(200, TEXT_HTML, doRestart?Page_system_update_HTML_POST:Page_system_update_HTML_POST_FAILED, Proc_WebPage_system_update_POST);
response->addHeader(F("Connection"), F("close"));
request->send(response);
} else {
request->send_P(200, TEXT_HTML, Page_system_update_HTML, Proc_WebPage_system_update);
}
}
/*******************************************************************************
* Subpage wipe
*/
String Proc_WebPage_system_wipe(const String& var) {
if(TestHeaderFooter(var)) {
return AddHeaderFooter(var, 2);
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_WIPE);
} else if(var == "WIPE_MSG") {
return String(Page_system_wipe_HTML_WIPE_MSG);
} else {
return String();
}
}
String Proc_WebPage_system_wipe_POST(const String& var) {
if(var == "WIPE_MSG") {
return String(Page_system_wipe_HTML_WIPE_MSG_POST);
} else {
return Proc_WebPage_system_wipe(var);
}
}
void WebPage_system_wipe(AsyncWebServerRequest *request) {
const static char LogLoc[] PROGMEM = "[Webserver:system:wipe]";
if(request->method() == HTTP_POST) {
request->send_P(200, TEXT_HTML, Page_system_wipe_HTML, Proc_WebPage_system_wipe_POST);
if(request->hasParam("confirmed", true)) {
Log.notice(F("%s POST[confirmed]: is set, triggering wipe / factory reset" CR), LogLoc);
LFS_Format();
Log.notice(F("%s triggering restart" CR), LogLoc);
doRestart = true;
}
} else {
request->send_P(200, TEXT_HTML, Page_system_wipe_HTML, Proc_WebPage_system_wipe);
}
}
/*******************************************************************************
* Subpage output
*/
String Proc_WebPage_system_output(const String& var) {
if(TestHeaderFooter(var)) {
return AddHeaderFooter(var, 2);
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_OUTPUT);
} else if(var == "ADD_DISABLED") {
/* check if there is a free Output Id. Give_Free_OutputId returns 255 if no id available, otherwise it
* gives us the next free id. Here we check if the given ID is greater then Max_Outputs. This will also
* reflect a valid result, if there is a free id left or not. */
if(Give_Free_OutputId() > Max_Outputs ) {
return F("disabled force_hide");
} else {
return String();
}
} else if(var == "TR_TD") {
// build table body
// i dont know a better way at the moment. if you do, please tell me!
String html;
for(byte i=0; i < Max_Outputs; i++) {
if(config.system.output.type[i] > 0) {
html += F("<tr><td>");
/* show warn sign if outputStatus is false (uninitialized), otherwise green checkmark */
if(outputStatus[i] == false) {
html += F(" &#x26A0;&#xFE0F;");
} else {
html += F(" &#x2705;");
}
/* bit spacing after the status icon */
html += F("&nbsp;&nbsp;");
html += F("</td><td>");
html += i;
html += F("</td><td>");
html += config.system.output.name[i];
html += F("</td><td>");
html += FPSTR(Output_Type_descr[config.system.output.type[i]]);
if((config.system.output.type[i] == OUTPUT_TYPE_GPIO) || ( (config.system.output.type[i] == OUTPUT_TYPE_I2C) && (config.system.output.i2c_type[i] > 0) )) {
html += F(" (");
switch(config.system.output.type[i]) {
case OUTPUT_TYPE_GPIO:
html += GPIOindex[config.system.output.gpio[i]].gpio;
break;
case OUTPUT_TYPE_I2C:
html += OutputI2Cindex[config.system.output.i2c_type[i]].name;
break;
default:
break;
}
html += F(")");
}
html += F("</td><td>");
html += FPSTR(Output_Device_descr[config.system.output.device[i]]);
html += F("</td><td>");
if(config.system.output.enabled[i] > 0) {
html += F("&nbsp;&#x1F7E2;&nbsp;");
} else {
html += F("&nbsp;&#x1F534;&nbsp;&nbsp;");
}
html += F("</td><td>");
// edit button
html += F("<form class='linkForm' action='/system/output/add' method='get'>");
html += F("<input type='hidden' name='edit' value='");
html += i;
html += F("'>");
html += F("<input type='submit' value='&#x270F;&#xFE0F;' title='Edit'></form> ");
// delete button
html += F("<form class='linkForm' action='/system/output/' method='post'>");
html += F("<input type='hidden' name='delete_output' value='");
html += i;
html += F("'>");
html += F("<input type='submit' value='&#x274C;' onclick=\"return confirmDelete('");
html += config.system.output.name[i];;
html += F("')\" title='Delete'></form>");
html += F("</td></tr>");
}
}
return html;
} else{
return String();
}
}
String Proc_WebPage_system_output_POST(const String& var) {
if(var == "SAVE_MSG") {
return String(Common_HTML_SAVE_MSG);
} else {
return Proc_WebPage_system_output(var);
}
}
void WebPage_system_output(AsyncWebServerRequest *request) {
if(request->method() == HTTP_POST) {
if(request->hasParam("delete_output", true)) {
byte outputId;
const AsyncWebParameter* param = request->getParam("delete_output", true);
outputId = param->value().toInt();
/* remove grow objects */
Output_Device_Grow_AddRemove(outputId, 1);
// we ensure that every field is empty
config.system.output.type[outputId] = 0;
config.system.output.device[outputId] = 0;
// set every field of char array to 0x00 with memset
memset(config.system.output.name[outputId], '\0', sizeof config.system.output.name[outputId]);
config.system.output.enabled[outputId] = 0;
config.system.output.gpio[outputId] = 0;
config.system.output.gpio_pwm[outputId] = 0;
config.system.output.invert[outputId] = 0;
config.system.output.i2c_type[outputId] = 0;
memset(config.system.output.webcall_host[outputId], '\0', sizeof config.system.output.webcall_host[outputId]);
memset(config.system.output.webcall_path_on[outputId], '\0', sizeof config.system.output.webcall_path_on[outputId]);
memset(config.system.output.webcall_path_off[outputId], '\0', sizeof config.system.output.webcall_path_off[outputId]);
#ifdef DEBUG
SaveConfig(true);
#endif
SaveConfig();
}
request->send_P(200, TEXT_HTML, Page_system_output_HTML, Proc_WebPage_system_output_POST);
} else {
if(request->hasParam("success")) {
// when GET param success is present, we use the _POST processor for the save message
request->send_P(200, TEXT_HTML, Page_system_output_HTML, Proc_WebPage_system_output_POST);
} else {
request->send_P(200, TEXT_HTML, Page_system_output_HTML, Proc_WebPage_system_output);
}
}
}
/*******************************************************************************
* Subpage output add
*/
/* returns select <option> list of available output types */
String Html_SelOpt_type_WebPage_system_output_i2c_add(byte selectId = 255) {
String html;
// go through all available Output I2C modules, skip 0 because it means unconfigured
for(byte i = 1; i <= OutputI2Cindex_length; i++) {
html += F("<option value='");
html += i;
html += F("'");
if(i == selectId) {
html += F(" selected");
}
html += F(">");
html += OutputI2Cindex[i].name;
html += F("</option>");
}
return html;
}
String Js_I2cAddr_Array_WebPage_system_output_i2c_add() {
const static char LogLoc[] PROGMEM = "[Webserver:system:output:add:i2c:Js_I2cAddr_Array]";
/* bit hacky, bit dirty, but may work
* here we return a 2-dimensional javascript array. the returned stuff
* gets directly injected in the template into a js function
*/
String js;
/* iterate through all OutputI2Cindex in index*/
for(byte i = 1; i <= OutputI2Cindex_length; i++) {
// name
js += F("[");
/* iterate through all available addresses */
for(byte j = 0; j < OutputI2Cindex[i].max; j++) {
js += F("['0x");
js += String(Output_I2C_Addr_Init_Update(i, j, 0, OUPUT_I2C_AIU_MODE_ADDR), HEX);
// value
js += F("', '");
js += j;
// used
js += F("', [");
/* check I2C module ports available */
for(byte k = 0; k < OUTPUT_TYPE_I2C_MAX_PORTS; k++) {
/* When this Port of the module offers a value */
if(OutputI2Cindex[i].port[k] > 0) {
js += F("'");
/* check if I2C module and port are used in config */
for(byte l = 0; l < Max_Outputs; l++) {
if((config.system.output.type[l] == OUTPUT_TYPE_I2C) && (config.system.output.i2c_type[l] == i) &&
(config.system.output.i2c_addr[l] == j) && (config.system.output.i2c_port[l] == k)) {
js += 1;
/* exit loop here */
l = Max_Outputs + 1;
}
}
js += F("',");
}
}
js += F("]],");
}
js += F("],\n");
}
return js;
}
String Proc_WebPage_system_output_add(const String& var) {
if(TestHeaderFooter(var)) {
return AddHeaderFooter(var, 2);
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_OUTPUT);
} else if(var == "ACTION") {
return F("&#10133; Add");
} else if(var == "OUTPUT_ID") {
// we check which id is free. A free ID as type == 0
return String(Give_Free_OutputId());
} else if(var == "OUTPUT_TYPE") {
return Html_SelectOpt_array(OUTPUT_TYPE__TOTAL, Output_Type_descr);
} else if(var == "OUTPUT_DEVICE") {
return Html_SelectOpt_array(OUTPUT_DEVICE__TOTAL, Output_Device_descr);
} else if(var == "OUTPUT_ENABLED") {
return Html_SelectOpt_bool();
} else if(var == "INVERT") {
return Html_SelectOpt_bool();
} else if(var == "GPIO_INDEX") {
return Html_SelectOpt_GPIOindex();
} else if(var == "GPIO_PWM") {
return Html_SelectOpt_bool();
} else if(var == "I2C_TYPE") {
return Html_SelOpt_type_WebPage_system_output_i2c_add();
} else if(var == "REPLACE_I2CADDR_JS") {
return Js_I2cAddr_Array_WebPage_system_output_i2c_add();
} else {
return String();
}
}
String Proc_WebPage_system_output_addEdit(const String& var) {
if(TestHeaderFooter(var)) {
return AddHeaderFooter(var, 2);
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_OUTPUT);
} else if(var == "ACTION") {
return F("&#x270F;&#xFE0F; Edit");
} else if(var == "EDIT_MODE") {
return F("editmode");
} else if(var == "OUTPUT_ID") {
// return the outputId we got from GET .../add?edit=ID
// dirty workaround to put this in a global variable
return String(tmpParam_editOutputId);
} else if(var == "OUTPUT_TYPE") {
return Html_SelectOpt_array(OUTPUT_TYPE__TOTAL, Output_Type_descr, config.system.output.type[tmpParam_editOutputId]);
} else if(var == "OUTPUT_DEVICE") {
return Html_SelectOpt_array(OUTPUT_DEVICE__TOTAL, Output_Device_descr, config.system.output.device[tmpParam_editOutputId]);
} else if(var == "OUTPUT_NAME") {
// "escape" % character, because it would break the template processor.
// tasmote webcall for example has percentage char in its path
String outputName = config.system.output.name[tmpParam_editOutputId];;
outputName.replace(F("%"), F("&#37;"));
return outputName;
} else if(var == "OUTPUT_ENABLED") {
return Html_SelectOpt_bool(config.system.output.enabled[tmpParam_editOutputId]);
} else if(var == "INVERT") {
return Html_SelectOpt_bool(config.system.output.invert[tmpParam_editOutputId]);
} else if(var == "GPIO_INDEX") {
return Html_SelectOpt_GPIOindex(config.system.output.gpio[tmpParam_editOutputId]);
} else if(var == "GPIO_PWM") {
return Html_SelectOpt_bool(config.system.output.gpio_pwm[tmpParam_editOutputId]);
} else if(var == "I2C_TYPE") {
//return String(config.system.output.i2c_type[tmpParam_editOutputId]);
//Html_SelectOpt_array(OutputI2Cindex_length, OutputI2Cindex[].name, config.system.output.i2c_type[tmpParam_editOutputId]);
return Html_SelOpt_type_WebPage_system_output_i2c_add(config.system.output.i2c_type[tmpParam_editOutputId]);
} else if(var == "REPLACE_I2CADDR_JS") {
return Js_I2cAddr_Array_WebPage_system_output_i2c_add();
} else if(var == "I2C_SAVED") {
/* Add bit javascript to ensure the saved value is selected */
String js;
js += F("showSelect('type_sel', 'type_', 'hidden'); SystemOutputAddselectRequired('type_sel');");
//js += F("document.getElementById('i2c_type').value='");
//js += config.system.output.i2c_type[tmpParam_editOutputId];
//js += F("';\n");
js += F("SystemOutputAdd_replaceI2cAddr('i2c_type', 'i2c_addr');");
js += F("document.getElementById('i2c_addr').value='");
js += config.system.output.i2c_addr[tmpParam_editOutputId];
js += F("';\n");
js += F("SystemOutputAdd_replaceI2cPort('i2c_type', 'i2c_addr', 'i2c_port');\n");
js += F("document.getElementById('i2c_port').value='");
js += config.system.output.i2c_port[tmpParam_editOutputId];
js += F("';\n");
//js += "SystemOutputAdd_replaceI2cPort('i2c_type', 'i2c_addr', 'i2c_port');";
return js;
} else if(var == "WEBCALL_HOST") {
return String(config.system.output.webcall_host[tmpParam_editOutputId]);
} else if(var == "WEBCALL_PATH_ON") {
String webcallPathOn = config.system.output.webcall_path_on[tmpParam_editOutputId];
webcallPathOn.replace(F("%"), F("&#37;"));
return webcallPathOn;
} else if(var == "WEBCALL_PATH_OFF") {
String webcallPathOff = config.system.output.webcall_path_off[tmpParam_editOutputId];
webcallPathOff.replace(F("%"), F("&#37;"));
return webcallPathOff;
} else if(
((var == "CLASS_TYPE_1") && (config.system.output.type[tmpParam_editOutputId] == 1)) ||
((var == "CLASS_TYPE_2") && (config.system.output.type[tmpParam_editOutputId] == 2)) ||
((var == "CLASS_TYPE_3") && (config.system.output.type[tmpParam_editOutputId] == 3))) {
// add class 'visible' which overwrites display with flex!important and justify center
return F("visible");
} else {
return String();
}
}
String Proc_WebPage_system_output_add_POST(const String& var) {
if(var == "SAVE_MSG") {
return String(Common_HTML_SAVE_MSG);
} else {
return Proc_WebPage_system_output_add(var);
}
}
void WebPage_system_output_add(AsyncWebServerRequest *request) {
const static char LogLoc[] PROGMEM = "[Webserver:system:output:add]";
if(request->method() == HTTP_POST) {
byte outputId;
byte outputType;
//byte outputType_old;
byte outputDevice_old;
if(request->hasParam("outputId", true)) {
const AsyncWebParameter* param = request->getParam("outputId", true);
outputId = param->value().toInt();
}
if(request->hasParam("type", true)) {
const AsyncWebParameter* param = request->getParam("type", true);
// put info config struct
config.system.output.type[outputId] = param->value().toInt();
// remember the value in own var to work later with here
//outputType = param->value().toInt();
}
/* save outputDevice_old */
outputDevice_old = config.system.output.device[outputId];
if(request->hasParam("device", true)) {
const AsyncWebParameter* param = request->getParam("device", true);
byte outputDevice = param->value().toInt();
/* check if output type has changed. if so, delete old Output
* Grow Devices and recreate them with new type later */
//if((outputType != outputType_old) || (outputDevice != outputDevice_old))
if(outputDevice != outputDevice_old) {
#ifdef DEBUG
Log.verbose(F("%s - device changed, delete old Grow object" CR), LogLoc);
#endif
Output_Device_Grow_AddRemove(outputId, 1);
//config.grow.light.configured[outputId] = false;
}
/* finally write the new device type into the config */
config.system.output.device[outputId] = param->value().toInt();
}
if(request->hasParam("name", true)) {
const AsyncWebParameter* param = request->getParam("name", true);
strlcpy(config.system.output.name[outputId], param->value().c_str(), sizeof(config.system.output.name[outputId]));
}
if(request->hasParam("enabled", true)) {
const AsyncWebParameter* param = request->getParam("enabled", true);
config.system.output.enabled[outputId] = param->value().toInt();
}
if(request->hasParam("invert", true)) {
const AsyncWebParameter* param = request->getParam("invert", true);
config.system.output.invert[outputId] = param->value().toInt();
}
// only fill the type related config vars
switch(config.system.output.type[outputId]) {
// GPIO
case OUTPUT_TYPE_GPIO:
if(request->hasParam("gpio", true)) {
byte old_gpio = config.system.output.gpio[outputId];
const AsyncWebParameter* param = request->getParam("gpio", true);
config.system.output.gpio[outputId] = param->value().toInt();
if(old_gpio != config.system.output.gpio[outputId])
needRestart = true;
}
if(request->hasParam("gpio_pwm", true)) {
const AsyncWebParameter* param = request->getParam("gpio_pwm", true);
config.system.output.gpio_pwm[outputId] = param->value().toInt();
}
break;
// I2C
case OUTPUT_TYPE_I2C:
if(request->hasParam("i2c_type", true)) {
byte old_i2c_type = config.system.output.i2c_type[outputId];
const AsyncWebParameter* param = request->getParam("i2c_type", true);
config.system.output.i2c_type[outputId] = param->value().toInt();
if(old_i2c_type != config.system.output.i2c_type[outputId])
needRestart = true;
outputStatus[outputId] = false;
}
if(request->hasParam("i2c_addr", true)) {
byte old_i2c_addr = config.system.output.i2c_addr[outputId];
const AsyncWebParameter* param = request->getParam("i2c_addr", true);
config.system.output.i2c_addr[outputId] = param->value().toInt();
if(old_i2c_addr != config.system.output.i2c_addr[outputId])
needRestart = true;
outputStatus[outputId] = false;
}
if(request->hasParam("i2c_port", true)) {
const AsyncWebParameter* param = request->getParam("i2c_port", true);
config.system.output.i2c_port[outputId] = param->value().toInt();
}
break;
// Webcall
case OUTPUT_TYPE_WEB:
if(request->hasParam("webcall_host", true)) {
const AsyncWebParameter* param = request->getParam("webcall_host", true);
strlcpy(config.system.output.webcall_host[outputId], param->value().c_str(), sizeof(config.system.output.webcall_host[outputId]));
}
if(request->hasParam("webcall_path_on", true)) {
const AsyncWebParameter* param = request->getParam("webcall_path_on", true);
strlcpy(config.system.output.webcall_path_on[outputId], param->value().c_str(), sizeof(config.system.output.webcall_path_on[outputId]));
}
if(request->hasParam("webcall_path_off", true)) {
const AsyncWebParameter* param = request->getParam("webcall_path_off", true);
strlcpy(config.system.output.webcall_path_off[outputId], param->value().c_str(), sizeof(config.system.output.webcall_path_off[outputId]));
}
/* reset in any case the webcall fail counter to trigger update retry */
outputWebcallFailed[outputId] = 0;
break;
default: break;
}
/* create grow objects */
if(request->hasParam("editmode")) {
//Log.verbose(F("%s - has edit" CR), LogLoc);
if(config.system.output.device[outputId] != outputDevice_old) {
#ifdef DEBUG
Log.verbose(F("%s - device changed, Recreate Grow object" CR), LogLoc);
#endif
// remove - already done few lines before
//Output_Device_Grow_AddRemove(outputId, 1);
// add empty
Output_Device_Grow_AddRemove(outputId, 0);
}
} else {
#ifdef DEBUG
Log.verbose(F("%s - no edit" CR), LogLoc);
#endif
Output_Device_Grow_AddRemove(outputId, 0);
}
#ifdef DEBUG
SaveConfig(true);
#endif
SaveConfig();
// request->send_P(200, "text/html", Page_system_output_add_HTML, Proc_WebPage_system_output_add_POST);
// I like it more when user gets redirected to the output overview after saving
request->redirect(F("/system/output/?success"));
} else {
/* When in edit mode */
if(request->hasParam("edit")) {
const AsyncWebParameter* param = request->getParam("edit");
tmpParam_editOutputId = param->value().toInt();
request->send_P(200, TEXT_HTML, Page_system_output_add_HTML, Proc_WebPage_system_output_addEdit);
/* when just adding a new output, check if there are free IDs */
} else if(Give_Free_OutputId() > Max_Outputs) {
/* if not, send error */
request->send_P(200, TEXT_HTML, Page_system_output_add_HTML_NO_ID_AVAILABLE, Proc_WebPage_system_output_add);
} else {
/* otherwise let the user create new output */
request->send_P(200, TEXT_HTML, Page_system_output_add_HTML, Proc_WebPage_system_output_add);
}
}
}
/*******************************************************************************
* Subpage sensor
*/
String Proc_WebPage_system_sensor(const String& var) {
if(TestHeaderFooter(var)) {
return AddHeaderFooter(var, 2);
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_SENSOR);
} else if(var == "ADD_DISABLED") {
if(Give_Free_SensorId() > Max_Outputs ) {
return F("disabled force_hide");
} else {
return String();
}
} else if(var == "TR_TD") {
// build table body
// i dont know a better way at the moment. if you do, please tell me!
String html;
for(byte i=0; i < Max_Sensors; i++) {
if(config.system.sensor.type[i] > 0) {
html += F("<tr><td>");
/* show warn sign if sensorStatus is false (uninitialized), otherwise green checkmark */
if(sensorStatus[i] == false) {
html += F(" &#x26A0;&#xFE0F;");
} else {
html += F(" &#x2705;");
}
/* bit spacing after the status icon */
html += F("&nbsp;&nbsp;");
html += F("</td><td>");
/* sens*/
html += i;
html += F("</td><td>");
html += config.system.sensor.name[i];
html += F("</td><td>");
html += SensorIndex[config.system.sensor.type[i]].name;
/* when GPIO pin or I2C sensor is configured (1 is int adc), shot the Pin / addr in overview */
if((
(SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_INTADC) && config.system.sensor.gpio[i][0] > 0) ||
(SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_ONEWIRE) ||
(SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_TWOWIRE) ||
(SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_I2C)) {
html += F(" (");
if(config.system.sensor.gpio[i][0] > 0) {
html += GPIOindex[config.system.sensor.gpio[i][0]].gpio;
if(config.system.sensor.gpio[i][1] > 0) {
html += F("/");
html += GPIOindex[config.system.sensor.gpio[i][1]].gpio;
}
} else if(SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_I2C) {
html += F("0x");
html += String(Sensor_Addr_Init_Update(config.system.sensor.type[i], config.system.sensor.i2c_addr[i], SENSOR_AIU_MODE_ADDR), HEX);
}
html += F(")");
}
html += F("</td><td>");
// calibrate button
html += F("<form class='linkForm' action='/system/sensor/calibrate' method='get'>");
html += F("<input type='hidden' name='calibrate' value='");
html += i;
html += F("'>");
html += F("<input type='submit' value='&#x1F39B;&#xFE0F;' title='Calibrate'></form> ");
// edit button
html += F("<form class='linkForm' action='/system/sensor/add' method='get'>");
html += F("<input type='hidden' name='edit' value='");
html += i;
html += F("'>");
html += F("<input type='submit' value='&#x270F;&#xFE0F;' title='Edit'></form> ");
// delete button
html += F("<form class='linkForm' action='/system/sensor/' method='post'>");
html += F("<input type='hidden' name='delete_sensor' value='");
html += i;
html += F("'>");
html += F("<input type='submit' value='&#x274C;' onclick=\"return confirmDelete('");
html += config.system.sensor.name[i];;
html += F("')\" title='Delete'></form>");
html += F("</td></tr>");
}
}
return html;
} else{
return String();
}
}
String Proc_WebPage_system_sensor_POST(const String& var) {
if(var == "SAVE_MSG") {
return String(Common_HTML_SAVE_MSG);
} else {
return Proc_WebPage_system_sensor(var);
}
}
void WebPage_system_sensor(AsyncWebServerRequest *request) {
if(request->method() == HTTP_POST) {
if(request->hasParam("delete_sensor", true)) {
byte sensorId;
const AsyncWebParameter* param = request->getParam("delete_sensor", true);
sensorId = param->value().toInt();
// we ensure that every field is empty
config.system.sensor.type[sensorId] = 0;
memset(config.system.sensor.name[sensorId], '\0', sizeof config.system.sensor.name[sensorId]);
/* go through all GPIOs */
for(byte i = 0; i < Max_Sensors_GPIO; i++) {
config.system.sensor.gpio[sensorId][i] = 0;
}
config.system.sensor.i2c_addr[sensorId] = 0;
sensorStatus[sensorId];
SaveConfig();
}
request->send_P(200, TEXT_HTML, Page_system_sensor_HTML, Proc_WebPage_system_sensor_POST);
} else {
if(request->hasParam("success")) {
// when GET param success is present, we use the _POST processor for the save message
request->send_P(200, TEXT_HTML, Page_system_sensor_HTML, Proc_WebPage_system_sensor_POST);
} else {
request->send_P(200, TEXT_HTML, Page_system_sensor_HTML, Proc_WebPage_system_sensor);
}
}
}
/*******************************************************************************
* Subpage sensor add
*/
/* returns select <option> list of available output types */
String Html_SelOpt_type_WebPage_system_sensor_add(byte selectId = 255) {
String html;
// go through all available Output Devices, skip 0 because it means unconfigured
for(byte i = 1; i <= SensorIndex_length; i++) {
html += F("<option value='");
html += i;
html += F("'");
if(i == selectId) {
html += F(" selected");
}
html += F(">");
html += SensorIndex[i].name;
html += F("</option>");
}
return html;
}
String Js_I2cAddr_Array_WebPage_system_sensor_add() {
const static char LogLoc[] PROGMEM = "[Webserver:system:sensor:add:Js_I2cAddr_Array]";
/* bit hacky, bit dirty, but may work
* here we return a 2-dimensional javascript array. the returned stuff
* gets directly injected in the template into a js function
*/
String js;
/* iterate through all SensorIds in index*/
for(byte i = 1; i <= SensorIndex_length; i++) {
// name
js += F("[");
/* iterate through all available addresses */
for(byte j = 0; j < SensorIndex[i].max; j++) {
js += F("['0x");
js += String(Sensor_Addr_Init_Update(i, j, SENSOR_AIU_MODE_ADDR), HEX);
// value
js += F("', '");
js += j;
// used
bool used;
js += F("', '");
/* check if addr is used */
for(byte k = 0; k < Max_Sensors; k++) {
if(config.system.sensor.type[k] == i ) {
if(config.system.sensor.i2c_addr[k] == j) {
js += F("1");
// exit loop here
k = Max_Sensors + 1;
} //else {
//js += "0";
//}
}
}
js += F("'],");
}
js += F("],\n");
}
return js;
}
String Proc_WebPage_system_sensor_add(const String& var) {
if(TestHeaderFooter(var)) {
return AddHeaderFooter(var, 2);
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_SENSOR);
} else if(var == "ACTION") {
return F("&#10133; Add");
} else if(var == "SENSOR_ID") {
// we check which id is free. A free ID as type == 0
return String(Give_Free_SensorId());
} else if(var == "SENSOR_TYPE") {
return Html_SelOpt_type_WebPage_system_sensor_add();
} else if(var == "GPIO_INDEX") {
return Html_SelectOpt_GPIOindex(255, true);
} else if(var == "ESP_PLATFORM") {
#ifdef ESP8266
return F("8266");
#endif
#ifdef ESP32
return F("32");
#endif
} else if(var == "REPLACE_I2CADDR_JS") {
return Js_I2cAddr_Array_WebPage_system_sensor_add();
} else {
return String();
}
}
String Proc_WebPage_system_sensor_addEdit(const String& var) {
if(TestHeaderFooter(var)) {
return AddHeaderFooter(var, 2);
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_SENSOR);
} else if(var == "ACTION") {
return F("&#x270F;&#xFE0F; Edit");
} else if(var == "SENSOR_ID") {
// return the sensorId we got from GET .../add?edit=ID
// dirty workaround to put this in a global variable
return String(tmpParam_editSensorId);
} else if(var == "SENSOR_TYPE") {
return Html_SelOpt_type_WebPage_system_sensor_add(config.system.sensor.type[tmpParam_editSensorId]);
} else if(var == "SENSOR_NAME") {
// "escape" % character, because it would break the template processor.
// tasmote webcall for example has percentage char in its path
String sensorName = config.system.sensor.name[tmpParam_editSensorId];;
sensorName.replace(F("%"), F("&#37;"));
return sensorName;
} else if(var == "GPIO_INDEX") {
return Html_SelectOpt_GPIOindex(config.system.sensor.gpio[tmpParam_editSensorId][0], true);
} else if(var == "ESP_PLATFORM") {
#ifdef ESP8266
return F("8266");
#endif
#ifdef ESP32
return F("32");
#endif
} else if(var == "REPLACE_I2CADDR_JS") {
return Js_I2cAddr_Array_WebPage_system_sensor_add();
} else if(var == "I2C_SAVED") {
/* Add bit javascript to ensure the saved value is selected */
String js;
js += F("SystemSensorAddGpioI2cSel('type_sel');SystemSensor_replaceAddr('type_sel', 'i2c_addr');");
js += F("document.getElementById('i2c_addr').value='");
js += config.system.sensor.i2c_addr[tmpParam_editSensorId];
js += F("';");
return js;
} else {
return String();
}
}
String Proc_WebPage_system_sensor_add_POST(const String& var) {
if(var == "SAVE_MSG") {
return String(Common_HTML_SAVE_MSG);
} else {
return Proc_WebPage_system_sensor_add(var);
}
}
void WebPage_system_sensor_add(AsyncWebServerRequest *request) {
if(request->method() == HTTP_POST) {
byte sensorId;
//byte sensorType;
if(request->hasParam("sensorId", true)) {
const AsyncWebParameter* param = request->getParam("sensorId", true);
sensorId = param->value().toInt();
}
if(request->hasParam("type", true)) {
const AsyncWebParameter* param = request->getParam("type", true);
byte old_type = config.system.sensor.type[sensorId];
// put info config struct
config.system.sensor.type[sensorId] = param->value().toInt();
/* when config changed to a different sensor which is not internal ADC, then need restart */
if((config.system.sensor.type[sensorId] != old_type) && (config.system.sensor.type[sensorId] != 1 ))
needRestart = true;
}
if(request->hasParam("name", true)) {
const AsyncWebParameter* param = request->getParam("name", true);
strlcpy(config.system.sensor.name[sensorId], param->value().c_str(), sizeof(config.system.sensor.name[sensorId]));
}
if(request->hasParam("i2c_addr", true)) {
const AsyncWebParameter* param = request->getParam("i2c_addr", true);
//strlcpy(config.system.sensor.i2c_addr[sensorId], param->value().c_str(), sizeof(config.system.sensor.i2c_addr[sensorId]));
byte old_i2c_addr = config.system.sensor.i2c_addr[sensorId];
config.system.sensor.i2c_addr[sensorId] = param->value().toInt();
/* when i2c address changes, we need a restart
* TODO or re-initialise the sensor(s) (possible?) */
// check if sensor is I2C one and i2c_addr differs from before, otherwise we dont care whats inside here
if((SensorIndex[config.system.sensor.type[sensorId]].type == SENSOR_TYPE_I2C) && (config.system.sensor.i2c_addr[sensorId] != old_i2c_addr)) {
needRestart = true;
/* disable sensor, otherwise ESP will crash */
sensorStatus[sensorId] = false;
}
}
if(request->hasParam("gpio", true)) {
const AsyncWebParameter* param = request->getParam("gpio", true);
byte old_gpio = config.system.sensor.gpio[sensorId][0];
config.system.sensor.gpio[sensorId][0] = param->value().toInt();
/* when internal ADC we can initialize the sensors here */
if((SensorIndex[config.system.sensor.type[sensorId]].type == SENSOR_TYPE_INTADC) && (config.system.sensor.gpio[sensorId][0] != old_gpio)) {
/* TODO Crashes on ESP8266 sometimes, idk why, is OK on ESP32 */
#ifdef ESP32
Sensor_Addr_Init_Update(config.system.sensor.type[sensorId], config.system.sensor.gpio[sensorId][0], SENSOR_AIU_MODE_INIT);
sensorStatus[sensorId] = true;
#endif
#ifdef ESP8266
/* because on ESP8266 it crashes sometimes, we force a restart until this get fixed */
needRestart = true;
sensorStatus[sensorId] = false;
#endif
}
}
SaveConfig();
// request->send_P(200, "text/html", Page_system_output_add_HTML, Proc_WebPage_system_output_add_POST);
// I like it more when user gets redirected to the output overview after saving
request->redirect("/system/sensor/?success");
} else {
/* when in edit mode */
if(request->hasParam("edit")) {
const AsyncWebParameter* param = request->getParam("edit");
tmpParam_editSensorId = param->value().toInt();
request->send_P(200, TEXT_HTML, Page_system_sensor_add_HTML, Proc_WebPage_system_sensor_addEdit);
/* if we want to add new sensor, check if a sensor id is available. if not send error */
} else if(Give_Free_SensorId() > Max_Sensors) {
request->send_P(200, TEXT_HTML, Page_system_sensor_add_HTML_NO_ID_AVAILABLE, Proc_WebPage_system_sensor_add);
/* Otherwise let the user create new sensor */
} else {
request->send_P(200, TEXT_HTML, Page_system_sensor_add_HTML, Proc_WebPage_system_sensor_add);
}
}
}
/*******************************************************************************
* Subpage sensor calibrate
*/
/*******************************************************************
String Js_I2cAddr_Array_WebPage_system_sensor_calibrate() {
const static char LogLoc[] PROGMEM = "[Webserver:system:sensor:add:Js_I2cAddr_Array]";
String js;
for(byte i = 1; i <= SensorIndex_length; i++) {
// name
js += F("[");
for(byte j = 0; j < SensorIndex[i].max; j++) {
js += F("['0x");
js += String(Sensor_Addr_Init_Update(i, j, SENSOR_AIU_MODE_ADDR), HEX);
// value
js += F("', '");
js += j;
// used
bool used;
js += F("', '");
for(byte k = 0; k < Max_Sensors; k++) {
if(config.system.sensor.type[k] == i ) {
if(config.system.sensor.i2c_addr[k] == j) {
js += F("1");
// exit loop here
k = Max_Sensors + 1;
} //else {
//js += "0";
//}
}
}
js += F("'],");
}
js += F("],\n");
}
return js;
}
String Proc_WebPage_system_sensor_calibrate3(const String& var) {
if(TestHeaderFooter(var)) {
return AddHeaderFooter(var, 2);
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_SENSOR);
} else if(var == "ACTION") {
return F("&#10133; Add");
} else if(var == "SENSOR_ID") {
// we check which id is free. A free ID as type == 0
return String(Give_Free_SensorId());
} else if(var == "SENSOR_TYPE") {
return Html_SelOpt_type_WebPage_system_sensor_add();
} else if(var == "GPIO_INDEX") {
return Html_SelectOpt_GPIOindex(255, true);
} else if(var == "ESP_PLATFORM") {
#ifdef ESP8266
return F("8266");
#endif
#ifdef ESP32
return F("32");
#endif
} else if(var == "REPLACE_I2CADDR_JS") {
return Js_I2cAddr_Array_WebPage_system_sensor_add();
} else {
return String();
}
}
*******************************************************************/
String Proc_WebPage_system_sensor_calibrate(const String& var) {
if(TestHeaderFooter(var)) {
return AddHeaderFooter(var, 2);
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_SENSOR);
} else if(var == "SENSOR_ID") {
// return the sensorId we got from GET .../add?edit=ID
// dirty workaround to put this in a global variable
return String(tmpParam_calibrateSensorId);
} else if(var == "SENSOR_NAME") {
// "escape" % character, because it would break the template processor.
// tasmote webcall for example has percentage char in its path
String sensorName = config.system.sensor.name[tmpParam_calibrateSensorId];;
sensorName.replace(F("%"), F("&#37;"));
return sensorName;
} else if(var == "SENSOR_READING") {
String html;
/* TODO the way this page is built is ulgy IMHO
* maybe i will replace everything dynamic at this place with javascript? or i can make
* more / better use of the ESPAsync template engine */
html += F("<script>SensorJsonRefresh(); var refreshJson = setInterval(SensorJsonRefresh, 1000);</script>");
for(byte i = 0; i < Max_Sensors_Read; i++) {
if(SensorIndex[config.system.sensor.type[tmpParam_calibrateSensorId]].read[i] > 0) {
html += F("<form method='post' action='/system/sensor/calibrate'>");
html += F("<input type='hidden' name='sensorId' value='");
html += tmpParam_calibrateSensorId;
html += F("'/>");
html += F("<input type='hidden' name='readId' value='");
html += i;
html += F("'/>");
html += F("<u>Reading ");
html += i;
html += F(":</u><br><b>");
html += FPSTR(Sensor_Read_descr[SensorIndex[config.system.sensor.type[tmpParam_calibrateSensorId]].read[i]]);
html += F("</b> (");
html += F("<span class='sensorReading' id='raw-");
html += tmpParam_calibrateSensorId;
html += "-";
html += i;
html += F("'>");
/* is sensor internal ADC ? */
if(SensorIndex[config.system.sensor.type[tmpParam_calibrateSensorId]].type == SENSOR_TYPE_INTADC) {
/* reading type RAW ? */
if(SensorIndex[config.system.sensor.type[tmpParam_calibrateSensorId]].read[i] == SENSOR_READ_TYPE_RAW) {
/* get RAW value */
html += (int)Sensor_getValue( config.system.sensor.type[tmpParam_calibrateSensorId], config.system.sensor.gpio[tmpParam_calibrateSensorId][0]);
} else {
/* print calibrated value if not RAW */
html += Sensor_getCalibratedValue(tmpParam_calibrateSensorId, i);
//html += Sensor_getValue( config.system.sensor.type[tmpParam_calibrateSensorId], config.system.sensor.gpio[tmpParam_calibrateSensorId][0]);
}
} else if(SensorIndex[config.system.sensor.type[tmpParam_calibrateSensorId]].type == SENSOR_TYPE_I2C) {
/* same stuff for i2c sensor */
if(SensorIndex[config.system.sensor.type[tmpParam_calibrateSensorId]].read[i] == SENSOR_READ_TYPE_RAW) {
html += (int)Sensor_getValue( config.system.sensor.type[tmpParam_calibrateSensorId], config.system.sensor.i2c_addr[tmpParam_calibrateSensorId], i);
} else {
html += Sensor_getCalibratedValue(tmpParam_calibrateSensorId, i);
html += " ";
String unit;
unit += FPSTR(Sensor_Read_unit[SensorIndex[config.system.sensor.type[tmpParam_calibrateSensorId]].read[i]]);
unit.replace(F("%"), F("&#37;"));
html += unit;
//html += Sensor_getValue( config.system.sensor.type[tmpParam_calibrateSensorId], config.system.sensor.i2c_addr[tmpParam_calibrateSensorId], i);
}
}
html += F("</span>)");
if(SensorIndex[config.system.sensor.type[tmpParam_calibrateSensorId]].read[i] == SENSOR_READ_TYPE_RAW) {
html += F("<script>var raw_");
html += tmpParam_calibrateSensorId;
html += F("_");
html += i;
html += F(" = setInterval(rawRefresh, 1000, ");
html += tmpParam_calibrateSensorId;
html += F(", ");
html += i;
html += F(", 'raw-');</script>");
}
html += F("<br>");
if(SensorIndex[config.system.sensor.type[tmpParam_calibrateSensorId]].read[i] == SENSOR_READ_TYPE_RAW) {
html += F("<u>Convert RAW value to:</u><br>");
html += F("<select name='rawConvert'><option value='0' >---</option>");
for(byte j = 1; j <= SENSOR_CONVERT_RAW_TYPE__TOTAL; j++) {
html += F("<option value='");
html += j;
html += F("'");
if(config.system.sensor.rawConvert[tmpParam_calibrateSensorId][i] == j)
html += F(" selected");
html += F(">");
html += FPSTR(Sensor_Convert_Raw_descr[j]);
html += F("</option>");
}
html += F("</select><br>");
/* when raw convert is set, display converted reading */
if(config.system.sensor.rawConvert[tmpParam_calibrateSensorId][i] > 0) {
//html += F("<span class='sensorReading'>");
html += F("<span class='sensorReading' id='reading-");
html += tmpParam_calibrateSensorId;
html += "-";
html += i;
html += F("'>");
html += Sensor_getCalibratedValue(tmpParam_calibrateSensorId, i);
html += " ";
String unit;
unit += FPSTR(Sensor_Convert_Raw_unit[config.system.sensor.rawConvert[tmpParam_calibrateSensorId][i]]);
unit.replace(F("%"), F("&#37;"));
html += unit;
html += F("</span>");
html += F("<script>var reading_");
html += tmpParam_calibrateSensorId;
html += F("_");
html += i;
html += F(" = setInterval(sensorRefresh, 1000, ");
html += tmpParam_calibrateSensorId;
html += F(", ");
html += i;
html += F(", 'reading-');</script>");
html += F("<br>");
}
html += F("<u>Low:</u><br>");
html += F("<input type='number' name='low' value='");
html += config.system.sensor.low[tmpParam_calibrateSensorId][i];
html += F("'/><br>");
html += F("<u>High:</u><br>");
html += F("<input type='number' name='high' value='");
html += config.system.sensor.high[tmpParam_calibrateSensorId][i];
html += F("'/><br>");
} else {
html += F("<u>Offset:</u><br>");
html += F("<input type='number' step='0.01' name='offset' value='");
html += config.system.sensor.offset[tmpParam_calibrateSensorId][i];
html += F("'/><br>");
}
html += F("<br><input type='submit' value='&#x1F4BE; Save settings'></form>");
html += F("<hr>\n\n");
}
}
return html;
} else {
return String();
}
}
String Proc_WebPage_system_sensor_calibrate_POST(const String& var) {
if(var == "SAVE_MSG") {
return String(Common_HTML_SAVE_MSG);
} else {
return Proc_WebPage_system_sensor_calibrate(var);
}
}
void WebPage_system_sensor_calibrate(AsyncWebServerRequest *request) {
if(request->method() == HTTP_POST) {
byte sensorId;
byte readId;
//byte sensorType;
if(request->hasParam("sensorId", true)) {
const AsyncWebParameter* param = request->getParam("sensorId", true);
sensorId = param->value().toInt();
tmpParam_calibrateSensorId = sensorId;
}
if(request->hasParam("readId", true)) {
const AsyncWebParameter* param = request->getParam("readId", true);
readId = param->value().toInt();
}
if(request->hasParam("offset", true)) {
const AsyncWebParameter* param = request->getParam("offset", true);
config.system.sensor.offset[sensorId][readId] = param->value().toFloat();
}
if(request->hasParam("low", true)) {
const AsyncWebParameter* param = request->getParam("low", true);
config.system.sensor.low[sensorId][readId] = param->value().toInt();
}
if(request->hasParam("high", true)) {
const AsyncWebParameter* param = request->getParam("high", true);
config.system.sensor.high[sensorId][readId] = param->value().toInt();
}
if(request->hasParam("rawConvert", true)) {
const AsyncWebParameter* param = request->getParam("rawConvert", true);
config.system.sensor.rawConvert[sensorId][readId] = param->value().toInt();
}
SaveConfig();
request->send_P(200, "text/html", Page_system_sensor_calibrate_HTML, Proc_WebPage_system_sensor_calibrate_POST);
// I like it more when user gets redirected to the output overview after saving
//request->redirect("/system/sensor/?success");
} else {
/* when in edit mode */
if(request->hasParam("calibrate")) {
const AsyncWebParameter* param = request->getParam("calibrate");
tmpParam_calibrateSensorId = param->value().toInt();
request->send_P(200, TEXT_HTML, Page_system_sensor_calibrate_HTML, Proc_WebPage_system_sensor_calibrate);
/* if we want to add new sensor, check if a sensor id is available. if not send error */
} else {
request->send_P(200, TEXT_HTML, Page_system_sensor_calibrate_HTML, Proc_WebPage_system_sensor_calibrate);
}
}
}