/* * * * Web related stuff * * */ /* * * return functions, they return things for the web stuff */ String returnHTMLheader(String MenuEntry = "") { String header; String activeMenu = "class='activeMenu'"; // add first part of the header header += FPSTR(HTMLheaderP1); // add title tag header += "<title>"; // check if GrowName was set. if yes, its part of the page title. if(strlen(GrowName) > 0) { header += "CanGrow - "; header += GrowName; } else { header += "CanGrow"; } // close title tag header += "</title>\n"; // add additional header stuff, like loading guage files if(MenuEntry == "root") { header += "<link rel='stylesheet' type='text/css' href='gauge.css'>"; } header += FPSTR(HTMLheaderP2); // first menu entry header += "<li><a href='/'>🌱 "; if(strlen(GrowName) > 0) { header += GrowName; } else { header += "CanGrow"; } header += "</a></li>\n"; // second menu entry header += "<li><a href='/growSettings' "; if(MenuEntry == "growSettings") { header += activeMenu; } header += ">🔆 Grow settings</a></li>\n"; // third menu entry header += "<li><a href='/systemSettings' "; if(MenuEntry == "systemSettings") { header += activeMenu; } header += ">⚙ System settings</a></li>\n"; // fourth menu entry header += "<li><a href='/wifiSettings' "; if(MenuEntry == "wifiSettings") { header += activeMenu; } header += ">📡 WiFi settings</a></li>\n"; // fifth menu entry header += "<li><a href='/help' "; if(MenuEntry == "help") { header += activeMenu; } header += ">❓ Help</a></li>\n"; // sixth menu entry - time and status icons / info header += "<li><span class='MenuTime'>"; header += timeClient.getFormattedTime(); // status icons and info if(MaintenanceMode == true) { // status icons header += " | ⏸️ "; header += MaintenanceDuration - ((millis() - MaintenanceStarted) / 1000); header += "s"; } header += "</span></li>\n"; // CanGrow Version header += "<li><a href='https://git.la10cy.net/DeltaLima/CanGrow' target='_blank'>CanGrow v"; header += CANGROW_VER; header += "</a></li>\n"; // close <ul> and start <div> header += "</ul><div class='center'>"; if(NeedRestart == true) { header += FPSTR(HTMLneedRestart); } return header; } /* * returnSelected(bool) * returns char[] "selected" if bool is true * useful for html forms, to represet a saved value as selected */ String returnStrSelected(byte savedValue, byte selectId) { String returnStr; if(configured == true) { if(savedValue == selectId) { returnStr = " selected "; } else { returnStr = ""; } } return returnStr; } String returnStrDateFromEpoch(unsigned long epochTime) { String dateStr; byte Day = day(epochTime); byte Month = month(epochTime); unsigned int Year = year(epochTime); dateStr = Year; dateStr += "-"; if(Month < 10) { dateStr += "0"; dateStr += Month; } else { dateStr += Month; } dateStr += "-"; if(Day < 10) { dateStr += "0"; dateStr += Day; } else { dateStr += Day; } return dateStr; } /* * * System pages like infos, errors * */ void SysRestart() { String body = returnHTMLheader(); // TODO only debug and development solution, remove this later if( (webserver.hasArg("confirmed")) || (NeedRestart == true) ) { body += "<h1>❗ Restarting</h1>"; body += "<div class='infomsg'>After restart CanGrow will be connected to WiFi SSID<br><b>"; body += WIFIssid; body += "</b><br>You get its IP-Address from the display or serial console.</div>"; body += FPSTR(HTMLfooter); webserver.send(200, "text/html", body); Serial.println("Restarting... see you soon space cowboy!"); delay(1000); ESP.restart(); } else { body += "<h1>❗ Restart CanGrow</h1>"; body += "<div class='infomsg'>Do you want to restart CanGrow?"; body += "<br>Please confirm."; body += "<form action='/system/restart'><input type='hidden' name='confirmed' value='true' /><input type='submit' value='Confirm restart' /></form>"; body += "</div>"; body += FPSTR(HTMLfooter); webserver.send(200, "text/html", body); } } void SysWipe() { String body = returnHTMLheader(); body += "<h1>❗❗ Wiping EEPROM</h1>"; body += "<div class='warnmsg'>All settings will be removed!!<br>"; body += "<br>Please confirm wiping the EEPROM"; body += "<form action='/system/wipeConfirm' method='post'><br>Please confirm: <input type='checkbox' id='confirm' name='confirm' required /><br><input type='submit' value='Confirm wiping' /></form>"; body += "</div>"; body += FPSTR(HTMLfooter); webserver.send(200, "text/html", body); } void Sys404() { String body = returnHTMLheader(); body += "<div class='warnmsg'><h1>❗ ️ 404 - not found</h1></div>"; body += FPSTR(HTMLfooter); webserver.send(404, "text/html", body); } void Syslogout() { String body = returnHTMLheader(); body += "<h1>you are logged out.</h1>"; body += FPSTR(HTMLfooter); // TODO does not work atm webserver.send(401, "text/html", body); } void SysMaintenance() { String body = returnHTMLheader(); // when requesting to handle Dimming if( (webserver.hasArg("DimmOn")) || (webserver.hasArg("DimmOff")) ) { // check first if PWM is disabled / relais is used if(UseLEDrelais == true) { // if not, do it if( (webserver.hasArg("DimmOn")) ) { MaintenanceMode = true; MaintenanceStarted = millis(); body += "<div class='infomsg'>⛅ Dimm LED On for "; body += MaintenanceDuration; body += "s</div>"; } else if( (webserver.hasArg("DimmOff")) ) { MaintenanceMode = false; body += "<div class='infomsg'>⛅ Dimm LED Off</div>"; } } else { // otherwise nice error body += "<div class='warnmsg'>⛅ LED setting <pre>Use relais for LED (disable PWM)<pre> is set to <b>Yes</b> in 🔆 Grow settings</div>"; } } else if(webserver.hasArg("PumpOnManual")) { if(UsePump == true) { PumpOnManual = true; body += "<div class='infomsg'>💧 Pump manual activated for "; body += PumpOnTime; body += "s</div>"; } else { body += "<div class='warnmsg'>💧 Pump mode is set to <b>Off</b> in 🔆 Grow settings</div>"; } } body += "<h2>🧰 Maintenance</h2>"; body += "Dimm LED <a class='button' href='/system/maintenance?DimmOn=1'>⛅ On</a> <a class='button' href='/system/maintenance?DimmOff=1'>☀️ Off</a><br><br><br>"; body += "Pump manual <a class='button' href='/system/maintenance?PumpOnManual=1'>💧 Activate for "; body += PumpOnTime; body += "s</a><br>"; body += FPSTR(HTMLfooter); webserver.send(200, "text/html", body); } void SysUpdate() { String body = returnHTMLheader(); body += "<h2>🔄 Firmware update</h2>"; body += "<b>Version:</b> "; body += CANGROW_VER; body += "<br><b>Build:</b> "; body += CANGROW_BUILD; body += FPSTR(HTMLupdate); body += FPSTR(HTMLfooter); webserver.send(200, "text/html", body); } /* * TODO * DOES NOT WORK WHEN CONNECTED TO EXISTING WIFI * IDK WHY * * void WebAuth() { * * char webAuthRealm[] = "CanGrowRealm"; * if(!webserver.authenticate(WebUiUsername, WebUiPassword)) { * String body = FPSTR(HTMLheader); * body += "<h1>Login failed.</h1>"; * body += FPSTR(HTMLfooter); * webserver.requestAuthentication(DIGEST_AUTH, webAuthRealm, body); * } * } * * void WebAuthApi() { * * TODO * DOES NOT WORK WHEN CONNECTED TO EXISTING WIFI * IDK WHY * * * char webAuthRealm[] = "CanGrowRealm"; * if(!webserver.authenticate(WebUiUsername, WebUiPassword)) { * webserver.requestAuthentication(DIGEST_AUTH, webAuthRealm); * } * } */ /* * * Main UI pages * */ /* * Gauge meter files */ void WEBgaugeCss() { //String css = CSSgauge; webserver.send(200, "text/css", FPSTR(CSSgauge)); } void WEBgaugeJs() { //String javascript = JSgauge; webserver.send(200, "text/javascript", FPSTR(JSgauge)); } /* * Root page */ void WEBroot() { if(FirstRun == true) { webserver.sendHeader("Location", String("/wifiSettings"), true); webserver.send(302, "text/plain", "please configure wifiSettings first"); } else if(configured == false){ webserver.sendHeader("Location", String("/systemSettings"), true); webserver.send(302, "text/plain", "please configure systemSettings first"); } else if(strlen(GrowName) < 1){ webserver.sendHeader("Location", String("/growSettings"), true); webserver.send(302, "text/plain", "please configure growSettings first"); } else { String body = returnHTMLheader("root"); body += "<h2>🌱 "; body += GrowName; body += "</h2>"; // add gauge meter body += FPSTR(HTMLgauge); // and give javascript the values // todo: auto refresh by api call body += "<script>"; body += "gaugeTemperature.value('"; body += valTemperature; body += "', 42, ' °C'); "; body += "gaugeHumidity.value('"; body += valHumidity; body += "'); "; body += "gaugeSoilmoisture.value('"; body += valSoilmoisture; body += "'); "; body += "</script><br>\n"; // when an ESP32-Cam IP is given, display picture from it if(strlen(Esp32CamIP) > 0) { body += "<a href='http://"; body += Esp32CamIP; body += "' target='_blank'><img class='centered' src='http://"; body += Esp32CamIP; body += "/capture' alt='Image capture from ESP32CAM at "; body += Esp32CamIP; body += "'></a>\n<br>\n"; } body += "<b>Grow started: 🗓️</b> "; body += returnStrDateFromEpoch(GrowStart); body += "<br>\n"; body += "<b>Day of Grow: </b> "; if(DayNight == true) { body += " 🌞 "; } else { body += " 🌚 "; } body += DayOfGrow; body += "<br>\n"; body += "<b>Grow status:</b> "; switch(growState()) { case 1: body += "🌱 vegetation<br>\n"; break; case 2: body += "🌼 bloom ("; body += DayOfGrow - DaysVeg; body += ")<br>\n"; break; case 3: body += "🍂 harvest ("; body += DayOfGrow - (DaysVeg + DaysBloom); body += ")<br>\n"; break; } // VPD body += "<b>VPD (est.): "; // apply text color to the value according to this chart if(valVPD < 0) { body += "⚠️ <span class=''>"; body += valVPD; body += "</span></b> (Danger! Check for disease!)"; } else if(valVPD < 0.4 ) { body += "⚠️ <span class='vpd_danger1'>"; body += valVPD; body += "</span></b> (Danger! Under transpiration!)"; } else if(valVPD < 0.8 ) { body += "🍃 <span class='vpd_earlyveg'>"; body += valVPD; body += "</span></b> (Early vegetation)"; } else if(valVPD < 1.2 ) { body += "🍃 <span class='vpd_lateveg'>"; body += valVPD; body += "</span></b> (Late vegetation)"; } else if(valVPD < 1.6 ) { body += "🍃 <span class='vpd_latebloom'>"; body += valVPD; body += "</span></b> (Late bloom)"; } else if(valVPD > 1.6 ) { body += "⚠️ <span class='vpd_danger2'>"; body += valVPD; body += "</span></b> (Danger - over transpiration!)"; } body += "<br>\n"; if(UsePump > 0) { body += "<b>Pump water level:</b> "; switch(getWaterlevel()) { case 0: body += "<span style='color: green;'>OK</span>"; break; case 1: body += "<span style='color: yellow;'>Warning</span>"; break; case 2: body += "<span style='color: red;'>Critical</span>"; break; } body += "<br>\n"; } body += "<b>Growlight brightness:</b> "; body += ((PinLEDPWM * 100) / 255); body += " %<br>\n"; //~ body += "<form method='post' action='/switch'>\n"; //~ body += "<b>MOSFET:</b> <select id='output' name='output' >\n"; //~ body += "<option disabled value='' selected hidden>---</option>\n"; //~ body += "<option value='1'>LED</option>\n"; //~ body += "<option value='2'>FAN</option>\n"; //~ body += "<option value='3'>PUMP</option>\n"; //~ body += "</select><br>"; //~ body += "<b>On/Off:</b> <select id='state' name='state' >\n"; //~ body += "<option disabled value='' selected hidden>---</option>\n"; //~ body += "<option value='1'>On</option>\n"; //~ body += "<option value='0'>Off</option>\n"; //~ body += "</select><br>\n"; //~ body += "<b>Intensity:</b> <input type='range' id='OutputPWM' name='OutputPWM' min='1' max='255' value='255'/><br>\n"; //~ body += "<input type='submit' value='Save'>\n"; //~ body += "</form><br>\n"; body += "<br><a class='button' href='/system/maintenance'>🧰 Maintenance</a>"; body += FPSTR(HTMLfooter); webserver.send(200, "text/html", body); } } /* * * settings pages * * */ /* * Grow page */ void WEBgrowSettings() { // if system settings are unconfigured, we cannot proceed with growSettings if(configured == false) { webserver.sendHeader("Location", String("/systemSettings"), true); webserver.send(302, "text/plain", "please configure systemSettings first"); } else { String body = returnHTMLheader("growSettings"); if(strlen(GrowName) < 1) { body += "<h1>Final step: Grow settings</h1>"; body += "<p>Please configure all settings<br>"; body += "</p>"; GrowStart = timeClient.getEpochTime(); } body += "<h2>🔆 Grow settings</h2>"; if(webserver.hasArg("success")) { body += FPSTR(HTMLsuccess); } body += "<p>Here you can set everything grow related, like light hours, how much water, LED brightness<br>"; body += "</p>"; body += "<form method='post' action='/growSettings/save'>\n"; body += "Grow name: <input type='text' name='GrowName' maxlength='31' value='"; body += GrowName; body+= "' required><br>\n"; body += "<input type='hidden' id='GrowStart' name='GrowStart' value='"; body += GrowStart; body+= "' required>\n"; // the input field, which calls javascript convertDateToEpoch() to write data to transmit to id GrowStart body += "Grow start date: <input type='date' id='GrowStart_sel' onChange='convertDateToEpoch(\"GrowStart_sel\", \"GrowStart\");' value='"; body += returnStrDateFromEpoch(GrowStart); body += "' required><br><br>\n"; body += "<b>Grow duration</b><br>"; body += "Vegetation duration: <input class='inputShort' type='number' name='DaysVeg' min='0' max='255' value='"; body += DaysVeg; body+= "' required> Days<br>\n"; body += "Bloom duration: <input class='inputShort' type='number' name='DaysBloom' min='0' max='255' value='"; body += DaysBloom; body+= "' required> Days<br><br>\n"; body += "<b>Light configuration</b><br>"; body += "LED ON vegetation: <input class='inputShort' type='number' name='LighthoursVeg' min='0' max='255' value='"; body += LighthoursVeg; body+= "' required> Hours<br>\n"; body += "LED ON bloom: <input class='inputShort' type='number' name='LighthoursBloom' min='0' max='255' value='"; body += LighthoursBloom; body+= "' required> Hours<br>\n"; body += "Sunrise: <input class='inputShort' type='number' name='SunriseHour' min='0' max='23' value='"; body += SunriseHour; body+= "' required>\n"; body += " <b>:</b> <input class='inputShort' type='number' name='SunriseMinute' min='0' max='59' value='"; body += SunriseMinute; body+= "' required><br>\n"; // SunFade bool body += "Fade in/out sunrise/sunset?: <select id='SunFade' name='SunFade' required>\n"; body += "<option value='1'" + returnStrSelected(SunFade, 1) + ">Yes</option>\n"; body += "<option value='0'" + returnStrSelected(SunFade, 0) + ">No</option>\n"; body += "</select><br>\n"; body += "Fade duration: <input class='inputShort' type='number' name='SunFadeDuration' min='1' max='255' value='"; body += SunFadeDuration; body+= "' required> Minutes<br>\n"; if(UseLEDrelais == false) { body += "LED brightness: <input type='range' id='PinLEDPWM' name='PinLEDPWM' min='0' max='255' value='"; body += PinLEDPWM; body += "'/> %<br>\n"; } else { body += "LED on/off: <select id='PinLEDPWM' name='PinLEDPWM' required>\n"; body += "<option value='1'" + returnStrSelected(PinLEDPWM, 1) + ">On</option>\n"; body += "<option value='0'" + returnStrSelected(PinLEDPWM, 0) + ">Off</option>\n"; body += "</select><br>\n"; } body += "<br>"; body += "<b>Fan configuration</b><br>"; if(UseFANrelais == false) { body += "FAN1 speed: <input type='range' id='PinFANPWM' name='PinFANPWM' min='0' max='255' value='"; body += PinFANPWM; body += "'/> %<br>\n"; } else { body += "FAN1 on/off: <select id='PinFANPWM' name='PinFANPWM' required>\n"; body += "<option value='1'" + returnStrSelected(PinFANPWM, 1) + ">On</option>\n"; body += "<option value='0'" + returnStrSelected(PinFANPWM, 0) + ">Off</option>\n"; body += "</select><br>\n"; } body += "FAN2 speed: <input type='range' id='PinFAN2PWM' name='PinFAN2PWM' min='0' max='255' value='"; body += PinFAN2PWM; body += "'/> %<br><br>\n"; body += "<b>Pump configuration</b><br>"; // PumpMode byte body += "Pump mode: <select id='UsePump' name='UsePump' required>\n"; if(configured == false){body += "<option disabled value='' selected hidden>---</option>\n";} body += "<option value='0'" + returnStrSelected(UsePump, 0) + ">Off</option>\n"; body += "<option value='1'" + returnStrSelected(UsePump, 1) + ">1</option>\n"; body += "<option value='2'" + returnStrSelected(UsePump, 2) + ">2</option>\n"; body += "<option value='3'" + returnStrSelected(UsePump, 3) + ">3</option>\n"; body += "</select><br><p class='helpbox'><b>1:</b> Water every few days.<br> \ <b>2:</b> Water if the soil moisture falls below <i>Soilmoisture low</i> value<br> \ <b>3:</b> Water every few days if the soil moisture falls below <i>Soilmoisture low</i> value.</p>\n"; // TODO ugly. can this done be better? // PumpOnTime int body += "Pump ON time: <input class='inputShort' type='number' name='PumpOnTime' min='0' max='255' value='"; body += PumpOnTime; body += "' required> Seconds<br>\n"; // SoilmoistureLow byte body += "Soilmoisture low: <input class='inputShort' type='number' name='SoilmoistureLow' min='0' value='"; body += SoilmoistureLow; body += "' required> %<br>\n"; body += "Pump interval vegetation: every <input class='inputShort' type='number' name='PumpIntervalVeg' min='0' max='255' value='"; body += PumpIntervalVeg; body += "' required> Days<br>\n"; body += "Pump interval bloom: every <input class='inputShort' type='number' name='PumpIntervalBloom' min='0' max='255' value='"; body += PumpIntervalBloom; body += "' required> Days<br><br>\n"; body += "<input type='submit' value='💾 Save settings'>\n"; body += "</form>\n"; body += FPSTR(JSconvertDateToEpoch); body += FPSTR(HTMLfooter); webserver.send(200, "text/html", body); } } void WEBsystemSettings() { // if wifi settings are unconfigured, we cannot proceed with systemSettings if(FirstRun == true) { webserver.sendHeader("Location", String("/wifiSettings"), true); webserver.send(302, "text/plain", "please configure wifiSettings first"); } else { String body = returnHTMLheader("systemSettings"); if(configured == false) { body += "<h1>Step 2: System settings</h1>"; body += "<p>Please configure all settings<br>"; body += "</p>"; } body += "<h2>⚙ System settings</h2>"; body += FPSTR(HTMLsystemSubNav); if(webserver.hasArg("success")) { body += FPSTR(HTMLsuccess); } body += "<p>here you can set which features and sensors you use<br>"; body += "</p>"; // form starts body += "<form method='post' action='/systemSettings/save'>\n"; // UseFan bool //~ body += "Fan mode: <select id='UseFan' name='UseFan' required>\n"; //~ if(configured == false){body += "<option disabled value='' selected hidden>---</option>\n";} //~ body += "<option value='1'" + returnStrSelected(UseFan, 0) + ">Off</option>\n"; //~ body += "<option value='1'" + returnStrSelected(UseFan, 1) + ">Use</option>\n"; //~ body += "<option value='0'" + returnStrSelected(UseFan, 2) + ">Do not use</option>\n"; //~ body += "</select><br>\n"; /* // UsePump bool body += "Use PUMP: <select id='UsePump' name='UsePump' required>\n"; if(configured == false){body += "<option disabled value='' selected hidden>---</option>\n";} body += "<option value='1'" + returnStrSelected(UsePump, 1) + ">Yes</option>\n"; body += "<option value='0'" + returnStrSelected(UsePump, 0) + ">No</option>\n"; body += "</select><br>\n"; */ body += "<b>Output configuration</b><br>"; // OutputInvert bool body += "Invert Outputs: <select id='OutputInvert' name='OutputInvert' required>\n"; if(configured == false){body += "<option disabled value='' selected hidden>---</option>\n";} body += "<option value='0'" + returnStrSelected(OutputInvert, 0) + ">No</option>\n"; body += "<option value='1'" + returnStrSelected(OutputInvert, 1) + ">Yes</option>\n"; body += "</select><br>\n"; body += "<p class='helpbox'>When using CanGrow PCB v0.6, set to <b>Yes</b></p>\n"; // UseLEDrelais bool body += "Use relais for LED (disable PWM): <select id='UseLEDrelais' name='UseLEDrelais' required>\n"; if(configured == false){body += "<option disabled value='' selected hidden>---</option>\n";} body += "<option value='0'" + returnStrSelected(UseLEDrelais, 0) + ">No</option>\n"; body += "<option value='1'" + returnStrSelected(UseLEDrelais, 1) + ">Yes</option>\n"; body += "</select><br>\n"; // UseFANrelais bool body += "Use relais for FAN1 (disable PWM): <select id='UseFANrelais' name='UseFANrelais' required>\n"; if(configured == false){body += "<option disabled value='' selected hidden>---</option>\n";} body += "<option value='0'" + returnStrSelected(UseFANrelais, 0) + ">No</option>\n"; body += "<option value='1'" + returnStrSelected(UseFANrelais, 1) + ">Yes</option>\n"; body += "</select><br><br>\n"; body += "<b>Sensor configuration</b><br>"; // MoistureSensor_Type byte body += "Soilmoisture sensor: <select id='SelMoistureSensor_Type' name='MoistureSensor_Type' onchange='MoistureSensorType();' required>\n"; if(configured == false) { body += "<option disabled value='' selected hidden>---</option>\n"; } body += "<option value='1'" + returnStrSelected(MoistureSensor_Type, 1) + ">Analog capacitive</option>\n"; body += "<option value='2'" + returnStrSelected(MoistureSensor_Type, 2) + ">I2C Chirp (0x20)</option>\n"; body += "</select><br>\n"; // SoilmoistureDry byte body += "Soilmoisture dry: <input type='number' id='iSoilmoistureDry' name='SoilmoistureDry' min='0' value='"; body += SoilmoistureDry; body += "' required>\n"; body += "<p class='helpbox'><b>Analog capacitive:</b> <i>360</i><br> \ <b>I2C Chirp:</b> <i>250</i></p>"; // SoilmoistureWet byte body += "Soilmoisture wet: <input type='number' id='iSoilmoistureWet' name='SoilmoistureWet' min='0' value='"; body += SoilmoistureWet; body += "' required>\n"; body += "<p class='helpbox'><b>Analog capacitive:</b> <i>160</i><br> \ <b>I2C Chirp:</b> <i>485</i></p>"; // MoistureSensor_Type Javascript body += FPSTR(JSsoilmoistureTypeSelect); // TemperatureSensor_Type byte body += "Temperature sensor: <select id='TemperatureSensor_Type' name='TemperatureSensor_Type' required>\n"; if(configured == false) { body += "<option disabled value='' selected hidden>---</option>\n"; } body += "<option value='1'" + returnStrSelected(TemperatureSensor_Type, 1) + ">I2C BME280 (0x76)</option>\n"; body += "<option value='2'" + returnStrSelected(TemperatureSensor_Type, 2) + ">I2C BME280 (0x77)</option>\n"; body += "<option value='3'" + returnStrSelected(TemperatureSensor_Type, 3) + ">I2C SHT31 (0x44)</option>\n"; body += "<option value='4'" + returnStrSelected(TemperatureSensor_Type, 4) + ">I2C SHT31 (0x45)</option>\n"; body += "<option value='5'" + returnStrSelected(TemperatureSensor_Type, 5) + ">I2C Chirp (0x20)</option>\n"; body += "</select><br>\n"; // HumiditySensor_Type byte body += "Humidity sensor: <select id='HumiditySensor_Type' name='HumiditySensor_Type' required>\n"; if(configured == false) { body += "<option disabled value='' selected hidden>---</option>\n"; } body += "<option value='1'" + returnStrSelected(HumiditySensor_Type, 1) + ">I2C BME280 (0x76)</option>\n"; body += "<option value='2'" + returnStrSelected(HumiditySensor_Type, 2) + ">I2C BME280 (0x77)</option>\n"; body += "<option value='3'" + returnStrSelected(HumiditySensor_Type, 3) + ">I2C SHT31 (0x44)</option>\n"; body += "<option value='4'" + returnStrSelected(HumiditySensor_Type, 4) + ">I2C SHT31 (0x45)</option>\n"; body += "</select><br><br>\n"; body += "<b>General configuration</b><br>"; // NtpOffset int body += "NTP offset/UTC timezone: <input class='inputShort' type='number' name='NtpOffset' min='-12' max='14' value='"; body += NtpOffset; body+= "' required> Hours<br>\n"; body += "Maintenance Duration: <input class='inputShort' type='number' name='MaintenanceDuration' min='0' max='900' value='"; body += MaintenanceDuration; body += "' required> Seconds<br>\n"; // PWMFrequency short body += "PWM Frequency: <input type='number' name='PWMFrequency' min='0' max='20000' value='"; body += PWMFrequency; body += "' required> Hz<br>\n"; // DisplayScreenDuration byte body += "Display rotation interval: <input class='inputShort' type='number' name='DisplayScreenDuration' min='0' max='255' value='"; body += DisplayScreenDuration; body += "' required> Seconds<br>\n"; body += "<p class='helpbox'><b>0</b> will always show sensor value screen</p>"; body += "ESP32-Cam IP (optional): <input type='text' name='Esp32CamIP' maxlength='16' value='"; body += Esp32CamIP; body += "' ><br><br>\n"; body += "<input type='submit' value='💾 Save settings'>\n"; body += "</form>\n"; body += FPSTR(HTMLfooter); webserver.send(200, "text/html", body); } } void WEBwifiSettings() { byte ssidsAvail = WiFi.scanNetworks(); String body = returnHTMLheader("wifiSettings"); if(FirstRun == true) { body += "<h1>Welcome!</h1>"; body += "<p>CanGrow is actually unconfigured. You need to Setup your WiFi first down below.<br>"; body += "<br>After you entered your WiFi connection details, you need to restart and are step closer to your grow 🥦"; body += "<br>"; body += "</p>"; } body += "<h2>📡 WiFi settings</h2>\n"; if(webserver.hasArg("success")) { body += FPSTR(HTMLsuccess); } if(FirstRun == false) { body += "<u>Current Settings:</u><br>"; body += "WiFi SSID: <b>"; body += WIFIssid; body += "</b><br>\n"; body += "Use DHCP: <b>"; body += WIFIuseDHCP; body += "</b><br>\n"; body += "IP address: <b>"; body += WiFi.localIP().toString(); body += "</b><br>\n"; body += "Subnet mask: <b>"; body += WiFi.subnetMask().toString(); body += "</b><br>\n"; body += "Gateway: <b>"; body += WiFi.gatewayIP().toString(); body += "</b><br>\n"; body += "DNS: <b>"; body += WiFi.dnsIP().toString(); body += "</b><br><br>\n"; } body += "<p>Select your wifi network from the SSID list.<br>To use DHCP leave IP, Subnet, Gateway and DNS fields blank!</p>"; body += "<form method='post' action='/wifiSettings/save'>\n"; body += "SSID: <select id='WIFIssid' name='WIFIssid' required>\n"; body += "<option disabled value='' selected hidden>-Select your network-</option>"; // build option list for selecting wifi Serial.println("Available Wifis: "); for(int i = 0 ; i < ssidsAvail; i++) { String wifiName = WiFi.SSID(i); Serial.println(wifiName); body += "<option value='" + wifiName + "'>"; body += wifiName + "</option>\n"; } body += "</select><br>\n"; body += "Password: <input type='password' name='WIFIpassword'><br>\n"; body += "IP: <input type='text' name='WIFIip'><br>\n"; body += "Subnet mask: <input type='text' name='WIFInetmask'><br>\n"; body += "Gateway: <input type='text' name='WIFIgateway'><br>\n"; body += "DNS: <input type='text' name='WIFIdns'><br><br>\n"; body += "<input type='submit' value='💾 Save settings'>\n"; body += "</form>\n"; body += FPSTR(HTMLfooter); webserver.send(200, "text/html", body); } void WEBhelp() { String body = returnHTMLheader("help"); body += FPSTR(HTMLhelp); body += FPSTR(HTMLfooter); webserver.send(200, "text/html", body); } /* * * POSTs * */ void POSTgrowSettings() { PinLEDPWM = webserver.arg("PinLEDPWM").toInt(); PinFANPWM = webserver.arg("PinFANPWM").toInt(); PinFAN2PWM = webserver.arg("PinFAN2PWM").toInt(); String GrowName_tmp = webserver.arg("GrowName"); GrowName_tmp.toCharArray(GrowName, 32); GrowStart = webserver.arg("GrowStart").toInt(); DaysVeg = webserver.arg("DaysVeg").toInt(); DaysBloom = webserver.arg("DaysBloom").toInt(); LighthoursVeg = webserver.arg("LighthoursVeg").toInt(); LighthoursBloom = webserver.arg("LighthoursBloom").toInt(); SunriseHour = webserver.arg("SunriseHour").toInt(); SunriseMinute = webserver.arg("SunriseMinute").toInt(); SunFade = webserver.arg("SunFade").toInt(); SunFadeDuration = webserver.arg("SunFadeDuration").toInt(); UsePump = webserver.arg("UsePump").toInt(); PumpOnTime = webserver.arg("PumpOnTime").toInt(); SoilmoistureLow = webserver.arg("SoilmoistureLow").toInt(); PumpIntervalVeg = webserver.arg("PumpIntervalVeg").toInt(); PumpIntervalBloom = webserver.arg("PumpIntervalBloom").toInt(); // size is 32 byte EEPROM.put(170, GrowName); // size is 4 byte EEPROM.put(202, GrowStart); // size is 1 byte EEPROM.put(206, DaysVeg); // size is 1 byte EEPROM.put(207, DaysBloom); // size is 1 byte EEPROM.put(208, LighthoursVeg); // size is 1 byte EEPROM.put(209, LighthoursBloom); // size is 1 byte EEPROM.put(210, SunriseHour); // size is 1 byte EEPROM.put(211, SunriseMinute); // size is 1 byte EEPROM.put(213, PinLEDPWM); // size is 1 byte EEPROM.put(216, PinFANPWM); // size is 1 byte EEPROM.put(248, PinFAN2PWM); EEPROM.put(217, SunFade); EEPROM.put(218, SunFadeDuration); // size is 1 byte EEPROM.put(163, UsePump); // size is 1 byte EEPROM.put(164, PumpOnTime); // size is 1 byte EEPROM.put(166, SoilmoistureLow); // size is 1 byte EEPROM.put(241, PumpIntervalVeg); // size is 1 byte EEPROM.put(242, PumpIntervalBloom); EEPROM.commit(); //analogWrite(PinLED, PinLEDPWM); Serial.println(":: POSTgrowSettings ::"); Serial.print("GrowName: "); Serial.println(GrowName); Serial.print("GrowStart: "); Serial.println(GrowStart); Serial.print("DaysVeg: "); Serial.println(DaysVeg); Serial.print("DaysBloom: "); Serial.println(DaysBloom); Serial.print("LighthoursVeg: "); Serial.println(LighthoursVeg); Serial.print("LighthoursBloom: "); Serial.println(LighthoursBloom); Serial.print("SunriseHour: "); Serial.println(SunriseHour); Serial.print("SunriseMinute: "); Serial.println(SunriseMinute); Serial.print("PinLEDPWM: "); Serial.println(PinLEDPWM); Serial.print("PinFANPWM: "); Serial.println(PinFANPWM); Serial.print("PinFAN2PWM: "); Serial.println(PinFAN2PWM); Serial.print("UsePump: "); Serial.println(UsePump); Serial.print("PumpOnTime: "); Serial.println(PumpOnTime); Serial.print("SoilmoistureLow: "); Serial.println(SoilmoistureLow); Serial.print("PumpIntervalVeg: "); Serial.println(PumpIntervalVeg); Serial.print("PumpIntervalBloom: "); Serial.println(PumpIntervalBloom); webserver.sendHeader("Location", String("/growSettings?success"), true); webserver.send(302, "text/plain", "growSettings/save: success!\n"); } void POSTsystemSettings() { unsigned short UseLEDrelais_old = UseLEDrelais; unsigned short UseFANrelais_old = UseFANrelais; unsigned short PWMFrequency_old = PWMFrequency; NtpOffset = webserver.arg("NtpOffset").toInt(); MoistureSensor_Type = webserver.arg("MoistureSensor_Type").toInt(); UseFan = webserver.arg("UseFan").toInt(); UseLEDrelais = webserver.arg("UseLEDrelais").toInt(); UseFANrelais = webserver.arg("UseFANrelais").toInt(); TemperatureSensor_Type = webserver.arg("TemperatureSensor_Type").toInt(); MaintenanceDuration = webserver.arg("MaintenanceDuration").toInt(); String Esp32CamIP_tmp = webserver.arg("Esp32CamIP"); Esp32CamIP_tmp.toCharArray(Esp32CamIP, 221); OutputInvert = webserver.arg("OutputInvert").toInt(); SoilmoistureWet = webserver.arg("SoilmoistureWet").toInt(); SoilmoistureDry = webserver.arg("SoilmoistureDry").toInt(); HumiditySensor_Type = webserver.arg("HumiditySensor_Type").toInt(); PWMFrequency = webserver.arg("PWMFrequency").toInt(); DisplayScreenDuration = webserver.arg("DisplayScreenDuration").toInt(); // when configured is false, set it to true and ensure outputs are set if(configured == false) { configured = true; pinMode(PinLED, OUTPUT); pinMode(PinPUMP, OUTPUT); pinMode(PinFAN, OUTPUT); } // size is 1 byte EEPROM.put(161, configured); // size is 1 byte EEPROM.put(162, UseFan); // size is 1 byte EEPROM.put(165, MoistureSensor_Type); // size is 2 byte EEPROM.put(167, NtpOffset); // size is 1 byte EEPROM.put(169, UseLEDrelais); // size is 1 byte EEPROM.put(214, TemperatureSensor_Type); // size is 1 byte EEPROM.put(215, UseFANrelais); EEPROM.put(219, MaintenanceDuration); EEPROM.put(221, Esp32CamIP); EEPROM.put(243, OutputInvert); EEPROM.put(244, SoilmoistureWet); EEPROM.put(246, SoilmoistureDry); EEPROM.put(249, HumiditySensor_Type); EEPROM.put(250, PWMFrequency); EEPROM.put(252, DisplayScreenDuration); // write data to EEPROM EEPROM.commit(); // update time with new offset timeClient.setTimeOffset(NtpOffset * 60 * 60); timeClient.update(); Serial.println(":: POSTsystemSettings ::"); // when user uses an relais for LED control, we force here PinLEDPWM to 255 // to ensure nothing bad happens and its turned on if( (UseLEDrelais == false) && (UseLEDrelais != UseLEDrelais_old) ) { PinLEDPWM = 255; EEPROM.put(213, PinLEDPWM); EEPROM.commit(); Serial.println("UseLEDrelais is 0, forcing PinLEDPWM to max to prevent relais damage and ensure its turned on"); } if( (UseFANrelais == false) && (UseFANrelais != UseFANrelais_old) ) { PinFANPWM = 255; EEPROM.put(215, PinFANPWM); EEPROM.commit(); Serial.println("UseFANrelais is 0, forcing PinFANPWM to max to prevent relais damage and ensure its turned on"); } if(PWMFrequency != PWMFrequency_old) { // if PWM freq changed, apply new settings analogWriteFreq(PWMFrequency); } Serial.print("configured: "); Serial.println(configured); Serial.print("UseFan: "); Serial.println(UseFan); Serial.print("MoistureSensor_Type: "); Serial.println(MoistureSensor_Type); Serial.print("NtpOffset: "); Serial.println(NtpOffset); Serial.print("UseLEDrelais: "); Serial.println(UseLEDrelais); Serial.print("UseFANrelais: "); Serial.println(UseFANrelais); Serial.print("TemperatureSensor_Type: "); Serial.println(TemperatureSensor_Type); Serial.print("MaintenanceDuration: "); Serial.println(MaintenanceDuration); if(strlen(GrowName) < 1) { webserver.sendHeader("Location", String("/growSettings?success"), true); } else { webserver.sendHeader("Location", String("/systemSettings?success"), true); } webserver.send(302, "text/plain", "systemSettings/save: success!\n"); } void POSTwifiSettings() { String WIFIssid_new = webserver.arg("WIFIssid"); String WIFIpassword_new = webserver.arg("WIFIpassword"); String WIFIip_new = webserver.arg("WIFIip"); String WIFInetmask_new = webserver.arg("WIFInetmask"); String WIFIgateway_new = webserver.arg("WIFIgateway"); String WIFIdns_new = webserver.arg("WIFIdns"); // convert String we got from webserver.arg to EEPROM friendly char[] WIFIssid_new.toCharArray(WIFIssid, 32); WIFIpassword_new.toCharArray(WIFIpassword, 64); // if WIFIip_new was not set, we assume DHCP should be used if(WIFIip_new.length() > 0) { WIFIip.fromString(WIFIip_new); WIFInetmask.fromString(WIFInetmask_new); WIFIgateway.fromString(WIFIgateway_new); WIFIdns.fromString(WIFIdns_new); // WIFIuseDHCP = false; } else { WIFIuseDHCP = true; } // restart is needed to load the new settings NeedRestart = true; EEPROM.put(0, WIFIssid); EEPROM.put(32, WIFIpassword); EEPROM.put(96, WIFIip); EEPROM.put(112, WIFInetmask); EEPROM.put(128, WIFIgateway); EEPROM.put(144, WIFIdns); EEPROM.put(160, WIFIuseDHCP); EEPROM.commit(); Serial.println(":: POSTwifiSettings ::"); Serial.print("WIFIssid: "); Serial.println(WIFIssid_new); Serial.println(WIFIssid); Serial.print("WIFIpassword: "); Serial.println(WIFIpassword_new); Serial.println(WIFIpassword); Serial.print("WIFIip: "); Serial.println(WIFIip_new); Serial.print("WIFInetmask: "); Serial.println(WIFInetmask_new); Serial.print("WIFIgateway: "); Serial.println(WIFIgateway_new); Serial.print("WIFIdns: "); Serial.println(WIFIdns_new); Serial.print("WIFIuseDHCP: "); Serial.println(WIFIuseDHCP); webserver.sendHeader("Location", String("/wifiSettings?success"), true); webserver.send(302, "text/plain", "wifiSettings/save: success!\n"); } void POSTsetOutput() { byte OutputState = webserver.arg("state").toInt(); byte OutputNr = webserver.arg("output").toInt(); //PinLEDPWM = webserver.arg("PinLEDPWM").toInt(); byte OutputPWM = webserver.arg("OutputPWM").toInt(); Serial.println(":: POSTsetOutput ::"); Serial.print("OutputState: "); Serial.println(OutputState); Serial.print("OutputNr: "); Serial.println(OutputNr); if((OutputNr > 3) || (OutputNr < 1) || (OutputState > 255) || (OutputState < 0)) { webserver.send(400, "text/plain", "not valid\n"); } else { if(OutputState > 0){ setOutput(OutputNr, OutputPWM); } else { setOutput(OutputNr, 0); } webserver.sendHeader("Location", String("/?success"), true); webserver.send(302, "text/plain", "switch: success!\n"); } } void POSTsetPumpManual() { PumpOnManual = webserver.arg("PumpOnManual").toInt(); webserver.sendHeader("Location", String("/?success"), true); webserver.send(302, "text/plain", "switch: success!\n"); } void POSTwipeConfirm() { String body = returnHTMLheader(); // TODO only debug and development solution, remove this later String confirm = webserver.arg("confirm"); if(confirm == "on") { body += "<div class='warnmsg'><h2>!! Wiping CanGrow's EEPROM !!</h2><br>Device will restart in a few seconds.<br>After restart a new WiFi 'CanGrow-unconfigured' will be created. To access the WebUI visit <a href='http://192.168.4.20'>http://192.168.4.20</a></div>"; body += FPSTR(HTMLfooter); webserver.send(200, "text/html", body); wipeEEPROM(); } else { webserver.send(400, "text/html", String("Error, 'confirm' missing nor wrong")); } } /* * API section * */ // return as json all sensor readings void APIgetSensors() { JsonDocument jsonSensors; jsonSensors["soilmoisture"] = valSoilmoisture; jsonSensors["soilmoistureAvg"] = valSoilmoistureAvg; jsonSensors["soilmoistureRaw"] = valSoilmoistureRaw; jsonSensors["temperature"] = valTemperature; jsonSensors["humidity"] = valHumidity; jsonSensors["waterlevel"] = valWaterlevel; jsonSensors["vpd"] = valVPD; String body; serializeJsonPretty(jsonSensors, body); webserver.send(200, "text/json", body); } void APIgetDebug() { JsonDocument jsonDebug; // Runtime vars JsonObject objRuntime = jsonDebug["runtime"].add<JsonObject>(); objRuntime["PumpOnTimePassed"] = PumpOnTimePassed; objRuntime["PumpOnManual"] = PumpOnManual; objRuntime["valTemperature"] = valTemperature; objRuntime["valHumidity"] = valHumidity; objRuntime["valSoilmoisture"] = valSoilmoisture; objRuntime["valSoilmoistureRaw"] = valSoilmoistureRaw; objRuntime["valSoilmoistureAvg"] = valSoilmoistureAvg; objRuntime["valSoilmoistureAvg_tmp"] = valSoilmoistureAvg_tmp; objRuntime["valSoilmoistureAvg_count"] = valSoilmoistureAvg_count; objRuntime["NeedRestart"] = NeedRestart; objRuntime["FirstRun"] = FirstRun; objRuntime["ScreenToDisplay"] = ScreenToDisplay; objRuntime["ScreenIterationPassed"] = ScreenIterationPassed; objRuntime["DayNight"] = DayNight; objRuntime["MaintenanceMode"] = MaintenanceMode; objRuntime["MaintenanceStarted"] = MaintenanceStarted; // WiFi JsonObject objWiFi = jsonDebug["wifi"].add<JsonObject>(); objWiFi["ssid"] = WIFIssid; objWiFi["dhcp"] = WIFIuseDHCP; objWiFi["ip"] = WiFi.localIP().toString(); objWiFi["netmask"] = WiFi.subnetMask().toString(); objWiFi["gateway"] = WiFi.gatewayIP().toString(); objWiFi["dns"] = WiFi.dnsIP().toString(); // System JsonObject objSystem = jsonDebug["system"].add<JsonObject>(); objSystem["UseFan"] = UseFan; objSystem["UsePump"] = UsePump; objSystem["UseLEDrelais"] = UseLEDrelais; objSystem["UseFANrelais"] = UseFANrelais; objSystem["PumpOnTime"] = PumpOnTime; objSystem["MoistureSensor_Type"] = MoistureSensor_Type; objSystem["SoilmoistureLow"] = SoilmoistureLow; objSystem["TemperatureSensor_Type"] = TemperatureSensor_Type; objSystem["NtpOffset"] = NtpOffset; objSystem["MaintenanceDuration"] = MaintenanceDuration; objSystem["PumpOnTime"] = PumpOnTime; objSystem["PumpLastOn"] = PumpLastOn; objSystem["OutputInvert"] = OutputInvert; objSystem["SoilmoistureWet"] = SoilmoistureWet; objSystem["SoilmoistureDry"] = SoilmoistureDry; objSystem["HumiditySensor_Type"] = HumiditySensor_Type; objSystem["PWMFrequency"] = PWMFrequency; objSystem["DisplayScreenDuration"] = DisplayScreenDuration; objSystem["Esp32CamIP"] = Esp32CamIP; // Grow JsonObject objGrow = jsonDebug["grow"].add<JsonObject>(); objGrow["GrowName"] = GrowName; objGrow["GrowStart"] = GrowStart; objGrow["GrowStartDate"] = returnStrDateFromEpoch(GrowStart); objGrow["DaysVeg"] = DaysVeg; objGrow["DaysBloom"] = DaysBloom; objGrow["LighthoursVeg"] = LighthoursVeg; objGrow["LighthoursBloom"] = LighthoursBloom; objGrow["SunriseHour"] = SunriseHour; objGrow["SunriseMinute"] = SunriseMinute; objGrow["SunFade"] = SunFade; objGrow["SunFadeDuration"] = SunFadeDuration; objGrow["PinLEDPWM"] = PinLEDPWM; objGrow["PinFANPWM"] = PinFANPWM; objGrow["PinFAN2PWM"] = PinFAN2PWM; objGrow["DayOfGrow"] = DayOfGrow; objGrow["PumpIntervalVeg"] = PumpIntervalVeg; objGrow["PumpIntervalBloom"] = PumpIntervalBloom; // Sensors JsonObject objSensors = jsonDebug["sensors"].add<JsonObject>(); // Chirp objSensors["chirp"]["temperature"] = getTemperature(5); objSensors["chirp"]["soilmoisture"] = getSoilmoisture(2); objSensors["chirp"]["soilmoistureRAW"] = getSoilmoisture(2, true); objSensors["chirp"]["light"] = getLightchirp(); // BME280 0x76 objSensors["bme280_0x76"]["temperature"] = getTemperature(1); objSensors["bme280_0x76"]["humidity"] = getHumidity(1); //objSensors["bme280_0x76"]["preassure"] = bme.readPressure() / 100.0F; //objSensors["bme280_0x76"]["appAltitude"] = bme.readAltitude(SEALEVELPRESSURE_HPA); // BME280 0x77 objSensors["bme280_0x77"]["temperature"] = getTemperature(2); objSensors["bme280_0x77"]["humidity"] = getHumidity(2); //objSensors["bme280_0x77"]["preassure"] = bme.readPressure() / 100.0F; //objSensors["bme280_0x77"]["appAltitude"] = bme.readAltitude(SEALEVELPRESSURE_HPA); // SHT31 0x44 objSensors["sht31_0x44"]["temperature"] = getTemperature(3); objSensors["sht31_0x44"]["humidity"] = getHumidity(3); // SHT31 0x45 objSensors["sht31_0x45"]["temperature"] = getTemperature(4); objSensors["sht31_0x45"]["humidity"] = getHumidity(4); // Analog objSensors["analog"]["soilmoisture"] = getSoilmoisture(1); objSensors["analog"]["soilmoistureRAW"] = getSoilmoisture(1, true); objSensors["analog"]["waterlevel"] = getWaterlevel(); String body; serializeJsonPretty(jsonDebug, body); webserver.send(200, "text/json", body); } /* * Web Handler */ void WebHandler() { /* * Webserver handlers * here are the generic webserver handlers like 404 not found * wifiSettings, ... * * if you are looking for the single webpages handler, have a look to * * WebHandler_unconfigured() and WebHandler_configured() */ // style.css //webserver.on("/style.css", HTTP_GET, WEBstyleCSS); // Web root webserver.on("/", HTTP_GET, WEBroot); // WiFi Stuff webserver.on("/wifiSettings", HTTP_GET, WEBwifiSettings); webserver.on("/wifiSettings/save", HTTP_POST, POSTwifiSettings); // System stuff webserver.on("/systemSettings", HTTP_GET, WEBsystemSettings); webserver.on("/systemSettings/save", HTTP_POST, POSTsystemSettings); // Grow stuff webserver.on("/growSettings", HTTP_GET, WEBgrowSettings); webserver.on("/growSettings/save", HTTP_POST, POSTgrowSettings); // help webserver.on("/help", HTTP_GET, WEBhelp); // restart when NeedRestart is true webserver.on("/system/restart", HTTP_GET, SysRestart); // wipe eeprom triggered from WebGui webserver.on("/system/wipe", HTTP_GET, SysWipe); // confirm wiping the device webserver.on("/system/wipeConfirm", HTTP_POST, POSTwipeConfirm); // Maintenance mode webserver.on("/system/maintenance", HTTP_GET, SysMaintenance); // system update with binary // update form webserver.on("/system/update", HTTP_GET, SysUpdate); // update itself webUpdater.setup(&webserver, "/system/applyUpdate"); // does not work atm TODO //webserver.on("/logout", [](){ webserver.send(401, "text/html", "logged out!"); }); // 404 handling // favicon.ico is a special one, because its requested everytime and i dont wont to deliver the // failed whole page every call. we can save up this 0,5kb traffic :o) webserver.on("/favicon.ico", [](){ webserver.send(404, "text/html", "404 - not found"); }); webserver.onNotFound(Sys404); // switching MOSFETs webserver.on("/switch", HTTP_POST, POSTsetOutput); webserver.on("/pumpManual", HTTP_POST, POSTsetPumpManual); // api stuff webserver.on("/api/sensors", HTTP_GET, APIgetSensors); webserver.on("/api/debug", HTTP_GET, APIgetDebug); // gauge meter stuff webserver.on("/gauge.css", HTTP_GET, WEBgaugeCss); webserver.on("/gauge.js", HTTP_GET, WEBgaugeJs); }