From abbaedfbbc2c2b55890b607ee391e9df9a128676 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 9 May 2024 20:30:25 +0200 Subject: [PATCH] firmware wip - begin to seperate things into header files --- Arduino/CanGrow/CanGrow.ino | 2435 +--------------------- Arduino/CanGrow/CanGrow_HTML.h | 435 ++++ Arduino/CanGrow/CanGrow_Init.h | 158 ++ Arduino/CanGrow/CanGrow_Logo.h | 40 + Arduino/CanGrow/CanGrow_PinAssignments.h | 30 + Arduino/CanGrow/CanGrow_Sensors.h | 160 ++ Arduino/CanGrow/CanGrow_SysFunctions.h | 566 +++++ Arduino/CanGrow/CanGrow_WebFunctions.h | 1034 +++++++++ 8 files changed, 2431 insertions(+), 2427 deletions(-) create mode 100644 Arduino/CanGrow/CanGrow_HTML.h create mode 100644 Arduino/CanGrow/CanGrow_Init.h create mode 100644 Arduino/CanGrow/CanGrow_Logo.h create mode 100644 Arduino/CanGrow/CanGrow_PinAssignments.h create mode 100644 Arduino/CanGrow/CanGrow_Sensors.h create mode 100644 Arduino/CanGrow/CanGrow_SysFunctions.h create mode 100644 Arduino/CanGrow/CanGrow_WebFunctions.h diff --git a/Arduino/CanGrow/CanGrow.ino b/Arduino/CanGrow/CanGrow.ino index d0e0105..9596f50 100644 --- a/Arduino/CanGrow/CanGrow.ino +++ b/Arduino/CanGrow/CanGrow.ino @@ -33,1405 +33,17 @@ // https://github.com/PaulStoffregen/Time #include +// CanGrow header files -/* - * - * Constants - * - */ -const char* CanGrowVer = "0.1"; -const char* APssid = "CanGrow-unconfigured"; -/* - * TODO - does not work atm. idk why. - * const char* APpass = "CanGrow"; -const int APchannel = 6; -const bool APhidden = false; -* -*/ +#include "CanGrow_PinAssignments.h" +#include "CanGrow_Init.h" +#include "CanGrow_Logo.h" +#include "CanGrow_Sensors.h" -/* - * - * Variables - * - */ +#include "CanGrow_HTML.h" +#include "CanGrow_SysFunctions.h" +#include "CanGrow_WebFunctions.h" -// valSoilmoisture - contains the value of getSoilmoisture() -byte valSoilmoisture; -// valTemperature - contains the value of getTemperature() -float valTemperature; -// valTemperature - contains the value of getHumidity() -float valHumidity; -// valWaterlevel - contains the value of getWaterlevel() -byte valWaterlevel; -// do we need a restart? (e.g. after wifi settings change) -bool NeedRestart; -bool FirstRun; -// which screen should be actually displayed -byte ScreenToDisplay = 0; -byte DisplayScreenDuration = 3; -// how many seconds actual screen got displayed -byte ScreenIterationPassed = 0; - -bool MaintenanceMode = false; -unsigned long MaintenanceStarted = 0; - -/* - * - * EEPROM saved variables - * - */ - -// -// WiFi -// - -// if empty, CanGrow start in AccessPoint mode -char WIFIssid[32]; -char WIFIpassword[64]; -// WIFIuseDHCP - if true, get IP by DHCP -bool WIFIuseDHCP; -IPAddress WIFIip(192,168,4,20); -IPAddress WIFInetmask(255,255,255,0); -IPAddress WIFIgateway(192,168,4,254); -IPAddress WIFIdns(0,0,0,0); -//char WebUiUsername[16] = "cangrow"; -//char WebUiPassword[32] = "cangrow"; - -// -// System -// -// configured - if false, let the user configure system settings first -bool configured = false; -// NTP Offset -short NtpOffset; -// MoistureSensor_Type - contains which moisture sensor to use -// 1: analog capacitive sensor -// 2: I2C chirp sensor from catnip electronics -byte MoistureSensor_Type; -// SoilmoistureLow - contains the value , when soil moisture is assumed to be low, -byte SoilmoistureLow = 20; -// UsePump - is the pump used? bool -bool UsePump; -// UseFan - is the fan used? bool -byte PumpOnTime = 3; -bool UseFan; -// In case the user uses no 12V LED on the LED output and an relais instead -// we have to disable PWM. So we ask here for what kind of light user is going -bool UseLEDrelais; -bool UseFANrelais; -// Which temperature sensor to use? -byte TemperatureSensor_Type; -unsigned short MaintenanceDuration = 60; - -// -// Grow Stuff -// - -// GrowName - contains the name of the grow/plant. Up to 32 byte -// if empty, let the user setup grow settings first -char GrowName[32]; -// GrowStart - contains unix timestamp from date where grow starts (00:00) -// unsigned long is 8 byte -unsigned long GrowStart; -// DayOfGrow contains on which day the grow is -byte DayOfGrow; -// DaysVeg - contains how many days to be in vegetation phase -byte DaysVeg = 35; -// DaysBloom - contains how many days to be in bloom phase -byte DaysBloom = 49; -// LighthoursVeg - contains how many hours the Growlight is on in Veg -byte LighthoursVeg = 16; -// LighthoursBloom - contains how many hours the Growlight is on in Bloom -byte LighthoursBloom = 12; -// SunriseHour - contains to which hour of day the growlight turns on -byte SunriseHour = 7; -// SunriseHour - contains to which minute of SunriseHour the growlight turns on -byte SunriseMinute = 0; -// PinLEDPWM - contains the PWM value for dimming the grow light -// default is 255 to ensure it is just on for the case UseLEDrelais is true -byte PinLEDPWM = 255; -byte PinFANPWM = 255; - -// fade in and out sunrise and sunset? -bool SunFade; -byte SunFadeDuration = 30; - -/* - * - * NTP - * - */ - -WiFiUDP ntpUDP; -NTPClient timeClient(ntpUDP); - -/* - * - * Webserver - * - */ - -ESP8266WebServer webserver(80); - -/* - * HTML constants for header, footer, css, ... - * Note: I know of the existence of SPIFFS and ESPHtmlTemplateProcessor, - * but to keep things simple for compiling and upload for others, I decided - * to not use those. - */ - -// Template: const char HTMLexamplepage[] PROGMEM = R"EOF()EOF"; - -// first part of HTML header stuff -const char HTMLheaderP1[] PROGMEM = R"EOF( - - - - - -)EOF"; - // here comes the page title in returnHTMLheader() - -// second part of HTML header stuff -// Having the whole CSS here ensures it's all the time present -const char HTMLheaderP2[] PROGMEM = R"EOF( - - - - -
"; - - 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 += "

❗ Restarting

"; - body += "
After restart CanGrow will be connected to WiFi SSID
"; - body += WIFIssid; - body += "
You get its IP-Address from the display or serial console.
"; - body += FPSTR(HTMLfooter); - webserver.send(200, "text/html", body); - Serial.println("Restarting... see you soon space cowboy!"); - delay(1000); - ESP.restart(); - } else { - body += "

❗ Restart CanGrow

"; - body += "
Do you want to restart CanGrow?"; - body += "
Please confirm."; - body += "
"; - body += "
"; - body += FPSTR(HTMLfooter); - webserver.send(200, "text/html", body); - } -} - -void SysWipe() { - String body = returnHTMLheader(); - // TODO only debug and development solution, remove this later - if(webserver.hasArg("confirmed")) { - body += "

!! Wiping CanGrow's EEPROM !!


Device will restart in a few seconds.
"; - body += FPSTR(HTMLfooter); - webserver.send(200, "text/html", body); - wipeEEPROM(); - } else { - body += "

❗❗ Wipeing EEPROM

"; - body += "
All settings will be removed!!
"; - body += "
Please confirm wiping the EEPROM"; - body += "
"; - body += "
"; - body += FPSTR(HTMLfooter); - webserver.send(200, "text/html", body); - } -} - -void Sys404() { - String body = returnHTMLheader(); - body += "

❗ ️ 404 - not found

"; - body += FPSTR(HTMLfooter); - webserver.send(404, "text/html", body); -} - -void Syslogout() { - String body = returnHTMLheader(); - body += "

you are logged out.

