cleanup old v0.1 stuff, lets begin from scratch
This commit is contained in:
parent
1c0beb58aa
commit
a6f5a6539b
8 changed files with 9 additions and 3324 deletions
|
@ -1,419 +1,17 @@
|
|||
/*
|
||||
* 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>
|
||||
// OTA update
|
||||
#include <ESP8266HTTPUpdateServer.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/Adafruit_BME280_Library/
|
||||
#include <Adafruit_Sensor.h>
|
||||
#include <Adafruit_BME280.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>
|
||||
// DHT support dropped
|
||||
// https://github.com/adafruit/DHT-sensor-library
|
||||
// #include "DHT.h"
|
||||
|
||||
/*
|
||||
* CanGrow header files
|
||||
*/
|
||||
|
||||
#include "CanGrow_PinAssignments.h"
|
||||
#include "CanGrow_Init.h"
|
||||
#include "CanGrow_Logo.h"
|
||||
#include "CanGrow_Sensors.h"
|
||||
|
||||
#include "CanGrow_Version.h"
|
||||
#include "CanGrow_HTML.h"
|
||||
#include "CanGrow_SysFunctions.h"
|
||||
#include "CanGrow_WebFunctions.h"
|
||||
|
||||
|
||||
/*
|
||||
* Setup
|
||||
*
|
||||
*/
|
||||
void setup() {
|
||||
|
||||
// setup pins
|
||||
pinMode(PinFAN, OUTPUT);
|
||||
//pinMode(PINdht, INPUT);
|
||||
pinMode(PINwaterlevel, OUTPUT);
|
||||
pinMode(PINsoilmoisture, OUTPUT);
|
||||
pinMode(PinLED, OUTPUT);
|
||||
pinMode(PinPUMP, OUTPUT);
|
||||
pinMode(PinWIPE, OUTPUT);
|
||||
|
||||
|
||||
// set all OUTPUT to low
|
||||
digitalWrite(PinFAN, HIGH);
|
||||
digitalWrite(PINwaterlevel, LOW);
|
||||
digitalWrite(PinLED, HIGH);
|
||||
digitalWrite(PinPUMP, HIGH);
|
||||
// except PINsoilmoisture
|
||||
// PINsoilmoisture is always HIGH and gets LOW in moment of waterlevel measurement
|
||||
digitalWrite(PINsoilmoisture, LOW);
|
||||
|
||||
// set PWM frequency to 13.37KHz
|
||||
analogWriteFreq(13370);
|
||||
|
||||
// Start EEPROM
|
||||
EEPROM.begin(512);
|
||||
|
||||
// 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
|
||||
Serial.println("420");
|
||||
Serial.print(".:: CanGrow firmware v");
|
||||
Serial.print(CanGrowVer);
|
||||
Serial.print(" build ");
|
||||
Serial.print(CanGrowBuild);
|
||||
Serial.println(" starting ::.");
|
||||
|
||||
Serial.println(":: initialise I2C ::");
|
||||
// initialise Wire for I2C
|
||||
Wire.begin();
|
||||
|
||||
Serial.println(":: initialise display ::");
|
||||
// initialise I2C display
|
||||
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Address 0x3C for 128x64
|
||||
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();
|
||||
|
||||
Serial.println(":: initialise chirp sensor if present ::");
|
||||
// reset chirp
|
||||
writeI2CRegister8bit(0x20, 6); //TODO: Do only, when configured
|
||||
|
||||
// initialise DHT11
|
||||
// dht support dropped
|
||||
// dht.begin(); //TODO: Do only, when configured
|
||||
|
||||
// initialise BME280
|
||||
Serial.println(":: initialise BME280 sensor ::");
|
||||
// ToDo: let the user configure somewhere the ID of the BME280 sensor
|
||||
if(!bme.begin(0x76)) {
|
||||
Serial.println("!! Cannot find BME280 on I2C bus. Please check connection or ID");
|
||||
}
|
||||
|
||||
Serial.println("To wipe the EEPROM saved data, set D4 (PinWIPE) 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 (PinWIPE) to GND");
|
||||
display.display();
|
||||
|
||||
// blink with the onboard LED on D4 (PinWIPE)
|
||||
for(byte i = 0; i <= 6 ; i++) {
|
||||
if(i % 2) {
|
||||
digitalWrite(PinWIPE, LOW);
|
||||
} else {
|
||||
digitalWrite(PinWIPE, HIGH);
|
||||
}
|
||||
delay(333);
|
||||
}
|
||||
// set back to HIGH because thats the default
|
||||
digitalWrite(PinWIPE, HIGH);
|
||||
//delay(2000);
|
||||
|
||||
// read status from PinWIPE to WIPE
|
||||
// when PinWIPE is set to LOW, wipe EEPROM
|
||||
if(digitalRead(PinWIPE) == 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();
|
||||
|
||||
Serial.println(".:: CanGrow Ready ::.");
|
||||
delay(1000);
|
||||
if(strlen(GrowName) > 0 ) {
|
||||
display.clearDisplay();
|
||||
display.display();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
* Loop
|
||||
* CanGrow - an OpenSource growcontroller firmware (for cannabis)
|
||||
*
|
||||
*
|
||||
*/
|
||||
void loop() {
|
||||
// var definition
|
||||
unsigned long currentRuntime = millis();
|
||||
|
||||
|
||||
// first we call webserver handle client
|
||||
webserver.handleClient();
|
||||
|
||||
|
||||
// do every second when everything is configured and grow is started
|
||||
if( (configured == true) && (strlen(GrowName) > 0) && (currentRuntime - outputPrevTime >= 1000) ){
|
||||
|
||||
// refresh all sensor values
|
||||
refreshSensors();
|
||||
|
||||
// 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
|
||||
|
||||
// set the actual state of the Grow LED
|
||||
// when being in Maintenance Mode and UseRelaisLED not true,
|
||||
// dimm the light
|
||||
if(MaintenanceMode == true) {
|
||||
if((currentRuntime - MaintenanceStarted <= MaintenanceDuration * 1000 ) && (UseLEDrelais == false)) {
|
||||
// in case of being in Maintenance Mode , dimm the grow light when not a relais is used
|
||||
setOutput(1, 15);
|
||||
} else {
|
||||
MaintenanceMode = false;
|
||||
}
|
||||
} else {
|
||||
controlLED();
|
||||
}
|
||||
|
||||
controlPUMP();
|
||||
|
||||
displayScreens();
|
||||
|
||||
// current time gets previous time for new interval
|
||||
outputPrevTime = currentRuntime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* TODO LIST / NOTES
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*
|
||||
* - when PWM for fan is set, set fan speed to regulate humidity and
|
||||
* temperature, depending on which phase of grow the plant is
|
||||
* (https://www.royalqueenseeds.de/blog-cannabisanbau-im-grow-room-relative-luftfeuchtigkeit-und-temperaturen-n243)
|
||||
* - re-organize EEPROM saved values.
|
||||
* - prevent GrowStart to be in the future
|
||||
* - maybe let the user configure some screens to display.
|
||||
* - put EEPROM adresses into DEFINEs
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Fan control
|
||||
*
|
||||
* Vars:
|
||||
* - FanVent (byte) Fan1 or Fan2
|
||||
* - FanExhaust (byte) Fan1 or Fan2
|
||||
* -
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
@ -1,525 +0,0 @@
|
|||
|
||||
/*
|
||||
* 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(
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||
)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(
|
||||
<link rel='icon' href='data:;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAABcElEQVQ4y42TzU/bQBDFf7Nx1qGuAYVgQSuo2khBggPhyIH//9AiJAQ9tEeLqCKiUD6sxF52OMSEBCdW57aa9968fTsr3V5XWVLPO6sANNL7ZRAMNeU6Ea4T1UEI6pr55kcAwhpMrYOpk2/r/yEQmKWkIonf+TZVgex4Fw0bIEtIAALF3gbZ8U5VwKa3PJ18JT9IpiLvyflBwuhLG5veVUM0/0aoCONPa2hQjWZ8uEVeupJnXSBwO8YOH8iTeAKc2Q4Xt2C1VZL93F7MjbK/bxDnp5Zn7b+So+9pdQ+K/Q5qJlrRj5Ts6DM+rK7Ih7Mr3HaM7jYQVZqXQ6Tb6yqBYdTfomhHiFfUyMI3f+01/z7RHNzTGDyWGThP63SA2d8EEfIkrgQpzmOvH0AV+3M4zegNpUwagAYG8Yp4BS0nl4Kz5Mpf0JXJMby6w/66Aa+M+9uE53/Iexsggq4ESOYWC0jmsBfX8xdXhcJjL4cLc3kBl8uJGQ/CrpAAAAAASUVORK5CYII='>
|
||||
<style>
|
||||
body {
|
||||
color: #cae0d0;
|
||||
background-color: #1d211e;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.center {
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.centered {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: 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;
|
||||
}
|
||||
|
||||
.helpbox {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.nav {
|
||||
background: #333;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.subnav {
|
||||
text-align: center;
|
||||
display: table;
|
||||
margin: auto;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.nav li {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.subnav li {
|
||||
background: #026b45;
|
||||
list-style: none;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.nav li:first-of-type {
|
||||
background: #026b45;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
.nav li a, .nav span, .subnav li a, .subnav span, .button, .button:link, input[type=button], input[type=submit], input[type=reset] {
|
||||
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, .subnav li a:hover, .activeMenu, .button:link:hover, .button:visited:hover, input[type=button]:hover, input[type=submit]:hover, input[type=reset]:hover {
|
||||
background: #04AA6D;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.nav li a:active, .subnav li a:active {
|
||||
background: #026b45;
|
||||
color: #cae0d0;
|
||||
}
|
||||
|
||||
.activeMenu {
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.MenuTime {
|
||||
background: #292929;
|
||||
}
|
||||
|
||||
.button, .button:link, .button:visited, input[type=button], input[type=submit], input[type=reset] {
|
||||
background: #026b45;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
padding: 6px 12px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.button:link:active, .button:visited:active, input[type=button]:active, input[type=submit]:active, input[type=reset]:active {
|
||||
background: #026b45;
|
||||
color: #cae0d0;
|
||||
}
|
||||
|
||||
input[type=text], input[type=date], input[type=number], input[type=password], select {
|
||||
background: #cae0d0;
|
||||
color: #1d211e;
|
||||
border: 1px solid #026b45;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1820px) {
|
||||
.center, .nav {
|
||||
width: 60%; min-width: 420px;
|
||||
}
|
||||
.subnav li {
|
||||
display: '';
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 640px) {
|
||||
.subnav li {
|
||||
display: inline-block;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ul class='nav'>)EOF";
|
||||
// here comes the menu as unordered List in returnHTMLheader()
|
||||
|
||||
|
||||
const char HTMLfooter[] PROGMEM = R"EOF(
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)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(
|
||||
<h2>❓ Help</h2>
|
||||
Here you will get some helpful help.
|
||||
<h3>API</h3>
|
||||
<a href='/api/sensors' target='_blank'>Sensor data</a>: <code>GET /api/sensors</code><br>
|
||||
<a href='/api/debug' target='_blank'>Debug all data:</a> <code>GET /api/debug</code>
|
||||
)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";
|
||||
|
||||
// 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; }
|
||||
.gaugeLabel { text-align: center; }
|
||||
|
||||
|
||||
@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(
|
||||
|
||||
<div class='gaugeWrapper'>
|
||||
<div class='gauge gauge--liveupdate spacer' id='gaugeTemperature' style='float:left; margin-right: 10px;'>
|
||||
<div class='gaugeLabel'>Temperature</div>
|
||||
<div class='gauge__container'>
|
||||
<div class='gauge__background'></div>
|
||||
<div class='gauge__center'></div>
|
||||
<div class='gauge__data'></div>
|
||||
<div class='gauge__needle'></div>
|
||||
</div>
|
||||
<div class='gauge__labels mdl-typography__headline'>
|
||||
<span class='gauge__label--low'></span>
|
||||
<span class='gauge__label--spacer'></span></span>
|
||||
<span class='gauge__label--high'></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='gauge gauge--liveupdate spacer' id='gaugeHumidity' style='float:left; margin-right: 10px;'>
|
||||
<div class='gaugeLabel'>Humidity</div>
|
||||
<div class='gauge__container'>
|
||||
<div class='gauge__background'></div>
|
||||
<div class='gauge__center'></div>
|
||||
<div class='gauge__data'></div>
|
||||
<div class='gauge__needle'></div>
|
||||
</div>
|
||||
<div class='gauge__labels mdl-typography__headline'>
|
||||
<span class='gauge__label--low'></span>
|
||||
<span class='gauge__label--spacer'></span>
|
||||
<span class='gauge__label--high'></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='gauge gauge--liveupdate' id='gaugeSoilmoisture' style='float:left;'>
|
||||
<div class='gaugeLabel'>Soilmoisture</div>
|
||||
<div class='gauge__container'>
|
||||
<div class='gauge__background'></div>
|
||||
<div class='gauge__center'></div>
|
||||
<div class='gauge__data'></div>
|
||||
<div class='gauge__needle'></div>
|
||||
</div>
|
||||
<div class='gauge__labels mdl-typography__headline'>
|
||||
<span class='gauge__label--low'></span>
|
||||
<span class='gauge__label--spacer'></span>
|
||||
<span class='gauge__label--high'></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script src='gauge.js'></script>
|
||||
<script>
|
||||
var gaugeTemperature = new Gauge(document.getElementById('gaugeTemperature'));
|
||||
var gaugeHumidity = new Gauge(document.getElementById('gaugeHumidity'));
|
||||
var gaugeSoilmoisture = new Gauge(document.getElementById('gaugeSoilmoisture'));
|
||||
</script>
|
||||
|
||||
)EOF";
|
||||
|
||||
|
||||
const char HTMLupdate[] PROGMEM = R"EOF(
|
||||
<p>You find the latest CanGrow firmware version on the <a href='https://git.la10cy.net/DeltaLima/CanGrow/releases' target='_blank'>release page</a> of the git repository.</p>
|
||||
<form method='POST' action='/system/applyUpdate' enctype='multipart/form-data' onsubmit="document.getElementById('divUploading').style.display = '';">
|
||||
<b>Select .bin file:</b><br>
|
||||
<input type='file' accept='.bin,.bin.gz' name='firmware' required>
|
||||
<input type='submit' value='Update Firmware'>
|
||||
</form>
|
||||
<div id='divUploading' style='display: none;' class='warnmsg'>🛜 Uploading, please wait...<div>
|
||||
)EOF";
|
||||
|
||||
const char HTMLsystemSubNav[] PROGMEM = R"EOF(
|
||||
<ul class='subnav'>
|
||||
<li><a href='/system/update'>🔄 Firmware update</a></li>
|
||||
<li><a href='/system/restart' >🔁 CanGrow restart</a></li>
|
||||
<li><a href='/system/wipe' >💣 Factory reset</a></li>
|
||||
</ul>
|
||||
)EOF";
|
|
@ -1,178 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Constants
|
||||
*
|
||||
*/
|
||||
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()
|
||||
unsigned short 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;
|
||||
|
||||
// helper variable to remember how many seconds the pump was
|
||||
// already on within the actual watering cycle
|
||||
byte PumpOnTimePassed = 0;
|
||||
bool PumpOnManual = false;
|
||||
|
||||
// helper variable for pump control with soilmoisture
|
||||
// average of last readings
|
||||
unsigned short valSoilmoistureAvg = 0;
|
||||
unsigned short valSoilmoistureAvg_tmp = 0;
|
||||
byte valSoilmoistureAvg_count = 0;
|
||||
//unsigned short
|
||||
|
||||
/*
|
||||
* 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 = 80;
|
||||
// UsePump - is the pump used? bool
|
||||
// PumpMode (short) 1: Pump on every n days, 2: Pump on when Soilmoisture <= SoilmoistureLow, 3: Both
|
||||
byte UsePump;
|
||||
// UseFan - is the fan used? bool
|
||||
// PumpOnTime in seconds
|
||||
byte PumpOnTime = 3;
|
||||
byte 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 = 300;
|
||||
char Esp32CamIP[16];
|
||||
// PumpLastOn (long) timestamp
|
||||
unsigned long PumpLastOn;
|
||||
|
||||
|
||||
|
||||
//
|
||||
// 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;
|
||||
|
||||
// PumpIntervalVeg (int) in days
|
||||
byte PumpIntervalVeg = 5;
|
||||
byte PumpIntervalBloom = 3;
|
||||
|
||||
/*
|
||||
*
|
||||
* NTP
|
||||
*
|
||||
*/
|
||||
|
||||
WiFiUDP ntpUDP;
|
||||
NTPClient timeClient(ntpUDP);
|
||||
|
||||
/*
|
||||
*
|
||||
* Webserver
|
||||
*
|
||||
*/
|
||||
|
||||
ESP8266WebServer webserver(80);
|
||||
ESP8266HTTPUpdateServer webUpdater;
|
||||
|
||||
/* I2C Stuff
|
||||
*
|
||||
*/
|
||||
#define WIRE Wire
|
||||
|
||||
/*
|
||||
* Display Stuff
|
||||
*/
|
||||
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &WIRE);
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* 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;
|
|
@ -1,205 +0,0 @@
|
|||
/*
|
||||
* DHT Stuff
|
||||
*
|
||||
*/
|
||||
// DHT support dropped to get a free pin for fan PWM
|
||||
//#define DHTTYPE DHT11
|
||||
//DHT dht(PINdht, DHTTYPE);
|
||||
|
||||
/*
|
||||
* BME280 Stuff
|
||||
*
|
||||
*/
|
||||
|
||||
#define SEALEVELPRESSURE_HPA (1013.25)
|
||||
Adafruit_BME280 bme;
|
||||
|
||||
|
||||
/*
|
||||
* 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(bool returnRAW = false) {
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
// disable first PINsoilmoisture
|
||||
digitalWrite(PINsoilmoisture, LOW);
|
||||
// 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
|
||||
|
||||
for(byte i = 0; i < 10 ; i++) {
|
||||
waterlevelRAW = waterlevelRAW + analogRead(PINanalog);
|
||||
}
|
||||
waterlevelRAW = waterlevelRAW / 10;
|
||||
|
||||
// disable Vcc for the sensor to prevent electrolysis effect and release analog pin
|
||||
digitalWrite(PINwaterlevel, LOW);
|
||||
// and turn soilmoisture back on
|
||||
digitalWrite(PINsoilmoisture, HIGH);
|
||||
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 BME280
|
||||
temperature = bme.readTemperature();
|
||||
// read temperature from DHT11
|
||||
// dht support dropped
|
||||
// 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() {
|
||||
// dht support dropped
|
||||
// return dht.readHumidity();
|
||||
return bme.readHumidity();
|
||||
}
|
||||
|
||||
int getSoilmoisture(byte moistureSensor, bool returnRAW = false) {
|
||||
/*
|
||||
* moistureSensor
|
||||
* ==============
|
||||
* 1 : analog capacitive moisture sensor
|
||||
* 2 : chirp I2C moisture sensor
|
||||
*/
|
||||
|
||||
// value to return
|
||||
int soilmoisture = 0;
|
||||
// value for wet
|
||||
int wet;
|
||||
// value for dry
|
||||
int dry;
|
||||
|
||||
switch(moistureSensor) {
|
||||
case 1:
|
||||
// read analog value from analog moisture sensor
|
||||
wet = 180;
|
||||
// this value was measured in air, without contact to anything
|
||||
//dry= 590;
|
||||
|
||||
// was measured in dry soil, not bone dry but really dry (6 days no watering)
|
||||
dry = 360;
|
||||
|
||||
digitalWrite(PINsoilmoisture, HIGH);
|
||||
// wait a bit to let the circuit stabilize
|
||||
delay(50);
|
||||
|
||||
// get analog input value
|
||||
// get values 10 times and get the middle for more precise data
|
||||
for(byte i = 0; i < 10 ; i++) {
|
||||
soilmoisture = soilmoisture + analogRead(PINanalog);
|
||||
}
|
||||
soilmoisture = soilmoisture / 10;
|
||||
|
||||
// disable Vcc for the sensor to release analog pin
|
||||
digitalWrite(PINsoilmoisture, LOW);
|
||||
break;
|
||||
case 2:
|
||||
// read soil moisture from chrip I2C
|
||||
// this value was measured in water
|
||||
// wet = 560;
|
||||
|
||||
// measured in fresh watered soil
|
||||
wet = 485;
|
||||
dry= 250;
|
||||
|
||||
// get raw value from I2C chirp sensor
|
||||
soilmoisture = readI2CRegister16bit(0x20, 0);
|
||||
break;
|
||||
default:
|
||||
wet = 0;
|
||||
dry = 1;
|
||||
soilmoisture = -1;
|
||||
}
|
||||
|
||||
if(returnRAW == true) {
|
||||
return soilmoisture;
|
||||
} else {
|
||||
short soilmoistureP = map(soilmoisture, wet, dry, 100, 0);
|
||||
// dont return negative percentage values
|
||||
if(soilmoistureP < 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return soilmoistureP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getLightchirp() {
|
||||
// get the "light value" from I2C chirp module
|
||||
writeI2CRegister8bit(0x20, 3); //request light measurement
|
||||
int lightchirp = readI2CRegister16bit(0x20, 4);
|
||||
return lightchirp;
|
||||
}
|
|
@ -1,752 +0,0 @@
|
|||
/*
|
||||
*
|
||||
*
|
||||
* System 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 (2 byte)
|
||||
* 221 Esp32CamIP (16 byte)
|
||||
* 237 PumpLastOn (4 byte)
|
||||
* 241 PumpIntervalVeg (1 byte)
|
||||
* 242 PumpIntervalBloom (1 byte)
|
||||
* 243 ...
|
||||
*
|
||||
*/
|
||||
|
||||
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);
|
||||
// size is 16 byte
|
||||
EEPROM.get(221, Esp32CamIP);
|
||||
// size is 4 byte
|
||||
EEPROM.get(237, PumpLastOn);
|
||||
|
||||
}
|
||||
// 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);
|
||||
// size is 1 byte
|
||||
EEPROM.get(241, PumpIntervalVeg);
|
||||
// size is 1 byte
|
||||
EEPROM.get(242, PumpIntervalBloom);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
unsigned short growState() {
|
||||
/*
|
||||
* growState()
|
||||
*
|
||||
* returns growState as short
|
||||
*
|
||||
* 1 - vegetation
|
||||
* 2 - bloom
|
||||
* 3 - harvest
|
||||
*
|
||||
*/
|
||||
unsigned short state;
|
||||
|
||||
if(DayOfGrow > (DaysVeg + DaysBloom ) ) {
|
||||
state = 3;
|
||||
} else if(DayOfGrow > DaysVeg ) {
|
||||
state = 2;
|
||||
} else {
|
||||
state = 1;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
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, 1 - OutputState);
|
||||
} else {
|
||||
analogWrite(OutputPin, 255 - 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();
|
||||
|
||||
switch(growState()) {
|
||||
case 1:
|
||||
lightHours = LighthoursVeg;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
lightHours = LighthoursBloom;
|
||||
break;
|
||||
default:
|
||||
lightHours = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// 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))) ) && (growState() < 3) ){
|
||||
//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() {
|
||||
byte soilmoistureAvgSampleCount = 5;
|
||||
|
||||
valSoilmoisture = getSoilmoisture(MoistureSensor_Type);
|
||||
valHumidity = getHumidity();
|
||||
valTemperature = getTemperature(TemperatureSensor_Type);
|
||||
valWaterlevel = getWaterlevel();
|
||||
|
||||
// get average of 5 readings for valSoilmoisture
|
||||
valSoilmoistureAvg_tmp = valSoilmoistureAvg_tmp + valSoilmoisture;
|
||||
if(valSoilmoistureAvg_count < soilmoistureAvgSampleCount - 1) {
|
||||
valSoilmoistureAvg_count++;
|
||||
} else {
|
||||
// build average
|
||||
valSoilmoistureAvg = valSoilmoistureAvg_tmp / soilmoistureAvgSampleCount;
|
||||
// reset everything
|
||||
valSoilmoistureAvg_tmp = 0;
|
||||
valSoilmoistureAvg_count = 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
switch(0) {
|
||||
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.print(" % ");
|
||||
display.println(valSoilmoistureAvg);
|
||||
display.println("");
|
||||
if(UsePump > 0) {
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Pump control
|
||||
*
|
||||
* Vars:
|
||||
* - UsePump (byte)
|
||||
* - PumpOnTime (byte) in sec
|
||||
* - SoilmoistureLow (byte) in %
|
||||
* - PumpLastOn (long) timestamp
|
||||
* - PumpMode (byte) 1: Pump on every n days, 2: Pump on when Soilmoisture <= SoilmoistureLow, 3: Both
|
||||
*
|
||||
* - PumpIntervalVeg (byte) in days
|
||||
* - PumpIntervalBloom (byte) in days
|
||||
*
|
||||
*/
|
||||
|
||||
void controlPUMP() {
|
||||
byte PumpInterval;
|
||||
|
||||
// UsePump true and not in harvest state?
|
||||
// dont water within the first 15 sec after startup
|
||||
if ( (UsePump > 0) && (growState() < 3) && (millis() > 15000) ) {
|
||||
switch(growState()) {
|
||||
case 1:
|
||||
PumpInterval = PumpIntervalVeg;
|
||||
break;
|
||||
case 2:
|
||||
PumpInterval = PumpIntervalBloom;
|
||||
break;
|
||||
default:
|
||||
PumpInterval = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// when PumpOnManuel is true, turn pump on for PumpOnTime seconds
|
||||
if(PumpOnManual == true) {
|
||||
if(PumpOnTimePassed < PumpOnTime) {
|
||||
setOutput(3, 1);
|
||||
//digitalWrite(PinPUMP, HIGH);
|
||||
PumpOnTimePassed++;
|
||||
} else {
|
||||
PumpOnManual = false;
|
||||
setOutput(3, 0);
|
||||
//digitalWrite(PinPUMP, LOW);
|
||||
EEPROM.put(237, PumpLastOn);
|
||||
PumpOnTimePassed = 0;
|
||||
}
|
||||
// otherwise check which PumpMode to use
|
||||
} else {
|
||||
switch(UsePump) {
|
||||
case 1:
|
||||
// when diff of time now and time pumpLastOn is greater then PumpInterval, do some watering (Or manual watering)
|
||||
if( (timeClient.getEpochTime() - PumpLastOn) >= (PumpInterval) ) { // TODO: * 24 * 60 * 60 PumpInterval
|
||||
// only water as long PumpOnTime
|
||||
if(PumpOnTimePassed < PumpOnTime) {
|
||||
setOutput(3, 1);
|
||||
//digitalWrite(PinPUMP, HIGH);
|
||||
PumpOnTimePassed++;
|
||||
} else {
|
||||
setOutput(3, 0);
|
||||
//digitalWrite(PinPUMP, LOW);
|
||||
PumpLastOn = timeClient.getEpochTime();
|
||||
// write the value to EEPROM for the case ESP gets restarted
|
||||
EEPROM.put(237, PumpLastOn);
|
||||
PumpOnTimePassed = 0;
|
||||
}
|
||||
} else {
|
||||
setOutput(3, 0);
|
||||
//digitalWrite(PinPUMP, LOW);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// when valSoilmoistureAvg is lower then SoilMoisture low do some watering
|
||||
if( (valSoilmoistureAvg < SoilmoistureLow) || ( (valSoilmoistureAvg >= SoilmoistureLow) && ( (PumpOnTimePassed > 0) && (PumpOnTimePassed <= PumpOnTime) ) ) ) {
|
||||
// check if we alerady exceeded max PumpOnTime
|
||||
if(PumpOnTimePassed < PumpOnTime) {
|
||||
setOutput(3, 1);
|
||||
//digitalWrite(PinPUMP, HIGH);
|
||||
PumpOnTimePassed++;
|
||||
} else {
|
||||
setOutput(3, 0);
|
||||
//digitalWrite(PinPUMP, LOW);
|
||||
PumpLastOn = timeClient.getEpochTime();
|
||||
PumpOnTimePassed = 0;
|
||||
}
|
||||
// when valSoilmoistureAvg is greater then the Low value,
|
||||
} else {
|
||||
setOutput(3, 0);
|
||||
//digitalWrite(PinPUMP, LOW);
|
||||
}
|
||||
break;
|
||||
|
||||
//
|
||||
case 3:
|
||||
if( ( (timeClient.getEpochTime() - PumpLastOn) >= (PumpInterval) ) && //TODO calculate PumpInterval into days as well here
|
||||
( (valSoilmoistureAvg < SoilmoistureLow) ||
|
||||
( (valSoilmoistureAvg >= SoilmoistureLow) && ( (PumpOnTimePassed > 0) && (PumpOnTimePassed <= PumpOnTime) ) )
|
||||
) ) {
|
||||
// check if we alerady exceeded max PumpOnTime
|
||||
if(PumpOnTimePassed < PumpOnTime) {
|
||||
setOutput(3, 1);
|
||||
//digitalWrite(PinPUMP, HIGH);
|
||||
PumpOnTimePassed++;
|
||||
} else {
|
||||
setOutput(3, 0);
|
||||
//digitalWrite(PinPUMP, LOW);
|
||||
PumpLastOn = timeClient.getEpochTime();
|
||||
EEPROM.put(237, PumpLastOn);
|
||||
PumpOnTimePassed = 0;
|
||||
}
|
||||
// when valSoilmoistureAvg is greater then the Low value,
|
||||
} else {
|
||||
setOutput(3, 0);
|
||||
//digitalWrite(PinPUMP, LOW);
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ensure pump is off when it should be off
|
||||
setOutput(3, 0);
|
||||
//digitalWrite(PinPUMP, LOW);
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
/* CanGrow_Version.h gets generated from cangrow.sh */
|
||||
|
||||
const char* CanGrowVer = "0.1-dev";
|
||||
const char* CanGrowBuild = "d2e264d-20240919022041";
|
||||
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue