cleanup old v0.1 stuff, lets begin from scratch

This commit is contained in:
Marcus 2024-10-16 19:45:16 +02:00
parent 1c0beb58aa
commit a6f5a6539b
8 changed files with 9 additions and 3324 deletions

View file

@ -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);
}
*/

View file

@ -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'>&#x2705; Successfully saved!</div>
)EOF";
const char HTMLneedRestart[] PROGMEM = R"EOF(
<div class='warnmsg'>&#10071; 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>&#x2753; 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'>&#x1F6DC; Uploading, please wait...<div>
)EOF";
const char HTMLsystemSubNav[] PROGMEM = R"EOF(
<ul class='subnav'>
<li><a href='/system/update'>&#x1F504; Firmware update</a></li>
<li><a href='/system/restart' >&#x1F501; CanGrow restart</a></li>
<li><a href='/system/wipe' >&#x1F4A3; Factory reset</a></li>
</ul>
)EOF";

View file

@ -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);

View file

@ -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;

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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