"; - body += FPSTR(HTMLfooter); - - // TODO does not work atm - webserver.send(401, "text/html", body); -} - -void SysMaintenance() { - String body = returnHTMLheader(); - - if( (webserver.hasArg("on")) ) { - MaintenanceMode = true; - MaintenanceStarted = millis(); - body += "
⏸️ On for "; - body += MaintenanceDuration; - body += "s
"; - } else if (webserver.hasArg("off")){ - MaintenanceMode = false; - body += "
⏸️ Off
"; - } - - - body += "

⏸️ Maintenance Mode

"; - body += "
"; - body += "
"; - - 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 += "

Login failed.

"; - * 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 += "

🌱 "; - body += GrowName; - body += "

"; - - // add gauge meter - body += FPSTR(HTMLgauge); - // and give javascript the values - // todo: auto refresh by api call - body += ""; - - body += "Grow started: "; - body += returnStrDateFromEpoch(GrowStart); - body += "
\n"; - body += "Day of Grow: "; - body += DayOfGrow; - body += "
\n"; - - if(UsePump == true) { - body += "Pump water level: "; - switch(getWaterlevel()) { - case 0: - body += "OK"; - break; - case 1: - body += "Warning"; - break; - case 2: - body += "Critical"; - break; - } - } - body += "
\n"; - body += "Growlight brightness: "; - body += ((PinLEDPWM * 100) / 255); - body += " %
\n"; - body += "
\n"; - body += "MOSFET
"; - - body += "On/Off:
\n"; - - body += "Intensity:
\n"; - - body += "\n"; - body += "

\n"; - body += "
"; - - - 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 += "

Final step: Grow settings

"; - body += "

Please configure all settings
"; - body += "

"; - GrowStart = timeClient.getEpochTime(); - } - - body += "

🔆 Grow settings

"; - if(webserver.hasArg("success")) { - body += FPSTR(HTMLsuccess); - } - body += "

Here you can set everything grow related, like light hours, how much water, LED brightness
"; - body += "

"; - - body += "
\n"; - - - - body += "Grow name:
\n"; - - - // the input field, which calls javascript convertDateToEpoch() to write data to transmit to id GrowStart - body += "Grow start date:
\n"; - - body += "\n"; - - - - body += "Vegetation duration: Days
\n"; - - body += "Bloom duration: Days
\n"; - - body += "Time LED ON vegetation: Hours
\n"; - - body += "Time LED ON bloom: Hours
\n"; - - body += "Sunrise: \n"; - body += " :
\n"; - - // SunFade bool - body += "Fade in/out sunrise/sunset?:
\n"; - - body += "Fade duration: Minutes
\n"; - - if(UseLEDrelais == false) { - body += "LED brightness: %
\n"; - } - - if(UseFANrelais == false) { - body += "FAN speed: %
\n"; - } - - body += "\n"; - body += "
\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 += "

Step 2: System settings

"; - body += "

Please configure all settings
"; - body += "

"; - } - - body += "

⚙ System settings

"; - if(webserver.hasArg("success")) { - body += FPSTR(HTMLsuccess); - } - body += "

here you can set which features and sensors you use
"; - body += "

"; - - // form starts - body += "
\n"; - - // UseFan bool - body += "Use FAN:
\n"; - - // UsePump bool - body += "Use PUMP:
\n"; - - // UseLEDrelais bool - body += "Use relais for LED:
\n"; - - // UseFANrelais bool - body += "Use relais for FAN:
\n"; - - // TODO ugly. can this done be better? - // PumpOnTime int - body += "PUMP ON time: Seconds
\n"; - - // MoistureSensor_Type byte - body += "Soilmoisture sensor:
\n"; - - // SoilmoistureLow byte - body += "Soilmoisture low: %
\n"; - - // TemperatureSensor_Type byte - body += "Temperature sensor:
\n"; - - // NtpOffset int - body += "NTP offset: Hours
\n"; - - body += "Maintenance Duration: Seconds
\n"; - - body += "\n"; - body += "
\n"; - - body += FPSTR(HTMLfooter); - - webserver.send(200, "text/html", body); - } -} - -void WEBwifiSettings() { - byte ssidsAvail = WiFi.scanNetworks(); - String body = returnHTMLheader("wifiSettings"); - if(FirstRun == true) { - body += "

Welcome!

"; - body += "

CanGrow is actually unconfigured. You need to Setup your WiFi first down below.
"; - body += "
After you entered your WiFi connection details, you need to restart and are step closer to your grow 🥦"; - body += "
"; - body += "

"; - } - body += "

📡 WiFi settings

\n"; - - if(webserver.hasArg("success")) { - body += FPSTR(HTMLsuccess); - } - - if(FirstRun == false) { - body += "Current Settings:
"; - body += "WiFi SSID: "; - body += WIFIssid; - body += "
\n"; - body += "Use DHCP: "; - body += WIFIuseDHCP; - body += "
\n"; - body += "IP address: "; - body += WiFi.localIP().toString(); - body += "
\n"; - body += "Subnet mask: "; - body += WiFi.subnetMask().toString(); - body += "
\n"; - body += "Gateway: "; - body += WiFi.gatewayIP().toString(); - body += "
\n"; - body += "DNS: "; - body += WiFi.dnsIP().toString(); - body += "

\n"; - } - - body += "

Select your wifi network from the SSID list.
To use DHCP leave IP, Subnet, Gateway and DNS fields blank!

