CanGrow/Arduino/CanGrow/CanGrow.ino

2096 lines
57 KiB
C++

/*
* CanGrow - simply DIY automatic plant grow system (for cannabis).
*
*/
/*
* Includes
*
*/
// Libraries
// https://github.com/arduino/ArduinoCore-avr/tree/master/libraries/SPI
#include <SPI.h>
// https://github.com/arduino/ArduinoCore-avr/tree/master/libraries/Wire
#include <Wire.h>
// https://github.com/arduino/ArduinoCore-avr/tree/master/libraries/EEPROM
#include <EEPROM.h>
// https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WiFi
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
// https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer
#include <ESP8266WebServer.h>
// https://github.com/adafruit/Adafruit-GFX-Library
#include <Adafruit_GFX.h>
// https://github.com/adafruit/Adafruit_SSD1306
#include <Adafruit_SSD1306.h>
// https://github.com/adafruit/DHT-sensor-library
#include "DHT.h"
// https://github.com/bblanchon/ArduinoJson
#include <ArduinoJson.h>
// https://github.com/arduino-libraries/NTPClient
#include <NTPClient.h>
// https://github.com/PaulStoffregen/Time
#include <TimeLib.h>
/*
*
* 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()
// do we need a restart? (e.g. after wifi settings change)
bool NeedRestart;
bool FirstRun;
/*
*
* 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;
// Which temperature sensor to use?
byte TemperatureSensor_Type;
//
// 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;
// PINled_PWM - 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 PINled_PWM = 255;
/*
*
* NTP
*
*/
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);
/*
*
* Webserver
*
*/
ESP8266WebServer webserver(80);
/*
* HTML constants for header, footer, css, ...
*/
// Template: const char HTMLexamplepage[] PROGMEM = R"EOF()EOF";
const char HTMLheader[] PROGMEM = R"EOF(
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CanGrow</title>
<!-- <link rel="stylesheet" href="/style.css"> -->
<style>
/* Having the whole CSS here ensures it's all the time present*/
body {
color: #cae0d0;
background-color: #1d211e;
font-family: helvetica;
}
.center {
width: 60%; min-width: 420px;
margin: auto;
}
h1, h2, h3, h4, h5 {
text-align: center;
}
a:link, a:visited {
color: #04AA6D;
}
a:hover {
color: #64AA6D;
}
a:active {
color: #04AA6D;
}
.infomsg , .warnmsg {
color: #fff;
border-radius: 3px;
padding: 4px;
width: fit-content; min-width: 200px; max-width: 420px;
margin: auto;
margin-bottom: 5px;
font-weight: bold;
text-align: center;
text-decoration: none;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.5);
}
.infomsg {
background: #04AA6D;
}
.warnmsg {
background: #aa4204;
}
.inputShort {
width: 42px;
}
/* from https://gist.github.com/iamhelenliu/5755179 - thank you! */
.nav {
background: #333;
width: 60%; min-width: 420px;
margin: auto;
margin-bottom: 10px;
padding: 0;
position: relative;
border-radius: 3px;
}
.nav li {
display: inline-block;
list-style: none;
}
.nav li:first-of-type {
background: #026b45;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
.nav li a , .nav span {
color: #ddd;
display: block;
font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
font-size:0.8em;
padding: 10px 20px;
text-decoration: none;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.5);
}
.nav li a:hover , .activeMenu , .menuTime{
background: #04AA6D;
color: #fff;
border-radius: 3px;
}
.nav li a:active {
color: #cae0d0;
}
.activeMenu {
background: #444;
}
.MenuTime {
background: #292929;
}
</style>
<script>
function highlightActiveMenu(menuEntry) {
document.getElementById(menuEntry).classList.add("activeMenu");
}
function replaceStr(dst, content) {
document.getElementById(dst).innerHTML = content;
}
</script>
</head>
<body>
<ul class="nav">
<li><a id="MenuGrowName" href="/">&#x1F331; CanGrow</a></li>
<li><a id="MgrowSettings" href="/growSettings">Grow settings</a></li>
<li><a id="MsystemSettings" href="/systemSettings">System settings</a></li>
<li><a id="MwifiSettings" href="/wifiSettings">WiFi settings</a></li>
<li><a id="Mhelp" href="/help">Help</a></li>
<li><span id="MenuTime" class="MenuTime"></span></li>
<li><a id="CanGrowVer" href='https://git.la10cy.net/DeltaLima/CanGrow' target="_blank">CanGrow</a></li>
</ul>
<div class="center">
)EOF";
const char HTMLfooter[] PROGMEM = R"EOF(
</div>
</body>
</html>
)EOF";
/*
* I decided to embed CSS into HTMLheader because after
* a wifi change triggerd restart of the esp, the the style.css never get deliverd
* because the esp restarts after the webpage was delivered. style.css would be a second call
* but when the browser sends this, it's too late.
const char HTMLstyleCSS[] PROGMEM = R"EOF(
)EOF";
*/
const char HTMLsuccess[] PROGMEM = R"EOF(
<div class='infomsg'>Successfully saved!</div>
)EOF";
const char HTMLneedRestart[] PROGMEM = R"EOF(
<div class='warnmsg'>Restart is required to apply new WiFi settings!
<form action="/system/restart">
<input type="submit" value="Restart now" />
</form>
</div>
)EOF";
const char HTMLhelp[] PROGMEM = R"EOF(
<h1>CanGrow help</h1>
Here you will get some helpful help.
)EOF";
const char JSreplaceStr[] PROGMEM = R"EOF(
<script>
</script>
)EOF";
const char JSconvertDateToEpoch[] PROGMEM = R"EOF(
<script>
function convertDateToEpoch(src, dst) {
var valGrowStart = document.getElementById(src).value ;
document.getElementById(dst).value = new Date(valGrowStart).getTime() / 1000;
}
</script>
)EOF";
/*
*
* Pin assignments
*
* D0 - MOSFET Fan
* D1, D2 - I2C
* D3 - DHT11
* D4 - PIN_WIPE
* D5 - MOSFET Pump
* 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 PINfan = 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 PIN_WIPE = D4;
const uint8_t PINpump = D5;
const uint8_t PINled = D6; //
const uint8_t PINwaterlevel = D7;
const uint8_t PINsoilmoisture = D8;
const uint8_t PINanalog = A0;
/*
* millis timer
*
*/
unsigned long outputPrevTime = 0;
/*
* Status vars
*
*/
int D6status = false;
/* I2C Stuff
*
*/
#define WIRE Wire
/*
* DHT Stuff
*
*/
#define DHTTYPE DHT11
DHT dht(PINdht, DHTTYPE);
/*
* Display Stuff
*/
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &WIRE);
// 'CanGrow_Logo', 128x32px
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
};
/*
*
*
* Functions
*
*
*/
/*
* 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;
}
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(PIN_WIPE) == LOW ) {
// only show the Serial message once
if(wipeMsg == 0) {
Serial.println("Please release PIN_WIPE to erase all data saved in EEPROM");
Serial.println("LAST CHANCE TO KEEP THE DATA BY RESETTING NOW!!");
display.setCursor(0,36);
display.println("RELEASE PIN_WIPE");
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.setCursor(0,36);
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 PIN_WIPE internal LED to Output to give feedback WIPE
// was done
pinMode(PIN_WIPE, 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(PIN_WIPE, LOW);
} else {
digitalWrite(PIN_WIPE, HIGH);
}
delay(125);
}
ESP.restart();
}
bool loadEEPROM() {
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
// I forgot to add TemperatureSensor_Type to EEPROM and noticed it
// quite late in the process of programming. So this value residents
// in 214 and not directly after UseLEDrelais
EEPROM.get(214, TemperatureSensor_Type);
}
// 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);
// to ensure PINled_PWM always set to 255 when UseLEDrelais is true
// we set it here again, does not matter whats stored.
// size is 1 byte
if(UseLEDrelais == true) {
PINled_PWM = 255;
} else {
EEPROM.get(213, PINled_PWM);
}
}
// 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("SoilmoistureLow: ");
Serial.println(SoilmoistureLow);
Serial.print("ntpOffset: ");
Serial.println(ntpOffset);
Serial.print("UseLEDrelais: ");
Serial.println(UseLEDrelais);
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("PINled_PWM: ");
Serial.println(PINled_PWM);
} 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());
// TODO does not work atm, idk why
//Serial.println("The login credentials for the WebUI are 'cangrow' for username and password");
}
/*
* Setup
*
*/
void setup() {
// Start EEPROM
EEPROM.begin(512);
// setup pins
pinMode(PINfan, OUTPUT);
pinMode(PINdht, INPUT);
pinMode(PINwaterlevel, OUTPUT);
pinMode(PINsoilmoisture, OUTPUT);
pinMode(PINled, OUTPUT);
pinMode(PINpump, OUTPUT);
pinMode(PIN_WIPE, OUTPUT);
// set all OUTPUT to low
digitalWrite(PINfan, LOW);
digitalWrite(PINwaterlevel, LOW);
digitalWrite(PINsoilmoisture, LOW);
digitalWrite(PINled, LOW);
digitalWrite(PINpump, LOW);
// Start Serial
Serial.begin(115200);
// Write a line before doing serious output, because before there is some garbage in serial
// whats get the cursor somewhere over the place
// output
Serial.println("420");
Serial.println(".:: CanGrow Start ::.");
// initialise Wire for I2C
Wire.begin();
// initialise I2C display
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Address 0x3C for 128x32
display.clearDisplay();
display.display();
// set display settings
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
// display Logo
display.drawBitmap(0, 0, bmpCanGrow_Logo, 128, 32, WHITE);
display.display();
// reset chirp
writeI2CRegister8bit(0x20, 6); //TODO: Do only, when configured
// initialise DHT11
dht.begin(); //TODO: Do only, when configured
Serial.println("To wipe the EEPROM saved data, set D4 (PIN_WIPE) to LOW - NOW! (2 seconds left)");
// wait a few seconds to let the user pull D4 down to wipe EEPROM
// and we can enjoy the boot screen meanwhile :p
// meanwhile blink with the led onboad :)
// 333 * 6 =~ 2 seconds
display.fillRect(0,36,128,64-36, 0);
display.setCursor(0,36);
display.println("To wipe EEPROM pull");
display.println("D4 (PIN_WIPE) to GND");
display.display();
for(byte i = 0; i <= 6 ; i++) {
if(i % 2) {
digitalWrite(PIN_WIPE, LOW);
} else {
digitalWrite(PIN_WIPE, HIGH);
}
delay(333);
}
// set back to HIGH because thats the default
digitalWrite(PIN_WIPE, HIGH);
//delay(2000);
// read status from PIN_WIPE to WIPE
// when PIN_WIPE is set to LOW, wipe EEPROM
if(digitalRead(PIN_WIPE) == LOW) {
// wipe EEPROM
wipeEEPROM();
}
/*
* load EEPROM and Setup WiFi
*
* call loadEEPROM() which returns a bool
* When true, CanGrow is already configured and EEPROM values are applied
* When false, CanGrow is unconfigured and we need to run the setup assistant
*/
// load stored values from EEPROM and check what var configured is returned
if(loadEEPROM()) {
// connect to wifi
wifiConnect();
// configured is 0, setup Access Point
} else {
// start an wifi accesspoint
wifiAp();
}
// set web handler
WebHandler();
// start webserver
webserver.begin();
}
/*
*
*
* Loop
*
*
*/
void loop() {
// var definition
unsigned int secondsSunrise = (SunriseHour * 60 * 60) + (SunriseMinute * 60);
unsigned int secondsToday = (timeClient.getHours() * 60 * 60) + (timeClient.getMinutes() * 60) + timeClient.getSeconds();
unsigned long currentRuntime = millis();
byte lightHours;
// first we call webserver handle client
webserver.handleClient();
// do every second when everything is configured
if( (configured == true) && (strlen(GrowName) > 0) && (currentRuntime - outputPrevTime >= 1000) ){
// debug output
Serial.print("secondsSunrise: ");
Serial.println(secondsSunrise);
Serial.print("secondsToday: ");
Serial.println(secondsToday);
//Serial.println("yolo");
// calculate acutal DayOfGrow
DayOfGrow = int(ceil(float((timeClient.getEpochTime() - GrowStart) / 60 / 60 / 24)));
// decide if we are in Veg or Bloom phase of grow
// when DayOfGrow is larger then DaysVeg we must be in Bloom
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) ) )){
// turn on light
// TODO write a LED control function which takes also takes care of UseLEDrelais
analogWrite(PINled, PINled_PWM);
} else {
// turn off
digitalWrite(PINled, LOW);
}
outputPrevTime = currentRuntime;
}
//delay(1000);
}
/*
* 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);
// 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(WEB404);
// switching MOSFETs
webserver.on("/switch", HTTP_POST, POSTswitchMOSFET);
// api stuff
webserver.on("/api/sensors", HTTP_GET, APIgetSensors);
}
/*
*
* return functions, they return things for the web stuff
*/
// returns footer with javascript stuff
String returnHTMLfooter(String MenuEntry = "") {
String footer;
// print actual time in header
footer += "<script>replaceStr('CanGrowVer', 'CanGrow v";
footer += CanGrowVer;
footer += "');\n";
footer += "replaceStr('MenuTime', '";
footer += timeClient.getFormattedTime();
footer += "');\n";
footer += "highlightActiveMenu('M";
footer += MenuEntry;
footer += "');";
footer += "</script>";
// show the GrowName in the menu if set
if(strlen(GrowName) > 0){
footer += "<script>replaceStr('MenuGrowName', '&#x1F331; ";
footer += GrowName;
footer += "');\n";
// include the GrowName in the title
footer += "document.title = 'CanGrow - ";
footer += GrowName;
footer += "';";
footer += "</script>";
}
footer += FPSTR(HTMLfooter);
return footer;
}
/*
* returnSelected(bool)
* returns char[] "selected" if bool is true
* useful for html forms, to preset 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;
}
void SysRestart() {
String body = FPSTR(HTMLheader);
// 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 += returnHTMLfooter();
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 += returnHTMLfooter();
webserver.send(200, "text/html", body);
}
}
void SysWipe() {
String body = FPSTR(HTMLheader);
// TODO only debug and development solution, remove this later
if(webserver.hasArg("confirmed")) {
body += "<div class='warnmsg'><h2>!! Wiping CanGrow's EEPROM !!</h2><br>Device will restart in a few seconds.</div>";
body += returnHTMLfooter();
webserver.send(200, "text/html", body);
wipeEEPROM();
} else {
body += "<h1>Wipeing EEPROM</h1>";
body += "<div class='warnmsg'>All settings will be removed!!<br>";
body += "<br>Please confirm wiping the EEPROM";
body += "<form action='/system/wipe'><input type='hidden' name='confirmed' value='true' /><input type='submit' value='Confirm wiping' /></form>";
body += "</div>";
body += returnHTMLfooter();
webserver.send(200, "text/html", body);
}
}
void WebAuth() {
/*
* TODO
* DOES NOT WORK WHEN CONNECTED TO EXISTING WIFI
* IDK WHY
*
*/
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);
}
}
/*
*
* Web pages
*
*/
// not really a webpage, but CSS is important for them :p
/*
void WEBstyleCSS() {
webserver.send(200, "text/css", HTMLstyleCSS);
}
*/
void WEB404() {
String body = FPSTR(HTMLheader);
body += "<div class='warnmsg'><h1>404 - not found</h1></div>";
body += returnHTMLfooter();
webserver.send(404, "text/html", body);
}
void WEBlogout() {
String body = FPSTR(HTMLheader);
body += "<h1>you are logged out.</h1>";
body += FPSTR(HTMLfooter);
// TODO does not work atm
webserver.send(401, "text/html", body);
}
void WEBhelp() {
String body = FPSTR(HTMLheader);
body += FPSTR(HTMLhelp);
body += returnHTMLfooter("help");
webserver.send(200, "text/html", body);
}
/*
* Root pages
*/
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 = FPSTR(HTMLheader);
body += "<h1>&#x1F331; ";
body += GrowName;
body += "</h1>";
body += "Grow started: ";
body += returnStrDateFromEpoch(GrowStart);
body += "<br>";
body += "Day of Grow: ";
body += DayOfGrow;
body += "<br>";
body += "Soil Moisture: ";
body += getSoilmoisture(MoistureSensor_Type);
body += " %<br>";
body += "Humidity: ";
body += getHumidity();
body += " %<br>";
body += "Temperature: ";
body += getTemperature(TemperatureSensor_Type);
body += " °C<br>";
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 += "<br>";
body += "Growlight brightnes: ";
body += ((PINled_PWM * 100) / 255);
body += " %<br>";
body += "<form method='post' action='/switch'>";
body += "MOSFET<select id='output' name='output' required>\n";
body += "<option disabled value='' selected hidden>---</option>\n";
body += "<option value='1'>LED</option>\n";
body += "<option value='2'>PUMP</option>\n";
body += "<option value='3'>FAN</option>\n";
body += "</select><br>";
body += "On/Off: <select id='state' name='state' required>\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 += "<input type='submit' value='Save'>\n";
body += "</form>";
body += returnHTMLfooter();
webserver.send(200, "text/html", body);
}
}
/*
* Config pages
*/
void WEBwifiSettings() {
byte ssidsAvail = WiFi.scanNetworks();
String body = FPSTR(HTMLheader);
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 &#129382;";
body += "<br>";
body += "</p>";
}
body += "<h2>WiFi config</h2>\n";
if(NeedRestart == true) {
body += FPSTR(HTMLneedRestart);
}
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>\n";
body += "<input type='submit' value='Save'>\n";
body += "</form>\n";
body += returnHTMLfooter("wifiSettings");
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 = FPSTR(HTMLheader);
if(configured == false) {
body += "<h1>Step 2: System settings</h1>";
body += "<p>Please configure all settings<br>";
body += "</p>";
}
body += "<h2>System settings</h2>";
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 += "Use fan: <select id='UseFan' name='UseFan' required>\n";
if(configured == false){body += "<option disabled value='' selected hidden>---</option>\n";}
body += "<option value='1'" + returnStrSelected(UseFan, 1) + ">Yes</option>\n";
body += "<option value='0'" + returnStrSelected(UseFan, 0) + ">No</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";
// UseLEDrelais bool
body += "Use relais for LED: <select id='UseLEDrelais' name='UseLEDrelais' required>\n";
if(configured == false){body += "<option disabled value='' selected hidden>---</option>\n";}
body += "<option value='1'" + returnStrSelected(UseLEDrelais, 1) + ">Yes</option>\n";
body += "<option value='0'" + returnStrSelected(UseLEDrelais, 0) + ">No</option>\n";
body += "</select><br>\n";
// TODO ugly. can this done be better?
// PumpOnTime int
body += "Pump on time: <input type='number' name='PumpOnTime' min='0' max='255' value='";
body += PumpOnTime;
body += "'required><br>\n";
// MoistureSensor_Type byte
body += "Moisture sensor type: <select id='MoistureSensor_Type' name='MoistureSensor_Type' 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</option>\n";
body += "</select><br>\n";
// SoilmoistureLow byte
body += "Soil moisture low: <input type='number' name='SoilmoistureLow' min='0' value='";
body += SoilmoistureLow;
body += "' required><br>\n";
// TemperatureSensor_Type byte
body += "Temperature sensor type: <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) + ">DHT11/22</option>\n";
body += "<option value='2'" + returnStrSelected(TemperatureSensor_Type, 2) + ">I2C chirp</option>\n";
body += "</select><br>\n";
// ntpOffset int
body += "NTP offset: <input type='number' name='ntpOffset' min='-12' max='14' value='";
body += ntpOffset;
body+= "' required><br>\n";
body += "<input type='submit' value='Save'>\n";
body += "</form>\n";
body += returnHTMLfooter("systemSettings");
webserver.send(200, "text/html", body);
}
}
/*
* Grow pages
*/
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 = FPSTR(HTMLheader);
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='32' value='";
body += GrowName;
body+= "' required><br>\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>\n";
body += "<input type='hidden' id='GrowStart' name='GrowStart' value='";
body += GrowStart;
body+= "' required>\n";
body += "Days of vegetation: <input type='number' name='DaysVeg' min='0' max='255' value='";
body += DaysVeg;
body+= "' required><br>\n";
body += "Days of bloom: <input type='number' name='DaysBloom' min='0' max='255' value='";
body += DaysBloom;
body+= "' required><br>\n";
body += "Hours light on vegetation: <input type='number' name='LighthoursVeg' min='0' max='255' value='";
body += LighthoursVeg;
body+= "' required><br>\n";
body += "Hours light on bloom: <input type='number' name='LighthoursBloom' min='0' max='255' value='";
body += LighthoursBloom;
body+= "' required><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";
if(UseLEDrelais == false) {
body += "Brightness LED: <input type='range' id='PINled_PWM' name='PINled_PWM' min='1' max='255' value='";
body += PINled_PWM;
body += "'/><br>\n";
}
body += "<input type='submit' value='Save'>\n";
body += "</form>\n";
body += FPSTR(JSconvertDateToEpoch);
body += returnHTMLfooter("growSettings");
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
PINled_PWM = 255;
} else {
// otherwise just do PWM
PINled_PWM = webserver.arg("PINled_PWM").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();
// 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, PINled_PWM);
EEPROM.commit();
//analogWrite(PINled, PINled_PWM);
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("PINled_PWM: ");
Serial.println(PINled_PWM);
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();
TemperatureSensor_Type = webserver.arg("TemperatureSensor_Type").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);
// 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 PINled_PWM to 255
// to ensure nothing bad happens
if(UseLEDrelais == true) {
PINled_PWM = 255;
EEPROM.put(213, PINled_PWM);
EEPROM.commit();
Serial.println("UseLEDrelais is 1, forcing PINled_PWM 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("TemperatureSensor_Type: ");
Serial.println(TemperatureSensor_Type);
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 POSTswitchMOSFET() {
byte MosfetState = webserver.arg("state").toInt();
byte MosfetNr = webserver.arg("output").toInt();
Serial.println(":: GETswitchMOSFET ::");
Serial.print("MosfetState: ");
Serial.println(MosfetState);
Serial.print("MosfetNr: ");
Serial.println(MosfetNr);
if((MosfetState > 1) || (MosfetState < 0)) {
webserver.send(400, "text/plain", "not valid\n");
} else {
switch(MosfetNr) {
case 1:
if( MosfetState == 1) {
analogWrite(PINled, PINled_PWM);
} else {
digitalWrite(PINled, MosfetState);
}
break;
case 2:
digitalWrite(PINpump, MosfetState);
break;
case 3:
digitalWrite(PINfan, MosfetState);
break;
default:
webserver.send(400, "text/plain", "not valid\n");
break;
}
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<JsonArray>();
arraySoilmoisture.add(getSoilmoisture(1));
arraySoilmoisture.add(getSoilmoisture(2));
JsonArray arrayTemperature = jsonSensors["temperature"].to<JsonArray>();
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);
}
/*
*
*
* PLAYGROUND / TRASH
*
*
*/
/*
unsigned long currentTime = millis();
int valSoilmoisture0 = getSoilmoisture(0);
int valSoilmoisture1 = getSoilmoisture(1);
float valTemperature0 = getTemperature(0);
float valTemperature1 = getTemperature(1);
float valHumidity = getHumidity();
int valWaterlevel = getWaterlevel();
switch(valWaterlevel) {
case 0:
digitalWrite(PINled, HIGH);
digitalWrite(PINpump, LOW);
digitalWrite(PINfan, LOW);
break;
case 1:
digitalWrite(PINled, LOW);
digitalWrite(PINpump, HIGH);
digitalWrite(PINfan, LOW);
break;
case 2:
digitalWrite(PINled, LOW);
digitalWrite(PINpump, LOW);
digitalWrite(PINfan, HIGH);
break;
}
// OUTPUT
if(currentTime - outputPrevTime >= 1000) {
// set display cursor to top left
display.setCursor(0,0);
// display text
display.print("I2C: ");
display.print(valSoilmoisture1);
display.print(", ");
display.println(valTemperature1);
Serial.print("I2C: ");
Serial.print(valSoilmoisture1);
Serial.print(", ");
Serial.println(valTemperature1);
display.print("DHT11: ");
display.print(valTemperature0);
display.print(", ");
display.println(valHumidity);
Serial.print("DHT11: ");
Serial.print(valTemperature0);
Serial.print(", ");
Serial.println(valHumidity);
display.print("Water Status: ");
display.println(valWaterlevel);
Serial.print("Water Status: ");
Serial.println(valWaterlevel);
display.print("ASM: ");
display.print(valSoilmoisture0);
display.println(", ");
Serial.print("ASM: ");
Serial.println(valSoilmoisture0);
// print everything on the display
display.display();
Serial.println("Test");
outputPrevTime = currentTime;
*/
/* if(D6status == true) {
digitalWrite(PINled, LOW);
digitalWrite(PINpump, LOW);
digitalWrite(PINfan, LOW);
D6status = false;
Serial.println("D6 is off now");
} else {
digitalWrite(PINled, HIGH);
digitalWrite(PINpump, HIGH);
digitalWrite(PINfan, HIGH);
D6status = true;
Serial.println("D6 is ON now");
}
*/
/*
for(int dutyCycle = 0; dutyCycle < 255; dutyCycle++){
// changing the LED brightness with PWM
analogWrite(PINled, dutyCycle);
delay(1);
}
// decrease the LED brightness
for(int dutyCycle = 255; dutyCycle > 0; dutyCycle--){
// changing the LED brightness with PWM
analogWrite(PINled, dutyCycle);
delay(1);
}
*/