"; - body += "
\n"; - body += "SSID:
\n"; - body += "Password:
\n"; - body += "IP:
\n"; - body += "Subnet mask:
\n"; - body += "Gateway:
\n"; - body += "DNS:
\n"; - body += "\n"; - body += "
\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() { - - if(UseLEDrelais == true) { - // if a relais is used to turn on grow light, we force PWM to max val - PinLEDPWM = 255; - } else { - // otherwise just do PWM - PinLEDPWM = webserver.arg("PinLEDPWM").toInt(); - } - - if(UseFANrelais == true) { - // if a relais is used to turn on grow light, we force PWM to max val - PinFANPWM = 255; - } else { - // otherwise just do PWM - PinFANPWM = webserver.arg("PinFANPWM").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(); - - // 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); - EEPROM.put(217, SunFade); - EEPROM.put(218, SunFadeDuration); - - 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); - - webserver.sendHeader("Location", String("/growSettings?success"), true); - webserver.send(302, "text/plain", "growSettings/save: success!\n"); -} - - -void POSTsystemSettings() { - NtpOffset = webserver.arg("NtpOffset").toInt(); - MoistureSensor_Type = webserver.arg("MoistureSensor_Type").toInt(); - SoilmoistureLow = webserver.arg("SoilmoistureLow").toInt(); - UsePump = webserver.arg("UsePump").toInt(); - PumpOnTime = webserver.arg("PumpOnTime").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(); - - configured = true; - - // size is 1 byte - EEPROM.put(161, configured); - // size is 1 byte - EEPROM.put(162, UseFan); - // size is 1 byte - EEPROM.put(163, UsePump); - // size is 1 byte - EEPROM.put(164, PumpOnTime); - // size is 1 byte - EEPROM.put(165, MoistureSensor_Type); - // size is 1 byte - EEPROM.put(166, SoilmoistureLow); - // 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); - - // 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 - if(UseLEDrelais == true) { - PinLEDPWM = 255; - EEPROM.put(213, PinLEDPWM); - EEPROM.commit(); - Serial.println("UseLEDrelais is 1, forcing PinLEDPWM to max to prevent relais damage"); - } - - if(UseFANrelais == true) { - PinFANPWM = 255; - EEPROM.put(215, PinFANPWM); - EEPROM.commit(); - Serial.println("UseFANrelais is 1, forcing PinFANPWM to max to prevent relais damage"); - } - - Serial.print("configured: "); - Serial.println(configured); - Serial.print("UseFan: "); - Serial.println(UseFan); - Serial.print("UsePump: "); - Serial.println(UsePump); - Serial.print("PumpOnTime: "); - Serial.println(PumpOnTime); - Serial.print("MoistureSensor_Type: "); - Serial.println(MoistureSensor_Type); - Serial.print("SoilmoistureLow: "); - Serial.println(SoilmoistureLow); - 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"); - } - -} - - - -/* - * API section - * - */ - -// return as json all sensor readings -void APIgetSensors() { - - JsonDocument jsonSensors; - - JsonArray arraySoilmoisture = jsonSensors["soilmoisture"].to(); - arraySoilmoisture.add(getSoilmoisture(1)); - arraySoilmoisture.add(getSoilmoisture(2)); - JsonArray arrayTemperature = jsonSensors["temperature"].to(); - arrayTemperature.add(getTemperature(1)); - arrayTemperature.add(getTemperature(2)); - jsonSensors["humidity"] = getHumidity(); - jsonSensors["chirpLight"] = getLightchirp(); - - String body; - serializeJsonPretty(jsonSensors, body); - webserver.send(200, "text/json", body); - -} - diff --git a/Arduino/CanGrow/CanGrow_HTML.h b/Arduino/CanGrow/CanGrow_HTML.h new file mode 100644 index 0000000..6702148 --- /dev/null +++ b/Arduino/CanGrow/CanGrow_HTML.h @@ -0,0 +1,435 @@ + +/* + * HTML constants for header, footer, css, ... + * Note: I know of the existence of SPIFFS and ESPHtmlTemplateProcessor, + * but to keep things simple for compiling and upload for others, I decided + * to not use those. + */ + +// Template: const char HTMLexamplepage[] PROGMEM = R"EOF()EOF"; + +// first part of HTML header stuff +const char HTMLheaderP1[] PROGMEM = R"EOF( + + + + + +)EOF"; + // here comes the page title in returnHTMLheader() + +// second part of HTML header stuff +// Having the whole CSS here ensures it's all the time present +const char HTMLheaderP2[] PROGMEM = R"EOF( + + + + +
+ + +)EOF"; + + +const char HTMLsuccess[] PROGMEM = R"EOF( +
✅ Successfully saved!
+)EOF"; + +const char HTMLneedRestart[] PROGMEM = R"EOF( +
❗ Restart is required to apply new WiFi settings! +
+ +
+
+)EOF"; + +const char HTMLhelp[] PROGMEM = R"EOF( +

❓ Help

+Here you will get some helpful help. +)EOF"; + + +const char JSconvertDateToEpoch[] PROGMEM = R"EOF( + +)EOF"; + +// The gauge meter are based on sathomas' gaugemeter +// https://github.com/sathomas/material-gauge + +const char CSSgauge[] PROGMEM = R"EOF( +.gauge { + position: relative; +} + +.gaugeWrapper { + overflow: hidden; + display: flex; + justify-content: center; +} + +.gauge__container { + margin: 0; + padding: 0; + position: absolute; + left: 50%; + overflow: hidden; + text-align: center; + -webkit-transform: translateX(-50%); + -moz-transform: translateX(-50%); + -ms-transform: translateX(-50%); + -o-transform: translateX(-50%); + transform: translateX(-50%); +} + +.gauge__background { + z-index: 0; + position: absolute; + background-color: #cae0d0; + top: 0; + border-radius: 300px 300px 0 0; +} + +.gauge__data { + z-index: 1; + position: absolute; + background-color: #04AA6D; + margin-left: auto; + margin-right: auto; + border-radius: 300px 300px 0 0; + -webkit-transform-origin: center bottom; + -moz-transform-origin: center bottom; + -ms-transform-origin: center bottom; + -o-transform-origin: center bottom; + transform-origin: center bottom; +} + +.gauge__center { + z-index: 2; + position: absolute; + background-color: #1d211e; + margin-right: auto; + border-radius: 300px 300px 0 0; +} + +.gauge__marker { + z-index: 3; + background-color: #fff; + position: absolute; + width: 1px; +} + +.gauge__needle { + z-index: 4; + background-color: #E91E63; + height: 3px; + position: absolute; + -webkit-transform-origin: left center; + -moz-transform-origin: left center; + -ms-transform-origin: left center; + -o-transform-origin: left center; + transform-origin: left center; +} + +.gauge__labels { + display: table; + margin: 0 auto; + position: relative; + font-weight: bold; +} + +.gauge__label--low { + display: table-cell; + text-align: center; +} + +.gauge__label--spacer { + display: table-cell; +} + +.gauge__label--high { + display: table-cell; + text-align: center; +} + + +.gauge { height: calc(60px + 3em); } +.gauge__container { width: 120px; height: 60px; } +.gauge__marker { height: 60px; left: 59.5px; } +.gauge__background { width: 120px; height: 60px; } +.gauge__center { width: 72px; height: 36px; top: 24px; margin-left: 24px; } +.gauge__data { width: 120px; height: 60px; } +.gauge__needle { left: 60px; top: 58px; width: 60px; } +.gauge__labels { top: 60px; width: 120px; } +.gauge__label--low { width: 24px; } +.gauge__label--spacer { width: 72px; text-align: center;} +.gauge__label--high { width: 24px; } + + @media only screen and (min-width: 720px) { + .gauge { height: calc(120px + 4.2em); } + .gauge__container { width: 240px; height: 120px; } + .gauge__marker { height: 120px; left: 119.5px; } + .gauge__background { width: 240px; height: 120px; } + .gauge__center { width: 144px; height: 72px; top: 48px; margin-left: 48px; } + .gauge__data { width: 240px; height: 120px; } + .gauge__needle { left: 120px; top: 117px; width: 120px; } + .gauge__labels { top: 120px; width: 240px; } + .gauge__label--low { width: 48px; } + .gauge__label--spacer { width: 144px; text-align: center;} + .gauge__label--high { width: 48px; } + .gaugeLabel { font-size: 1.3em; } + .gauge__labels { font-size: 2em; } + } + +.gauge--liveupdate .gauge__data, +.gauge--liveupdate .gauge__needle { + -webkit-transition: all 1s ease-in-out; + -moz-transition: all 1s ease-in-out; + -ms-transition: all 1s ease-in-out; + -o-transition: all 1s ease-in-out; + transition: all 1s ease-in-out; +} + + +.gauge__data { + -webkit-transform: rotate(-.50turn); + -moz-transform: rotate(-.50turn); + -ms-transform: rotate(-.50turn); + -o-transform: rotate(-.50turn); + transform: rotate(-.50turn); +} +.gauge__needle { + -webkit-transform: rotate(-.50turn); + -moz-transform: rotate(-.50turn); + -ms-transform: rotate(-.50turn); + -o-transform: rotate(-.50turn); + transform: rotate(-.50turn); +} + +)EOF"; + +const char JSgauge[] PROGMEM = R"EOF( + +function Gauge(el) { + +var element, // Containing element for the info component + data, // `.gauge__data` element + needle, // `.gauge__needle` element + value = 0.0, // Current gauge value from 0 to 1 + prop, // Style for transform + valueLabel; // `.gauge__label--spacer` element + +var setElement = function(el) { + // Keep a reference to the various elements and sub-elements + element = el; + data = element.querySelector('.gauge__data'); + needle = element.querySelector('.gauge__needle'); + valueLabel = element.querySelector('.gauge__label--spacer'); + +}; + +var setValue = function(x, max, unit) { + percentage = x * 100 / max; + value = percentage / 100; + var turns = -0.5 + (value * 0.5); + data.style[prop] = 'rotate(' + turns + 'turn)'; + needle.style[prop] = 'rotate(' + turns + 'turn)'; + valueLabel.textContent = x + unit; + +}; + +function exports() { }; + +exports.element = function(el) { + if (!arguments.length) { return element; } + setElement(el); + return this; +}; + +exports.value = function(x, max=100, unit='%') { + if (!arguments.length) { return value; } + setValue(x, max, unit); + return this; +}; + +var body = document.getElementsByTagName('body')[0]; +['webkitTransform', 'mozTransform', 'msTransform', 'oTransform', 'transform']. + forEach(function(p) { + if (typeof body.style[p] !== 'undefined') { prop = p; } + } +); + +if (arguments.length) { + setElement(el); +} + +return exports; + +}; + + +)EOF"; + +const char HTMLgauge[] PROGMEM = R"EOF( + +
+
+ Temperature +
+
+
+
+
+
+
+ + + +
+
+ +
+ Humidity +
+
+
+
+
+
+
+ + + +
+
+ +
+ Soilmoisture +
+
+
+
+
+
+
+ + + +
+
+ + +
+ + + + +)EOF"; diff --git a/Arduino/CanGrow/CanGrow_Init.h b/Arduino/CanGrow/CanGrow_Init.h new file mode 100644 index 0000000..38c0c1e --- /dev/null +++ b/Arduino/CanGrow/CanGrow_Init.h @@ -0,0 +1,158 @@ + +/* + * + * Constants + * + */ +const char* CanGrowVer = "0.1"; +const char* APssid = "CanGrow-unconfigured"; +/* + * TODO - does not work atm. idk why. + * const char* APpass = "CanGrow"; +const int APchannel = 6; +const bool APhidden = false; +* +*/ + +/* + * + * Variables + * + */ + +// valSoilmoisture - contains the value of getSoilmoisture() +byte valSoilmoisture; +// valTemperature - contains the value of getTemperature() +float valTemperature; +// valTemperature - contains the value of getHumidity() +float valHumidity; +// valWaterlevel - contains the value of getWaterlevel() +byte valWaterlevel; +// do we need a restart? (e.g. after wifi settings change) +bool NeedRestart; +bool FirstRun; +// which screen should be actually displayed +byte ScreenToDisplay = 0; +byte DisplayScreenDuration = 3; +// how many seconds actual screen got displayed +byte ScreenIterationPassed = 0; + +bool MaintenanceMode = false; +unsigned long MaintenanceStarted = 0; + +/* + * millis timer + * + */ +unsigned long outputPrevTime = 0; + + +/* + * + * EEPROM saved variables + * + */ + +// +// WiFi +// + +// if empty, CanGrow start in AccessPoint mode +char WIFIssid[32]; +char WIFIpassword[64]; +// WIFIuseDHCP - if true, get IP by DHCP +bool WIFIuseDHCP; +IPAddress WIFIip(192,168,4,20); +IPAddress WIFInetmask(255,255,255,0); +IPAddress WIFIgateway(192,168,4,254); +IPAddress WIFIdns(0,0,0,0); +//char WebUiUsername[16] = "cangrow"; +//char WebUiPassword[32] = "cangrow"; + +// +// System +// +// configured - if false, let the user configure system settings first +bool configured = false; +// NTP Offset +short NtpOffset; +// MoistureSensor_Type - contains which moisture sensor to use +// 1: analog capacitive sensor +// 2: I2C chirp sensor from catnip electronics +byte MoistureSensor_Type; +// SoilmoistureLow - contains the value , when soil moisture is assumed to be low, +byte SoilmoistureLow = 20; +// UsePump - is the pump used? bool +bool UsePump; +// UseFan - is the fan used? bool +byte PumpOnTime = 3; +bool UseFan; +// In case the user uses no 12V LED on the LED output and an relais instead +// we have to disable PWM. So we ask here for what kind of light user is going +bool UseLEDrelais; +bool UseFANrelais; +// Which temperature sensor to use? +byte TemperatureSensor_Type; +unsigned short MaintenanceDuration = 60; + +// +// Grow Stuff +// + +// GrowName - contains the name of the grow/plant. Up to 32 byte +// if empty, let the user setup grow settings first +char GrowName[32]; +// GrowStart - contains unix timestamp from date where grow starts (00:00) +// unsigned long is 8 byte +unsigned long GrowStart; +// DayOfGrow contains on which day the grow is +byte DayOfGrow; +// DaysVeg - contains how many days to be in vegetation phase +byte DaysVeg = 35; +// DaysBloom - contains how many days to be in bloom phase +byte DaysBloom = 49; +// LighthoursVeg - contains how many hours the Growlight is on in Veg +byte LighthoursVeg = 16; +// LighthoursBloom - contains how many hours the Growlight is on in Bloom +byte LighthoursBloom = 12; +// SunriseHour - contains to which hour of day the growlight turns on +byte SunriseHour = 7; +// SunriseHour - contains to which minute of SunriseHour the growlight turns on +byte SunriseMinute = 0; +// PinLEDPWM - contains the PWM value for dimming the grow light +// default is 255 to ensure it is just on for the case UseLEDrelais is true +byte PinLEDPWM = 255; +byte PinFANPWM = 255; + +// fade in and out sunrise and sunset? +bool SunFade; +byte SunFadeDuration = 30; + +/* + * + * NTP + * + */ + +WiFiUDP ntpUDP; +NTPClient timeClient(ntpUDP); + +/* + * + * Webserver + * + */ + +ESP8266WebServer webserver(80); + +/* I2C Stuff + * +*/ +#define WIRE Wire + +/* + * Display Stuff + */ +Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &WIRE); +// 'CanGrow_Logo', 128x32px + diff --git a/Arduino/CanGrow/CanGrow_Logo.h b/Arduino/CanGrow/CanGrow_Logo.h new file mode 100644 index 0000000..8d923b9 --- /dev/null +++ b/Arduino/CanGrow/CanGrow_Logo.h @@ -0,0 +1,40 @@ +const unsigned char bmpCanGrow_Logo [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0e, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x38, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x70, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x03, 0x00, 0x00, 0x00, 0x04, 0x07, 0xe0, 0x20, 0x60, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x03, 0x00, 0x00, 0x00, 0x06, 0x07, 0xe0, 0xe0, 0x60, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x03, 0x87, 0xe1, 0xc0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x3f, 0xc3, 0xff, 0x03, 0xc7, 0xe3, 0xc0, 0xcf, 0xf9, 0xff, 0xe3, 0xfc, 0xc1, 0x83, + 0x30, 0x00, 0x7f, 0xe3, 0xff, 0x83, 0xe7, 0xe7, 0xc0, 0xcf, 0xfb, 0xff, 0xe7, 0xfe, 0xc3, 0x87, + 0x30, 0x00, 0xe0, 0x73, 0x80, 0xc1, 0xf7, 0xef, 0xc0, 0xc0, 0x1b, 0x80, 0x0e, 0x03, 0xc3, 0x86, + 0x30, 0x00, 0xc0, 0x33, 0x00, 0xc1, 0xff, 0xff, 0x80, 0xc0, 0x1b, 0x00, 0x0c, 0x03, 0xc7, 0x8e, + 0x30, 0x01, 0xc0, 0x37, 0x00, 0xc0, 0xff, 0xff, 0x80, 0xc0, 0x3b, 0x00, 0x1c, 0x03, 0xc7, 0x8c, + 0x60, 0x01, 0xc0, 0x37, 0x00, 0xc0, 0xff, 0xff, 0x01, 0x80, 0x3f, 0x00, 0x18, 0x03, 0xcf, 0x9c, + 0x60, 0x00, 0x00, 0x37, 0x00, 0xc0, 0x7f, 0xfe, 0x01, 0x80, 0x37, 0x00, 0x18, 0x03, 0xcf, 0x9c, + 0x60, 0x00, 0x00, 0x76, 0x01, 0xc0, 0x1f, 0xfc, 0x01, 0x80, 0x36, 0x00, 0x18, 0x06, 0xdf, 0xb8, + 0x60, 0x00, 0x7f, 0xe6, 0x01, 0x9f, 0x9f, 0xfc, 0xf9, 0x80, 0x36, 0x00, 0x18, 0x06, 0xdd, 0xb8, + 0x60, 0x00, 0xff, 0xe6, 0x01, 0x87, 0xff, 0xff, 0xf1, 0x80, 0x76, 0x00, 0x18, 0x06, 0xdd, 0xb0, + 0xc0, 0x01, 0xc0, 0xee, 0x01, 0x83, 0xff, 0xff, 0xc3, 0x00, 0x7e, 0x00, 0x30, 0x06, 0xf9, 0xf0, + 0xc0, 0x0b, 0x80, 0x6e, 0x01, 0x81, 0xff, 0xff, 0x83, 0x00, 0x6e, 0x00, 0x30, 0x06, 0xf9, 0xe0, + 0xc0, 0x1b, 0x00, 0xec, 0x01, 0x80, 0x1f, 0xf8, 0x03, 0x00, 0x6c, 0x00, 0x30, 0x0e, 0xf1, 0xe0, + 0xc0, 0x3b, 0x00, 0xcc, 0x03, 0x80, 0x3f, 0xfc, 0x03, 0x00, 0xec, 0x00, 0x30, 0x0c, 0xf1, 0xc0, + 0xc0, 0x7b, 0x01, 0xcc, 0x03, 0x00, 0x7f, 0xfe, 0x03, 0x01, 0xec, 0x00, 0x30, 0x1c, 0xe1, 0xc0, + 0x7f, 0xf1, 0xff, 0xdc, 0x03, 0x00, 0xf0, 0x8f, 0x01, 0xff, 0xfc, 0x00, 0x1f, 0xf8, 0xe1, 0xc0, + 0x3f, 0xe0, 0xff, 0xcc, 0x03, 0x00, 0x00, 0x80, 0x00, 0xff, 0xcc, 0x00, 0x0f, 0xf0, 0xc1, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 528) +const int bmpallArray_LEN = 1; +const unsigned char* bmpallArray[1] = { + bmpCanGrow_Logo +}; diff --git a/Arduino/CanGrow/CanGrow_PinAssignments.h b/Arduino/CanGrow/CanGrow_PinAssignments.h new file mode 100644 index 0000000..5d59c03 --- /dev/null +++ b/Arduino/CanGrow/CanGrow_PinAssignments.h @@ -0,0 +1,30 @@ +/* + * + * Pin assignments + * + * D0 - MOSFET Pump + * D1, D2 - I2C + * D3 - DHT11 + * D4 - PinWIPE + * D5 - MOSFET Fan + * D6 - MOSFET Grow LED, PWM + * D7 - waterlevel (set HIGH to read value) + * D8 - analog soil moisture (set HIGH to read value) + * A0 - analog input for soil moisture and waterlevel readings + * + * D4 and D7 cannot be HIGH at the same time! + */ + +// D0 is HIGH at boot, no PWM +const uint8_t PinPUMP = D0; +// If D3 is pulled to LOW, boot fails +const uint8_t PINdht = D3; +// D4 is HIGH at boot, boot fail if pulled to LOW +// During Start Screen you can pull D4 to LOW to wipe saved data in EEPROM +// DO NOT PULL D4 DOWN AT WHEN POWERING ON !!! BOOT WILL FAIL +const uint8_t PinWIPE = D4; +const uint8_t PinFAN = D5; +const uint8_t PinLED = D6; // +const uint8_t PINwaterlevel = D7; +const uint8_t PINsoilmoisture = D8; +const uint8_t PINanalog = A0; diff --git a/Arduino/CanGrow/CanGrow_Sensors.h b/Arduino/CanGrow/CanGrow_Sensors.h new file mode 100644 index 0000000..c671089 --- /dev/null +++ b/Arduino/CanGrow/CanGrow_Sensors.h @@ -0,0 +1,160 @@ +/* + * DHT Stuff + * +*/ +#define DHTTYPE DHT11 +DHT dht(PINdht, DHTTYPE); + + +/* + * Chirp functions + */ +void writeI2CRegister8bit(int addr, int value) { + Wire.beginTransmission(addr); + Wire.write(value); + Wire.endTransmission(); +} + +unsigned int readI2CRegister16bit(int addr, int reg) { + Wire.beginTransmission(addr); + Wire.write(reg); + Wire.endTransmission(); + delay(20); + Wire.requestFrom(addr, 2); + unsigned int t = Wire.read() << 8; + t = t | Wire.read(); + return t; +} + +/* + * + * Sensor functions + * + */ + +int getWaterlevel() { + + /* + * waterlevelRAW + * =========== + * 0 - 199 : CRITICAL + * 200 - 399 : WARNING + * >400 : OK + * + * waterlevel + * ========== + * 2 : CRITICAL + * 1 : WARNING + * 0 : OK + */ + + short waterlevelWARN = 200; + short waterlevelOK = 400; + short waterlevelRAW = 0; + byte waterlevel = 0; + + // enable Vcc for water level sensor + digitalWrite(PINwaterlevel, HIGH); + // wait a bit to let the circuit stabilize + // TODO: replace delay() with millis() + delay(100); + // get the value + waterlevelRAW = analogRead(PINanalog); + // disable Vcc for the sensor to prevent electrolysis effect and release analog pin + digitalWrite(PINwaterlevel, LOW); + + if( waterlevelRAW >= waterlevelOK) { + waterlevel = 0; + } else if( waterlevelRAW >= waterlevelWARN) { + waterlevel = 1; + } else { + waterlevel = 2; + } + + return waterlevel; +} + +float getTemperature(byte tempSensor) { + /* + * tempSensor + * ========== + * 1 : DHT11 temp sensor + * 2 : chirp I2C temp sensor + */ + + float temperature = 0; + + switch(tempSensor) { + case 1: + // read temperature from DHT11 + temperature = dht.readTemperature(); + break; + case 2: + // read temperature from chrip I2C + temperature = readI2CRegister16bit(0x20, 5) * 0.10 ; + break; + default: + // if sensor type is not recognized, return 99 + temperature = 99.99; + } + + return temperature; +} + +float getHumidity() { + float humidity = dht.readHumidity(); + return humidity; +} + +int getSoilmoisture(byte moistureSensor) { + /* + * moistureSensor + * ============== + * 1 : analog capacitive moisture sensor + * 2 : chirp I2C moisture sensor + */ + + // value to return + int soilmoisture; + // value for wet + int wet; + // value for dry + int dry; + + switch(moistureSensor) { + case 1: + // read analog value from analog moisture sensor + wet = 180; + dry= 590; + + digitalWrite(PINsoilmoisture, HIGH); + // wait a bit to let the circuit stabilize + delay(100); + // get analog input value + soilmoisture = analogRead(PINanalog); + // disable Vcc for the sensor to release analog pin + digitalWrite(PINsoilmoisture, LOW); + break; + case 2: + // read soil moisture from chrip I2C + wet = 560; + dry= 250; + + // get raw value from I2C chirp sensor + soilmoisture = readI2CRegister16bit(0x20, 0); + break; + default: + wet = 0; + dry = 1; + soilmoisture = -1; + } + + return map(soilmoisture, wet, dry, 100, 0); +} + +int getLightchirp() { + // get the "light value" from I2C chirp module + writeI2CRegister8bit(0x20, 3); //request light measurement + int lightchirp = readI2CRegister16bit(0x20, 4); + return lightchirp; +} diff --git a/Arduino/CanGrow/CanGrow_SysFunctions.h b/Arduino/CanGrow/CanGrow_SysFunctions.h new file mode 100644 index 0000000..7e6e812 --- /dev/null +++ b/Arduino/CanGrow/CanGrow_SysFunctions.h @@ -0,0 +1,566 @@ + +/* + * + * + * Functions + * + * + */ + + +void wipeEEPROM() { + Serial.println(":: wipe EEPROM ::"); + + // wipeMsg is helper variable to know if the Serial.print Message was + // already sent + byte wipeMsg = 0; + while(digitalRead(PinWIPE) == LOW ) { + // only show the Serial message once + if(wipeMsg == 0) { + Serial.println("Please release PinWIPE to erase all data saved in EEPROM"); + Serial.println("LAST CHANCE TO KEEP THE DATA BY RESETTING NOW!!"); + + display.clearDisplay(); + display.setCursor(0,0); + display.println("!!!!!!!!!!!!!!!!!!!!!"); + display.println(""); + display.println("RELEASE PinWIPE"); + display.println("TO WIPE EEPROM"); + display.display(); + + // increase i to show the serial message only once + wipeMsg = 1; + } + delay(500); + } + + // write a 0 to all 512 bytes of the EEPROM + Serial.print("wiping EEPROM... "); + display.println("Wiping EEPROM..."); + display.println("Will restart in 3s"); + display.display(); + for (int i = 0; i < 512; i++) { EEPROM.write(i, 0); } + + // commit everything to EEPROM and end here + EEPROM.end(); + + Serial.println("DONE"); + + // set D4 PinWIPE internal LED to Output to give feedback WIPE + // was done + pinMode(PinWIPE, OUTPUT); + + Serial.println("!! Device will restart in 3 seconds !!"); + + // let the internal led blink fast to signalize wipe is done + for(byte i = 0; i <= 24 ; i++) { + if(i % 2) { + digitalWrite(PinWIPE, LOW); + } else { + digitalWrite(PinWIPE, HIGH); + } + delay(125); + } + ESP.restart(); +} + + +bool loadEEPROM() { + /* + * EEPROM Save table + * + * 0 WIFIssid + * 32 WIFIpassword + * 96 WIFIip + * 112 WIFInetmask + * 128 WIFIgateway + * 144 WIFIdns + * 160 WIFIuseDHCP + * + * 161 configured + * 162 UseFan + * 163 UsePump + * 164 PumpOnTime + * 165 MoistureSensor_Type + * 166 SoilmoistureLow + * 167 NtpOffset + * 169 UseLEDrelais + * + * 170 GrowName + * 202 GrowStart + * 206 DaysVeg + * 207 DaysBloom + * 208 LighthoursVet + * 209 LighthoursBloom + * 210 SunriseHour + * 211 SunriseMinute + * 212 DayOfGrow + * + * -- afterwards added, need to sort -- + * + * 213 PinLEDPWM + * 214 TemperatureSensor_Type + * 215 UseFANrelais + * 216 PinFANPWM + * 217 SunFade + * 218 SunFadeDuration + * 219 MaintenanceDuration + * 221 .. + * + */ + + Serial.println(":: loading EEPROM ::"); + + display.setCursor(0,36); + display.fillRect(0,36,128,64-36, 0); + display.println("loading EEPROM"); + display.display(); + // read var WIFIssid from address 0, 32 byte long + // read this first, because we decide on the ssid length (>0?) if + // we run in unconfigured AP mode, nor not + EEPROM.get(0, WIFIssid); + + // when length is > 0 then read furter EEPROM config data + if(strlen(WIFIssid)) { + /* + * WIFI settings + */ + + // read var WIFIpassword from address 32, 64 byte long + EEPROM.get(32, WIFIpassword); + // read var WIFIip from address 96, 16 byte long + EEPROM.get(96, WIFIip); + // read var WIFInetmask from address 112, 16 byte long + EEPROM.get(112, WIFInetmask); + // read var WIFIgateway from address 128, 16 byte long + EEPROM.get(128, WIFIgateway); + // read var WIFIgateway from address 128, 16 byte long + EEPROM.get(144, WIFIdns); + // read var WIFIuseDHCP from Address 160, 1 byte long + EEPROM.get(160, WIFIuseDHCP); + + + /* + * System settings + */ + + // size is 1 byte + EEPROM.get(161, configured); + if(configured == true) { + // size is 1 byte + EEPROM.get(162, UseFan); + // size is 1 byte + EEPROM.get(163, UsePump); + // size is 1 byte + EEPROM.get(164, PumpOnTime); + // size is 1 byte + EEPROM.get(165, MoistureSensor_Type); + // size is 1 byte + EEPROM.get(166, SoilmoistureLow); + // size is 2 byte + EEPROM.get(167, NtpOffset); + // size is 1 byte + EEPROM.get(169, UseLEDrelais); + // size is 1 byte + EEPROM.get(214, TemperatureSensor_Type); + // size is 1 byte + EEPROM.get(215, UseFANrelais); + // size is 2 byte + EEPROM.get(219, MaintenanceDuration); + } + // TODO auth does not work atm + // EEPROM.get(160, WebUiUsername); + // EEPROM.get(176, WebUiPassword); + + /* + * Grow settings + */ + + // size is 32 byte + EEPROM.get(170, GrowName); + if(strlen(GrowName) > 0) { + // size is 4 byte + EEPROM.get(202, GrowStart); + // size is 1 byte + EEPROM.get(206, DaysVeg); + // size is 1 byte + EEPROM.get(207, DaysBloom); + // size is 1 byte + EEPROM.get(208, LighthoursVeg); + // size is 1 byte + EEPROM.get(209, LighthoursBloom); + // size is 1 byte + EEPROM.get(210, SunriseHour); + // size is 1 byte + EEPROM.get(211, SunriseMinute); + // size is 1 byte + EEPROM.get(212, DayOfGrow); + // size is 1 byte + EEPROM.get(213, PinLEDPWM); + // size is 1 byte + EEPROM.get(216, PinFANPWM); + EEPROM.get(217, SunFade); + EEPROM.get(218, SunFadeDuration); + + } + + + + + // print values to Serial output + Serial.println("---- WiFi values ----"); + Serial.print("WIFIssid: "); + Serial.println(WIFIssid); + Serial.print("WIFIpassword: "); + Serial.println(WIFIpassword); + Serial.print("Use DHCP: "); + Serial.println(WIFIuseDHCP); + + Serial.println("---- System values ----"); + Serial.print("configured: "); + Serial.println(configured); + Serial.print("UseFan: "); + Serial.println(UseFan); + Serial.print("UsePump: "); + Serial.println(UsePump); + Serial.print("PumpOnTime: "); + Serial.println(PumpOnTime); + Serial.print("MoistureSensor_Type: "); + Serial.println(MoistureSensor_Type); + Serial.print("TemperatureSensor_Type: "); + Serial.println(TemperatureSensor_Type); + Serial.print("SoilmoistureLow: "); + Serial.println(SoilmoistureLow); + Serial.print("NtpOffset: "); + Serial.println(NtpOffset); + Serial.print("UseLEDrelais: "); + Serial.println(UseLEDrelais); + Serial.print("UseFANrelais: "); + Serial.println(UseFANrelais); + Serial.print("MaintenanceDuration: "); + Serial.println(MaintenanceDuration); + + Serial.println("---- Grow values ----"); + 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("DayOfGrow: "); + Serial.println(DayOfGrow); + Serial.print("PinLEDPWM: "); + Serial.println(PinLEDPWM); + Serial.print("PinFANPWM: "); + Serial.println(PinFANPWM); + Serial.print("SunFade: "); + Serial.println(SunFade); + Serial.print("SunFadeDuration: "); + Serial.println(SunFadeDuration); + + + } else { + Serial.println("EEPROM value WIFIssid is empty"); + } + Serial.println(":: EEPROM loaded ::"); + + display.setCursor(0,42); + display.println("EEPROM loaded"); + display.display(); + + return(strlen(WIFIssid)); +} + +void wifiConnect() { + Serial.println(":: Connecting to WiFi ::"); + FirstRun = false; + Serial.print("SSID: "); + Serial.println(WIFIssid); + + display.fillRect(0,36,128,64-36, 0); + display.setCursor(0,36); + display.println("Connecting to WiFi"); + display.println(WIFIssid); + display.display(); + + // Start WiFi connection + WiFi.begin(WIFIssid, WIFIpassword); + if(WIFIuseDHCP == false) { + WiFi.config(WIFIip, WIFIdns, WIFIgateway, WIFInetmask); + } + + // wait until WiFi connection is established + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(" CONNECTED!"); + Serial.print("IP: "); + Serial.println(WiFi.localIP()); + + Serial.println(":: Getting time from NTP ::"); + display.fillRect(0,36,128,64-36, 0); + display.setCursor(0,36); + display.println("Getting NTP time"); + display.display(); + + timeClient.begin(); + timeClient.setTimeOffset(NtpOffset * 60 * 60); + timeClient.update(); + while ( ! timeClient.isTimeSet()) { + timeClient.update(); + delay(500); + Serial.print("."); + } + + Serial.println(timeClient.getFormattedTime()); + Serial.println(timeClient.getEpochTime()); + display.println(timeClient.getFormattedTime()); + display.display(); + display.print("IP: "); + display.print(WiFi.localIP()); + display.display(); +} + +void wifiAp() { + Serial.println(":: Creating Accesspoint ::"); + + display.fillRect(0,36,128,64-36, 0); + display.setCursor(0,36); + display.println("Creating AccessPoint"); + display.println(APssid); + display.display(); + + FirstRun = true; + // configure WiFi Access Point + WiFi.softAPConfig(WIFIip, WIFIgateway, WIFInetmask); + // start Access Point + // TODO make AP with password - does not work atm. idk why. + WiFi.softAP(APssid); + Serial.print("SSID: "); + Serial.println(APssid); + Serial.print("CanGrow IP address: "); + Serial.println(WiFi.softAPIP()); + + display.print("IP: "); + display.println(WiFi.softAPIP()); + display.display(); + // TODO does not work atm, idk why + //Serial.println("The login credentials for the WebUI are 'cangrow' for username and password"); +} + +void setOutput(byte Output, byte OutputState) { + /* + * Pin assignments + * + * 1 - LED + * 2 - FAN + * 3 - PUMP + * + */ + bool UseRelais = true; + byte OutputPin; + + switch(Output) { + case 1: + OutputPin = PinLED; + if(UseLEDrelais == true) { + UseRelais = true; + } else { + UseRelais = false; + } + break; + case 2: + OutputPin = PinFAN; + if(UseFANrelais == true) { + UseRelais = true; + } else { + UseRelais = false; + } + break; + // PUMP Pin (D0) does not support PWM, so we do not need to care about + case 3: + OutputPin = PinPUMP; + break; + } + + //~ Serial.print("Output: "); + //~ Serial.println(Output); + //~ Serial.print("OutputPin: "); + //~ Serial.println(OutputPin); + //~ Serial.print("OutputState: "); + //~ Serial.println(OutputState); + //~ Serial.print("UseRelais: "); + //~ Serial.println(UseRelais); + + if( (UseRelais == true) || (OutputPin == PinPUMP) ) { + digitalWrite(OutputPin, OutputState); + } else { + analogWrite(OutputPin, OutputState); + } +} + + +void controlLED() { + byte lightHours; + byte PinLEDPWM_tmp; + unsigned int secondsSunrise = (SunriseHour * 60 * 60) + (SunriseMinute * 60); + unsigned int secondsToday = (timeClient.getHours() * 60 * 60) + (timeClient.getMinutes() * 60) + timeClient.getSeconds(); + + if(DayOfGrow > DaysVeg ) { + lightHours = LighthoursBloom; + } else { + lightHours = LighthoursVeg; + } + + // check if secondsToday is larger then secondsSunrise time AND if + // secondsToday is smaller then the sum of secondsSunrise + seconds of lightHours + if( ((secondsToday >= secondsSunrise) && (secondsToday <= ( secondsSunrise + (lightHours * 60 * 60))) ) ){ + //Serial.println("light on time"); + + // when SunFade is true, fade LED light. Otherwise just turn on or off + if( (SunFade == true) && (UseLEDrelais == false) && (secondsSunrise + SunFadeDuration * 60 >= secondsToday) ) { + // in the first n minutes of lighting (SunFadeDuration), we want + // to raise the light slowly to prevent stress from the plant + // convert progress sunrise to PWM value + PinLEDPWM_tmp = (SunFadeDuration * 60 - ((secondsSunrise + SunFadeDuration * 60) - secondsToday)) * PinLEDPWM / (SunFadeDuration * 60); + setOutput(1, PinLEDPWM_tmp); + //Serial.print("sunrise PWM; "); + //Serial.println(PinLEDPWM_tmp); + + } else if( (SunFade == true) && (UseLEDrelais == false) && (secondsToday >= ((secondsSunrise + lightHours * 60 * 60) - SunFadeDuration * 60) ) ) { + // calculate progress sunset to PWM value + PinLEDPWM_tmp = (secondsSunrise + (lightHours * 60 * 60) - secondsToday) * PinLEDPWM / (SunFadeDuration * 60); + setOutput(1, PinLEDPWM_tmp); + //Serial.print("sunset PWM: "); + //Serial.println(PinLEDPWM_tmp); + + } else { + //Serial.println("just turn on the light"); + // no sunrise or sunset, just keep the LED turned on + setOutput(1, PinLEDPWM); + } + + } else { + //Serial.println("good night time"); + // turn off + setOutput(1, 0); + } +} + +void refreshSensors() { + valSoilmoisture = getSoilmoisture(MoistureSensor_Type); + valHumidity = getHumidity(); + valTemperature = getTemperature(TemperatureSensor_Type); + valWaterlevel = getWaterlevel(); +} + +void displayScreens() { + /* + * which screen to display + * interate through different screens + * + */ + if(ScreenIterationPassed > DisplayScreenDuration){ + ScreenIterationPassed = 0; + // helper variable, maybe i find a better way in future + byte LastScreen = 2; + // when the next screen gets displayed, clear display + display.clearDisplay(); + display.display(); + // when ScreenToDisplay has reach last number of screens, reset to first (0) + if(ScreenToDisplay >= LastScreen) { + ScreenToDisplay = 0; + } else { + ScreenToDisplay++; + } + } + + display.setCursor(0,0); + + if(MaintenanceMode == true) { + display.drawBitmap(0, 0, bmpCanGrow_Logo, 128, 32, WHITE); + display.display(); + display.setCursor(0,32); + display.println("Maintenance mode active"); + display.print("Time left: "); + display.print(MaintenanceDuration - ((millis() - MaintenanceStarted) / 1000)); + display.println("s"); + } else { + // in this switch case the single screens gets defined + switch(ScreenToDisplay) { + case 0: + display.print("Humidity: "); + display.print(valHumidity); + display.println(" %"); + display.println(""); + display.print("Temperature: "); + display.print(valTemperature); + display.println(" C"); + display.println(""); + display.print("Moisture: "); + display.print(valSoilmoisture); + display.println(" %"); + display.println(""); + if(UsePump == true) { + display.print("Pump Waterlvl: "); + switch(valWaterlevel) { + case 0: + display.println("OK"); + break; + case 1: + display.println("Warn"); + break; + case 2: + display.println("Crit"); + break; + } + } + break; + + case 1: + display.print("LED: "); + display.print(PinLEDPWM * 100 / 255); + display.println(" %"); + display.print("State: "); + display.println(digitalRead(PinLED)); + display.println(""); + display.print("FAN: "); + display.print(PinFANPWM * 100 / 255); + display.println(" %"); + display.print("State: "); + display.println(digitalRead(PinFAN)); + display.println(""); + display.print("Pump state: "); + display.println(digitalRead(PinPUMP)); + break; + + case 2: + // display Logo + display.drawBitmap(0, 0, bmpCanGrow_Logo, 128, 32, WHITE); + display.display(); + display.setCursor(0,32); + display.println(GrowName); + display.print("DoG: "); + display.print(DayOfGrow); + display.print(", "); + display.println(timeClient.getFormattedTime()); + display.print("IP: "); + display.println(WiFi.localIP()); + break; + } + } + ScreenIterationPassed++; + display.display(); + +} diff --git a/Arduino/CanGrow/CanGrow_WebFunctions.h b/Arduino/CanGrow/CanGrow_WebFunctions.h new file mode 100644 index 0000000..1f91918 --- /dev/null +++ b/Arduino/CanGrow/CanGrow_WebFunctions.h @@ -0,0 +1,1034 @@ + +/* + * + * + * 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 += ""; + // 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 += "\n"; + + // add additional header stuff, like loading guage files + if(MenuEntry == "root") { + header += ""; + } + + header += FPSTR(HTMLheaderP2); + + // first menu entry + header += "
  • 🌱 "; + if(strlen(GrowName) > 0) { + header += GrowName; + } else { + header += "CanGrow"; + } + header += "
  • \n"; + + // second menu entry + header += "
  • and start
    + header += "
    "; + + 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 += "

    ❗ Restarting

    "; + body += "
    After restart CanGrow will be connected to WiFi SSID
    "; + body += WIFIssid; + body += "
    You get its IP-Address from the display or serial console.
    "; + body += FPSTR(HTMLfooter); + webserver.send(200, "text/html", body); + Serial.println("Restarting... see you soon space cowboy!"); + delay(1000); + ESP.restart(); + } else { + body += "

    ❗ Restart CanGrow

    "; + body += "
    Do you want to restart CanGrow?"; + body += "
    Please confirm."; + body += "
    "; + body += "
    "; + body += FPSTR(HTMLfooter); + webserver.send(200, "text/html", body); + } +} + +void SysWipe() { + String body = returnHTMLheader(); + // TODO only debug and development solution, remove this later + if(webserver.hasArg("confirmed")) { + body += "

    !! Wiping CanGrow's EEPROM !!


    Device will restart in a few seconds.
    "; + body += FPSTR(HTMLfooter); + webserver.send(200, "text/html", body); + wipeEEPROM(); + } else { + body += "

    ❗❗ Wipeing EEPROM

    "; + body += "
    All settings will be removed!!
    "; + body += "
    Please confirm wiping the EEPROM"; + body += "
    "; + body += "
    "; + body += FPSTR(HTMLfooter); + webserver.send(200, "text/html", body); + } +} + +void Sys404() { + String body = returnHTMLheader(); + body += "

    ❗ ️ 404 - not found

    "; + body += FPSTR(HTMLfooter); + webserver.send(404, "text/html", body); +} + +void Syslogout() { + String body = returnHTMLheader(); + body += "

    you are logged out.

    "; + body += FPSTR(HTMLfooter); + + // TODO does not work atm + webserver.send(401, "text/html", body); +} + +void SysMaintenance() { + String body = returnHTMLheader(); + + if( (webserver.hasArg("on")) ) { + MaintenanceMode = true; + MaintenanceStarted = millis(); + body += "
    ⏸️ On for "; + body += MaintenanceDuration; + body += "s
    "; + } else if (webserver.hasArg("off")){ + MaintenanceMode = false; + body += "
    ⏸️ Off
    "; + } + + + body += "

    ⏸️ Maintenance Mode

    "; + body += "
    "; + body += "
    "; + + 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 += "

    Login failed.

    "; + * 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 += "

    🌱 "; + body += GrowName; + body += "

    "; + + // add gauge meter + body += FPSTR(HTMLgauge); + // and give javascript the values + // todo: auto refresh by api call + body += ""; + + body += "Grow started: "; + body += returnStrDateFromEpoch(GrowStart); + body += "
    \n"; + body += "Day of Grow: "; + body += DayOfGrow; + body += "
    \n"; + + if(UsePump == true) { + body += "Pump water level: "; + switch(getWaterlevel()) { + case 0: + body += "OK"; + break; + case 1: + body += "Warning"; + break; + case 2: + body += "Critical"; + break; + } + } + body += "
    \n"; + body += "Growlight brightness: "; + body += ((PinLEDPWM * 100) / 255); + body += " %
    \n"; + body += "
    \n"; + body += "MOSFET
    "; + + body += "On/Off:
    \n"; + + body += "Intensity:
    \n"; + + body += "\n"; + body += "

    \n"; + body += "
    "; + + + 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 += "

    Final step: Grow settings

    "; + body += "

    Please configure all settings
    "; + body += "

    "; + GrowStart = timeClient.getEpochTime(); + } + + body += "

    🔆 Grow settings

    "; + if(webserver.hasArg("success")) { + body += FPSTR(HTMLsuccess); + } + body += "

    Here you can set everything grow related, like light hours, how much water, LED brightness
    "; + body += "

    "; + + body += "
    \n"; + + + + body += "Grow name:
    \n"; + + + // the input field, which calls javascript convertDateToEpoch() to write data to transmit to id GrowStart + body += "Grow start date:
    \n"; + + body += "\n"; + + + + body += "Vegetation duration: Days
    \n"; + + body += "Bloom duration: Days
    \n"; + + body += "Time LED ON vegetation: Hours
    \n"; + + body += "Time LED ON bloom: Hours
    \n"; + + body += "Sunrise: \n"; + body += " :
    \n"; + + // SunFade bool + body += "Fade in/out sunrise/sunset?:
    \n"; + + body += "Fade duration: Minutes
    \n"; + + if(UseLEDrelais == false) { + body += "LED brightness: %
    \n"; + } + + if(UseFANrelais == false) { + body += "FAN speed: %
    \n"; + } + + body += "\n"; + body += "
    \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 += "

    Step 2: System settings

    "; + body += "

    Please configure all settings
    "; + body += "

    "; + } + + body += "

    ⚙ System settings

    "; + if(webserver.hasArg("success")) { + body += FPSTR(HTMLsuccess); + } + body += "

    here you can set which features and sensors you use
    "; + body += "

    "; + + // form starts + body += "
    \n"; + + // UseFan bool + body += "Use FAN:
    \n"; + + // UsePump bool + body += "Use PUMP:
    \n"; + + // UseLEDrelais bool + body += "Use relais for LED:
    \n"; + + // UseFANrelais bool + body += "Use relais for FAN:
    \n"; + + // TODO ugly. can this done be better? + // PumpOnTime int + body += "PUMP ON time: Seconds
    \n"; + + // MoistureSensor_Type byte + body += "Soilmoisture sensor:
    \n"; + + // SoilmoistureLow byte + body += "Soilmoisture low: %
    \n"; + + // TemperatureSensor_Type byte + body += "Temperature sensor:
    \n"; + + // NtpOffset int + body += "NTP offset: Hours
    \n"; + + body += "Maintenance Duration: Seconds
    \n"; + + body += "\n"; + body += "
    \n"; + + body += FPSTR(HTMLfooter); + + webserver.send(200, "text/html", body); + } +} + +void WEBwifiSettings() { + byte ssidsAvail = WiFi.scanNetworks(); + String body = returnHTMLheader("wifiSettings"); + if(FirstRun == true) { + body += "

    Welcome!

    "; + body += "

    CanGrow is actually unconfigured. You need to Setup your WiFi first down below.
    "; + body += "
    After you entered your WiFi connection details, you need to restart and are step closer to your grow 🥦"; + body += "
    "; + body += "

    "; + } + body += "

    📡 WiFi settings

    \n"; + + if(webserver.hasArg("success")) { + body += FPSTR(HTMLsuccess); + } + + if(FirstRun == false) { + body += "Current Settings:
    "; + body += "WiFi SSID: "; + body += WIFIssid; + body += "
    \n"; + body += "Use DHCP: "; + body += WIFIuseDHCP; + body += "
    \n"; + body += "IP address: "; + body += WiFi.localIP().toString(); + body += "
    \n"; + body += "Subnet mask: "; + body += WiFi.subnetMask().toString(); + body += "
    \n"; + body += "Gateway: "; + body += WiFi.gatewayIP().toString(); + body += "
    \n"; + body += "DNS: "; + body += WiFi.dnsIP().toString(); + body += "

    \n"; + } + + body += "

    Select your wifi network from the SSID list.
    To use DHCP leave IP, Subnet, Gateway and DNS fields blank!

    "; + body += "
    \n"; + body += "SSID:
    \n"; + body += "Password:
    \n"; + body += "IP:
    \n"; + body += "Subnet mask:
    \n"; + body += "Gateway:
    \n"; + body += "DNS:
    \n"; + body += "\n"; + body += "
    \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() { + + if(UseLEDrelais == true) { + // if a relais is used to turn on grow light, we force PWM to max val + PinLEDPWM = 255; + } else { + // otherwise just do PWM + PinLEDPWM = webserver.arg("PinLEDPWM").toInt(); + } + + if(UseFANrelais == true) { + // if a relais is used to turn on grow light, we force PWM to max val + PinFANPWM = 255; + } else { + // otherwise just do PWM + PinFANPWM = webserver.arg("PinFANPWM").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(); + + // 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); + EEPROM.put(217, SunFade); + EEPROM.put(218, SunFadeDuration); + + 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); + + webserver.sendHeader("Location", String("/growSettings?success"), true); + webserver.send(302, "text/plain", "growSettings/save: success!\n"); +} + + +void POSTsystemSettings() { + NtpOffset = webserver.arg("NtpOffset").toInt(); + MoistureSensor_Type = webserver.arg("MoistureSensor_Type").toInt(); + SoilmoistureLow = webserver.arg("SoilmoistureLow").toInt(); + UsePump = webserver.arg("UsePump").toInt(); + PumpOnTime = webserver.arg("PumpOnTime").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(); + + configured = true; + + // size is 1 byte + EEPROM.put(161, configured); + // size is 1 byte + EEPROM.put(162, UseFan); + // size is 1 byte + EEPROM.put(163, UsePump); + // size is 1 byte + EEPROM.put(164, PumpOnTime); + // size is 1 byte + EEPROM.put(165, MoistureSensor_Type); + // size is 1 byte + EEPROM.put(166, SoilmoistureLow); + // 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); + + // 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 + if(UseLEDrelais == true) { + PinLEDPWM = 255; + EEPROM.put(213, PinLEDPWM); + EEPROM.commit(); + Serial.println("UseLEDrelais is 1, forcing PinLEDPWM to max to prevent relais damage"); + } + + if(UseFANrelais == true) { + PinFANPWM = 255; + EEPROM.put(215, PinFANPWM); + EEPROM.commit(); + Serial.println("UseFANrelais is 1, forcing PinFANPWM to max to prevent relais damage"); + } + + Serial.print("configured: "); + Serial.println(configured); + Serial.print("UseFan: "); + Serial.println(UseFan); + Serial.print("UsePump: "); + Serial.println(UsePump); + Serial.print("PumpOnTime: "); + Serial.println(PumpOnTime); + Serial.print("MoistureSensor_Type: "); + Serial.println(MoistureSensor_Type); + Serial.print("SoilmoistureLow: "); + Serial.println(SoilmoistureLow); + 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"); + } + +} + + + +/* + * API section + * + */ + +// return as json all sensor readings +void APIgetSensors() { + + JsonDocument jsonSensors; + + JsonArray arraySoilmoisture = jsonSensors["soilmoisture"].to(); + arraySoilmoisture.add(getSoilmoisture(1)); + arraySoilmoisture.add(getSoilmoisture(2)); + JsonArray arrayTemperature = jsonSensors["temperature"].to(); + arrayTemperature.add(getTemperature(1)); + arrayTemperature.add(getTemperature(2)); + jsonSensors["humidity"] = getHumidity(); + jsonSensors["chirpLight"] = getLightchirp(); + + String body; + serializeJsonPretty(jsonSensors, 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); + // Maintenance mode + webserver.on("/system/maintenance", HTTP_GET, SysMaintenance); + // 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); + + // api stuff + webserver.on("/api/sensors", HTTP_GET, APIgetSensors); + + + // gauge meter stuff + webserver.on("/gauge.css", HTTP_GET, WEBgaugeCss); + webserver.on("/gauge.js", HTTP_GET, WEBgaugeJs); +}