Compare commits
132 commits
main
...
espasyncwe
Author | SHA1 | Date | |
---|---|---|---|
84c4840993 | |||
92724fa1f4 | |||
cf824c1c61 | |||
b28c71c9a4 | |||
790b9bb9c9 | |||
735cff463e | |||
9af343bd3e | |||
1419d625a4 | |||
fba2210bbd | |||
aaf9688d1a | |||
8955824884 | |||
40d0175564 | |||
b7d21ca868 | |||
c466f1396f | |||
54b6d48e1e | |||
a983129085 | |||
c8247268cc | |||
22c316edbb | |||
bc15bbab46 | |||
b0f3d05576 | |||
f7f5fe073b | |||
55997e82eb | |||
bba1687022 | |||
f4624f860e | |||
10a0906a93 | |||
f7c4739f0d | |||
9c51386be9 | |||
dadaf09232 | |||
98ef3de395 | |||
945c208ba4 | |||
3517f6abf4 | |||
d679d896b2 | |||
ca4eb8cfd6 | |||
8e5268df22 | |||
0d0efbb2c1 | |||
4808af281c | |||
7da5bc38d7 | |||
1765748422 | |||
bafb623392 | |||
e3c739e745 | |||
2ad9c4c03d | |||
00a4acfd13 | |||
dc8ba42909 | |||
5c019f8df6 | |||
458077442a | |||
99d598e05d | |||
bf1e25d04c | |||
d68d0fbf79 | |||
4c9c280d45 | |||
94e79b8fa2 | |||
4f42b64c7e | |||
13087c0cc1 | |||
0227427b6f | |||
8861393e80 | |||
2dfa2b0c13 | |||
f91b2de002 | |||
c3b9f0f8a6 | |||
f1a1cb3aa9 | |||
947af0dd4d | |||
4a14629e48 | |||
44ca2c6a8e | |||
5af5bcd94d | |||
22fdfc57c2 | |||
e7bb42f72b | |||
f093ac843b | |||
de26abaf05 | |||
827c8cd184 | |||
825f2e8e19 | |||
ae7f6cd3f7 | |||
c2d6d508a2 | |||
e843df1a28 | |||
979b214d43 | |||
6e4127398b | |||
b5991a576b | |||
5b54ae3658 | |||
57fa57fc94 | |||
187ade247f | |||
2252fe0142 | |||
0ef3656bf6 | |||
2c2adc9678 | |||
7d0880343b | |||
a0735829ae | |||
45ea8eabaa | |||
8e9a07a65b | |||
6ac7b31602 | |||
a044c65503 | |||
d4e1108759 | |||
52cd7d469f | |||
219586de93 | |||
603118ad0c | |||
f4482ee37b | |||
f918529e57 | |||
42d5939dbc | |||
16074669c3 | |||
f7224a1588 | |||
9d2faf4e4c | |||
b07696a1c4 | |||
ac10feccf7 | |||
a3fef36ecb | |||
f3f1629001 | |||
1d44b6736d | |||
1d55f36387 | |||
f347a375ce | |||
764cf45b80 | |||
717b201889 | |||
06d4ba73c0 | |||
36b2da7802 | |||
f130700cf0 | |||
985b2cac5d | |||
4595bea4e5 | |||
c4dea65157 | |||
d97a220f42 | |||
26f0939cc6 | |||
7dcfc375bb | |||
989fefd1e2 | |||
a330268960 | |||
04e8de0fde | |||
73f017cd2f | |||
e82046d297 | |||
7b43171231 | |||
c4155e1f9b | |||
4c57546657 | |||
3f8b060c66 | |||
3a29ebd316 | |||
11b7217f57 | |||
529bd7e556 | |||
cf58ef292b | |||
ccca8b10ab | |||
8fb27a1720 | |||
8940a9a136 | |||
fec057b8db | |||
a6f5a6539b |
55 changed files with 4174 additions and 12753 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,4 +3,4 @@ KiCad/CanGrow/CanGrow.kicad_sch-bak
|
|||
KiCad/CanGrow/fp-info-cache
|
||||
KiCad/CanGrow/gerber/*.zip
|
||||
Arduino/CanGrow/CanGrow.geany
|
||||
build/
|
||||
Arduino/CanGrow/build/
|
||||
|
|
24
.gitmodules
vendored
24
.gitmodules
vendored
|
@ -1,24 +0,0 @@
|
|||
[submodule "Arduino/CanGrow/Adafruit-GFX-Library"]
|
||||
path = Arduino/CanGrow/Adafruit-GFX-Library
|
||||
url = https://github.com/adafruit/Adafruit-GFX-Library
|
||||
[submodule "Arduino/CanGrow/lib/Adafruit-GFX-Library"]
|
||||
path = Arduino/CanGrow/lib/Adafruit-GFX-Library
|
||||
url = https://github.com/adafruit/Adafruit-GFX-Library
|
||||
[submodule "Arduino/CanGrow/lib/Adafruit_SSD1306"]
|
||||
path = Arduino/CanGrow/lib/Adafruit_SSD1306
|
||||
url = https://github.com/adafruit/Adafruit_SSD1306
|
||||
[submodule "Arduino/CanGrow/lib/Adafruit_BME280_Library"]
|
||||
path = Arduino/CanGrow/lib/Adafruit_BME280_Library
|
||||
url = https://github.com/adafruit/Adafruit_BME280_Library/
|
||||
[submodule "Arduino/CanGrow/lib/NTPClient"]
|
||||
path = Arduino/CanGrow/lib/NTPClient
|
||||
url = https://github.com/arduino-libraries/NTPClient
|
||||
[submodule "Arduino/CanGrow/lib/Time"]
|
||||
path = Arduino/CanGrow/lib/Time
|
||||
url = https://github.com/PaulStoffregen/Time
|
||||
[submodule "Arduino/CanGrow/lib/Adafruit_SHT31"]
|
||||
path = Arduino/CanGrow/lib/Adafruit_SHT31
|
||||
url = https://github.com/adafruit/Adafruit_SHT31/
|
||||
[submodule "Arduino/CanGrow/lib/Adafruit_Sensor"]
|
||||
path = Arduino/CanGrow/lib/Adafruit_Sensor
|
||||
url = https://github.com/adafruit/Adafruit_Sensor
|
|
@ -28,30 +28,34 @@ long_line_behaviour=1
|
|||
long_line_column=72
|
||||
|
||||
[files]
|
||||
current_page=1
|
||||
FILE_NAME_0=2265;Arduino;0;EUTF-8;0;1;0;%2Fhome%2Fmarcus%2Fdistrobox%2Fdebian-cangrow%2FCanGrow%2FArduino%2FCanGrow%2FCanGrow.ino;0;2
|
||||
FILE_NAME_1=14609;C++;0;EUTF-8;0;1;0;%2Fhome%2Fmarcus%2Fdistrobox%2Fdebian-cangrow%2FCanGrow%2FArduino%2FCanGrow%2FCanGrow_HTML.h;0;2
|
||||
FILE_NAME_2=2884;C++;0;EUTF-8;0;1;0;%2Fhome%2Fmarcus%2Fdistrobox%2Fdebian-cangrow%2FCanGrow%2FArduino%2FCanGrow%2FCanGrow_Init.h;0;2
|
||||
FILE_NAME_3=0;C++;0;EUTF-8;0;1;0;.%2FCanGrow_Logo.h;0;2
|
||||
FILE_NAME_4=0;C++;0;EUTF-8;0;1;0;.%2FCanGrow_PinAssignments.h;0;2
|
||||
FILE_NAME_5=4277;C++;0;EUTF-8;0;1;0;%2Fhome%2Fmarcus%2Fdistrobox%2Fdebian-cangrow%2FCanGrow%2FArduino%2FCanGrow%2FCanGrow_Sensors.h;0;2
|
||||
FILE_NAME_6=4737;C++;0;EUTF-8;0;1;0;%2Fhome%2Fmarcus%2Fdistrobox%2Fdebian-cangrow%2FCanGrow%2FArduino%2FCanGrow%2FCanGrow_SysFunctions.h;0;2
|
||||
FILE_NAME_7=27858;C++;0;EUTF-8;0;1;0;%2Fhome%2Fmarcus%2Fdistrobox%2Fdebian-cangrow%2FCanGrow%2FArduino%2FCanGrow%2FCanGrow_WebFunctions.h;0;2
|
||||
current_page=0
|
||||
FILE_NAME_0=493;Sh;0;EUTF-8;0;1;0;.%2Fcangrow.sh;0;2
|
||||
FILE_NAME_1=0;Arduino;0;EUTF-8;0;1;0;.%2FCanGrow.ino;0;2
|
||||
FILE_NAME_2=0;C++;0;EUTF-8;0;1;0;.%2Finclude%2FCanGrow.h;0;2
|
||||
FILE_NAME_3=0;C++;0;EUTF-8;0;1;0;.%2Finclude%2FCanGrow_Core.h;0;2
|
||||
FILE_NAME_4=0;C++;0;EUTF-8;0;1;0;.%2Finclude%2FCanGrow_ESP32.h;0;2
|
||||
FILE_NAME_5=0;C++;0;EUTF-8;0;1;0;.%2Finclude%2FCanGrow_ESP8266.h;0;2
|
||||
FILE_NAME_6=0;C++;0;EUTF-8;0;1;0;.%2Finclude%2FCanGrow_LittleFS.h;0;2
|
||||
FILE_NAME_7=0;C++;0;EUTF-8;0;1;0;.%2Finclude%2FCanGrow_Logo.h;0;2
|
||||
FILE_NAME_8=0;C++;0;EUTF-8;0;1;0;.%2Finclude%2FCanGrow_Version.h;0;2
|
||||
|
||||
[build-menu]
|
||||
C++FT_00_LB=_Compile
|
||||
C++FT_00_CM=~/.local/bin/arduino-cli --no-color compile -b esp8266:esp8266:d1_mini_clone "%d/CanGrow.ino"
|
||||
C++FT_00_CM=cd .. ; ./cangrow.sh build
|
||||
C++FT_00_WD=
|
||||
filetypes=C++;Arduino;
|
||||
filetypes=C++;Arduino;Sh;
|
||||
ArduinoFT_00_LB=_Build
|
||||
ArduinoFT_00_CM=~/.local/bin/arduino-cli --no-color compile -b esp8266:esp8266:d1_mini_clone "%d/CanGrow.ino"
|
||||
ArduinoFT_00_CM=./cangrow.sh build
|
||||
ArduinoFT_00_WD=
|
||||
ArduinoFT_01_LB=Build & Upload
|
||||
ArduinoFT_01_CM=~/.local/bin/arduino-cli --no-color compile -v -b esp8266:esp8266:d1_mini_clone -u -p /dev/ttyUSB0 "%d/CanGrow.ino"
|
||||
ArduinoFT_01_CM=./cangrow.sh upload
|
||||
ArduinoFT_01_WD=
|
||||
C++FT_01_LB=_Build & Upload
|
||||
C++FT_01_CM=~/.local/bin/arduino-cli --no-color compile -v -b esp8266:esp8266:d1_mini_clone -u -p /dev/ttyUSB0 "%d/CanGrow.ino"
|
||||
C++FT_01_CM=cd .. ; ./cangrow.sh upload
|
||||
C++FT_01_WD=
|
||||
|
||||
[VTE]
|
||||
last_dir=/home/marcus/distrobox/debian-cangrow
|
||||
ShFT_00_LB=Build
|
||||
ShFT_00_CM=./cangrow.sh build
|
||||
ShFT_00_WD=
|
||||
ShFT_01_LB=Build & Upload
|
||||
ShFT_01_CM=./cangrow.sh upload
|
||||
ShFT_01_WD=
|
||||
|
|
|
@ -1,474 +1,169 @@
|
|||
/*
|
||||
* CanGrow - simply DIY automatic plant grow system (for cannabis).
|
||||
*
|
||||
* CanGrow - an OpenSource growcontroller firmware (for cannabis)
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Includes
|
||||
*
|
||||
* Libraries include
|
||||
*/
|
||||
|
||||
// Libraries internal (Arduino Core / ESP)
|
||||
#include "Arduino.h"
|
||||
|
||||
// * ESP8266 *
|
||||
#ifdef ESP8266
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
|
||||
// * ESP32 *
|
||||
#ifdef ESP32
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include <Update.h>
|
||||
#endif
|
||||
|
||||
// https://github.com/mathieucarbou/ESPAsyncWebServer
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
// LittleFS filesystem
|
||||
#include "FS.h"
|
||||
// arduino-core for esp8266 and esp32
|
||||
#include "LittleFS.h"
|
||||
|
||||
// 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>
|
||||
|
||||
// Libraries external
|
||||
// https://github.com/adafruit/Adafruit-GFX-Library
|
||||
//include "lib/Adafruit-GFX-Library/Adafruit_GFX.h"
|
||||
#include <Adafruit_GFX.h>
|
||||
// https://github.com/adafruit/Adafruit_SSD1306
|
||||
//include "lib/Adafruit_SSD1306/Adafruit_SSD1306.h"
|
||||
#include <Adafruit_SSD1306.h>
|
||||
// https://github.com/adafruit/Adafruit_Sensor
|
||||
// include "lib/Adafruit_Sensor/Adafruit_Sensor.h"
|
||||
#include <Adafruit_Sensor.h>
|
||||
// https://github.com/adafruit/Adafruit_BME280_Library
|
||||
//include "lib/Adafruit_BME280_Library/Adafruit_BME280.h"
|
||||
#include <Adafruit_BME280.h>
|
||||
// https://github.com/adafruit/Adafruit_SHT31/
|
||||
//#include "lib/Adafruit_SHT31/Adafruit_SHT31.h"
|
||||
#include <Adafruit_SHT31.h>
|
||||
// https://github.com/bblanchon/ArduinoJson
|
||||
#include "lib/ArduinoJson/ArduinoJson-v7.2.1.h"
|
||||
// https://github.com/arduino-libraries/NTPClient
|
||||
//#include "lib/NTPClient/NTPClient.h"
|
||||
#include <NTPClient.h>
|
||||
#include <ArduinoJson.h>
|
||||
// https://github.com/PaulStoffregen/Time
|
||||
//#include "lib/Time/TimeLib.h"
|
||||
#include <TimeLib.h>
|
||||
|
||||
// DHT support dropped
|
||||
// https://github.com/adafruit/DHT-sensor-library
|
||||
// #include "DHT.h"
|
||||
|
||||
/*
|
||||
* CanGrow header files
|
||||
*/
|
||||
#include "CanGrow_Version.h"
|
||||
#include "CanGrow_PinAssignments.h"
|
||||
#include "CanGrow_Init.h"
|
||||
#include "CanGrow_Logo.h"
|
||||
#include "CanGrow_Sensors.h"
|
||||
#include "CanGrow_HTML.h"
|
||||
#include "CanGrow_SysFunctions.h"
|
||||
#include "CanGrow_WebFunctions.h"
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Setup
|
||||
*
|
||||
* CanGrow includes
|
||||
*/
|
||||
|
||||
/* main header file, where all variables, consts and structs get defined */
|
||||
#include "include/CanGrow.h"
|
||||
/* CanGrow platform specific includes */
|
||||
#include "include/CanGrow_ESP8266.h"
|
||||
#include "include/CanGrow_ESP32.h"
|
||||
/* CanGrow header with all functions */
|
||||
#include "include/CanGrow_Core.h"
|
||||
#include "include/CanGrow_Wifi.h"
|
||||
#include "include/CanGrow_LittleFS.h"
|
||||
#include "include/CanGrow_Webserver.h"
|
||||
|
||||
|
||||
void setup() {
|
||||
|
||||
// setup pins
|
||||
//pinMode(PINdht, INPUT);
|
||||
pinMode(PINwaterlevel, OUTPUT);
|
||||
pinMode(PINsoilmoisture, OUTPUT);
|
||||
pinMode(PinFAN2, OUTPUT);
|
||||
|
||||
// define output for onboard LED/WIPE pin
|
||||
pinMode(PinWIPE, OUTPUT);
|
||||
// set all OUTPUT to low
|
||||
digitalWrite(PINwaterlevel, LOW);
|
||||
|
||||
// its better to leave them untuched until we know from EEPROM if they have to be inverted or not
|
||||
//~ digitalWrite(PinFAN, HIGH);
|
||||
//~ digitalWrite(PinLED, HIGH);
|
||||
//~ digitalWrite(PinPUMP, HIGH);
|
||||
// except PINsoilmoisture
|
||||
// PINsoilmoisture is always HIGH and gets LOW in moment of waterlevel measurement
|
||||
digitalWrite(PINsoilmoisture, HIGH);
|
||||
// set FAN2 to off with digitalWrite LOW
|
||||
analogWrite(PinFAN2, PinFAN2PWM);
|
||||
|
||||
// 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(CANGROW_VER);
|
||||
Serial.print(" build ");
|
||||
Serial.print(CANGROW_BUILD);
|
||||
Serial.println(" starting ::.");
|
||||
|
||||
Serial.println(":: initialise I2C ::");
|
||||
// initialise Wire for I2C
|
||||
Wire.begin();
|
||||
// just for testing
|
||||
//Wire.setClockStretchLimit(2500);
|
||||
Serial.println(":: initialise display ::");
|
||||
// initialise I2C display
|
||||
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Address 0x3C for 128x64
|
||||
display.clearDisplay();
|
||||
display.display();
|
||||
Serial.printf(".:: CanGrow firmware v%s build %s starting ::.\n", CANGROW_VER, CANGROW_BUILD);
|
||||
|
||||
Serial.print("II To format / factory reset LittleFS, pull GPIO ");
|
||||
Serial.print(PinWIPE);
|
||||
Serial.print(" (PinWIPE) to ");
|
||||
// we need to invert the default to tell that user the state for an action
|
||||
Serial.print(1 - PinWIPE_default);
|
||||
Serial.println(" - NOW! (2 seconds left) II");
|
||||
|
||||
// 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
|
||||
// dirty way of supporting multiple addresses, DRY? :p
|
||||
Serial.println(":: initialise BME280 sensor, address 0x76 ::");
|
||||
// ToDo: let the user configure somewhere the ID of the BME280 sensor
|
||||
if(!bme_0x76.begin(0x76)) {
|
||||
Serial.println("!! Cannot find BME280 on I2C bus at address 0x76. Please check connection or ID");
|
||||
}
|
||||
Serial.println(":: initialise BME280 sensor, address 0x77 ::");
|
||||
// ToDo: let the user configure somewhere the ID of the BME280 sensor
|
||||
if(!bme_0x77.begin(0x77)) {
|
||||
Serial.println("!! Cannot find BME280 on I2C bus at address 0x77. Please check connection or ID");
|
||||
}
|
||||
|
||||
// initialise SHT31
|
||||
Serial.println(":: initialise SHT31 sensor, address 0x44 ::");
|
||||
if (! sht31_0x44.begin(0x44)) { // Set to 0x45 for alternate i2c addr
|
||||
Serial.println("!! Cannot find SHT31 on I2C bus at address 0x45. Please check connection or ID");
|
||||
}
|
||||
Serial.println(":: initialise SHT31 sensor, address 0x45 ::");
|
||||
if (! sht31_0x45.begin(0x45)) { // Set to 0x45 for alternate i2c addr
|
||||
Serial.println("!! Cannot find SHT31 on I2C bus at address 0x45. Please check connection or ID");
|
||||
}
|
||||
|
||||
Serial.print(":: SHT31 (0x44) heater enable state ::");
|
||||
if (sht31_0x44.isHeaterEnabled())
|
||||
Serial.println("ENABLED");
|
||||
else
|
||||
Serial.println("DISABLED");
|
||||
|
||||
Serial.print(":: SHT31 (0x45) heater enable state ::");
|
||||
if (sht31_0x45.isHeaterEnabled())
|
||||
Serial.println("ENABLED");
|
||||
else
|
||||
Serial.println("DISABLED");
|
||||
|
||||
|
||||
|
||||
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)
|
||||
// blink with the onboard LED on D4/GPIO2 (PinWIPE)
|
||||
for(byte i = 0; i <= 6 ; i++) {
|
||||
if(i % 2) {
|
||||
digitalWrite(PinWIPE, LOW);
|
||||
digitalWrite(PinWIPE, 1 - PinWIPE_default);
|
||||
} else {
|
||||
digitalWrite(PinWIPE, HIGH);
|
||||
digitalWrite(PinWIPE, PinWIPE_default);
|
||||
}
|
||||
delay(333);
|
||||
}
|
||||
// set back to HIGH because thats the default
|
||||
digitalWrite(PinWIPE, HIGH);
|
||||
//delay(2000);
|
||||
|
||||
// set PinWIPE back to its default
|
||||
digitalWrite(PinWIPE, PinWIPE_default);
|
||||
|
||||
|
||||
// read status from PinWIPE to WIPE
|
||||
// when PinWIPE is set to LOW, wipe EEPROM
|
||||
if(digitalRead(PinWIPE) == LOW) {
|
||||
// wipe EEPROM
|
||||
wipeEEPROM();
|
||||
// when PinWIPE is set to LOW, format LittleFS
|
||||
if(digitalRead(PinWIPE) != PinWIPE_default) {
|
||||
LFS_Format();
|
||||
Restart();
|
||||
}
|
||||
|
||||
|
||||
LFS_Init();
|
||||
LoadConfig();
|
||||
Wifi_Init();
|
||||
Webserver_Init();
|
||||
Serial.printf(":: [SETUP] Usable Pins: %d\n", GPIOindex_length);
|
||||
for(byte i = 0; i < GPIOindex_length; i++) {
|
||||
Serial.printf(":: [SETUP] Pin Index: %d, GPIO: %d, Notes: ", i, GPIOindex[i].gpio);
|
||||
Serial.println(GPIO_Index_note_descr[GPIOindex[i].note]);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
// at the end of setup, set the outputs, when configured true
|
||||
// we do this here because otherwise on inverted
|
||||
// boards like CanGrow PCB v0.6 it would be turned on
|
||||
if(configured == true) {
|
||||
initOutputs();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool alrdySaved = false;
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
* Loop
|
||||
*
|
||||
*
|
||||
*/
|
||||
void loop() {
|
||||
// var definition
|
||||
unsigned long currentRuntime = millis();
|
||||
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
// first we call webserver handle client
|
||||
webserver.handleClient();
|
||||
if(currentMillis - schedulerPrevMillis >= config.system.schedulerInterval) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 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 VPD - https://www.grower.ch/forum/threads/diy-grow-controller-cangrow-projektvorstellung.163654/page-4#post-4294197
|
||||
valVPD = (((100 - valHumidity) / 100) * (610.7 * (pow(10, (7.5 * valTemperature / (237.3 + valTemperature))))))/1000;
|
||||
|
||||
// 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();
|
||||
|
||||
controlFAN();
|
||||
|
||||
displayScreens();
|
||||
|
||||
// current time gets previous time for new interval
|
||||
outputPrevTime = currentRuntime;
|
||||
if((digitalRead(PinWIPE) != PinWIPE_default) && (alrdySaved == false)) {
|
||||
Serial.println(":: [LOOP] PinWIPE is triggered");
|
||||
// save config to littlefs as json
|
||||
SaveConfig();
|
||||
// only print json to serial
|
||||
SaveConfig(true);
|
||||
alrdySaved = true;
|
||||
} else if( (digitalRead(PinWIPE) != PinWIPE_default) && (alrdySaved == true) ) {
|
||||
alrdySaved = true;
|
||||
} else {
|
||||
alrdySaved = false;
|
||||
}
|
||||
|
||||
// if global var doRestart is true, perform a restart
|
||||
if(doRestart == true) {
|
||||
Restart();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* TODO LIST / NOTES
|
||||
*
|
||||
*
|
||||
* - 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,600 +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;
|
||||
margin-left: 15px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
}
|
||||
/* VPD colors */
|
||||
|
||||
.vpd_danger1 {
|
||||
color: #1a6c9c;
|
||||
}
|
||||
.vpd_earlyveg {
|
||||
color: #22ab9c;
|
||||
}
|
||||
.vpd_lateveg {
|
||||
color: #9cc55b;
|
||||
}
|
||||
.vpd_latebloom {
|
||||
color: #9cc55b;
|
||||
}
|
||||
.vpd_danger2 {
|
||||
color: #1a6c9c;
|
||||
}
|
||||
|
||||
</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";
|
||||
|
||||
const char JSsoilmoisture[] PROGMEM = R"EOF(
|
||||
<script>
|
||||
function MoistureSensorType() {
|
||||
let selVal = document.getElementById('SelMoistureSensor_Type').value;
|
||||
let wet = document.getElementById('iSoilmoistureWet');
|
||||
let dry = document.getElementById('iSoilmoistureDry');
|
||||
switch(selVal) {
|
||||
case '1':
|
||||
wet.value = 160;
|
||||
dry.value = 360;
|
||||
console.log(selVal);
|
||||
break;
|
||||
|
||||
case '2':
|
||||
wet.value = 485;
|
||||
dry.value = 250;
|
||||
console.log(selVal);
|
||||
break;
|
||||
|
||||
default:
|
||||
wet.value = 0;
|
||||
dry.value = 0;
|
||||
console.log(selVal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function loadJSON(callback) {
|
||||
var xobj = new XMLHttpRequest();
|
||||
xobj.overrideMimeType("application/json");
|
||||
xobj.open('GET', '/api/sensors', true);
|
||||
xobj.onreadystatechange = function() {
|
||||
if (xobj.readyState == 4 && xobj.status == "200") {
|
||||
callback(xobj.responseText);
|
||||
}
|
||||
}
|
||||
xobj.send(null);
|
||||
}
|
||||
|
||||
function SoilmoistureRefresh() {
|
||||
loadJSON(function(response) {
|
||||
json = JSON.parse(response);
|
||||
document.getElementById('iSoilmoistureRaw').textContent = json.soilmoistureRaw;
|
||||
console.log(json.soilmoistureRaw);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
)EOF";
|
||||
|
||||
const char HTMLsoilmoistureCalibrateText[] PROGMEM = R"EOF(<p class='helpbox'> <b>Calibration</b><br>
|
||||
Put your soilmoisture sensor into dry soil and hit Refresh.<br>
|
||||
Adjust the value of '<i>Soilmoisture dry</i>' if needed according to the reading.<br>
|
||||
Repeat this with wet soil for '<i>Soilmoisture wet</i>'.</p>)EOF";
|
|
@ -1,194 +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;
|
||||
// helper variable for pump control with soilmoisture
|
||||
// average of last readings
|
||||
unsigned short valSoilmoistureAvg = 0;
|
||||
unsigned short valSoilmoistureAvg_tmp = 0;
|
||||
byte valSoilmoistureAvg_count = 0;
|
||||
short valSoilmoistureRaw;
|
||||
|
||||
// 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;
|
||||
// how many seconds actual screen got displayed
|
||||
byte ScreenIterationPassed = 0;
|
||||
|
||||
// VPD value - https://www.grower.ch/forum/threads/diy-grow-controller-cangrow-projektvorstellung.163654/page-4#post-4294197
|
||||
float valVPD;
|
||||
|
||||
// DayNight -
|
||||
// true Day , false night
|
||||
bool DayNight;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* 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;
|
||||
// which humidity sensor to use
|
||||
byte HumiditySensor_Type;
|
||||
unsigned short MaintenanceDuration = 300;
|
||||
char Esp32CamIP[16];
|
||||
// PumpLastOn (long) timestamp
|
||||
unsigned long PumpLastOn;
|
||||
bool OutputInvert;
|
||||
unsigned short SoilmoistureWet;
|
||||
unsigned short SoilmoistureDry;
|
||||
unsigned short PWMFrequency = 13370;
|
||||
byte DisplayScreenDuration = 3;
|
||||
|
||||
|
||||
//
|
||||
// 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;
|
||||
byte PinFAN2PWM = 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,31 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Pin assignments
|
||||
*
|
||||
* D0 - MOSFET Pump
|
||||
* D1, D2 - I2C
|
||||
* D3 - Fan2 PWM
|
||||
* D4 - PinWIPE
|
||||
* D5 - MOSFET Fan1, PWM
|
||||
* 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 PinFAN2 = D3;
|
||||
const uint8_t PinLED = D6; //
|
||||
const uint8_t PINwaterlevel = D7;
|
||||
const uint8_t PINsoilmoisture = D8;
|
||||
const uint8_t PINanalog = A0;
|
|
@ -1,254 +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)
|
||||
// dirty way of having multiple addresses configurable
|
||||
Adafruit_BME280 bme_0x76;
|
||||
Adafruit_BME280 bme_0x77;
|
||||
|
||||
/*
|
||||
* SHT30/31 Stuff
|
||||
*
|
||||
*/
|
||||
|
||||
Adafruit_SHT31 sht31_0x44 = Adafruit_SHT31();
|
||||
Adafruit_SHT31 sht31_0x45 = Adafruit_SHT31();
|
||||
|
||||
/*
|
||||
* 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 : BME280 0x76 temp sensor
|
||||
* 2 : BME280 0x77 temp sensor
|
||||
* 3 : SHT31 0x44 temp sensor
|
||||
* 4 : SHT31 0x45 temp sensor
|
||||
* 5 : Chirp I2C 0x20 temp sensor
|
||||
*/
|
||||
|
||||
float temperature = 0;
|
||||
|
||||
switch(tempSensor) {
|
||||
case 1:
|
||||
// read temperature from BME280
|
||||
temperature = bme_0x76.readTemperature();
|
||||
break;
|
||||
case 2:
|
||||
// read temperature from BME280
|
||||
temperature = bme_0x77.readTemperature();
|
||||
break;
|
||||
case 3:
|
||||
// read temp from SHT31
|
||||
temperature = sht31_0x44.readTemperature();
|
||||
break;
|
||||
case 4:
|
||||
// read temp from SHT31
|
||||
temperature = sht31_0x45.readTemperature();
|
||||
break;
|
||||
case 5:
|
||||
// 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(byte HumSensor) {
|
||||
|
||||
/*
|
||||
* sensors:
|
||||
* 1 : BME280 0x76 humidity sensor
|
||||
* 2 : BME280 0x77 humidity sensor
|
||||
* 3 : SHT31 0x44 humidity sensor
|
||||
* 4 : SHT31 0x45 humidity sensor
|
||||
*
|
||||
*/
|
||||
float humidity;
|
||||
|
||||
switch(HumSensor) {
|
||||
case 1:
|
||||
humidity = bme_0x76.readHumidity();
|
||||
break;
|
||||
case 2:
|
||||
humidity = bme_0x77.readHumidity();
|
||||
break;
|
||||
case 3:
|
||||
humidity = sht31_0x44.readHumidity();
|
||||
break;
|
||||
case 4:
|
||||
humidity = sht31_0x45.readHumidity();
|
||||
break;
|
||||
default:
|
||||
humidity = 0.0;
|
||||
break;
|
||||
}
|
||||
|
||||
// return dht.readHumidity();
|
||||
return humidity;
|
||||
}
|
||||
|
||||
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, SoilmoistureWet, SoilmoistureDry, 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,843 +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 OutputInvert (1 byte)
|
||||
* 244 SoilmoistureWet (2 byte)
|
||||
* 246 SoilmoistureDry (2 byte)
|
||||
* 248 PinFAN2PWM (1 byte)
|
||||
* 249 HumiditySensor_Type (1 byte)
|
||||
* 250 PWMFrequency (2 byte)
|
||||
* 252 DisplayScreenDuration (1 byte)
|
||||
* 253 ...
|
||||
*
|
||||
*/
|
||||
|
||||
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);
|
||||
// size is 1 byte
|
||||
EEPROM.get(243, OutputInvert);
|
||||
// size is 1 byte
|
||||
EEPROM.get(244, SoilmoistureWet);
|
||||
// size is 1 byte
|
||||
EEPROM.get(246, SoilmoistureDry);
|
||||
// size is 1 byte
|
||||
EEPROM.get(249, HumiditySensor_Type);
|
||||
// size is 2 byte
|
||||
EEPROM.get(250, PWMFrequency);
|
||||
// size is 1 byte
|
||||
EEPROM.get(252, DisplayScreenDuration);
|
||||
}
|
||||
// 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);
|
||||
// size is 1 byte
|
||||
EEPROM.get(248, PinFAN2PWM);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 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.print("PumpLastOn: ");
|
||||
Serial.println(PumpLastOn);
|
||||
Serial.print("OutputInvert: ");
|
||||
Serial.println(OutputInvert);
|
||||
Serial.print("SoilmoistureWet: ");
|
||||
Serial.println(SoilmoistureWet);
|
||||
Serial.print("SoilmoistureDry: ");
|
||||
Serial.println(SoilmoistureDry);
|
||||
Serial.print("HumiditySensor_Type: ");
|
||||
Serial.println(HumiditySensor_Type);
|
||||
Serial.print("PWMFrequency: ");
|
||||
Serial.println(PWMFrequency);
|
||||
|
||||
|
||||
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("PinFAN2PWM: ");
|
||||
Serial.println(PinFAN2PWM);
|
||||
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) {
|
||||
/*
|
||||
* Output assignments
|
||||
*
|
||||
* 1 - LED
|
||||
* 2 - FAN
|
||||
* 3 - PUMP
|
||||
* 4 - FAN2
|
||||
*
|
||||
*/
|
||||
bool UseRelais = true;
|
||||
byte OutputPin;
|
||||
byte OutputState_tmp;
|
||||
|
||||
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;
|
||||
case 4:
|
||||
OutputPin = PinFAN2;
|
||||
UseRelais = false;
|
||||
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);
|
||||
|
||||
// TODO read config for inverted outputs
|
||||
|
||||
if( (UseRelais == true) || (OutputPin == PinPUMP) ) {
|
||||
// convert OutputState to bool when using relais, so we can invert it easy
|
||||
bool OutputState_bool = OutputState;
|
||||
if(OutputInvert == true) {
|
||||
OutputState_tmp = 1 - OutputState_bool;
|
||||
} else {
|
||||
OutputState_tmp = OutputState_bool;
|
||||
}
|
||||
digitalWrite(OutputPin, OutputState_tmp);
|
||||
} else {
|
||||
// when OutputInvert is set true AND output is not Fan2, invert
|
||||
// for the 4-pin Fan PWM we dont need to invert, this could
|
||||
if( (OutputInvert == true) && (OutputPin != PinFAN2) ) {
|
||||
OutputState_tmp = 255 - OutputState;
|
||||
} else {
|
||||
OutputState_tmp = OutputState;
|
||||
}
|
||||
analogWrite(OutputPin, OutputState_tmp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// its daytime
|
||||
DayNight = true;
|
||||
|
||||
} else {
|
||||
//Serial.println("good night time");
|
||||
// turn off
|
||||
setOutput(1, 0);
|
||||
// nighttime
|
||||
DayNight = false;
|
||||
}
|
||||
}
|
||||
|
||||
void refreshSensors() {
|
||||
byte soilmoistureAvgSampleCount = 5;
|
||||
|
||||
valSoilmoisture = getSoilmoisture(MoistureSensor_Type);
|
||||
valSoilmoistureRaw = getSoilmoisture(MoistureSensor_Type, true);
|
||||
valHumidity = getHumidity(HumiditySensor_Type);
|
||||
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
|
||||
// when DisplayScreenDuration is 0, always show 0
|
||||
if(DisplayScreenDuration == 0) {
|
||||
ScreenToDisplay = 0;
|
||||
}
|
||||
switch(ScreenToDisplay) {
|
||||
// switch(0) { // just for testing, show screen 0 only
|
||||
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);
|
||||
PumpLastOn = timeClient.getEpochTime();
|
||||
EEPROM.put(237, PumpLastOn);
|
||||
EEPROM.commit(); //write to EEPROM
|
||||
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 * 24 * 60 *60) ) { // PumpInterval to, Days * 24 * 60 * 60
|
||||
// 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);
|
||||
EEPROM.commit(); //write to EEPROM
|
||||
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 * 24 * 60 *60) ) && // PumpInterval to, Days * 24 * 60 * 60
|
||||
( (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);
|
||||
EEPROM.commit(); //write to EEPROM
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void controlFAN() {
|
||||
// FAN1
|
||||
setOutput(2, PinFANPWM);
|
||||
// FAN2
|
||||
setOutput(4, PinFAN2PWM);
|
||||
}
|
||||
|
||||
void initOutputs() {
|
||||
pinMode(PinLED, OUTPUT);
|
||||
pinMode(PinPUMP, OUTPUT);
|
||||
pinMode(PinFAN, OUTPUT);
|
||||
// set PWM frequency
|
||||
analogWriteFreq(PWMFrequency);
|
||||
for(byte i = 1; i <= 4; i++) {
|
||||
setOutput(i, 0);
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
/*
|
||||
*
|
||||
*
|
||||
* Version
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#define CANGROW_VER "0.1.4"
|
||||
// CANGROW_BUILD default dummy value if not set as Compiler Flag
|
||||
#ifndef CANGROW_BUILD
|
||||
#define CANGROW_BUILD "1a2b3c4-0000000000000"
|
||||
#endif
|
File diff suppressed because it is too large
Load diff
|
@ -1,10 +1,50 @@
|
|||
# CanGrow - Arduino Code
|
||||
# CanGrow - An OpenSource grow controller firmware
|
||||
|
||||
Here the Code for CanGrow is contained.
|
||||
A ESP8266 with Arduino Firmware is used (should most times be the standard already).
|
||||
## Build environment
|
||||
The helper script `cangrow.sh` is written for a Debian 12 system.
|
||||
|
||||
To install all dependencies you need for building the firmware, run the cangrow.sh setup:
|
||||
|
||||
```sh
|
||||
$ ./cangrow.sh help
|
||||
./cangrow.sh [setup|build|upload|webupload|monitor]
|
||||
setup: setup build environment, download arduino-cli, install all dependencies for arduino ide
|
||||
build: build firmware binary. will be saved into build/
|
||||
upload: upload firmware by serial connection /dev/ttyUSB0
|
||||
webupload: upload firmware with webupload to 192.168.4.20
|
||||
monitor: serial monitor /dev/ttyUSB0
|
||||
|
||||
# Install all dependencies for build environment
|
||||
$ ./cangrow.sh setup
|
||||
```
|
||||
|
||||
The script installs [arduino-cli](https://github.com/arduino/arduino-cli) to `~/.local/bin/arduino-cli`.
|
||||
|
||||
## Compile
|
||||
|
||||
```sh
|
||||
# compile and output to build/CanGrow_v0.2...bin
|
||||
# Default Target is ESP8266 D1 Mini
|
||||
$ ./cangrow.sh build
|
||||
|
||||
# Compile for ESP32 D1 Mini
|
||||
$ export BOARD="esp32:esp32:d1_mini32"
|
||||
$ ./cangrow.sh build
|
||||
|
||||
# Build and webupload to IP
|
||||
$ export IP="192.168.4.69"
|
||||
$ ./cangrow.sh build # need to make .bin first
|
||||
$ ./cangrow.sh webupload # upload
|
||||
|
||||
# listen to serial monitor on /dev/ttyUSB2
|
||||
$ export TTY="/dev/ttyUSB2"
|
||||
./cangrow.sh monitor
|
||||
```
|
||||
|
||||
I wrote this project using [Geany IDE. ](https://www.geany.org/). The Geany Projectfile is also included, just run
|
||||
```sh
|
||||
$ geany CanGrow.geany
|
||||
```
|
||||
|
||||
I wrote this project using [Geany IDE. ](https://www.geany.org/). The Geany Projectfile is also included.
|
||||
Compiling is done with [arduino-cli](https://github.com/arduino/arduino-cli). It is supposed to be in
|
||||
`~/.local/bin/arduino-cli`.
|
||||
**F8 compiles** the project, **F9 uploads** firmware to /dev/ttyUSB0. You can change these settings for .ino and .h files
|
||||
in Project -> Settings -> Create/Make.
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 372 KiB After Width: | Height: | Size: 291 KiB |
|
@ -1,3 +1,4 @@
|
|||
board_manager:
|
||||
additional_urls:
|
||||
- http://arduino.esp8266.com/stable/package_esp8266com_index.json
|
||||
- https://espressif.github.io/arduino-esp32/package_esp32_index.json
|
145
Arduino/CanGrow/cangrow.sh
Executable file
145
Arduino/CanGrow/cangrow.sh
Executable file
|
@ -0,0 +1,145 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
|
||||
test -z $TTY && TTY="/dev/ttyUSB0"
|
||||
test -z $IP && IP="192.168.4.20"
|
||||
test -z $VER && VER="0.2-dev"
|
||||
test -z $BOARD && BOARD="esp8266:esp8266:d1_mini_clone"
|
||||
#test -z $BOARD && BOARD="esp32:esp32:d1_mini32"
|
||||
|
||||
BUILD="$(git rev-parse --short HEAD)-$(echo $BOARD | cut -d : -f1)-$(date '+%Y%m%d%H%M%S')"
|
||||
|
||||
ACLI="$HOME/.local/bin/arduino-cli"
|
||||
ACLI_CMD="$ACLI --config-file arduino-cli.yml"
|
||||
test -z $BUILDDIR && BUILDDIR="build"
|
||||
|
||||
|
||||
function help() {
|
||||
echo "$0 [setup|build|upload|webupload|monitor]"
|
||||
echo "setup: setup build environment, download arduino-cli, install all dependencies for arduino ide"
|
||||
echo "build: build firmware binary. will be saved into ${BUILDDIR}/"
|
||||
echo "upload: upload firmware by serial connection $TTY"
|
||||
echo "webupload: upload firmware with webupload to $IP"
|
||||
echo "monitor: serial monitor $TTY"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function check_acli() {
|
||||
if [ ! -x $ACLI ]
|
||||
then
|
||||
echo "$ACLI does not exist nor is executable. Please run '$0 setup' first"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
test -z $1 && help
|
||||
|
||||
case $1 in
|
||||
s|setup)
|
||||
ACLI_DIR="$(dirname $ACLI)"
|
||||
declare -a CORES=(
|
||||
"esp8266:esp8266@3.1.2"
|
||||
"esp32:esp32@3.0.5"
|
||||
)
|
||||
declare -a LIBS=(
|
||||
"Adafruit SSD1306@2.5.12"
|
||||
"Adafruit BME280 Library@2.2.4"
|
||||
"ArduinoJson@7.2.0"
|
||||
"NTPClient@3.2.1"
|
||||
"Time@1.6.1"
|
||||
"ESP Async WebServer@3.3.17"
|
||||
"Async TCP@3.2.10"
|
||||
"ESPAsyncTCP@1.2.4"
|
||||
)
|
||||
echo ":: Setting up build environment for CanGrow Firmware."
|
||||
echo " This will download the binary for arduino-cli and install"
|
||||
echo " the packages for the arduino ide from the debian repository."
|
||||
echo " !! This script is meant to be executed on a Debian stable (bookworm) system !!"
|
||||
echo ""
|
||||
echo ":: Press Enter to continue"
|
||||
read
|
||||
echo ""
|
||||
echo ":: Installing Arduino IDE packages with apt, please enter sudo password:"
|
||||
sudo apt update || exit 1
|
||||
sudo apt install arduino python3 python3-serial wget curl xxd || exit 1
|
||||
echo ":: Ensure directory ${ACLI_DIR} is present"
|
||||
test -d ${ACLI_DIR} || mkdir -p ${ACLI_DIR}
|
||||
echo ":: Please ensure ${ACLI_DIR} is in your \$PATH, I wont do it."
|
||||
echo ""
|
||||
echo ":: Downloading arduino-cli 1.0.0 into ${ACLI_DIR}/"
|
||||
wget -O - "https://github.com/arduino/arduino-cli/releases/download/v1.0.4/arduino-cli_1.0.4_Linux_64bit.tar.gz" | tar -C ${ACLI_DIR} -zxvf - arduino-cli
|
||||
chmod +x ${ACLI}
|
||||
echo ""
|
||||
echo ":: Installing ESP8266 and ESP32 cores for Arduino"
|
||||
for core in ${!CORES[@]}
|
||||
do
|
||||
${ACLI_CMD} core install ${CORES[$core]}
|
||||
done
|
||||
echo ":: Installing Arduino libraries"
|
||||
${ACLI_CMD} lib update-index || exit 1
|
||||
for lib in ${!LIBS[@]}
|
||||
do
|
||||
echo " - ${LIBS[$lib]}"
|
||||
done
|
||||
|
||||
for lib in ${!LIBS[@]}
|
||||
do
|
||||
${ACLI_CMD} lib install "${LIBS[$lib]}" || exit 1
|
||||
done
|
||||
echo ""
|
||||
echo ":: Setup build environment done! You can now build the firmware"
|
||||
echo " with: $0 build"
|
||||
|
||||
;;
|
||||
b|build)
|
||||
check_acli
|
||||
ACLI_CMD="${ACLI_CMD} --output-dir ${BUILDDIR}"
|
||||
echo ":: Building firmware $VER $BUILD, target dir: ${BUILDDIR}/"
|
||||
|
||||
test -d ${BUILDDIR} || mkdir ${BUILDDIR}
|
||||
|
||||
|
||||
# esp8266 and esp32 compiler have to use different compile flags for VER and BUILD
|
||||
if [ "$(echo $BOARD | cut -d : -f1)" == "esp8266" ]
|
||||
then
|
||||
${ACLI_CMD} --no-color compile -b ${BOARD} --build-property "build.extra_flags=-DCANGROW_VER=\"${VER}\" -DCANGROW_BUILD=\"${BUILD}\"" "CanGrow.ino" || exit 1
|
||||
elif [ "$(echo $BOARD | cut -d : -f1)" == "esp32" ]
|
||||
then
|
||||
${ACLI_CMD} --no-color compile -b ${BOARD} --build-property "build.defines=-DCANGROW_VER=\"${VER}\" -DCANGROW_BUILD=\"${BUILD}\"" "CanGrow.ino" || exit 1
|
||||
fi
|
||||
|
||||
cp ${BUILDDIR}/CanGrow.ino.bin ${BUILDDIR}/CanGrow_v${VER}_${BUILD}.bin
|
||||
;;
|
||||
u|upload)
|
||||
check_acli
|
||||
echo ":: Build and upload firmware $VER $BUILD to $TTY"
|
||||
|
||||
test -d build || mkdir build
|
||||
|
||||
# esp8266 and esp32 compiler have to use different compile flags for VER and BUILD
|
||||
if [ "$(echo $BOARD | cut -d : -f1)" == "esp8266" ]
|
||||
then
|
||||
${ACLI_CMD} --no-color compile -b ${BOARD} --build-property "build.extra_flags=-DCANGROW_VER=\"${VER}\" -DCANGROW_BUILD=\"${BUILD}\"" ${ACLI_BUILD_OPTS} -u -p $TTY "CanGrow.ino"
|
||||
elif [ "$(echo $BOARD | cut -d : -f1)" == "esp32" ]
|
||||
then
|
||||
${ACLI_CMD} --no-color compile -b ${BOARD} --build-property "build.defines=-DCANGROW_VER=\"${VER}\" -DCANGROW_BUILD=\"${BUILD}\"" ${ACLI_BUILD_OPTS} -u -p $TTY "CanGrow.ino"
|
||||
fi
|
||||
|
||||
;;
|
||||
w|webupload)
|
||||
test -z "$2" && UPLOAD_FILE="${BUILDDIR}/CanGrow.ino.bin"
|
||||
test -n "$2" && UPLOAD_FILE="$2"
|
||||
|
||||
echo ":: Uploading $UPLOAD_FILE to $IP"
|
||||
curl -v http://$IP/system/update -X POST -H 'Content-Type: multipart/form-data' -F "firmware=@${UPLOAD_FILE}"
|
||||
echo
|
||||
;;
|
||||
m|mon|monitor)
|
||||
check_acli
|
||||
echo ":: Open serial monitor $TTY"
|
||||
${ACLI_CMD} monitor -c baudrate=115200 -b ${BOARD} -p $TTY
|
||||
;;
|
||||
*)
|
||||
help
|
||||
;;
|
||||
esac
|
282
Arduino/CanGrow/include/CanGrow.h
Normal file
282
Arduino/CanGrow/include/CanGrow.h
Normal file
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow.h - main header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
//#include "CanGrow_Version.h"
|
||||
|
||||
/* ensure the code will also compile when CANGROW_VER and CANGROW_BUILD
|
||||
* are not defined by the compiler arguments
|
||||
* like -DCANGROW_VER="0.x-dev" or -DCANGROW_BUILD="commitid-core-timestamp"
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
* Constants
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CANGROW_VER
|
||||
#define CANGROW_VER "0.x-dev"
|
||||
#endif
|
||||
#ifndef CANGROW_BUILD
|
||||
#define CANGROW_BUILD "0000"
|
||||
#endif
|
||||
|
||||
#define CANGROW_SSID "CanGrow-unconfigured"
|
||||
|
||||
/* actual structure initialization for GPIO_Index is done within the header files
|
||||
* for ESP32 and ESP8266
|
||||
*
|
||||
* GPIO_Index.note explenation:
|
||||
* 1 - BOOTFAILS_LOW: BootFails when LOW
|
||||
* 2 - BOOTFAILS_HIGH: BootFails when HIGH
|
||||
* 3 - FLASHMODE_LOW: FlashMode needs LOW to enter
|
||||
* 4 - INPUT_ONLY: Input Only
|
||||
* 5 - NO_PWM: No PWM output
|
||||
* 6 - PWM_BOOT: PWM at boot time
|
||||
*/
|
||||
const byte BOOTFAILS_LOW = 1;
|
||||
const byte BOOTFAILS_HIGH = 2;
|
||||
const byte FLASHMODE_LOW = 3;
|
||||
const byte INPUT_ONLY = 4;
|
||||
const byte NO_PWM = 5;
|
||||
const byte HIGH_BOOT = 6;
|
||||
|
||||
char BOOTFAILS_LOW_descr[] = "BF_LOW";
|
||||
char BOOTFAILS_HIGH_descr[] = "BF_HIGH";
|
||||
char FLASMODE_LOW_descr[] = "FM_LOW";
|
||||
char INPUT_ONLY_descr[] = "IN_ONLY";
|
||||
char NO_PWM_descr[] = "NO_PWM";
|
||||
char HIGH_BOOT_descr[] = "B_HIGH";
|
||||
|
||||
const char * GPIO_Index_note_descr[] = {
|
||||
NULL, // 0 - no note
|
||||
BOOTFAILS_LOW_descr, // 1
|
||||
BOOTFAILS_HIGH_descr, // 2
|
||||
FLASMODE_LOW_descr, // 3
|
||||
INPUT_ONLY_descr, // 4
|
||||
NO_PWM_descr, // 5
|
||||
HIGH_BOOT_descr, // 6
|
||||
};
|
||||
|
||||
/*
|
||||
* Output Type
|
||||
*/
|
||||
|
||||
// 0 is unconfigured
|
||||
const byte Output_Type_total = 4;
|
||||
|
||||
const byte OUTPUT_TYPE_GPIO = 1;
|
||||
const byte OUTPUT_TYPE_I2C = 2;
|
||||
const byte OUTPUT_TYPE_WEB = 3;
|
||||
|
||||
char OUTPUT_TYPE_GPIO_descr[] = "GPIO";
|
||||
char OUTPUT_TYPE_I2C_descr[] = "I2C";
|
||||
char OUTPUT_TYPE_WEB_descr[] = "Webcall";
|
||||
|
||||
const char * Output_Type_descr[] = {
|
||||
NULL, // 0 - no description because 0 means unconfigured
|
||||
OUTPUT_TYPE_GPIO_descr,
|
||||
OUTPUT_TYPE_I2C_descr,
|
||||
OUTPUT_TYPE_WEB_descr,
|
||||
};
|
||||
|
||||
/*
|
||||
* Output Device
|
||||
*/
|
||||
// 0 is unconfigured
|
||||
const byte Output_Device_total = 7;
|
||||
|
||||
const byte OUTPUT_DEVICE_LIGHT = 1;
|
||||
const byte OUTPUT_DEVICE_FAN = 2;
|
||||
const byte OUTPUT_DEVICE_PUMP = 3;
|
||||
const byte OUTPUT_DEVICE_HUMIDIFIER = 4;
|
||||
const byte OUTPUT_DEVICE_DEHUMIDIFIER = 5;
|
||||
const byte OUTPUT_DEVICE_HEATING = 6;
|
||||
|
||||
char OUTPUT_DEVICE_LIGHT_descr[] = "Light";
|
||||
char OUTPUT_DEVICE_FAN_descr[] = "Fan";
|
||||
char OUTPUT_DEVICE_PUMP_descr[] = "Pump";
|
||||
char OUTPUT_DEVICE_HUMIDIFIER_descr[] = "Humidifier";
|
||||
char OUTPUT_DEVICE_DEHUMIDIFIER_descr[] = "Dehumidifier";
|
||||
char OUTPUT_DEVICE_HEATING_descr[] = "Heating";
|
||||
|
||||
|
||||
const char * Output_Device_descr[] = {
|
||||
NULL, // 0 - no description because 0 means unconfigured
|
||||
OUTPUT_DEVICE_LIGHT_descr,
|
||||
OUTPUT_DEVICE_FAN_descr,
|
||||
OUTPUT_DEVICE_PUMP_descr,
|
||||
OUTPUT_DEVICE_HUMIDIFIER_descr,
|
||||
OUTPUT_DEVICE_DEHUMIDIFIER_descr,
|
||||
OUTPUT_DEVICE_HEATING_descr,
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct GPIO_Index {
|
||||
const byte gpio;
|
||||
const byte note;
|
||||
};
|
||||
|
||||
const byte Max_Outputs = 16;
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* Config
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Config WiFi
|
||||
*/
|
||||
struct Config_WiFi {
|
||||
char ssid[32];
|
||||
char password[64];
|
||||
bool dhcp;
|
||||
byte ip[4] = {192,168,4,20};
|
||||
byte netmask[4] = {255,255,255,0};
|
||||
byte gateway[4] = {0,0,0,0};
|
||||
byte dns[4] = {0,0,0,0};
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Config System
|
||||
*/
|
||||
|
||||
struct Config_System_Output {
|
||||
|
||||
/*
|
||||
* Config System Output
|
||||
*
|
||||
* - output_type: output type like GPIO, I2C, URL
|
||||
* 1 - GPIO
|
||||
* 2 - I2C
|
||||
* 3 - Web
|
||||
* - device: what this output is connected to
|
||||
* 1 - Light
|
||||
* 2 - Fan
|
||||
* 3 - Pump
|
||||
* 4 - Humudifier
|
||||
* 5 - Dehumidifier
|
||||
* 6 - Heating
|
||||
* - name: name of output
|
||||
* - enabled: enable output
|
||||
* - gpio: which gpio is used
|
||||
* - gpio_invert: invert gpio output
|
||||
* - gpio_pwm: enable pwm for output
|
||||
* - i2c:
|
||||
* - webcall_host: ip to smart plug (tasmota e.g.)
|
||||
* - webcall_path_on: GET request path to turn ON
|
||||
* - webcall_path_off: GET request path to turn OFF
|
||||
|
||||
*
|
||||
*/
|
||||
byte type[Max_Outputs];
|
||||
byte device[Max_Outputs];
|
||||
char name[Max_Outputs][32];
|
||||
bool enabled[Max_Outputs];
|
||||
byte gpio[Max_Outputs];
|
||||
bool gpio_pwm[Max_Outputs];
|
||||
bool gpio_invert[Max_Outputs];
|
||||
char i2c[Max_Outputs][8];
|
||||
char webcall_host[Max_Outputs][32];
|
||||
char webcall_path_on[Max_Outputs][32];
|
||||
char webcall_path_off[Max_Outputs][32];
|
||||
};
|
||||
|
||||
/* main System struct */
|
||||
struct Config_System {
|
||||
byte ntpOffset;
|
||||
unsigned short maintenanceDuration;
|
||||
char esp32camIp[16];
|
||||
char httpUser[32];
|
||||
char httpPass[32];
|
||||
bool httpLogSerial;
|
||||
unsigned short schedulerInterval = 1000;
|
||||
Config_System_Output output;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Config Grow
|
||||
*/
|
||||
struct Config_Grow {
|
||||
char growName[64] = "CanGrow";
|
||||
unsigned short dayOfGrow;
|
||||
byte daysSeed;
|
||||
byte daysVeg;
|
||||
byte daysBloom;
|
||||
byte lightHoursVeg;
|
||||
byte lightHoursBloom;
|
||||
byte sunriseHour;
|
||||
byte sunriseMinute;
|
||||
bool sunFade;
|
||||
byte sunFadeDuration;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* main Config struct
|
||||
*/
|
||||
struct Config {
|
||||
char test[16] = "123";
|
||||
Config_WiFi wifi;
|
||||
Config_System system;
|
||||
Config_Grow grow;
|
||||
|
||||
|
||||
};
|
||||
|
||||
Config config;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
* Global Runtime variables
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// do we need a restart? (e.g. after wifi settings change)
|
||||
bool needRestart = false;
|
||||
// this triggers Restart() from the main loop
|
||||
bool doRestart = false;
|
||||
// previous value of millis within the scheduler loop
|
||||
unsigned long schedulerPrevMillis = 0;
|
114
Arduino/CanGrow/include/CanGrow_Core.h
Normal file
114
Arduino/CanGrow/include/CanGrow_Core.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_Core.h - core stuff header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// blink fast with the built in LED in an infinite loop
|
||||
void Restart() {
|
||||
Serial.println(":: [Core:Restart] got triggered, restarting in 2 seconds");
|
||||
byte i = 0;
|
||||
while(i <= 16) {
|
||||
if(i % 2) {
|
||||
digitalWrite(PinWIPE, 1 - PinWIPE_default);
|
||||
|
||||
} else {
|
||||
digitalWrite(PinWIPE, PinWIPE_default);
|
||||
|
||||
}
|
||||
i++;
|
||||
delay(125);
|
||||
}
|
||||
ESP.restart();
|
||||
|
||||
}
|
||||
|
||||
|
||||
// IP2Char helper function to convert ip arrarys to char arrays
|
||||
char* IP2Char(IPAddress ipaddr){
|
||||
// https://forum.arduino.cc/t/trouble-returning-char-array-string/473246/6
|
||||
static char buffer[18];
|
||||
sprintf(buffer, "%d.%d.%d.%d", ipaddr[0], ipaddr[1], ipaddr[2], ipaddr[3] );
|
||||
return buffer;
|
||||
}
|
||||
|
||||
byte Give_Free_OutputId() {
|
||||
byte outputId_free;
|
||||
for(byte i=0; i < Max_Outputs; i++) {
|
||||
if(config.system.output.type[i] > 0) {
|
||||
// here i define that 255 stands for "no more free outputs"
|
||||
outputId_free = 255;
|
||||
} else {
|
||||
outputId_free = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#ifndef DEBUG
|
||||
Serial.printf("DB [Core:Give_Free_OutputId] next free output id: %d\n", outputId_free);
|
||||
#endif
|
||||
return outputId_free;
|
||||
}
|
||||
|
||||
//bool Check_OutputId_Used(byte outputId) {
|
||||
|
||||
//}
|
||||
|
||||
// checks if GPIO is already in use by output or sensor
|
||||
bool Check_GPIOindex_Used(byte gpio) {
|
||||
bool used;
|
||||
#ifndef DEBUG
|
||||
Serial.printf("DB [Core:Check_GPIOindex_Used] check GPIO: %d\n", gpio);
|
||||
#endif
|
||||
// go through each outputid
|
||||
for(byte i=0; i < Max_Outputs; i++) {
|
||||
#ifndef DEBUG
|
||||
//Serial.printf("DB [Core:Check_GPIOindex_Used] OutputId: %d , type: %d\n", i, config.system.output.type[i]);
|
||||
#endif
|
||||
// check if output type is gpio
|
||||
if(config.system.output.type[i] == OUTPUT_TYPE_GPIO) {
|
||||
#ifndef DEBUG
|
||||
Serial.printf("DB [Core:Check_GPIOindex_Used] OutputId: %d is GPIO (type %d)\n", i, config.system.output.type[i]);
|
||||
#endif
|
||||
// check if gpio id is already in use
|
||||
if(config.system.output.gpio[i] == gpio) {
|
||||
#ifndef DEBUG
|
||||
Serial.printf("DB [Core:Check_GPIOindex_Used] output.gpio[%d](%d) == GPIO %d\n", i, config.system.output.gpio[i], gpio);
|
||||
#endif
|
||||
used = true;
|
||||
break;
|
||||
} else {
|
||||
used = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifndef DEBUG
|
||||
Serial.printf("DB [Core:Check_GPIOindex_Used] GPIO: %d, used: %d\n", gpio, used);
|
||||
#endif
|
||||
// check sensors
|
||||
|
||||
return used;
|
||||
}
|
87
Arduino/CanGrow/include/CanGrow_ESP32.h
Normal file
87
Arduino/CanGrow/include/CanGrow_ESP32.h
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_ESP32.h - ESP32 specific header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#ifdef ESP32
|
||||
|
||||
#define PinWIPE 2
|
||||
#define PinWIPE_default LOW
|
||||
#define Pin_I2C_SCL = 22
|
||||
#define Pin_I2C_SDA = 21
|
||||
|
||||
/* https://randomnerdtutorials.com/esp32-pinout-reference-gpios/
|
||||
*
|
||||
* free usable pins
|
||||
* - GPIO 0 PU OK outputs PWM signal at boot, must be LOW to enter flashing mode
|
||||
* - GPIO 4 OK OK
|
||||
* - GPIO 5 OK OK outputs PWM signal at boot, strapping pin
|
||||
* - GPIO 12 OK OK boot fails if pulled high, strapping pin
|
||||
* - GPIO 13 OK OK
|
||||
* - GPIO 14 OK OK outputs PWM signal at boot
|
||||
* - GPIO 15 OK OK outputs PWM signal at boot, strapping pin
|
||||
* - GPIO 16 OK OK
|
||||
* - GPIO 17 OK OK
|
||||
* - GPIO 18 OK OK
|
||||
* - GPIO 19 OK OK
|
||||
* - GPIO 23 OK OK
|
||||
* - GPIO 25 OK OK
|
||||
* - GPIO 26 OK OK
|
||||
* - GPIO 27 OK OK
|
||||
* - GPIO 32 OK OK
|
||||
* - GPIO 33 OK OK
|
||||
* - GPIO 34 OK input only
|
||||
* - GPIO 35 OK input only
|
||||
* - GPIO 36 OK input only
|
||||
* - GPIO 39 OK input only
|
||||
*/
|
||||
|
||||
|
||||
//
|
||||
const byte GPIOindex_length = 21;
|
||||
// initialize pinIndex with all usable GPIOs
|
||||
GPIO_Index GPIOindex[] = { { 0, FLASHMODE_LOW },
|
||||
{ 4 },
|
||||
{ 5 },
|
||||
{ 12, BOOTFAILS_HIGH },
|
||||
{ 13 },
|
||||
{ 14 },
|
||||
{ 15 },
|
||||
{ 16 },
|
||||
{ 17 },
|
||||
{ 18 },
|
||||
{ 19 },
|
||||
{ 23 },
|
||||
{ 25 },
|
||||
{ 26 },
|
||||
{ 27 },
|
||||
{ 32 },
|
||||
{ 33 },
|
||||
{ 34, INPUT_ONLY },
|
||||
{ 35, INPUT_ONLY },
|
||||
{ 36, INPUT_ONLY },
|
||||
{ 39, INPUT_ONLY } };
|
||||
#endif
|
57
Arduino/CanGrow/include/CanGrow_ESP8266.h
Normal file
57
Arduino/CanGrow/include/CanGrow_ESP8266.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_ESP8266.h - ESP8266 specific header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#ifdef ESP8266
|
||||
|
||||
// GPIO 2 Boot fails if pulled to LOW
|
||||
#define PinWIPE 2
|
||||
#define PinWIPE_default HIGH
|
||||
#define Pin_I2C_SCL = 5
|
||||
#define Pin_I2C_SDA = 4
|
||||
|
||||
/* https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/
|
||||
*
|
||||
* free usable pins
|
||||
* - GPIO 0 / D3 boot fails if pulled LOW
|
||||
* - GPIO 12 / D6
|
||||
* - GPIO 13 / D7
|
||||
* - GPIO 14 / D5
|
||||
* - GPIO 15 / D8 Boot fails if pulled HIGH
|
||||
* - GPIO 16 / D0
|
||||
*/
|
||||
|
||||
const byte GPIOindex_length = 6;
|
||||
// initialize pinIndex with all usable GPIOs
|
||||
GPIO_Index GPIOindex[] = { { 0, BOOTFAILS_LOW },
|
||||
{ 12 },
|
||||
{ 13 },
|
||||
{ 14 },
|
||||
{ 15, BOOTFAILS_HIGH },
|
||||
{ 16, NO_PWM } };
|
||||
|
||||
#endif
|
583
Arduino/CanGrow/include/CanGrow_LittleFS.h
Normal file
583
Arduino/CanGrow/include/CanGrow_LittleFS.h
Normal file
|
@ -0,0 +1,583 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_LittleFS.h - LittleFS handling header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#define CANGROW_CFG "/config.json"
|
||||
|
||||
// LittleFS auto format
|
||||
#define FORMAT_LITTLEFS_IF_FAILED true
|
||||
|
||||
void LFS_Init() {
|
||||
Serial.println(":: [LittleFS] initializing");
|
||||
// ESP8266 crashes with first argument set
|
||||
#ifdef ESP8266
|
||||
if(!LittleFS.begin()) {
|
||||
#endif
|
||||
// ESP32 works, do autoformat if mount fails
|
||||
#ifdef ESP32
|
||||
if(!LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED)) {
|
||||
#endif
|
||||
Serial.println("!! [LittleFS] FAILED initializing. You have to format LittleFS manually. Will now restart.");
|
||||
Restart();
|
||||
}
|
||||
}
|
||||
|
||||
void LFS_Format() {
|
||||
Serial.println(":: [LittleFS] formatting...");
|
||||
// ESP32 LittleFS needs begin() first, otherwise it would crash
|
||||
// ESP8266 does not need it, so we leave it
|
||||
#ifdef ESP32
|
||||
LittleFS.begin();
|
||||
#endif
|
||||
if(LittleFS.format()) {
|
||||
Serial.println(":: [LittleFS] done formatting");
|
||||
} else {
|
||||
Serial.println("!! [LittleFS] FAILED formatting");
|
||||
}
|
||||
}
|
||||
|
||||
bool existFile(const char *path) {
|
||||
#ifdef ESP8266
|
||||
File file = LittleFS.open(path, "r");
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(path);
|
||||
#endif
|
||||
|
||||
if (!file) {
|
||||
Serial.printf(":: [LittleFS] file does not exist: %s\n", path);
|
||||
file.close();
|
||||
return false;
|
||||
} else {
|
||||
Serial.printf(":: [LittleFS] file does exist: %s\n", path);
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void readFile(const char *path) {
|
||||
#ifdef ESP8266
|
||||
File file = LittleFS.open(path, "r");
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(path);
|
||||
#endif
|
||||
|
||||
if (!file) {
|
||||
Serial.printf(":: [LittleFS] FAILED to open file for reading: %s\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.printf(":: [LittleFS] file content: %s\n", path);
|
||||
Serial.println("----");
|
||||
while (file.available()) { Serial.write(file.read()); }
|
||||
Serial.println("\n----");
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
void writeFile(const char *path, const char *message) {
|
||||
#ifdef ESP8266
|
||||
File file = LittleFS.open(path, "w");
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(path, FILE_WRITE);
|
||||
#endif
|
||||
|
||||
if (!file) {
|
||||
Serial.printf(":: [LittleFS] FAILED to open file for writing: %s\n", path);
|
||||
return;
|
||||
}
|
||||
if (file.print(message)) {
|
||||
Serial.printf(":: [LittleFS] file written: %s\n", path);
|
||||
} else {
|
||||
Serial.printf(":: [LittleFS] writing file FAILED: %s\n", path);
|
||||
}
|
||||
//delay(2000); // Make sure the CREATE and LASTWRITE times are different
|
||||
file.close();
|
||||
}
|
||||
|
||||
void deleteFile(const char *path) {
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(path, FILE_WRITE);
|
||||
#endif
|
||||
|
||||
Serial.printf(":: [LittleFS] deleting file: %s\n", path);
|
||||
#ifdef ESP8266
|
||||
if (LittleFS.remove(path)) {
|
||||
#endif
|
||||
#ifdef ESP32
|
||||
if (fs.remove(path)) {
|
||||
#endif
|
||||
Serial.printf(":: [LittleFS] deleted file: %s\n", path);
|
||||
} else {
|
||||
Serial.printf(":: [LittleFS] deleting file FAILED: %s\n", path);
|
||||
}
|
||||
}
|
||||
|
||||
// https://arduinojson.org/v7/example/config/
|
||||
// https://arduinojson.org/v7/assistant/
|
||||
bool LoadConfig() {
|
||||
#ifdef ESP8266
|
||||
File file = LittleFS.open(CANGROW_CFG, "r");
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(CANGROW_CFG);
|
||||
#endif
|
||||
|
||||
Serial.printf(":: [LittleFS:LoadConfig] loading config from: %s\n", CANGROW_CFG);
|
||||
|
||||
JsonDocument doc;
|
||||
// Deserialize the JSON document
|
||||
DeserializationError error = deserializeJson(doc, file);
|
||||
if(error) {
|
||||
Serial.printf(":: [LittleFS:LoadConfig] FAILED to load config: %s\n", CANGROW_CFG);
|
||||
if (existFile(CANGROW_CFG)) {
|
||||
readFile(CANGROW_CFG);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* put json values into config structs
|
||||
*/
|
||||
|
||||
// Copy strings from the JsonDocument to the Config struct as char
|
||||
strlcpy(config.test, doc["test"], sizeof(config.test));
|
||||
|
||||
/* WiFi */
|
||||
JsonObject objWifi = doc["wifi"][0];
|
||||
strlcpy(config.wifi.ssid, objWifi["ssid"], sizeof(config.wifi.ssid));
|
||||
strlcpy(config.wifi.password, objWifi["password"], sizeof(config.wifi.password));
|
||||
// Copy bool / int directly into struct
|
||||
config.wifi.dhcp = objWifi["dhcp"];
|
||||
// load the ip addresses as array
|
||||
|
||||
for(byte i=0; i < 4 ; i++) {
|
||||
config.wifi.ip[i] = objWifi["ip"][i];
|
||||
config.wifi.netmask[i] = objWifi["netmask"][i];
|
||||
config.wifi.gateway[i] = objWifi["gateway"][i];
|
||||
config.wifi.dns[i] = objWifi["dns"][i];
|
||||
}
|
||||
|
||||
|
||||
/* System */
|
||||
JsonObject objSystem = doc["system"][0];
|
||||
config.system.ntpOffset = objSystem["ntpOffset"];
|
||||
config.system.maintenanceDuration = objSystem["maintenanceDuration"];
|
||||
strlcpy(config.system.esp32camIp, objSystem["esp32camIp"], sizeof(config.system.esp32camIp));
|
||||
strlcpy(config.system.httpUser, objSystem["httpUser"], sizeof(config.system.httpUser));
|
||||
strlcpy(config.system.httpPass, objSystem["httpPass"], sizeof(config.system.httpPass));
|
||||
config.system.httpLogSerial = objSystem["httpLogSerial"];
|
||||
config.system.schedulerInterval = objSystem["schedulerInterval"];
|
||||
|
||||
JsonObject objSystemOutput = objSystem["output"][0];
|
||||
|
||||
/* System Outputs */
|
||||
for(byte i=0; i < Max_Outputs; i++) {
|
||||
if(objSystemOutput["type"][i] > 0) {
|
||||
config.system.output.type[i] = objSystemOutput["type"][i];
|
||||
config.system.output.device[i] = objSystemOutput["device"][i];
|
||||
strlcpy(config.system.output.name[i], objSystemOutput["name"][i], sizeof(config.system.output.name[i]));
|
||||
config.system.output.enabled[i] = objSystemOutput["enabled"][i];
|
||||
// gpio
|
||||
config.system.output.gpio[i] = objSystemOutput["gpio"][i];
|
||||
config.system.output.gpio_invert[i] = objSystemOutput["gpio_invert"][i];
|
||||
config.system.output.gpio_pwm[i] = objSystemOutput["gpio_pwm"][i];
|
||||
// i2c
|
||||
strlcpy(config.system.output.i2c[i], objSystemOutput["i2c"][i], sizeof(config.system.output.i2c[i]));
|
||||
// web
|
||||
strlcpy(config.system.output.webcall_host[i], objSystemOutput["webcall_host"][i], sizeof(config.system.output.webcall_host[i]));
|
||||
strlcpy(config.system.output.webcall_path_on[i], objSystemOutput["webcall_path_on"][i], sizeof(config.system.output.webcall_path_on[i]));
|
||||
strlcpy(config.system.output.webcall_path_off[i], objSystemOutput["webcall_path_off"][i], sizeof(config.system.output.webcall_path_off[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/* Grow */
|
||||
JsonObject objGrow = doc["grow"][0];
|
||||
strlcpy(config.grow.growName, objGrow["growName"], sizeof(config.grow.growName));
|
||||
config.grow.dayOfGrow = objGrow["dayOfGrow"];
|
||||
config.grow.daysSeed = objGrow["daysSeed"];
|
||||
config.grow.daysVeg = objGrow["daysVeg"];
|
||||
config.grow.daysBloom = objGrow["daysBloom"];
|
||||
config.grow.lightHoursVeg = objGrow["lightHoursVeg"];
|
||||
config.grow.lightHoursBloom = objGrow["lightHoursBloom"];
|
||||
config.grow.sunriseHour = objGrow["sunriseHour"];
|
||||
config.grow.sunriseMinute = objGrow["sunriseMinute"];
|
||||
config.grow.sunFade = objGrow["sunFade"];
|
||||
config.grow.sunFadeDuration = objGrow["sunFadeDuration"];
|
||||
|
||||
|
||||
|
||||
// Close the file (Curiously, File's destructor doesn't close the file)
|
||||
file.close();
|
||||
Serial.println(":: [LittleFS:LoadConfig] config successfully loaded");
|
||||
Serial.println(":: [LittleFS:LoadConfig] --- runtime config ---");
|
||||
serializeJsonPretty(doc, Serial);
|
||||
Serial.println("");
|
||||
Serial.println(":: [LittleFS:LoadConfig] ----------------------");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SaveConfig(bool writeToSerial = false) {
|
||||
/*
|
||||
* Building config.json here
|
||||
*/
|
||||
JsonDocument doc;
|
||||
|
||||
/* Root */
|
||||
doc["test"] = config.test;
|
||||
|
||||
/* WiFi */
|
||||
JsonObject objWifi = doc["wifi"].add<JsonObject>();
|
||||
objWifi["ssid"] = config.wifi.ssid;
|
||||
objWifi["password"] = config.wifi.password;
|
||||
// save the ip addressess as array
|
||||
int i;
|
||||
for(i=0; i <4 ; i++) {
|
||||
objWifi["ip"][i] = config.wifi.ip[i];
|
||||
objWifi["netmask"][i] = config.wifi.netmask[i];
|
||||
objWifi["gateway"][i] = config.wifi.gateway[i];
|
||||
objWifi["dns"][i] = config.wifi.dns[i];
|
||||
}
|
||||
objWifi["dhcp"] = config.wifi.dhcp;
|
||||
|
||||
/* System */
|
||||
JsonObject objSystem = doc["system"].add<JsonObject>();
|
||||
objSystem["ntpOffset"] = config.system.ntpOffset;
|
||||
objSystem["maintenanceDuration"] = config.system.maintenanceDuration;
|
||||
objSystem["esp32camIp"] = config.system.esp32camIp;
|
||||
objSystem["httpUser"] = config.system.httpUser;
|
||||
objSystem["httpPass"] = config.system.httpPass;
|
||||
objSystem["httpLogSerial"] = config.system.httpLogSerial;
|
||||
objSystem["schedulerInterval"] = config.system.schedulerInterval;
|
||||
|
||||
/* System Outputs */
|
||||
JsonObject objSystemOutput = objSystem["output"].add<JsonObject>();
|
||||
for(byte i=0; i < Max_Outputs; i++) {
|
||||
if(config.system.output.type[i] > 0) {
|
||||
objSystemOutput["type"][i] = config.system.output.type[i];
|
||||
objSystemOutput["device"][i] = config.system.output.device[i];
|
||||
objSystemOutput["name"][i] = config.system.output.name[i];
|
||||
objSystemOutput["enabled"][i] = config.system.output.enabled[i];
|
||||
// gpio
|
||||
objSystemOutput["gpio"][i] = config.system.output.gpio[i];
|
||||
objSystemOutput["gpio_invert"][i] = config.system.output.gpio_invert[i];
|
||||
objSystemOutput["gpio_pwm"][i] = config.system.output.gpio_pwm[i];
|
||||
// i2c
|
||||
objSystemOutput["i2c"][i] = config.system.output.i2c[i];
|
||||
// web
|
||||
objSystemOutput["webcall_host"][i] = config.system.output.webcall_host[i];
|
||||
objSystemOutput["webcall_path_on"][i] = config.system.output.webcall_path_on[i];
|
||||
objSystemOutput["webcall_path_off"][i] = config.system.output.webcall_path_off[i];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Grow */
|
||||
JsonObject objGrow = doc["grow"].add<JsonObject>();
|
||||
objGrow["growName"] = config.grow.growName;
|
||||
objGrow["dayOfGrow"] = config.grow.dayOfGrow;
|
||||
objGrow["daysSeed"] = config.grow.daysSeed;
|
||||
objGrow["daysVeg"] = config.grow.daysVeg;
|
||||
objGrow["daysBloom"] = config.grow.daysBloom;
|
||||
objGrow["lightHoursVeg"] = config.grow.lightHoursVeg;
|
||||
objGrow["lightHoursBloom"] = config.grow.lightHoursBloom;
|
||||
objGrow["sunriseHour"] = config.grow.sunriseHour;
|
||||
objGrow["sunriseMinute"] = config.grow.sunriseMinute;
|
||||
objGrow["sunFade"] = config.grow.sunFade;
|
||||
objGrow["sunFadeDuration"] = config.grow.sunFadeDuration;
|
||||
|
||||
/*
|
||||
* END Building config.json here
|
||||
*/
|
||||
|
||||
// if writeToSerial is true, output json to serial, but do not write to LittleFS
|
||||
if(writeToSerial == false) {
|
||||
#ifdef ESP8266
|
||||
File file = LittleFS.open(CANGROW_CFG, "w");
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(CANGROW_CFG, FILE_WRITE);
|
||||
#endif
|
||||
|
||||
if (!file) {
|
||||
Serial.printf("!! [LittleFS:SaveConfig] FAILED to open configfile for writing: %s\n", CANGROW_CFG);
|
||||
return false;
|
||||
} else {
|
||||
Serial.printf(":: [LittleFS:SaveConfig] opened for writing %s\n", CANGROW_CFG);
|
||||
}
|
||||
// Serialize JSON to file
|
||||
if (serializeJson(doc, file) == 0) {
|
||||
Serial.printf("!! [LittleFS:SaveConfig] FAILED to write configfile: %s\n", CANGROW_CFG);
|
||||
} else {
|
||||
Serial.printf(":: [LittleFS:SaveConfig] successfully written %s\n", CANGROW_CFG);
|
||||
}
|
||||
file.close();
|
||||
} else {
|
||||
Serial.printf(":: [LittleFS:SaveConfig] --- %s ---\n", CANGROW_CFG);
|
||||
serializeJsonPretty(doc, Serial);
|
||||
Serial.println("");
|
||||
Serial.printf(":: [LittleFS:SaveConfig] ----------------------\n", CANGROW_CFG);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
///*
|
||||
//* ESP8266 functions
|
||||
//*/
|
||||
|
||||
///*functions from https://github.com/esp8266/Arduino/blob/master/libraries/LittleFS/examples/LittleFS_Timestamp/LittleFS_Timestamp.ino*/
|
||||
//#ifdef ESP8266
|
||||
//void listDir(const char *dirname) {
|
||||
//Serial.printf("Listing directory: %s\n", dirname);
|
||||
|
||||
//Dir root = LittleFS.openDir(dirname);
|
||||
|
||||
//while (root.next()) {
|
||||
//File file = root.openFile("r");
|
||||
//Serial.print(" FILE: ");
|
||||
//Serial.print(root.fileName());
|
||||
//Serial.print(" SIZE: ");
|
||||
//Serial.print(file.size());
|
||||
//time_t cr = file.getCreationTime();
|
||||
//time_t lw = file.getLastWrite();
|
||||
//file.close();
|
||||
//struct tm *tmstruct = localtime(&cr);
|
||||
//Serial.printf(" CREATION: %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec);
|
||||
//tmstruct = localtime(&lw);
|
||||
//Serial.printf(" LAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec);
|
||||
//}
|
||||
//}
|
||||
|
||||
|
||||
//void readFile(const char *path) {
|
||||
//Serial.printf("Reading file: %s\n", path);
|
||||
|
||||
//File file = LittleFS.open(path, "r");
|
||||
//if (!file) {
|
||||
//Serial.println("Failed to open file for reading");
|
||||
//return;
|
||||
//}
|
||||
|
||||
//Serial.print("Read from file: ");
|
||||
//while (file.available()) { Serial.write(file.read()); }
|
||||
//file.close();
|
||||
//}
|
||||
|
||||
//void writeFile(const char *path, const char *message) {
|
||||
//Serial.printf("Writing file: %s\n", path);
|
||||
|
||||
//File file = LittleFS.open(path, "w");
|
||||
//if (!file) {
|
||||
//Serial.println("Failed to open file for writing");
|
||||
//return;
|
||||
//}
|
||||
//if (file.print(message)) {
|
||||
//Serial.println("File written");
|
||||
//} else {
|
||||
//Serial.println("Write failed");
|
||||
//}
|
||||
//delay(2000); // Make sure the CREATE and LASTWRITE times are different
|
||||
//file.close();
|
||||
//}
|
||||
|
||||
//void appendFile(const char *path, const char *message) {
|
||||
//Serial.printf("Appending to file: %s\n", path);
|
||||
|
||||
//File file = LittleFS.open(path, "a");
|
||||
//if (!file) {
|
||||
//Serial.println("Failed to open file for appending");
|
||||
//return;
|
||||
//}
|
||||
//if (file.print(message)) {
|
||||
//Serial.println("Message appended");
|
||||
//} else {
|
||||
//Serial.println("Append failed");
|
||||
//}
|
||||
//file.close();
|
||||
//}
|
||||
|
||||
//void renameFile(const char *path1, const char *path2) {
|
||||
//Serial.printf("Renaming file %s to %s\n", path1, path2);
|
||||
//if (LittleFS.rename(path1, path2)) {
|
||||
//Serial.println("File renamed");
|
||||
//} else {
|
||||
//Serial.println("Rename failed");
|
||||
//}
|
||||
//}
|
||||
|
||||
//void deleteFile(const char *path) {
|
||||
//Serial.printf("Deleting file: %s\n", path);
|
||||
//if (LittleFS.remove(path)) {
|
||||
//Serial.println("File deleted");
|
||||
//} else {
|
||||
//Serial.println("Delete failed");
|
||||
//}
|
||||
//}
|
||||
//#endif
|
||||
|
||||
|
||||
///*
|
||||
//* ESP32 functions
|
||||
//*/
|
||||
|
||||
///*functions from https://github.com/espressif/arduino-esp32/blob/master/libraries/LittleFS/examples/LITTLEFS_time/LITTLEFS_time.ino*/
|
||||
//#ifdef ESP32
|
||||
//void listDir(fs::FS &fs, const char *dirname, uint8_t levels) {
|
||||
//Serial.printf("Listing directory: %s\n", dirname);
|
||||
|
||||
//File root = fs.open(dirname);
|
||||
//if (!root) {
|
||||
//Serial.println("Failed to open directory");
|
||||
//return;
|
||||
//}
|
||||
//if (!root.isDirectory()) {
|
||||
//Serial.println("Not a directory");
|
||||
//return;
|
||||
//}
|
||||
|
||||
//File file = root.openNextFile();
|
||||
//while (file) {
|
||||
//if (file.isDirectory()) {
|
||||
//Serial.print(" DIR : ");
|
||||
//Serial.print(file.name());
|
||||
//time_t t = file.getLastWrite();
|
||||
//struct tm *tmstruct = localtime(&t);
|
||||
//Serial.printf(
|
||||
//" LAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour,
|
||||
//tmstruct->tm_min, tmstruct->tm_sec
|
||||
//);
|
||||
//if (levels) {
|
||||
//listDir(fs, file.path(), levels - 1);
|
||||
//}
|
||||
//} else {
|
||||
//Serial.print(" FILE: ");
|
||||
//Serial.print(file.name());
|
||||
//Serial.print(" SIZE: ");
|
||||
//Serial.print(file.size());
|
||||
//time_t t = file.getLastWrite();
|
||||
//struct tm *tmstruct = localtime(&t);
|
||||
//Serial.printf(
|
||||
//" LAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour,
|
||||
//tmstruct->tm_min, tmstruct->tm_sec
|
||||
//);
|
||||
//}
|
||||
//file = root.openNextFile();
|
||||
//}
|
||||
//}
|
||||
|
||||
//void removeDir(fs::FS &fs, const char *path) {
|
||||
//Serial.printf("Removing Dir: %s\n", path);
|
||||
//if (fs.rmdir(path)) {
|
||||
//Serial.println("Dir removed");
|
||||
//} else {
|
||||
//Serial.println("rmdir failed");
|
||||
//}
|
||||
//}
|
||||
|
||||
//void readFile(fs::FS &fs, const char *path) {
|
||||
//Serial.printf("Reading file: %s\n", path);
|
||||
|
||||
//File file = fs.open(path);
|
||||
//if (!file) {
|
||||
//Serial.println("Failed to open file for reading");
|
||||
//return;
|
||||
//}
|
||||
|
||||
//Serial.print("Read from file: ");
|
||||
//while (file.available()) {
|
||||
//Serial.write(file.read());
|
||||
//}
|
||||
//file.close();
|
||||
//}
|
||||
|
||||
//void writeFile(fs::FS &fs, const char *path, const char *message) {
|
||||
//Serial.printf("Writing file: %s\n", path);
|
||||
|
||||
//File file = fs.open(path, FILE_WRITE);
|
||||
//if (!file) {
|
||||
//Serial.println("Failed to open file for writing");
|
||||
//return;
|
||||
//}
|
||||
//if (file.print(message)) {
|
||||
//Serial.println("File written");
|
||||
//} else {
|
||||
//Serial.println("Write failed");
|
||||
//}
|
||||
//file.close();
|
||||
//}
|
||||
|
||||
//void appendFile(fs::FS &fs, const char *path, const char *message) {
|
||||
//Serial.printf("Appending to file: %s\n", path);
|
||||
|
||||
//File file = fs.open(path, FILE_APPEND);
|
||||
//if (!file) {
|
||||
//Serial.println("Failed to open file for appending");
|
||||
//return;
|
||||
//}
|
||||
//if (file.print(message)) {
|
||||
//Serial.println("Message appended");
|
||||
//} else {
|
||||
//Serial.println("Append failed");
|
||||
//}
|
||||
//file.close();
|
||||
//}
|
||||
|
||||
//void renameFile(fs::FS &fs, const char *path1, const char *path2) {
|
||||
//Serial.printf("Renaming file %s to %s\n", path1, path2);
|
||||
//if (fs.rename(path1, path2)) {
|
||||
//Serial.println("File renamed");
|
||||
//} else {
|
||||
//Serial.println("Rename failed");
|
||||
//}
|
||||
//}
|
||||
|
||||
//void deleteFile(fs::FS &fs, const char *path) {
|
||||
//Serial.printf("Deleting file: %s\n", path);
|
||||
//if (fs.remove(path)) {
|
||||
//Serial.println("File deleted");
|
||||
//} else {
|
||||
//Serial.println("Delete failed");
|
||||
//}
|
||||
//}
|
||||
//#endif
|
138
Arduino/CanGrow/include/CanGrow_Webserver.h
Normal file
138
Arduino/CanGrow/include/CanGrow_Webserver.h
Normal file
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_Webserver.h - webserver header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* include static files files
|
||||
*/
|
||||
#include "Webserver/File_cangrow_CSS.h"
|
||||
#include "Webserver/File_cangrow_JS.h"
|
||||
#include "Webserver/File_favicon_ico.h"
|
||||
|
||||
/*
|
||||
* include webpages header files
|
||||
*/
|
||||
#include "Webserver/Header.h"
|
||||
#include "Webserver/Footer.h"
|
||||
#include "Webserver/Common.h"
|
||||
|
||||
#include "Webserver/Page_root.h"
|
||||
|
||||
#include "Webserver/Page_wifi.h"
|
||||
|
||||
#include "Webserver/Page_system.h"
|
||||
|
||||
|
||||
AsyncWebServer webserver(80);
|
||||
// load requestLogger middleware
|
||||
LoggingMiddleware requestLogger;
|
||||
|
||||
|
||||
/*
|
||||
* 404 error page begins
|
||||
*/
|
||||
|
||||
// 404 page is a good page template btw
|
||||
const char* Page_404_HTML PROGMEM = R"(%HEADER%
|
||||
<div class='warnmsg'><h1>❗ ️ 404 - not found</h1></div>
|
||||
%FOOTER%)";
|
||||
|
||||
/* processor */
|
||||
String Proc_WebPage_404(const String& var) {
|
||||
return AddHeaderFooter(var);
|
||||
}
|
||||
|
||||
// https://github.com/mathieucarbou/ESPAsyncWebServer/blob/main/examples/SimpleServer/SimpleServer.ino
|
||||
void WebserverNotFound(AsyncWebServerRequest* request) {
|
||||
request->send(404, "text/html", Page_404_HTML, Proc_WebPage_404);
|
||||
}
|
||||
|
||||
/*
|
||||
* 404 error page ends
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* setup all the webhandlers
|
||||
*/
|
||||
void Webserver_Init() {
|
||||
Serial.println(":: [Webserver] initializing");
|
||||
|
||||
/* url handler definition */
|
||||
webserver.on("/", HTTP_GET, WebPage_root);
|
||||
webserver.on("/cangrow.css", HTTP_GET, WebFile_cangrow_CSS);
|
||||
webserver.on("/cangrow.js", HTTP_GET, WebFile_cangrow_JS);
|
||||
webserver.on("/favicon.ico", HTTP_GET, WebFile_favicon_ico);
|
||||
|
||||
webserver.on("/wifi/", HTTP_GET, WebPage_wifi);
|
||||
webserver.on("/wifi/", HTTP_POST, WebPage_wifi);
|
||||
webserver.on("/system/", HTTP_GET, WebPage_system);
|
||||
webserver.on("/system/", HTTP_POST, WebPage_system);
|
||||
|
||||
webserver.on("/system/update", HTTP_GET, WebPage_system_update);
|
||||
webserver.on("/system/update", HTTP_POST, WebPage_system_update, WebPage_system_update_ApplyUpdate);
|
||||
|
||||
webserver.on("/system/restart", HTTP_GET, WebPage_system_restart);
|
||||
webserver.on("/system/restart", HTTP_POST, WebPage_system_restart);
|
||||
|
||||
webserver.on("/system/wipe", HTTP_GET, WebPage_system_wipe);
|
||||
webserver.on("/system/wipe", HTTP_POST, WebPage_system_wipe);
|
||||
|
||||
webserver.on("/system/output/", HTTP_GET, WebPage_system_output);
|
||||
webserver.on("/system/output/", HTTP_POST, WebPage_system_output);
|
||||
|
||||
webserver.on("/system/output/add", HTTP_GET, WebPage_system_output_add);
|
||||
webserver.on("/system/output/add", HTTP_POST, WebPage_system_output_add);
|
||||
|
||||
requestLogger.setOutput(Serial);
|
||||
// this activates the middleware
|
||||
if(config.system.httpLogSerial == true) {
|
||||
Serial.println(":: [Webserver] serial logging: enabled");
|
||||
webserver.addMiddleware(&requestLogger);
|
||||
} else {
|
||||
Serial.println(":: [Webserver] serial logging: disabled");
|
||||
}
|
||||
|
||||
webserver.onNotFound(WebserverNotFound);
|
||||
|
||||
// Workaround, see comment at
|
||||
// https://github.com/mathieucarbou/ESPAsyncWebServer/blob/main/docs/index.md#scanning-for-available-wifi-networks
|
||||
// call the network scan once, so there are some values at the first call
|
||||
// of the wifi settings page. otherwise the first call of the wifi scan would return
|
||||
// an empty list of networks
|
||||
Serial.println(":: [Webserver] call [wifi:ScanNetworks] to workaround empty scan results bug");
|
||||
WebPage_wifi_ScanNetworks();
|
||||
|
||||
webserver.begin();
|
||||
Serial.println(":: [Webserver] ready to serve");
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
81
Arduino/CanGrow/include/CanGrow_Wifi.h
Normal file
81
Arduino/CanGrow/include/CanGrow_Wifi.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_Wifi.h - Wifi stuff header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
void Wifi_Connect() {
|
||||
Serial.printf(":: [WiFi] connecting to SSID: %s\n", config.wifi.ssid);
|
||||
WiFi.begin(config.wifi.ssid, config.wifi.password);
|
||||
if(config.wifi.dhcp == false) {
|
||||
Serial.println(":: [WiFi] using static ip configuration:");
|
||||
Serial.printf(":: [WiFi] IP : %s\n", IP2Char(config.wifi.ip));
|
||||
Serial.printf(":: [WiFi] Netmask: %s\n", IP2Char(config.wifi.netmask));
|
||||
Serial.printf(":: [WiFi] Gateway: %s\n", IP2Char(config.wifi.gateway));
|
||||
Serial.printf(":: [WiFi] DNS : %s\n", IP2Char(config.wifi.dns));
|
||||
|
||||
WiFi.config(config.wifi.ip, config.wifi.dns, config.wifi.gateway, config.wifi.netmask);
|
||||
} else {
|
||||
Serial.println(":: [WiFi] using DHCP for ip configuration");
|
||||
}
|
||||
|
||||
Serial.print(":: [WiFi] ");
|
||||
// wait until WiFi connection is established
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("CONNECTED!");
|
||||
|
||||
if(config.wifi.dhcp == true) {
|
||||
Serial.println(":: [WiFi] DHCP offered ip configuration:");
|
||||
Serial.printf(":: [WiFi] IP : %s\n", IP2Char(WiFi.localIP()));
|
||||
Serial.printf(":: [WiFi] Netmask: %s\n", IP2Char(WiFi.subnetMask()));
|
||||
Serial.printf(":: [WiFi] Gateway: %s\n", IP2Char(WiFi.gatewayIP()));
|
||||
Serial.printf(":: [WiFi] DNS : %s\n", IP2Char(WiFi.dnsIP()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Wifi_AP() {
|
||||
Serial.printf(":: [WiFi] create access point: %s\n", CANGROW_SSID);
|
||||
WiFi.softAPConfig(config.wifi.ip, config.wifi.gateway, config.wifi.netmask);
|
||||
WiFi.softAP(CANGROW_SSID);
|
||||
Serial.println(":: [WiFi] access point started:");
|
||||
Serial.printf(":: [WiFi] IP : %s\n", IP2Char(config.wifi.ip));
|
||||
Serial.printf(":: [WiFi] Netmask: %s\n", IP2Char(config.wifi.netmask));
|
||||
}
|
||||
|
||||
void Wifi_Init() {
|
||||
Serial.println(":: [WiFi] initializing");
|
||||
if(strlen(config.wifi.ssid) == 0) {
|
||||
Serial.println(":: [WiFi] config.wifi.ssid is unset");
|
||||
Wifi_AP();
|
||||
} else {
|
||||
Wifi_Connect();
|
||||
}
|
||||
}
|
169
Arduino/CanGrow/include/Webserver/Common.h
Normal file
169
Arduino/CanGrow/include/Webserver/Common.h
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Common.h - header file with common webserver functions
|
||||
* HTML header or footer to a String()
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Common_HTML.h"
|
||||
|
||||
/*
|
||||
* TestHeaderFooter - checks if the given var from the webserver processor
|
||||
* is actual a template variable from header or footer.
|
||||
*/
|
||||
bool TestHeaderFooter(const String& var) {
|
||||
#ifdef DEBUG
|
||||
Serial.print(":: [Webserver:Page:root:proc:hf] var: ");
|
||||
Serial.println(var);
|
||||
#endif
|
||||
|
||||
if(
|
||||
(var == "HEADER") ||
|
||||
(var == "FOOTER") ||
|
||||
(var == "CGVER") ||
|
||||
(var == "CGBUILD") ||
|
||||
(var == "GROWNAME") ||
|
||||
(var == "CANGROW_CSS") ||
|
||||
(var == "NEED_RESTART") ||
|
||||
(var == "ACTIVE_NAV_GROW") ||
|
||||
(var == "ACTIVE_NAV_SYSTEM") ||
|
||||
(var == "ACTIVE_NAV_WIFI") ||
|
||||
(var == "ACTIVE_NAV_HELP") ||
|
||||
(var == "PLACEHOLDER")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AddHeaderFooter - processor for header and footer template variables
|
||||
*
|
||||
* String& var:
|
||||
* the string we receive from the processor is the actual
|
||||
* variable name we replace here.
|
||||
* byte activeNav:
|
||||
* contains the number representing which page is active
|
||||
* 1 - grow settings
|
||||
* 2 - system settings
|
||||
* 3 - wifi settings
|
||||
* 4 - help page
|
||||
*/
|
||||
String AddHeaderFooter(const String& var, byte activeNav = 0) {
|
||||
String activeNav_ClassName = "activeNav";
|
||||
if(var == "HEADER") {
|
||||
return String(Header_HTML);
|
||||
} else if(var == "FOOTER") {
|
||||
return String(Footer_HTML);
|
||||
} else if(var == "CGVER") {
|
||||
return String(CANGROW_VER);
|
||||
} else if(var == "CGBUILD") {
|
||||
return String(CANGROW_BUILD);
|
||||
} else if(var == "GROWNAME") {
|
||||
return String(config.grow.growName);
|
||||
} else if(var == "CANGROW_CSS") {
|
||||
return String(File_cangrow_CSS);
|
||||
} else if((var == "ACTIVE_NAV_GROW") && (activeNav == 1)) {
|
||||
return activeNav_ClassName;
|
||||
} else if((var == "ACTIVE_NAV_SYSTEM") && (activeNav == 2)) {
|
||||
return activeNav_ClassName;
|
||||
} else if((var == "ACTIVE_NAV_WIFI") && (activeNav == 3)) {
|
||||
return activeNav_ClassName;
|
||||
} else if((var == "ACTIVE_NAV_HELP") && (activeNav == 4)) {
|
||||
return activeNav_ClassName;
|
||||
} else if(var == "NEED_RESTART") {
|
||||
if(needRestart == true) {
|
||||
return String(Common_HTML_NEED_RESTART);
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Html_SelectOpt_GPIOindex
|
||||
*
|
||||
* returns <option> list as string with available gpios
|
||||
*/
|
||||
|
||||
String Html_SelectOpt_GPIOindex(byte selectId = 255) {
|
||||
|
||||
String gpioIndex_html;
|
||||
// iterate through through all available GPIOs in index
|
||||
for(byte i = 0; i < GPIOindex_length; i++) {
|
||||
bool gpioUsed = Check_GPIOindex_Used(i);
|
||||
|
||||
gpioIndex_html += "<option value='";
|
||||
gpioIndex_html += i;
|
||||
gpioIndex_html += "'";
|
||||
// set disabled option for gpio which are already in use or incompatible
|
||||
if( (((gpioUsed == true) && (i != selectId)) || (GPIOindex[i].note == INPUT_ONLY)) ) {
|
||||
gpioIndex_html += " disabled";
|
||||
}
|
||||
|
||||
if(i == selectId) {
|
||||
gpioIndex_html += " selected";
|
||||
}
|
||||
gpioIndex_html += ">GPIO ";
|
||||
gpioIndex_html += GPIOindex[i].gpio;
|
||||
//add gpio note if there is some
|
||||
//if(GPIOindex[i].note > 0) {
|
||||
gpioIndex_html += " ";
|
||||
gpioIndex_html += GPIO_Index_note_descr[GPIOindex[i].note];
|
||||
|
||||
// disable output incompatible gpio
|
||||
if(GPIOindex[i].note == INPUT_ONLY) {
|
||||
gpioIndex_html += " (N/A)";
|
||||
// add USED if gpio is already in use
|
||||
} else if((gpioUsed == true) && (i != selectId)) {
|
||||
gpioIndex_html += " (used)";
|
||||
}
|
||||
gpioIndex_html += "</option>";
|
||||
}
|
||||
return gpioIndex_html;
|
||||
}
|
||||
|
||||
|
||||
String Html_SelectOpt_bool(byte selectVal = 255, String trueStr = "Yes", String falseStr = "No") {
|
||||
String html;
|
||||
html += "<option value='1'";
|
||||
html += (selectVal == true) ? " selected" : "";
|
||||
html += ">";
|
||||
html += trueStr;
|
||||
html += "</option>";
|
||||
|
||||
html += "<option value='0'";
|
||||
html += (selectVal == false) ? " selected" : "";
|
||||
html += ">";
|
||||
html += falseStr;
|
||||
html += "</option>";
|
||||
return html;
|
||||
}
|
47
Arduino/CanGrow/include/Webserver/Common_HTML.h
Normal file
47
Arduino/CanGrow/include/Webserver/Common_HTML.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Common_HTML.h - header file with common HTML snippets
|
||||
* HTML header or footer to a String()
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// double div to force a linebreak. infomsg , warnmsg are inline-block
|
||||
const char Common_HTML_SAVE_MSG[] PROGMEM = R"EOF(
|
||||
<div><div class='infomsg'>✅ Successfully saved!</div></div>
|
||||
)EOF";
|
||||
|
||||
const char Common_HTML_SAVE_MSG_ERR[] PROGMEM = R"EOF(
|
||||
<div><div class='infomsg'>!! ERROR saving!</div></div>
|
||||
)EOF";
|
||||
|
||||
const char Common_HTML_NEED_RESTART[] PROGMEM = R"EOF(
|
||||
<div><div class='warnmsg'>❗ Restart is required to apply new settings!
|
||||
<form action='/system/restart' method='post'><input type='hidden' name='confirmed' value='true' />
|
||||
<input type='submit' value='Restart now' />
|
||||
</form>
|
||||
</div></div>
|
||||
)EOF";
|
247
Arduino/CanGrow/include/Webserver/File_cangrow_CSS.h
Normal file
247
Arduino/CanGrow/include/Webserver/File_cangrow_CSS.h
Normal file
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/File_cangrow_CSS.h - /cangrow.css header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
const char* File_cangrow_CSS PROGMEM = R"(body {
|
||||
color: #cae0d0;
|
||||
background-color: #1d211e;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer {
|
||||
color: #343B35;
|
||||
}
|
||||
|
||||
.center {
|
||||
/*width: 100; */
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.centered {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
border-bottom: 1px solid #262B27;
|
||||
}
|
||||
|
||||
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;*/
|
||||
display: inline-block;
|
||||
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;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.subnav {
|
||||
/*text-align: center;*/
|
||||
margin: auto;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.subnavTitle {
|
||||
font-size: 1em;
|
||||
/*font-weight: bold;*/
|
||||
margin-top: -10px;
|
||||
margin-bottom: 10px;
|
||||
/*text-align: center;*/
|
||||
}
|
||||
.nav li {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.subnav li {
|
||||
background: #262B27;
|
||||
list-style: none;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 3px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.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], .linkForm input[type=submit] {
|
||||
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);
|
||||
}
|
||||
|
||||
.subnav li a, .subnav span {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.nav li a:hover, .subnav li a:hover, .activeNav, .button:link:hover, .button:visited:hover, input[type=button]:hover,
|
||||
input[type=submit]:hover, input[type=reset]:hover, .linkForm input[type=submit]:hover {
|
||||
background: #04AA6D;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.nav li a:active, .subnav li a:active {
|
||||
background: #026b45;
|
||||
color: #cae0d0;
|
||||
}
|
||||
|
||||
.activeNav {
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.navTime {
|
||||
background: #292929;
|
||||
}
|
||||
|
||||
.button, .button:link, .button:visited, input[type=button], input[type=submit],input[type=reset],
|
||||
.linkForm input[type=submit] {
|
||||
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, .linkForm input[type=submit]: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;
|
||||
}
|
||||
|
||||
.linkForm {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.linkForm input[type=submit] {
|
||||
background: #262B27;
|
||||
padding: 5px;
|
||||
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.visible {
|
||||
display: inline;
|
||||
/*justify-content: center!important;*/
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
||||
}*/)";
|
||||
|
||||
void WebFile_cangrow_CSS(AsyncWebServerRequest *request) {
|
||||
request->send_P(200, "text/css", File_cangrow_CSS);
|
||||
}
|
69
Arduino/CanGrow/include/Webserver/File_cangrow_JS.h
Normal file
69
Arduino/CanGrow/include/Webserver/File_cangrow_JS.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/File_cangrow_JS.h - /cangrow.js header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
const char* File_cangrow_JS PROGMEM = R"(function toggleDisplay(id) {
|
||||
let el = document.getElementById(id);
|
||||
let el_cs = getComputedStyle(el);
|
||||
|
||||
if (el_cs.getPropertyValue('display') === 'none') {
|
||||
el.style.display = 'inline';
|
||||
} else {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function hideAllClass(classname) {
|
||||
|
||||
const el = document.getElementsByClassName(classname);
|
||||
|
||||
for(let i = 0; i < el.length ; i++) {
|
||||
el[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function showSelect(selectId, prefix, hideClass = '') {
|
||||
if(hideClass != '') {
|
||||
hideAllClass(hideClass);
|
||||
}
|
||||
|
||||
let selVal = document.getElementById(selectId).value;
|
||||
toggleId = prefix + selVal;
|
||||
if(document.getElementById(toggleId) !== null ) {
|
||||
toggleDisplay(toggleId);
|
||||
}
|
||||
}
|
||||
|
||||
function confirmDelete(name) {
|
||||
return confirm('Delete ' + name + '?');
|
||||
})";
|
||||
|
||||
void WebFile_cangrow_JS(AsyncWebServerRequest *request) {
|
||||
request->send_P(200, "text/javascript", File_cangrow_JS);
|
||||
}
|
37
Arduino/CanGrow/include/Webserver/File_favicon_ico.h
Normal file
37
Arduino/CanGrow/include/Webserver/File_favicon_ico.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
unsigned char File_favicon_ico_gz[] = {
|
||||
0x1f, 0x8b, 0x08, 0x08, 0x11, 0x71, 0x19, 0x67, 0x00, 0x03, 0x43, 0x61,
|
||||
0x6e, 0x47, 0x72, 0x6f, 0x77, 0x5f, 0x66, 0x61, 0x76, 0x69, 0x63, 0x6f,
|
||||
0x2e, 0x69, 0x63, 0x6f, 0x00, 0xed, 0x94, 0x49, 0x4b, 0xc3, 0x40, 0x18,
|
||||
0x86, 0xdf, 0xd8, 0xc5, 0xaa, 0xe9, 0x12, 0xa7, 0xcd, 0xd2, 0x26, 0x99,
|
||||
0x7c, 0x89, 0x76, 0x45, 0xb4, 0x2a, 0xb6, 0xa2, 0x42, 0xb1, 0x52, 0x73,
|
||||
0x11, 0xd4, 0x83, 0xdb, 0xc1, 0x8b, 0x08, 0x75, 0xf9, 0xff, 0x67, 0xbf,
|
||||
0x49, 0x3c, 0x58, 0xa4, 0x17, 0xc1, 0x5b, 0x9e, 0xe4, 0x1d, 0xe6, 0xf9,
|
||||
0x86, 0x61, 0x32, 0x03, 0x19, 0x40, 0xe3, 0xa7, 0x56, 0x03, 0xb7, 0x25,
|
||||
0xcc, 0x0b, 0x80, 0x09, 0xa0, 0xcb, 0xe1, 0x12, 0x02, 0xa4, 0xf5, 0x65,
|
||||
0x44, 0xed, 0x08, 0x51, 0x27, 0xc2, 0x56, 0x2f, 0xc2, 0x76, 0x9f, 0x33,
|
||||
0x88, 0xd0, 0xde, 0x09, 0xd1, 0xd9, 0x0b, 0xd1, 0x1d, 0x86, 0xe8, 0x1d,
|
||||
0x86, 0xe8, 0x8f, 0x08, 0x83, 0x31, 0x61, 0x77, 0x12, 0x60, 0x38, 0x0d,
|
||||
0xb0, 0x3f, 0x93, 0x38, 0x88, 0x25, 0x8e, 0xae, 0x7c, 0x8c, 0xae, 0x7d,
|
||||
0x8c, 0x6f, 0x3c, 0x8e, 0x8f, 0xe3, 0x5b, 0x0f, 0x27, 0x77, 0x2e, 0x4e,
|
||||
0xef, 0x39, 0x0f, 0x2e, 0xce, 0x1e, 0x39, 0x4f, 0x2d, 0x4c, 0x9e, 0x9b,
|
||||
0x38, 0x7f, 0x71, 0x30, 0xe5, 0x5c, 0xbc, 0xda, 0x98, 0xcd, 0x2d, 0xcc,
|
||||
0xde, 0x2c, 0x5c, 0xbe, 0x5b, 0x88, 0x3f, 0x4c, 0xc4, 0x9f, 0xe6, 0xd2,
|
||||
0xef, 0xcb, 0xc8, 0xc8, 0xf8, 0x7f, 0x7e, 0xfc, 0x81, 0x45, 0x61, 0xe4,
|
||||
0x57, 0xac, 0xbc, 0x21, 0x8a, 0xa9, 0x6b, 0x82, 0xa4, 0x0c, 0x24, 0x09,
|
||||
0x2d, 0x55, 0x43, 0x78, 0xc4, 0x78, 0xc2, 0x50, 0x85, 0x9c, 0x4d, 0x92,
|
||||
0x12, 0x24, 0xd9, 0x1b, 0x7c, 0xe9, 0x08, 0x51, 0x72, 0x13, 0x5f, 0xaf,
|
||||
0x8a, 0x4d, 0x35, 0x61, 0xd5, 0x4b, 0xc7, 0xfd, 0xef, 0x5b, 0xa8, 0x4e,
|
||||
0x14, 0x24, 0xaf, 0x9f, 0xe8, 0x5a, 0x40, 0x75, 0x87, 0xc8, 0xe1, 0xb2,
|
||||
0xae, 0xbc, 0x2c, 0x85, 0xa6, 0x9c, 0x97, 0x2d, 0x2b, 0x6f, 0x15, 0x34,
|
||||
0x28, 0x87, 0x56, 0x6d, 0x2a, 0xcf, 0x71, 0x2a, 0xca, 0xd3, 0x6e, 0x0a,
|
||||
0x51, 0x65, 0x61, 0x3b, 0x44, 0x8b, 0xdb, 0x6b, 0x34, 0x16, 0x5d, 0xd7,
|
||||
0xff, 0x74, 0x4a, 0xbf, 0xf9, 0x02, 0x31, 0x98, 0x4b, 0x6b, 0x7e, 0x05,
|
||||
0x00, 0x00
|
||||
};
|
||||
unsigned int File_favicon_ico_gz_len = 326;
|
||||
|
||||
void WebFile_favicon_ico(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, "image/x-icon", File_favicon_ico_gz, File_favicon_ico_gz_len);
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
request->send(response);
|
||||
}
|
31
Arduino/CanGrow/include/Webserver/Footer.h
Normal file
31
Arduino/CanGrow/include/Webserver/Footer.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/footer_HTML.h - footer page HTML header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
const char* Footer_HTML PROGMEM = R"(<div class='footer'><span>Build: %CGBUILD%</span></div>
|
||||
</div></body></html>)";
|
49
Arduino/CanGrow/include/Webserver/Header.h
Normal file
49
Arduino/CanGrow/include/Webserver/Header.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/header_HTML.h - header page HTML header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
const char* Header_HTML PROGMEM = R"(<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||
<title>%GROWNAME% - CanGrow v%CGVER%</title>
|
||||
<link rel='stylesheet' href='/cangrow.css'>
|
||||
<script type='text/javascript' src='/cangrow.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<ul class='nav'><li><a href='/'>🌱 %GROWNAME%</a></li>
|
||||
<li><a class='%ACTIVE_NAV_GROW%' href='/grow/' >🔆 Grow settings</a></li>
|
||||
<li><a class='%ACTIVE_NAV_SYSTEM%' href='/system/' >⚙ System settings</a></li>
|
||||
<li><a class='%ACTIVE_NAV_WIFI%' href='/wifi/' >📡 WiFi settings</a></li>
|
||||
<li><a class='%ACTIVE_NAV_HELP%' href='/help' >❓ Help</a></li>
|
||||
<li><span class='navTime'>04:20:23</span></li>
|
||||
<li><a href='https://git.la10cy.net/DeltaLima/CanGrow' target='_blank'>CanGrow v%CGVER%</a></li>
|
||||
</ul>
|
||||
<div class='center'>
|
||||
%NEED_RESTART%)";
|
30
Arduino/CanGrow/include/Webserver/Page_grow.h
Normal file
30
Arduino/CanGrow/include/Webserver/Page_grow.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_grow.h - grow page header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
28
Arduino/CanGrow/include/Webserver/Page_grow_HTML.h
Normal file
28
Arduino/CanGrow/include/Webserver/Page_grow_HTML.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_grow_HTML.h - grow page HTML header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
49
Arduino/CanGrow/include/Webserver/Page_root.h
Normal file
49
Arduino/CanGrow/include/Webserver/Page_root.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_root.h - root page header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Page_root_HTML.h"
|
||||
|
||||
|
||||
|
||||
// https://techtutorialsx.com/2018/07/23/esp32-arduino-http-server-template-processing-with-multiple-placeholders/
|
||||
String Proc_WebPage_root(const String& var) {
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var);
|
||||
} else if(var == "LOL") {
|
||||
return String("Nice");
|
||||
} else if(var == "LOL") {
|
||||
return String("Jojoojo :)");
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
void WebPage_root(AsyncWebServerRequest *request) {
|
||||
request->send_P(200, "text/html", Page_root_HTML, Proc_WebPage_root);
|
||||
}
|
33
Arduino/CanGrow/include/Webserver/Page_root_HTML.h
Normal file
33
Arduino/CanGrow/include/Webserver/Page_root_HTML.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_root_HTML.h - root page HTML header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
const char* Page_root_HTML PROGMEM = R"(%HEADER%
|
||||
<h2>🌱 Hello world!</h2>
|
||||
%FOOTER%)";
|
736
Arduino/CanGrow/include/Webserver/Page_system.h
Normal file
736
Arduino/CanGrow/include/Webserver/Page_system.h
Normal file
|
@ -0,0 +1,736 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_system.h - system settings page header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "Page_system_HTML.h"
|
||||
|
||||
/* global runtime variables */
|
||||
|
||||
/* VERY VERY DIRTY WORKAROUND
|
||||
* I have the problem, that I cannot pass a parameter I receive from a http
|
||||
* request to it's template processor. In my case i want to edit an output,
|
||||
* the user should click an edit button on the system/output overview page.
|
||||
* I am lazy so i want to reuse the output_add page, because it is quite
|
||||
* kinda exactly the same. so i want to call GET /system/output/add?edit=ID
|
||||
* I have searched and came to the conclusion, that at this point i see no
|
||||
* other way then giving the parameter I need, the outputId, to an global
|
||||
* variable, so the template processor can read it.
|
||||
*/
|
||||
byte tmpParam_editOutputId = 255;
|
||||
|
||||
|
||||
|
||||
|
||||
/* subnav processor */
|
||||
|
||||
bool Test_WebPage_system_SUBNAV(const String& var) {
|
||||
if(
|
||||
(var == "SUBNAV") ||
|
||||
(var == "ACTIVE_SUBNAV_OUTPUT") ||
|
||||
(var == "ACTIVE_SUBNAV_UPDATE") ||
|
||||
(var == "ACTIVE_SUBNAV_RESTART") ||
|
||||
(var == "ACTIVE_SUBNAV_WIPE")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Proc_WebPage_system_SUBNAV - subnav processor for system
|
||||
* this function works as same as AddHeaderFooter from Common.h
|
||||
* byte activeSubnav:
|
||||
* 1 - Output
|
||||
* 2 - Update
|
||||
* 3 - Restart
|
||||
* 4 - Wipe
|
||||
*/
|
||||
String Proc_WebPage_system_SUBNAV(const String& var, byte activeSubnav = 0) {
|
||||
String activeSubnav_ClassName = "activeNav";
|
||||
if(var == "SUBNAV") {
|
||||
return String(Page_system_HTML_SUBNAV);
|
||||
} else if((var == "ACTIVE_SUBNAV_OUTPUT") && (activeSubnav == 1)) {
|
||||
return activeSubnav_ClassName;
|
||||
} else if((var == "ACTIVE_SUBNAV_UPDATE") && (activeSubnav == 2)) {
|
||||
return activeSubnav_ClassName;
|
||||
} else if((var == "ACTIVE_SUBNAV_RESTART") && (activeSubnav == 3)) {
|
||||
return activeSubnav_ClassName;
|
||||
} else if((var == "ACTIVE_SUBNAV_WIPE") && (activeSubnav == 4)) {
|
||||
return activeSubnav_ClassName;
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Main system page
|
||||
*/
|
||||
// https://techtutorialsx.com/2018/07/23/esp32-arduino-http-server-template-processing-with-multiple-placeholders/
|
||||
String Proc_WebPage_system(const String& var) {
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var);
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_POST(const String& var) {
|
||||
if(var == "SAVE_MSG") {
|
||||
return String(Common_HTML_SAVE_MSG);
|
||||
} else {
|
||||
return Proc_WebPage_system(var);
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_POST_ERR(const String& var) {
|
||||
if(var == "SAVE_MSG") {
|
||||
return String(Common_HTML_SAVE_MSG_ERR);
|
||||
} else {
|
||||
return Proc_WebPage_system(var);
|
||||
}
|
||||
}
|
||||
|
||||
void WebPage_system(AsyncWebServerRequest *request) {
|
||||
if(request->method() == HTTP_POST) {
|
||||
request->send_P(200, "text/html", Page_system_HTML, Proc_WebPage_system_POST);
|
||||
Serial.println(":: [Webserver:system:output] [POST] hello");
|
||||
|
||||
if(request->hasParam("config.system.ntpOffset", true)) {
|
||||
const AsyncWebParameter* p_ntpOffset = request->getParam("config.system.ntpOffset", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_ntpOffset->name().c_str(), p_ntpOffset->value().c_str());
|
||||
config.system.ntpOffset = p_ntpOffset->value().toInt();
|
||||
}
|
||||
|
||||
if(request->hasParam("config.system.httpLogSerial", true)) {
|
||||
const AsyncWebParameter* p_httpLogSerial = request->getParam("config.system.httpLogSerial", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_httpLogSerial->name().c_str(), p_httpLogSerial->value().c_str());
|
||||
config.system.httpLogSerial = p_httpLogSerial->value().toInt();
|
||||
}
|
||||
|
||||
if(SaveConfig()) {
|
||||
// we need a restart to apply the new settings
|
||||
needRestart = true;
|
||||
Serial.println(":: [Webserver:system] config saved");
|
||||
request->send_P(200, "text/html", Page_system_HTML, Proc_WebPage_system_POST);
|
||||
} else {
|
||||
Serial.println("!! [Webserver:system] ERROR saving config ");
|
||||
request->send_P(200, "text/html", Page_system_HTML, Proc_WebPage_system_POST_ERR);
|
||||
}
|
||||
} else {
|
||||
request->send_P(200, "text/html", Page_system_HTML, Proc_WebPage_system);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Subpage restart
|
||||
*/
|
||||
String Proc_WebPage_system_restart(const String& var) {
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var, 3);
|
||||
} else if(var == "RESTART_MSG") {
|
||||
return String(Page_system_restart_HTML_RESTART_MSG);
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_restart_POST(const String& var) {
|
||||
if(var == "RESTART_MSG") {
|
||||
return String(Page_system_restart_HTML_RESTART_MSG_POST);
|
||||
} else {
|
||||
return Proc_WebPage_system_restart(var);
|
||||
}
|
||||
}
|
||||
|
||||
void WebPage_system_restart(AsyncWebServerRequest *request) {
|
||||
if(request->method() == HTTP_POST) {
|
||||
if(request->hasParam("confirmed", true)) {
|
||||
doRestart = false;
|
||||
}
|
||||
request->send_P(200, "text/html", Page_system_restart_HTML, Proc_WebPage_system_restart_POST);
|
||||
|
||||
if(request->hasParam("confirmed", true)) {
|
||||
Serial.println(":: [Webserver:system:restart] POST[confirmed]: is set, triggering restart");
|
||||
|
||||
doRestart = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
request->send_P(200, "text/html", Page_system_restart_HTML, Proc_WebPage_system_restart);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Subpage update
|
||||
*/
|
||||
|
||||
// https://github.com/mathieucarbou/ESPAsyncWebServer/blob/main/docs/index.md#setting-up-the-server
|
||||
void WebPage_system_update_ApplyUpdate(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
|
||||
if(!index){
|
||||
Serial.printf(":: [Webserver:system:update:ApplyUpdate] Update Start: %s\n", filename.c_str());
|
||||
|
||||
// https://github.com/me-no-dev/ESPAsyncWebServer/issues/455#issuecomment-451728099
|
||||
// workaround for bug with ESP32
|
||||
#ifdef ESP8266
|
||||
Update.runAsync(true);
|
||||
#endif
|
||||
if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)){
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
if(!Update.hasError()){
|
||||
if(Update.write(data, len) != len){
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
if(final){
|
||||
if(Update.end(true)){
|
||||
Serial.printf(":: [Webserver:system:update:ApplyUpdate] Update Success: %uB\n", index+len);
|
||||
} else {
|
||||
Serial.println(":: [Webserver:system:update:ApplyUpdate] FAILED Update:");
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_update(const String& var) {
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var, 2);
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
/* After an update.bin file was uploaded*/
|
||||
String Proc_WebPage_system_update_POST(const String& var) {
|
||||
if(var == "CONFIGWIFI_IP") {
|
||||
if(config.wifi.dhcp == true) {
|
||||
return WiFi.localIP().toString();
|
||||
} else {
|
||||
return String(IP2Char(config.wifi.ip));
|
||||
}
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
void WebPage_system_update(AsyncWebServerRequest *request) {
|
||||
if(request->method() == HTTP_POST) {
|
||||
doRestart = !Update.hasError();
|
||||
// when doRestart is true, deliver Page_system_update_HTML_POST
|
||||
// otherwise Page_system_update_HTML_POST_FAILED
|
||||
AsyncWebServerResponse *response = request->beginResponse(200, "text/html", doRestart?Page_system_update_HTML_POST:Page_system_update_HTML_POST_FAILED, Proc_WebPage_system_update_POST);
|
||||
response->addHeader("Connection", "close");
|
||||
request->send(response);
|
||||
} else {
|
||||
request->send_P(200, "text/html", Page_system_update_HTML, Proc_WebPage_system_update);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Subpage wipe
|
||||
*/
|
||||
String Proc_WebPage_system_wipe(const String& var) {
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var, 4);
|
||||
} else if(var == "WIPE_MSG") {
|
||||
return String(Page_system_wipe_HTML_WIPE_MSG);
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_wipe_POST(const String& var) {
|
||||
if(var == "WIPE_MSG") {
|
||||
return String(Page_system_wipe_HTML_WIPE_MSG_POST);
|
||||
} else {
|
||||
return Proc_WebPage_system_wipe(var);
|
||||
}
|
||||
}
|
||||
|
||||
void WebPage_system_wipe(AsyncWebServerRequest *request) {
|
||||
if(request->method() == HTTP_POST) {
|
||||
request->send_P(200, "text/html", Page_system_wipe_HTML, Proc_WebPage_system_wipe_POST);
|
||||
|
||||
if(request->hasParam("confirmed", true)) {
|
||||
Serial.println(":: [Webserver:system:wipe] POST[confirmed]: is set, triggering wipe / factory reset");
|
||||
LFS_Format();
|
||||
Serial.println(":: [Webserver:system:wipe] triggering restart");
|
||||
doRestart = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
request->send_P(200, "text/html", Page_system_wipe_HTML, Proc_WebPage_system_wipe);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Subpage output
|
||||
*/
|
||||
String Proc_WebPage_system_output(const String& var) {
|
||||
#ifndef DEBUG
|
||||
Serial.print("DB [Webserver:system:output(Proc)] var: ");
|
||||
Serial.println(var);
|
||||
#endif
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var, 1);
|
||||
} else if(var == "OUTPUT_TR_TD") {
|
||||
// build table body
|
||||
// i dont know a better way at the moment. if you do, please tell me!
|
||||
String output_tr_td;
|
||||
for(byte i=0; i < Max_Outputs; i++) {
|
||||
if(config.system.output.type[i] > 0) {
|
||||
#ifndef DEBUG
|
||||
Serial.printf("DB [Webserver:system:output(Proc)] OutputID %d Type %d\n", i, config.system.output.type[i]);
|
||||
#endif
|
||||
output_tr_td += "<tr><td>";
|
||||
output_tr_td += i;
|
||||
output_tr_td += "</td><td>";
|
||||
output_tr_td += config.system.output.name[i];
|
||||
output_tr_td += "</td><td>";
|
||||
output_tr_td += Output_Type_descr[config.system.output.type[i]];
|
||||
output_tr_td += "</td><td>";
|
||||
output_tr_td += Output_Device_descr[config.system.output.device[i]];
|
||||
output_tr_td += "</td><td>";
|
||||
output_tr_td += config.system.output.enabled[i];
|
||||
output_tr_td += "</td><td>";
|
||||
|
||||
// edit button
|
||||
output_tr_td += "<form class='linkForm' action='/system/output/add' method='get'>";
|
||||
output_tr_td += "<input type='hidden' name='edit' value='";
|
||||
output_tr_td += i;
|
||||
output_tr_td += "'>";
|
||||
output_tr_td += "<input type='submit' value='✏️'></form> ";
|
||||
|
||||
|
||||
// delete button
|
||||
output_tr_td += "<form class='linkForm' action='/system/output/' method='post'>";
|
||||
output_tr_td += "<input type='hidden' name='delete_output' value='";
|
||||
output_tr_td += i;
|
||||
output_tr_td += "'>";
|
||||
output_tr_td += "<input type='submit' value='❌' onclick=\"return confirmDelete('";
|
||||
output_tr_td += config.system.output.name[i];;
|
||||
output_tr_td += "')\"></form>";
|
||||
|
||||
output_tr_td += "</td></tr>";
|
||||
}
|
||||
}
|
||||
|
||||
return output_tr_td;
|
||||
} else{
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_output_POST(const String& var) {
|
||||
if(var == "SAVE_MSG") {
|
||||
return String(Common_HTML_SAVE_MSG);
|
||||
} else {
|
||||
return Proc_WebPage_system_output(var);
|
||||
}
|
||||
}
|
||||
|
||||
void WebPage_system_output(AsyncWebServerRequest *request) {
|
||||
if(request->method() == HTTP_POST) {
|
||||
if(request->hasParam("delete_output", true)) {
|
||||
byte outputId;
|
||||
|
||||
const AsyncWebParameter* p_delete_output = request->getParam("delete_output", true);
|
||||
Serial.printf(":: [Webserver:system:output] POST[%s]: %s\n", p_delete_output->name().c_str(), p_delete_output->value().c_str());
|
||||
|
||||
outputId = p_delete_output->value().toInt();
|
||||
|
||||
Serial.printf(":: [Webserver:system:output] Deleting output: %d\n", outputId);
|
||||
|
||||
// we ensure that every field is empty
|
||||
config.system.output.type[outputId] = 0;
|
||||
config.system.output.device[outputId] = 0;
|
||||
// set every field of char array to 0x00 with memset
|
||||
memset(config.system.output.name[outputId], '\0', sizeof config.system.output.name[outputId]);
|
||||
config.system.output.enabled[outputId] = 0;
|
||||
config.system.output.gpio[outputId] = 0;
|
||||
config.system.output.gpio_pwm[outputId] = 0;
|
||||
config.system.output.gpio_invert[outputId] = 0;
|
||||
memset(config.system.output.i2c[outputId], '\0', sizeof config.system.output.i2c[outputId]);
|
||||
memset(config.system.output.webcall_host[outputId], '\0', sizeof config.system.output.webcall_host[outputId]);
|
||||
memset(config.system.output.webcall_path_on[outputId], '\0', sizeof config.system.output.webcall_path_on[outputId]);
|
||||
memset(config.system.output.webcall_path_off[outputId], '\0', sizeof config.system.output.webcall_path_off[outputId]);
|
||||
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
|
||||
request->send_P(200, "text/html", Page_system_output_HTML, Proc_WebPage_system_output_POST);
|
||||
Serial.println(":: [Webserver:system:output] [POST] hello");
|
||||
|
||||
|
||||
} else {
|
||||
if(request->hasParam("success")) {
|
||||
// when GET param success is present, we use the _POST processor for the save message
|
||||
request->send_P(200, "text/html", Page_system_output_HTML, Proc_WebPage_system_output_POST);
|
||||
} else {
|
||||
request->send_P(200, "text/html", Page_system_output_HTML, Proc_WebPage_system_output);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Subpage output add
|
||||
*/
|
||||
|
||||
/* returns select <option> list of available output types */
|
||||
String Html_SelOpt_type_WebPage_system_output_add(byte selectId = 255) {
|
||||
String outputType_html;
|
||||
// go through all available Output Devices, skip 0 because it means unconfigured
|
||||
for(byte i = 1; i < Output_Type_total; i++) {
|
||||
outputType_html += "<option value='";
|
||||
outputType_html += i;
|
||||
outputType_html += "'";
|
||||
if(i == selectId) {
|
||||
outputType_html += " selected";
|
||||
}
|
||||
outputType_html += ">";
|
||||
outputType_html += Output_Type_descr[i];
|
||||
outputType_html += "</option>";
|
||||
}
|
||||
return outputType_html;
|
||||
}
|
||||
|
||||
String Html_SelOpt_device_WebPage_system_output_add(byte selectId = 255) {
|
||||
String outputDevice_html;
|
||||
// go through all available Output Devices, skip 0 because it means unconfigured
|
||||
for(byte i = 1; i < Output_Device_total; i++) {
|
||||
outputDevice_html += "<option value='";
|
||||
outputDevice_html += i;
|
||||
outputDevice_html += "'";
|
||||
if(i == selectId) {
|
||||
outputDevice_html += " selected";
|
||||
}
|
||||
outputDevice_html += ">";
|
||||
outputDevice_html += Output_Device_descr[i];
|
||||
outputDevice_html += "</option>";
|
||||
}
|
||||
return outputDevice_html;
|
||||
}
|
||||
|
||||
|
||||
String Proc_WebPage_system_output_addCommon(const String& var) {
|
||||
#ifndef DEBUG
|
||||
Serial.print("DB [Webserver:system:output:add(Proc)] var: ");
|
||||
Serial.println(var);
|
||||
#endif
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var, 1);
|
||||
} else if(var == "OUTPUT_ID") {
|
||||
// we check which id is free. A free ID as type == 0
|
||||
return String(Give_Free_OutputId());
|
||||
|
||||
/* OUTPUT_TYPE */
|
||||
} else if(var == "OUTPUT_TYPE") {
|
||||
return Html_SelOpt_type_WebPage_system_output_add();
|
||||
|
||||
/* OUTPUT_DEVICE */
|
||||
} else if(var == "OUTPUT_DEVICE") {
|
||||
return Html_SelOpt_device_WebPage_system_output_add();
|
||||
|
||||
/* GPIO_INDEX */
|
||||
} else if(var == "GPIO_INDEX") {
|
||||
return Html_SelectOpt_GPIOindex();
|
||||
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String Proc_WebPage_system_output_add(const String& var) {
|
||||
#ifndef DEBUG
|
||||
Serial.print("DB [Webserver:system:output:add(Proc)] var: ");
|
||||
Serial.println(var);
|
||||
#endif
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var, 1);
|
||||
} else if(var == "ACTION") {
|
||||
return String("➕ Add");
|
||||
|
||||
} else if(var == "OUTPUT_ID") {
|
||||
// we check which id is free. A free ID as type == 0
|
||||
return String(Give_Free_OutputId());
|
||||
|
||||
|
||||
} else if(var == "OUTPUT_TYPE") {
|
||||
return Html_SelOpt_type_WebPage_system_output_add();
|
||||
|
||||
} else if(var == "OUTPUT_DEVICE") {
|
||||
return Html_SelOpt_device_WebPage_system_output_add();
|
||||
|
||||
} else if(var == "OUTPUT_ENABLED") {
|
||||
return Html_SelectOpt_bool();
|
||||
|
||||
} else if(var == "GPIO_INDEX") {
|
||||
return Html_SelectOpt_GPIOindex();
|
||||
|
||||
} else if(var == "GPIO_PWM") {
|
||||
return Html_SelectOpt_bool();
|
||||
|
||||
} else if(var == "GPIO_INVERT") {
|
||||
return Html_SelectOpt_bool();
|
||||
|
||||
//} else if(
|
||||
//(var == "CLASS_TYPE_1") ||
|
||||
//(var == "CLASS_TYPE_2") ||
|
||||
//(var == "CLASS_TYPE_3")) {
|
||||
/* all type div container are hidden when adding */
|
||||
//return String("hidden");
|
||||
|
||||
//}
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_output_addEdit(const String& var) {
|
||||
#ifndef DEBUG
|
||||
Serial.print("DB [Webserver:system:output:addEdit(Proc)] var: ");
|
||||
Serial.println(var);
|
||||
#endif
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var, 1);
|
||||
} else if(var == "ACTION") {
|
||||
return String("✏️ Edit");
|
||||
|
||||
} else if(var == "OUTPUT_ID") {
|
||||
// return the outputId we got from GET .../add?edit=ID
|
||||
// dirty workaround to put this in a global variable
|
||||
return String(tmpParam_editOutputId);
|
||||
|
||||
} else if(var == "OUTPUT_TYPE") {
|
||||
return Html_SelOpt_type_WebPage_system_output_add(config.system.output.type[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "OUTPUT_DEVICE") {
|
||||
return Html_SelOpt_device_WebPage_system_output_add(config.system.output.device[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "OUTPUT_NAME") {
|
||||
// "escape" % character, because it would break the template processor.
|
||||
// tasmote webcall for example has percentage char in its path
|
||||
String outputName = config.system.output.name[tmpParam_editOutputId];;
|
||||
outputName.replace("%", "%");
|
||||
return outputName;
|
||||
|
||||
} else if(var == "OUTPUT_ENABLED") {
|
||||
return Html_SelectOpt_bool(config.system.output.enabled[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "GPIO_INDEX") {
|
||||
return Html_SelectOpt_GPIOindex(config.system.output.gpio[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "GPIO_PWM") {
|
||||
return Html_SelectOpt_bool(config.system.output.gpio_pwm[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "GPIO_INVERT") {
|
||||
return Html_SelectOpt_bool(config.system.output.gpio_invert[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "I2C") {
|
||||
return String(config.system.output.i2c[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "WEBCALL_HOST") {
|
||||
return String(config.system.output.webcall_host[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "WEBCALL_PATH_ON") {
|
||||
String webcallPathOn = config.system.output.webcall_path_on[tmpParam_editOutputId];
|
||||
webcallPathOn.replace("%", "%");
|
||||
return webcallPathOn;
|
||||
|
||||
} else if(var == "WEBCALL_PATH_OFF") {
|
||||
String webcallPathOff = config.system.output.webcall_path_off[tmpParam_editOutputId];
|
||||
webcallPathOff.replace("%", "%");
|
||||
return webcallPathOff;
|
||||
|
||||
} else if(
|
||||
((var == "CLASS_TYPE_1") && (config.system.output.type[tmpParam_editOutputId] == 1)) ||
|
||||
((var == "CLASS_TYPE_2") && (config.system.output.type[tmpParam_editOutputId] == 2)) ||
|
||||
((var == "CLASS_TYPE_3") && (config.system.output.type[tmpParam_editOutputId] == 3))) {
|
||||
// add class 'visible' which overwrites display with flex!important and justify center
|
||||
return String("visible");
|
||||
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_output_add_POST(const String& var) {
|
||||
if(var == "SAVE_MSG") {
|
||||
return String(Common_HTML_SAVE_MSG);
|
||||
} else {
|
||||
return Proc_WebPage_system_output_add(var);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WebPage_system_output_add(AsyncWebServerRequest *request) {
|
||||
if(request->method() == HTTP_POST) {
|
||||
Serial.println(":: [Webserver:system:output:add] [POST] hello");
|
||||
byte outputId;
|
||||
byte outputType;
|
||||
if(request->hasParam("outputId", true)) {
|
||||
const AsyncWebParameter* p_outputId = request->getParam("outputId", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_outputId->name().c_str(), p_outputId->value().c_str());
|
||||
outputId = p_outputId->value().toInt();
|
||||
}
|
||||
|
||||
if(request->hasParam("type", true)) {
|
||||
const AsyncWebParameter* p_type = request->getParam("type", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_type->name().c_str(), p_type->value().c_str());
|
||||
// put info config struct
|
||||
config.system.output.type[outputId] = p_type->value().toInt();
|
||||
|
||||
// remember the value in own var to work later with here
|
||||
outputType = p_type->value().toInt();
|
||||
}
|
||||
|
||||
if(request->hasParam("device", true)) {
|
||||
const AsyncWebParameter* p_device = request->getParam("device", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_device->name().c_str(), p_device->value().c_str());
|
||||
config.system.output.device[outputId] = p_device->value().toInt();
|
||||
}
|
||||
|
||||
if(request->hasParam("name", true)) {
|
||||
const AsyncWebParameter* p_name = request->getParam("name", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_name->name().c_str(), p_name->value().c_str());
|
||||
strlcpy(config.system.output.name[outputId], p_name->value().c_str(), sizeof(config.system.output.name[outputId]));
|
||||
}
|
||||
|
||||
if(request->hasParam("enabled", true)) {
|
||||
const AsyncWebParameter* p_enabled = request->getParam("enabled", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_enabled->name().c_str(), p_enabled->value().c_str());
|
||||
config.system.output.enabled[outputId] = p_enabled->value().toInt();
|
||||
}
|
||||
|
||||
// only fill the type related config vars
|
||||
switch(outputType) {
|
||||
// GPIO
|
||||
case 1:
|
||||
if(request->hasParam("gpio", true)) {
|
||||
const AsyncWebParameter* p_gpio = request->getParam("gpio", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_gpio->name().c_str(), p_gpio->value().c_str());
|
||||
config.system.output.gpio[outputId] = p_gpio->value().toInt();
|
||||
}
|
||||
|
||||
if(request->hasParam("gpio_pwm", true)) {
|
||||
const AsyncWebParameter* p_gpio_pwm = request->getParam("gpio_pwm", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_gpio_pwm->name().c_str(), p_gpio_pwm->value().c_str());
|
||||
config.system.output.gpio_pwm[outputId] = p_gpio_pwm->value().toInt();
|
||||
}
|
||||
|
||||
if(request->hasParam("gpio_invert", true)) {
|
||||
const AsyncWebParameter* p_gpio_invert = request->getParam("gpio_invert", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_gpio_invert->name().c_str(), p_gpio_invert->value().c_str());
|
||||
config.system.output.gpio_invert[outputId] = p_gpio_invert->value().toInt();
|
||||
}
|
||||
break;
|
||||
|
||||
// I2C
|
||||
case 2:
|
||||
if(request->hasParam("i2c", true)) {
|
||||
const AsyncWebParameter* p_i2c = request->getParam("i2c", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_i2c->name().c_str(), p_i2c->value().c_str());
|
||||
strlcpy(config.system.output.i2c[outputId], p_i2c->value().c_str(), sizeof(config.system.output.i2c[outputId]));
|
||||
}
|
||||
break;
|
||||
// Webcall
|
||||
case 3:
|
||||
if(request->hasParam("webcall_host", true)) {
|
||||
const AsyncWebParameter* p_webcall_host = request->getParam("webcall_host", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_webcall_host->name().c_str(), p_webcall_host->value().c_str());
|
||||
strlcpy(config.system.output.webcall_host[outputId], p_webcall_host->value().c_str(), sizeof(config.system.output.webcall_host[outputId]));
|
||||
}
|
||||
|
||||
if(request->hasParam("webcall_path_on", true)) {
|
||||
const AsyncWebParameter* p_webcall_path_on = request->getParam("webcall_path_on", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_webcall_path_on->name().c_str(), p_webcall_path_on->value().c_str());
|
||||
strlcpy(config.system.output.webcall_path_on[outputId], p_webcall_path_on->value().c_str(), sizeof(config.system.output.webcall_path_on[outputId]));
|
||||
}
|
||||
|
||||
if(request->hasParam("webcall_path_off", true)) {
|
||||
const AsyncWebParameter* p_webcall_path_off = request->getParam("webcall_path_off", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_webcall_path_off->name().c_str(), p_webcall_path_off->value().c_str());
|
||||
strlcpy(config.system.output.webcall_path_off[outputId], p_webcall_path_off->value().c_str(), sizeof(config.system.output.webcall_path_off[outputId]));
|
||||
}
|
||||
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
||||
SaveConfig();
|
||||
|
||||
// request->send_P(200, "text/html", Page_system_output_add_HTML, Proc_WebPage_system_output_add_POST);
|
||||
// I like it more when user gets redirected to the output overview after saving
|
||||
request->redirect("/system/output/?success");
|
||||
} else {
|
||||
|
||||
if(request->hasParam("edit")) {
|
||||
|
||||
|
||||
const AsyncWebParameter* p_edit = request->getParam("edit");
|
||||
Serial.println("DB [Webserver:system:output:add?edit] ");
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %d\n", p_edit->name().c_str(), p_edit->value().toInt());
|
||||
|
||||
tmpParam_editOutputId = p_edit->value().toInt();
|
||||
|
||||
request->send_P(200, "text/html", Page_system_output_add_HTML, Proc_WebPage_system_output_addEdit);
|
||||
|
||||
} else {
|
||||
request->send_P(200, "text/html", Page_system_output_add_HTML, Proc_WebPage_system_output_add);
|
||||
}
|
||||
}
|
||||
}
|
230
Arduino/CanGrow/include/Webserver/Page_system_HTML.h
Normal file
230
Arduino/CanGrow/include/Webserver/Page_system_HTML.h
Normal file
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_system_HTML.h - system settings page HTML header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
const char* Page_system_HTML PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
%SAVE_MSG%
|
||||
<p>here you can set which features and sensors you use<br></p><form method='post' action='/system/'>
|
||||
|
||||
<u>NTP offset</u>:<br>
|
||||
<input class='inputShort' type='number' name='ntpOffset' min='-12' max='14' value='%CONFIG_SYSTEM_NTPOFFSET%' required> Hours<br>
|
||||
|
||||
<u>Maintenance duration</u>:<br> <input class='inputShort' type='number' name='maintenanceDuration' min='0' max='900' value='%CONFIG_SYSTEM_MAINTDUR%' required> Seconds<br>
|
||||
|
||||
<u>ESP32-Cam IP (optional)</u>:<br>
|
||||
<input type='text' name='esp32camIp' maxlength='16' value='%CONFIG_SYSTEM_ESP32CAMIP%' ><br>
|
||||
|
||||
<u>HTTP log to serial</u>:<br>
|
||||
<select name='config.system.httpLogSerial' required>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
<option value='1'>On</option>
|
||||
<option value='0'>Off</option>
|
||||
</select><br>
|
||||
|
||||
<input type='submit' value='💾 Save settings'>
|
||||
</form>
|
||||
%FOOTER%)";
|
||||
|
||||
const char* Page_system_HTML_SUBNAV PROGMEM = R"(<ul class='subnav'>
|
||||
<li><a class='' href='/system/sensor/'>🌡️ Sensor configuration</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_OUTPUT%' href='/system/output/'>⚡ Output configuration</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_UPDATE%' href='/system/update'>🔄 Firmware update</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_RESTART%' href='/system/restart' >🔁 CanGrow restart</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_WIPE%' href='/system/wipe' >💣 Factory reset</a></li>
|
||||
</ul>)";
|
||||
|
||||
/*
|
||||
* Subpage update
|
||||
*/
|
||||
const char* Page_system_update_HTML PROGMEM = R"(%HEADER%
|
||||
|
||||
%SUBNAV%
|
||||
Version: %CGVER% <br>
|
||||
Build : %CGBUILD% <br>
|
||||
|
||||
<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/update' 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>
|
||||
|
||||
%FOOTER%)";
|
||||
|
||||
const char* Page_system_update_HTML_POST PROGMEM = R"(<html>
|
||||
<head><meta http-equiv='refresh' content='15;url=/' /></head>
|
||||
<body><h1>Successfully updated!
|
||||
<br>Restarting...</h1><br>
|
||||
Redirecting to "/" in 15 seconds
|
||||
</body></html>)";
|
||||
|
||||
const char* Page_system_update_HTML_POST_FAILED PROGMEM = R"(<html>
|
||||
<head></head><body><h1>UPDATE FAILED</h1><br>
|
||||
Please see messages on serial monitor for more information and go back to <a href='/system/update'>\"System settings > Firmware update\"</a> and try another file.
|
||||
</body></html>)";
|
||||
|
||||
/*
|
||||
* Subpage restart
|
||||
*/
|
||||
const char* Page_system_restart_HTML PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
<div class='warnmsg'>
|
||||
%RESTART_MSG%
|
||||
</div>
|
||||
%FOOTER%)";
|
||||
|
||||
const char* Page_system_restart_HTML_RESTART_MSG PROGMEM = R"(Do you want to restart CanGrow?<br>Please confirm.
|
||||
<form action='/system/restart' method='post'><input type='hidden' name='confirmed' value='true' />
|
||||
<input type='submit' value='Confirm restart' />
|
||||
</form>)";
|
||||
|
||||
const char* Page_system_restart_HTML_RESTART_MSG_POST PROGMEM = R"(Restarting...)";
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Subpage wipe
|
||||
*/
|
||||
const char* Page_system_wipe_HTML PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
<div class='warnmsg'>
|
||||
%WIPE_MSG%
|
||||
</div>
|
||||
|
||||
%FOOTER%)";
|
||||
|
||||
const char* Page_system_wipe_HTML_WIPE_MSG PROGMEM = R"(All settings will be removed!!<br><br>
|
||||
Please confirm wiping LittleFS
|
||||
<form action='/system/wipe' method='post'><br>
|
||||
Please confirm: <input type='checkbox' id='confirmed' name='confirmed' required /><br>
|
||||
<input type='submit' value='Confirm wiping' />
|
||||
</form>)";
|
||||
|
||||
|
||||
const char* Page_system_wipe_HTML_WIPE_MSG_POST PROGMEM = R"(Restarting...)";
|
||||
|
||||
/*
|
||||
* Subpage output
|
||||
*/
|
||||
const char* Page_system_output_HTML PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
%SAVE_MSG%
|
||||
<a class='button' href='/system/output/add'>➕ Add output</a>
|
||||
<table class='centered'>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Device</th>
|
||||
<th>Enabled</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
%OUTPUT_TR_TD%
|
||||
</table>
|
||||
%FOOTER%)";
|
||||
|
||||
/*
|
||||
* Subpage output add
|
||||
*/
|
||||
const char* Page_system_output_add_HTML PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
<h3>%ACTION% output ID %OUTPUT_ID%</h3>
|
||||
%SAVE_MSG%
|
||||
|
||||
<p>Add/Edit CanGrow output.</p>
|
||||
<form method='post' action='/system/output/add'>
|
||||
<input type='hidden' name='outputId' value='%OUTPUT_ID%' />
|
||||
<u>Type</u>:<br>
|
||||
<select id='type_sel' name='type' onchange="showSelect('type_sel', 'type_', 'hidden');" required>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%OUTPUT_TYPE%
|
||||
</select><br>
|
||||
|
||||
<u>Device</u>:<br>
|
||||
<select name='device' required>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%OUTPUT_DEVICE%
|
||||
</select><br>
|
||||
|
||||
<u>Name</u>:<br>
|
||||
<input type='text' name='name' maxlength='16' value='%OUTPUT_NAME%' required><br>
|
||||
|
||||
<u>Enable</u>:<br>
|
||||
<select name='enabled' required>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%OUTPUT_ENABLED%
|
||||
</select><br>
|
||||
|
||||
<div class='hidden %CLASS_TYPE_1%' id='type_1'>
|
||||
<u>GPIO</u>:<br>
|
||||
<select name='gpio'>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%GPIO_INDEX%
|
||||
</select><br>
|
||||
|
||||
<u>GPIO PWM</u>:<br>
|
||||
<select name='gpio_pwm' >
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%GPIO_PWM%
|
||||
</select><br>
|
||||
|
||||
<u>GPIO invert</u>:<br>
|
||||
<select name='gpio_invert' >
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%GPIO_INVERT%
|
||||
</select><br>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='hidden %CLASS_TYPE_2%' id='type_2'>
|
||||
<u>I2C</u>:<br>
|
||||
<input type='text' name='i2c' maxlength='16' value='%I2C%' ><br>
|
||||
</div>
|
||||
|
||||
<div class='hidden %CLASS_TYPE_3%' id='type_3'>
|
||||
<u>Webcall host</u>:<br>
|
||||
<input type='text' name='webcall_host' maxlength='32' value='%WEBCALL_HOST%' ><br>
|
||||
|
||||
<u>Webcall path 'on'</u>:<br>
|
||||
<input type='text' name='webcall_path_on' maxlength='32' value='%WEBCALL_PATH_ON%' ><br>
|
||||
|
||||
<u>Webcall path 'off'</u>:<br>
|
||||
<input type='text' name='webcall_path_off' maxlength='32' value='%WEBCALL_PATH_OFF%' ><br>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<input type='submit' value='💾 Save settings'>
|
||||
</form>
|
||||
|
||||
%FOOTER%)";
|
212
Arduino/CanGrow/include/Webserver/Page_wifi.h
Normal file
212
Arduino/CanGrow/include/Webserver/Page_wifi.h
Normal file
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_wifi.h - wifi page header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "Page_wifi_HTML.h"
|
||||
|
||||
String WebPage_wifi_ScanNetworks() {
|
||||
String html;
|
||||
|
||||
Serial.println(":: [Webserver:wifi:ScanNetworks] scanning for available networks:");
|
||||
// https://github.com/mathieucarbou/ESPAsyncWebServer/blob/main/docs/index.md#scanning-for-available-wifi-networks
|
||||
int n = WiFi.scanComplete();
|
||||
if(n == -2){
|
||||
WiFi.scanNetworks(true);
|
||||
} else if(n){
|
||||
for (int i = 0; i < n; ++i){
|
||||
html += "<option value='" + WiFi.SSID(i) + "'>" + WiFi.SSID(i) + "</option>";
|
||||
Serial.print(":: [Webserver:wifi:ScanNetworks] - ");
|
||||
Serial.println(WiFi.SSID(i));
|
||||
}
|
||||
WiFi.scanDelete();
|
||||
if(WiFi.scanComplete() == -2){
|
||||
WiFi.scanNetworks(true);
|
||||
}
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
// https://techtutorialsx.com/2018/07/23/esp32-arduino-http-server-template-processing-with-multiple-placeholders/
|
||||
String Proc_WebPage_wifi(const String& var) {
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 3);
|
||||
//CURRENT_SETTINGS
|
||||
} else if(var == "CURRENT_SETTINGS") {
|
||||
if(strlen(config.wifi.ssid) > 0) {
|
||||
return String(Page_wifi_HTML_CURRENT_SETTINGS);
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
} else if(var == "CONFIGWIFI_SSID") {
|
||||
return String(config.wifi.ssid);
|
||||
} else if(var == "CONFIGWIFI_DHCP") {
|
||||
return String(config.wifi.dhcp);
|
||||
} else if(var == "CONFIGWIFI_IP") {
|
||||
return String(WiFi.localIP().toString());
|
||||
} else if(var == "CONFIGWIFI_NETMASK") {
|
||||
return String(WiFi.subnetMask().toString());
|
||||
} else if(var == "CONFIGWIFI_GATEWAY") {
|
||||
return String(WiFi.gatewayIP().toString());
|
||||
} else if(var == "CONFIGWIFI_DNS") {
|
||||
return String(WiFi.dnsIP().toString());
|
||||
} else if(var == "WIFI_LIST") {
|
||||
return String(WebPage_wifi_ScanNetworks());
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String Proc_WebPage_wifi_POST(const String& var) {
|
||||
if(var == "SAVE_MSG") {
|
||||
return String(Common_HTML_SAVE_MSG);
|
||||
} else {
|
||||
return Proc_WebPage_wifi(var);
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_wifi_POST_ERR(const String& var) {
|
||||
if(var == "SAVE_MSG") {
|
||||
return String(Common_HTML_SAVE_MSG_ERR);
|
||||
} else {
|
||||
return Proc_WebPage_wifi(var);
|
||||
}
|
||||
}
|
||||
|
||||
void WebPage_wifi(AsyncWebServerRequest *request) {
|
||||
|
||||
if(request->method() == HTTP_POST) {
|
||||
if(request->hasParam("config.wifi.ssid", true)) {
|
||||
const AsyncWebParameter* p_ssid = request->getParam("config.wifi.ssid", true);
|
||||
Serial.printf(":: [Webserver:wifi] POST[%s]: %s\n", p_ssid->name().c_str(), p_ssid->value().c_str());
|
||||
strlcpy(config.wifi.ssid, p_ssid->value().c_str(), sizeof(config.wifi.ssid));
|
||||
|
||||
}
|
||||
|
||||
if(request->hasParam("config.wifi.password", true)) {
|
||||
const AsyncWebParameter* p_password = request->getParam("config.wifi.password", true);
|
||||
Serial.printf(":: [Webserver:wifi] POST[%s]: %s\n", p_password->name().c_str(), p_password->value().c_str());
|
||||
strlcpy(config.wifi.password, p_password->value().c_str(), sizeof(config.wifi.password));
|
||||
}
|
||||
|
||||
|
||||
if(
|
||||
(request->hasParam("config.wifi.ip0", true)) &&
|
||||
(request->hasParam("config.wifi.ip1", true)) &&
|
||||
(request->hasParam("config.wifi.ip2", true)) &&
|
||||
(request->hasParam("config.wifi.ip3", true))) {
|
||||
|
||||
const AsyncWebParameter* p_ip0 = request->getParam("config.wifi.ip0", true);
|
||||
const AsyncWebParameter* p_ip1 = request->getParam("config.wifi.ip1", true);
|
||||
const AsyncWebParameter* p_ip2 = request->getParam("config.wifi.ip2", true);
|
||||
const AsyncWebParameter* p_ip3 = request->getParam("config.wifi.ip3", true);
|
||||
Serial.printf(":: [Webserver:wifi] POST[config.wifi.ip0-3]: %s . %s . %s . %s\n", p_ip0->value().c_str(), p_ip1->value().c_str(), p_ip2->value().c_str(), p_ip3->value().c_str());
|
||||
config.wifi.ip[0] = p_ip0->value().toInt();
|
||||
config.wifi.ip[1] = p_ip1->value().toInt();
|
||||
config.wifi.ip[2] = p_ip2->value().toInt();
|
||||
config.wifi.ip[3] = p_ip3->value().toInt();
|
||||
}
|
||||
|
||||
|
||||
if(
|
||||
(request->hasParam("config.wifi.netmask0", true)) &&
|
||||
(request->hasParam("config.wifi.netmask1", true)) &&
|
||||
(request->hasParam("config.wifi.netmask2", true)) &&
|
||||
(request->hasParam("config.wifi.netmask3", true))) {
|
||||
|
||||
const AsyncWebParameter* p_netmask0 = request->getParam("config.wifi.netmask0", true);
|
||||
const AsyncWebParameter* p_netmask1 = request->getParam("config.wifi.netmask1", true);
|
||||
const AsyncWebParameter* p_netmask2 = request->getParam("config.wifi.netmask2", true);
|
||||
const AsyncWebParameter* p_netmask3 = request->getParam("config.wifi.netmask3", true);
|
||||
Serial.printf(":: [Webserver:wifi] POST[config.wifi.netmask0-3]: %s . %s . %s . %s\n", p_netmask0->value().c_str(), p_netmask1->value().c_str(), p_netmask2->value().c_str(), p_netmask3->value().c_str());
|
||||
config.wifi.netmask[0] = p_netmask0->value().toInt();
|
||||
config.wifi.netmask[1] = p_netmask1->value().toInt();
|
||||
config.wifi.netmask[2] = p_netmask2->value().toInt();
|
||||
config.wifi.netmask[3] = p_netmask3->value().toInt();
|
||||
}
|
||||
|
||||
if(
|
||||
(request->hasParam("config.wifi.gateway0", true)) &&
|
||||
(request->hasParam("config.wifi.gateway1", true)) &&
|
||||
(request->hasParam("config.wifi.gateway2", true)) &&
|
||||
(request->hasParam("config.wifi.gateway3", true))) {
|
||||
|
||||
const AsyncWebParameter* p_gateway0 = request->getParam("config.wifi.gateway0", true);
|
||||
const AsyncWebParameter* p_gateway1 = request->getParam("config.wifi.gateway1", true);
|
||||
const AsyncWebParameter* p_gateway2 = request->getParam("config.wifi.gateway2", true);
|
||||
const AsyncWebParameter* p_gateway3 = request->getParam("config.wifi.gateway3", true);
|
||||
Serial.printf(":: [Webserver:wifi] POST[config.wifi.gateway0-3]: %s . %s . %s . %s\n", p_gateway0->value().c_str(), p_gateway1->value().c_str(), p_gateway2->value().c_str(), p_gateway3->value().c_str());
|
||||
config.wifi.gateway[0] = p_gateway0->value().toInt();
|
||||
config.wifi.gateway[1] = p_gateway1->value().toInt();
|
||||
config.wifi.gateway[2] = p_gateway2->value().toInt();
|
||||
config.wifi.gateway[3] = p_gateway3->value().toInt();
|
||||
}
|
||||
|
||||
if(
|
||||
(request->hasParam("config.wifi.dns0", true)) &&
|
||||
(request->hasParam("config.wifi.dns1", true)) &&
|
||||
(request->hasParam("config.wifi.dns2", true)) &&
|
||||
(request->hasParam("config.wifi.dns3", true))) {
|
||||
|
||||
const AsyncWebParameter* p_dns0 = request->getParam("config.wifi.dns0", true);
|
||||
const AsyncWebParameter* p_dns1 = request->getParam("config.wifi.dns1", true);
|
||||
const AsyncWebParameter* p_dns2 = request->getParam("config.wifi.dns2", true);
|
||||
const AsyncWebParameter* p_dns3 = request->getParam("config.wifi.dns3", true);
|
||||
Serial.printf(":: [Webserver:wifi] POST[config.wifi.dns0-3]: %s . %s . %s . %s\n", p_dns0->value().c_str(), p_dns1->value().c_str(), p_dns2->value().c_str(), p_dns3->value().c_str());
|
||||
config.wifi.dns[0] = p_dns0->value().toInt();
|
||||
config.wifi.dns[1] = p_dns1->value().toInt();
|
||||
config.wifi.dns[2] = p_dns2->value().toInt();
|
||||
config.wifi.dns[3] = p_dns3->value().toInt();
|
||||
}
|
||||
|
||||
if(request->hasParam("config.wifi.dhcp", true)) {
|
||||
const AsyncWebParameter* p_dhcp = request->getParam("config.wifi.dhcp", true);
|
||||
Serial.printf(":: [Webserver:wifi] POST[%s]: %s\n", p_dhcp->name().c_str(), p_dhcp->value().c_str());
|
||||
config.wifi.dhcp = p_dhcp->value().toInt();
|
||||
}
|
||||
|
||||
if(SaveConfig()) {
|
||||
// we need a restart to apply the new settings
|
||||
needRestart = true;
|
||||
Serial.println(":: [Webserver:wifi] config saved");
|
||||
request->send_P(200, "text/html", Page_wifi_HTML, Proc_WebPage_wifi_POST);
|
||||
} else {
|
||||
Serial.println("!! [Webserver:wifi] ERROR saving config ");
|
||||
request->send_P(200, "text/html", Page_wifi_HTML, Proc_WebPage_wifi_POST_ERR);
|
||||
}
|
||||
|
||||
} else {
|
||||
request->send_P(200, "text/html", Page_wifi_HTML, Proc_WebPage_wifi);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
93
Arduino/CanGrow/include/Webserver/Page_wifi_HTML.h
Normal file
93
Arduino/CanGrow/include/Webserver/Page_wifi_HTML.h
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_wifi_HTML.h - wifi page HTML header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
const char* Page_wifi_HTML PROGMEM = R"(%HEADER%
|
||||
%SAVE_MSG%
|
||||
%CURRENT_SETTINGS%
|
||||
|
||||
<p>Select your wifi network from the SSID list.
|
||||
<br>Reload the page, if your network is not listed.</p>
|
||||
<form method='post' action='/wifi/'>
|
||||
|
||||
<u>SSID</u>:<br>
|
||||
<select id='config.wifi.ssid' name='config.wifi.ssid' required>
|
||||
<option disabled value='' selected hidden>-Select your network-</option>
|
||||
|
||||
%WIFI_LIST%
|
||||
|
||||
</select><br>
|
||||
|
||||
<u>Password</u>:<br>
|
||||
<input type='password' name='config.wifi.password'><br>
|
||||
|
||||
<u>DHCP</u>:<br>
|
||||
<select id='dhcp_sel' name='config.wifi.dhcp' onchange="showSelect('dhcp_sel', 'dhcp_', 'hidden');" required>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
<option value='1'>On</option>
|
||||
<option value='0'>Off</option>
|
||||
</select><br>
|
||||
|
||||
<div class='hidden' id='dhcp_0'>
|
||||
<u>IP</u>:<br>
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.ip0'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.ip1'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.ip2'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.ip3'><br>
|
||||
|
||||
<u>Netmask</u>:<br>
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.netmask0'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.netmask1'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.netmask2'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.netmask3'><br>
|
||||
|
||||
<u>Gateway</u>:<br>
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.gateway0'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.gateway1'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.gateway2'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.gateway3'><br>
|
||||
|
||||
<u>DNS</u>:<br>
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.dns0'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.dns1'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.dns2'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.dns3'><br>
|
||||
</div>
|
||||
<br>
|
||||
<input type='submit' value='💾 Save settings'>
|
||||
</form>
|
||||
%FOOTER%)";
|
||||
|
||||
const char* Page_wifi_HTML_CURRENT_SETTINGS PROGMEM = R"(<b><u>Current Settings:</u></b><br>WiFi SSID: <b>%CONFIGWIFI_SSID%</b><br>
|
||||
Use DHCP: <b>%CONFIGWIFI_DHCP%</b><br>
|
||||
IP address: <b>%CONFIGWIFI_IP%</b><br>
|
||||
Subnet mask: <b>%CONFIGWIFI_NETMASK%</b><br>
|
||||
Gateway: <b>%CONFIGWIFI_GATEWAY%</b><br>
|
||||
DNS: <b>%CONFIGWIFI_DNS%</b><br><br>)";
|
||||
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit d4720987895bc49bdb38beb9d3e288de8bd59078
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 9412f3d1a2e334a3415d79df706dad02925488aa
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 5e34a20df62371150f7cb10330919e9393b884b1
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 78d6a130b2045a8b7b76616da81603796757ed47
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 7b2473b6b24ae340f41685b5f5b2b90ad896db04
|
File diff suppressed because it is too large
Load diff
|
@ -1 +0,0 @@
|
|||
Subproject commit 61684d4516839b579b81105be3447499c1417908
|
|
@ -1 +0,0 @@
|
|||
Subproject commit a18e50dcea4ee17285d732d39e7bc559482d1d3d
|
73
README.md
73
README.md
|
@ -9,82 +9,13 @@ An easy to use DIY grow controller firmware (for cannabis).
|
|||
![Screenshot_WebUI_root.png](Arduino/CanGrow/Screenshot_WebUI_root.png)
|
||||
![CanGrow_PCB_Front.png](KiCad/CanGrow/CanGrow_PCB_Front_small.png)
|
||||
|
||||
## WORK IN PROGRESS
|
||||
# WORK IN PROGRESS
|
||||
|
||||
## Motivation
|
||||
I havn't found an already existing grow controller project within the ESP / Arduino Core eco system which
|
||||
met my personal requirements.
|
||||
Those are an easy DIY, using low cost parts, Arduino Core sourcecode to hack own things together, having a WebUI, grab some Metrics for monitoring, standalone and my very special need that the Hardware should run completely with 12V.
|
||||
|
||||
### Update 09.12.2024 - Release of CanGrow firmware v0.1.0
|
||||
|
||||
After some feedback of users from the forum [grower.ch](https://grower.ch) (thank you guys!) I somewhat finalized firmware version v0.1 so this would be in a usable state for others to use.
|
||||
Work on v0.2 is progressing slowly, so it is better when the code, which already exists, exists in usable way :)
|
||||
|
||||
**Release notes:**
|
||||
|
||||
First non-dev release of CanGrow Firmware v0.1 branch
|
||||
|
||||
I would call this the first "official" release, which is usable. It does not (yet) contain all functions and features I planned to implement, but those will come with v0.2 of the firmware, which is already in progress for about a month now.
|
||||
|
||||
CanGrow Firmware v0.1.0 is made for the CanGrow PCB, but you can of course run it with your own hardware. Firmware v0.2 will be fully platform independent.
|
||||
|
||||
|
||||
# Setup
|
||||
|
||||
## Perform a factory reset, after you have flashed the firmware to your ESP8266 (for the first time or having problem like crash loops)!
|
||||
|
||||
You perform a factory reset (wipe) by connecting Pin D4 with GND (WIPE on the CanGrow PCB) when the builtin LED of the ESP8266 blinks three times during start. Hold for three seconds, then release. The builtin LED should now flash faster and the ESP should restart.
|
||||
Now you will see an open unprotected Wifi called `CanGrow-unconfigured`, connect to it and open the address [http://192.168.4.20](http://192.168.4.20) and you can now start setup up your CanGrow device :)
|
||||
|
||||
|
||||
# Features
|
||||
|
||||
- Gather Values from sensors
|
||||
- Temperature
|
||||
- Humidity
|
||||
- Soilmoisture
|
||||
- Time based (ntp) Light control
|
||||
- Smooth Fade in and fade out configurable
|
||||
- Control of two Fans
|
||||
- FAN1 is controlled by PWM supply Voltage
|
||||
- FAN2 is controlled by PWM 4 Pin Fan header
|
||||
- Simple watering control
|
||||
- 3 modes for water plan
|
||||
- By soilmoisture value
|
||||
- by time (every n days, different interval for vegetation and bloom phase)
|
||||
- both combined
|
||||
- Define the different phases of your grow
|
||||
- Set amount of days for vegetation and bloom phase.
|
||||
- After bloom phase CanGrow enters harvest mode by its own, which means the light and watering system gets disabled
|
||||
- Maintenance Mode
|
||||
- When using LED PWM, you can activate Maintenance Mode, which dimms the light. (at night it will turn on when enabling maintenance mode)
|
||||
- ESP32Cam integration on the Dashboard
|
||||
- Support for SSD1306 128x64 I2C OLED display
|
||||
- rotates through three different screens
|
||||
- Get measured values as json
|
||||
- `GET /api/sensors`
|
||||
|
||||
# Supported Sensors
|
||||
- Analog capacitive soilmoisture
|
||||
- I2C Chirp (https://wemakethings.net/chirp/)
|
||||
- Soilmoisture
|
||||
- Temperature
|
||||
- I2C BME280
|
||||
- Temperature
|
||||
- Humidity
|
||||
- I2C SHT31
|
||||
- Temperature
|
||||
- Humidity
|
||||
- Analog water level indicator
|
||||
- Builtin on CanGrow PCB
|
||||
- simple voltage divider, check out https://deltalima.org/blog/index.ph...e-step-water-level-indicator-for-arduino-esp/
|
||||
|
||||
# Todo
|
||||
This version branch v0.1.x will receive only bugfixes and small changes. Work on the next v0.2 has already began, which is a complete rewrite. If you are curious , check out the branch "firmware_v0.2-dev", any feedback is welcome!
|
||||
|
||||
***
|
||||
|
||||
### Update 14.09.2024 - Code Rewrite v0.2
|
||||
|
||||
I took some "summer break" from the project, and had the opportunity to talk to different people about it.
|
||||
|
@ -127,7 +58,7 @@ and let the user decide at which pin which output, sensor or whatever will be co
|
|||
- MQTT support
|
||||
- API
|
||||
|
||||
***
|
||||
|
||||
|
||||
## Old v0.1 Features / ToDo List
|
||||
|
||||
|
|
100
cangrow.sh
100
cangrow.sh
|
@ -1,100 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
|
||||
test -z $TTY && TTY="/dev/ttyUSB0"
|
||||
test -z $IP && IP="192.168.4.20"
|
||||
|
||||
VER="$(grep "define CANGROW_VER" Arduino/CanGrow/CanGrow_Version.h | cut -d \" -f2 |sed -e 's/\"//g')"
|
||||
BUILD="$(git rev-parse --short HEAD)-$(date '+%Y%m%d%H%M%S')"
|
||||
|
||||
ACLI="$HOME/.local/bin/arduino-cli"
|
||||
ACLI_CMD="$ACLI --config-file arduino-cli.yml"
|
||||
BOARD="esp8266:esp8266:d1_mini_clone"
|
||||
|
||||
function help() {
|
||||
echo "$0 [setup|build|upload|webupload|monitor]"
|
||||
echo "setup: setup build environment, download arduino-cli, install all dependencies for arduino ide"
|
||||
echo "build: build firmware binary. will be saved into build/"
|
||||
echo "upload: upload firmware by serial connection $TTY"
|
||||
echo "webupload: upload firmware with webupload to $IP"
|
||||
echo "monitor: serial monitor $TTY"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function check_acli() {
|
||||
if [ ! -x $ACLI ]
|
||||
then
|
||||
echo "$ACLI does not exist nor is executable. Please run '$0 setup' first"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
test -z $1 && help
|
||||
|
||||
case $1 in
|
||||
s|setup)
|
||||
ACLI_DIR="$(dirname $ACLI)"
|
||||
declare -a LIBS=( "Adafruit SSD1306" "Adafruit BME280 Library" "ArduinoJson" "NTPClient" "Time" "Adafruit SHT31 Library" )
|
||||
echo ":: Setting up build environment for CanGrow Firmware."
|
||||
echo " This will download the binary for arduino-cli and install"
|
||||
echo " the packages for the arduino ide from the debian repository."
|
||||
echo " !! This script is meant to be executed on a Debian stable (bookworm) system !!"
|
||||
echo ""
|
||||
echo ":: Press Enter to continue"
|
||||
read
|
||||
echo ""
|
||||
echo ":: Installing Arduino IDE packages with apt, please enter sudo password:"
|
||||
sudo apt update
|
||||
sudo apt install arduino python3 wget curl xxd
|
||||
echo ":: Ensure directory ${ACLI_DIR} is present"
|
||||
test -d ${ACLI_DIR} || mkdir -p ${ACLI_DIR}
|
||||
echo ":: Please ensure ${ACLI_DIR} is in your \$PATH, I wont do it."
|
||||
echo ""
|
||||
echo ":: Downloading arduino-cli 1.0.0 into ${ACLI_DIR}/"
|
||||
wget -O - "https://github.com/arduino/arduino-cli/releases/download/v1.0.0/arduino-cli_1.0.0_Linux_64bit.tar.gz" | tar -C ${ACLI_DIR} -zxvf - arduino-cli
|
||||
chmod +x ${ACLI}
|
||||
echo ""
|
||||
echo ":: Installing ESP8266 core for Arduino"
|
||||
${ACLI_CMD} core install esp8266:esp8266
|
||||
echo ":: Installing Arduino libraries"
|
||||
for lib in ${!LIBS[@]}
|
||||
do
|
||||
echo " - ${LIBS[$lib]}"
|
||||
done
|
||||
|
||||
for lib in ${!LIBS[@]}
|
||||
do
|
||||
${ACLI_CMD} lib install "${LIBS[$lib]}"
|
||||
done
|
||||
echo ""
|
||||
echo ":: Setup build environment done! You can now build the firmware"
|
||||
echo " with: $0 build"
|
||||
|
||||
;;
|
||||
b|build)
|
||||
check_acli
|
||||
echo ":: Building firmware $VER $BUILD, target dir: $(pwd)/build/"
|
||||
test -d build || mkdir build
|
||||
${ACLI_CMD} --no-color compile -b ${BOARD} "Arduino/CanGrow/CanGrow.ino" --build-property "build.extra_flags=-DCANGROW_BUILD=\"${BUILD}\"" --output-dir build/ || exit 1
|
||||
#${ACLI_CMD} --no-color compile -b ${BOARD} "Arduino/CanGrow/CanGrow.ino" --build-property "build.extra_flags=-DCANGROW_VER=\"${VER}\" -DCANGROW_BUILD=\"${BUILD}\"" --output-dir build/ || exit 1
|
||||
cp build/CanGrow.ino.bin build/CanGrow_v${VER}_${BUILD}.bin
|
||||
;;
|
||||
u|upload)
|
||||
check_acli
|
||||
echo ":: Uploading to $TTY"
|
||||
${ACLI_CMD} --no-color compile -v -b ${BOARD} -u -p $TTY "Arduino/CanGrow/CanGrow.ino"
|
||||
;;
|
||||
w|webupload)
|
||||
echo ":: Uploading to $IP"
|
||||
curl -v http://$IP/system/applyUpdate -X POST -H 'Content-Type: multipart/form-data' -F "firmware=@$(pwd)/build/CanGrow.ino.bin"
|
||||
echo
|
||||
;;
|
||||
m|mon|monitor)
|
||||
check_acli
|
||||
echo ":: Open serial monitor $TTY"
|
||||
${ACLI_CMD} monitor -c baudrate=115200 -b ${BOARD} -p $TTY
|
||||
;;
|
||||
*)
|
||||
help
|
||||
;;
|
||||
esac
|
203
playground/html/root/cangrow.css
Normal file
203
playground/html/root/cangrow.css
Normal file
|
@ -0,0 +1,203 @@
|
|||
body {
|
||||
color: #cae0d0;
|
||||
background-color: #1d211e;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer {
|
||||
color: #343B35;
|
||||
}
|
||||
|
||||
.center {
|
||||
/*width: 100; */
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.centered {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
border-bottom: 1px solid #262B27;
|
||||
}
|
||||
|
||||
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;*/
|
||||
display: inline-block;
|
||||
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;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.subnav {
|
||||
/*text-align: center;*/
|
||||
margin: auto;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.subnavTitle {
|
||||
font-size: 1em;
|
||||
/*font-weight: bold;*/
|
||||
margin-top: -10px;
|
||||
margin-bottom: 10px;
|
||||
/*text-align: center;*/
|
||||
}
|
||||
.nav li {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.subnav li {
|
||||
background: #262B27;
|
||||
list-style: none;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 3px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.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], .linkForm input[type=submit] {
|
||||
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);
|
||||
}
|
||||
|
||||
.subnav li a, .subnav span {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.nav li a:hover, .subnav li a:hover, .activeNav, .button:link:hover, .button:visited:hover, input[type=button]:hover,
|
||||
input[type=submit]:hover, input[type=reset]:hover, .linkForm input[type=submit]:hover {
|
||||
background: #04AA6D;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.nav li a:active, .subnav li a:active {
|
||||
background: #026b45;
|
||||
color: #cae0d0;
|
||||
}
|
||||
|
||||
.activeNav {
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.navTime {
|
||||
background: #292929;
|
||||
}
|
||||
|
||||
.button, .button:link, .button:visited, input[type=button], input[type=submit],input[type=reset],
|
||||
.linkForm input[type=submit] {
|
||||
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, .linkForm input[type=submit]: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;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.visible {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
||||
}*/
|
35
playground/html/root/cangrow.js
Normal file
35
playground/html/root/cangrow.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
function toggleDisplay(id) {
|
||||
let el = document.getElementById(id);
|
||||
let el_cs = getComputedStyle(el);
|
||||
|
||||
if (el_cs.getPropertyValue('display') === 'none') {
|
||||
el.style.display = 'inline';
|
||||
} else {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function hideAllClass(classname) {
|
||||
|
||||
const el = document.getElementsByClassName(classname);
|
||||
|
||||
for(let i = 0; i < el.length ; i++) {
|
||||
el[i].style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
function showSelect(selectId, prefix, hideClass = '') {
|
||||
if(hideClass != '') {
|
||||
hideAllClass(hideClass);
|
||||
}
|
||||
|
||||
let selVal = document.getElementById(selectId).value;
|
||||
toggleId = prefix + selVal;
|
||||
if(document.getElementById(toggleId) !== null ) {
|
||||
toggleDisplay(toggleId);
|
||||
}
|
||||
}
|
||||
|
||||
function confirmDelete(name) {
|
||||
return confirm('Delete ' + name + '?');
|
||||
}
|
BIN
playground/html/root/favicon.ico
Normal file
BIN
playground/html/root/favicon.ico
Normal file
Binary file not shown.
|
@ -1,147 +0,0 @@
|
|||
.gauge {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gaugeWrapper {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.gauge__container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
-webkit-transform: translateX(-50%);
|
||||
-moz-transform: translateX(-50%);
|
||||
-ms-transform: translateX(-50%);
|
||||
-o-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.gauge__background {
|
||||
z-index: 0;
|
||||
position: absolute;
|
||||
background-color: #cae0d0;
|
||||
top: 0;
|
||||
border-radius: 300px 300px 0 0;
|
||||
}
|
||||
|
||||
.gauge__data {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
background-color: #04AA6D;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 300px 300px 0 0;
|
||||
-webkit-transform-origin: center bottom;
|
||||
-moz-transform-origin: center bottom;
|
||||
-ms-transform-origin: center bottom;
|
||||
-o-transform-origin: center bottom;
|
||||
transform-origin: center bottom;
|
||||
}
|
||||
|
||||
.gauge__center {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
background-color: #1d211e;
|
||||
margin-right: auto;
|
||||
border-radius: 300px 300px 0 0;
|
||||
}
|
||||
|
||||
.gauge__marker {
|
||||
z-index: 3;
|
||||
background-color: #fff;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.gauge__needle {
|
||||
z-index: 4;
|
||||
background-color: #E91E63;
|
||||
height: 3px;
|
||||
position: absolute;
|
||||
-webkit-transform-origin: left center;
|
||||
-moz-transform-origin: left center;
|
||||
-ms-transform-origin: left center;
|
||||
-o-transform-origin: left center;
|
||||
transform-origin: left center;
|
||||
}
|
||||
|
||||
.gauge__labels {
|
||||
display: table;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.gauge__label--low {
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.gauge__label--spacer {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.gauge__label--high {
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.gauge { height: calc(60px + 3em); }
|
||||
.gauge__container { width: 120px; height: 60px; }
|
||||
.gauge__marker { height: 60px; left: 59.5px; }
|
||||
.gauge__background { width: 120px; height: 60px; }
|
||||
.gauge__center { width: 72px; height: 36px; top: 24px; margin-left: 24px; }
|
||||
.gauge__data { width: 120px; height: 60px; }
|
||||
.gauge__needle { left: 60px; top: 58px; width: 60px; }
|
||||
.gauge__labels { top: 60px; width: 120px; }
|
||||
.gauge__label--low { width: 24px; }
|
||||
.gauge__label--spacer { width: 72px; text-align: center;}
|
||||
.gauge__label--high { width: 24px; }
|
||||
|
||||
@media only screen and (min-width: 720px) {
|
||||
.gauge { height: calc(120px + 4.2em); }
|
||||
.gauge__container { width: 240px; height: 120px; }
|
||||
.gauge__marker { height: 120px; left: 119.5px; }
|
||||
.gauge__background { width: 240px; height: 120px; }
|
||||
.gauge__center { width: 144px; height: 72px; top: 48px; margin-left: 48px; }
|
||||
.gauge__data { width: 240px; height: 120px; }
|
||||
.gauge__needle { left: 120px; top: 117px; width: 120px; }
|
||||
.gauge__labels { top: 120px; width: 240px; }
|
||||
.gauge__label--low { width: 48px; }
|
||||
.gauge__label--spacer { width: 144px; text-align: center;}
|
||||
.gauge__label--high { width: 48px; }
|
||||
.gaugeLabel { font-size: 1.3em; }
|
||||
.gauge__labels { font-size: 2em; }
|
||||
}
|
||||
|
||||
.gauge--liveupdate .gauge__data,
|
||||
.gauge--liveupdate .gauge__needle {
|
||||
-webkit-transition: all 1s ease-in-out;
|
||||
-moz-transition: all 1s ease-in-out;
|
||||
-ms-transition: all 1s ease-in-out;
|
||||
-o-transition: all 1s ease-in-out;
|
||||
transition: all 1s ease-in-out;
|
||||
}
|
||||
|
||||
|
||||
.gauge__data {
|
||||
-webkit-transform: rotate(-.50turn);
|
||||
-moz-transform: rotate(-.50turn);
|
||||
-ms-transform: rotate(-.50turn);
|
||||
-o-transform: rotate(-.50turn);
|
||||
transform: rotate(-.50turn);
|
||||
}
|
||||
.gauge__needle {
|
||||
-webkit-transform: rotate(-.50turn);
|
||||
-moz-transform: rotate(-.50turn);
|
||||
-ms-transform: rotate(-.50turn);
|
||||
-o-transform: rotate(-.50turn);
|
||||
transform: rotate(-.50turn);
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
|
||||
|
||||
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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -1,161 +1,149 @@
|
|||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||
<title>CanGrow - Amnesia Haze</title>
|
||||
|
||||
<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='>
|
||||
<title>CanGrow - CanGrow v0.2-dev</title>
|
||||
<link rel='stylesheet' href='cangrow.css'>
|
||||
<script type='text/javascript' src='cangrow.js'></script>
|
||||
<style>
|
||||
body {
|
||||
color: #cae0d0;
|
||||
background-color: #1d211e;
|
||||
font-family: helvetica;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.nav {
|
||||
background: #333;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.nav li {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.nav li:first-of-type {
|
||||
background: #026b45;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
.nav li a , .nav span, .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 , .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 {
|
||||
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: 1280px) {
|
||||
.center, .nav {
|
||||
width: 60%; min-width: 420px;
|
||||
.linkForm {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.linkForm input[type=submit] {
|
||||
background: #262B27;
|
||||
padding: 5px;
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ul class='nav'><li><a href='/'>🌱 Amnesia Haze</a></li>
|
||||
<li><a href='/growSettings' >🔆 Grow settings</a></li>
|
||||
<li><a href='/systemSettings' >⚙ System settings</a></li>
|
||||
<li><a href='/wifiSettings' >📡 WiFi settings</a></li>
|
||||
<li><a href='/help' >❓ Help</a></li>
|
||||
<li><span class='MenuTime'>09:48:27</span></li>
|
||||
<li><a href='https://git.la10cy.net/DeltaLima/CanGrow' target='_blank'>CanGrow v0.1</a></li>
|
||||
</ul><div class='center'><h2>🔄 Firmware update</h2>Version: 0.1<br>Build: 4ad16c9
|
||||
<p>You find the latest CanGrow Firmware Version on the projects <a href='https://git.la10cy.net/DeltaLima/CanGrow/releases' target='_blank'>release page</a></p>
|
||||
<form method='GET' action='' enctype='multipart/form-data'>
|
||||
Firmware .bin file:<br>
|
||||
<input type='file' accept='.bin,.bin.gz' name='firmware'>
|
||||
|
||||
</form>
|
||||
<button onclick="document.getElementById('divUploading').style.display = ''; window.alert('click');">asd</button>
|
||||
<div id='divUploading' style='display: none;' class='warnmsg'>Uploading, please wait...<div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<ul class='nav'><li><a href='/'>🌱 CanGrow</a></li>
|
||||
<li><a class='' href='/grow/' >🔆 Grow settings</a></li>
|
||||
<li><a class='activeNav' href='/system/' >⚙ System settings</a></li>
|
||||
<li><a class='' href='/wifi/' >📡 WiFi settings</a></li>
|
||||
<li><a class='' href='/help' >❓ Help</a></li>
|
||||
<li><span class='navTime'>04:20:23</span></li>
|
||||
<li><a href='https://git.la10cy.net/DeltaLima/CanGrow' target='_blank'>CanGrow v0.2-dev</a></li>
|
||||
</ul>
|
||||
<div class='center'>
|
||||
|
||||
<ul class='subnav'>
|
||||
<li><a class='activeNav' href='/system/output/'>⚡ Output configuration</a></li>
|
||||
<li><a class='' href='/system/update'>🔄 Firmware update</a></li>
|
||||
<li><a class='' href='/system/restart' >🔁 CanGrow restart</a></li>
|
||||
<li><a class='' href='/system/wipe' >💣 Factory reset</a></li>
|
||||
</ul>
|
||||
<a class='button' href='/system/output/add'>➕ Add output</a>
|
||||
<table class='centered hidden visible'>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Device</th>
|
||||
<th>Enabled</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
<tr><td>0</td>
|
||||
<td>Tasmota LED1</td>
|
||||
<td>Webcall</td>
|
||||
<td>Light</td>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<form class='linkForm' action='add?edit=0'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='✏️'>
|
||||
</form>
|
||||
<form class='linkForm' method='post' action='index.html'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='❌' onclick="return confirmDelete('Tasmota LED1')">
|
||||
</form>
|
||||
</td></tr><tr><td>1</td>
|
||||
<td>LED2 (red)</td>
|
||||
<td>GPIO</td>
|
||||
<td>Light</td>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<form class='linkForm' action='add?edit=0'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='✏️'>
|
||||
</form>
|
||||
<form class='linkForm' method='post' action='index.html'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='❌' onclick="return confirmDelete('Tasmota LED1')">
|
||||
</form>
|
||||
</td></tr><tr><td>2</td>
|
||||
<td>LED1 Dimmer</td>
|
||||
<td>I2C</td>
|
||||
<td>Light</td>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<form class='linkForm' action='add?edit=0'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='✏️'>
|
||||
</form>
|
||||
<form class='linkForm' method='post' action='index.html'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='❌' onclick="return confirmDelete('Tasmota LED1')">
|
||||
</form>
|
||||
</td></tr><tr><td>3</td>
|
||||
<td>Fan exhaust</td>
|
||||
<td>GPIO</td>
|
||||
<td>Fan</td>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<form class='linkForm' action='add?edit=0'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='✏️'>
|
||||
</form>
|
||||
<form class='linkForm' method='post' action='index.html'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='❌' onclick="return confirmDelete('Tasmota LED1')">
|
||||
</form>
|
||||
</td></tr><tr><td>4</td>
|
||||
<td>Fan inside</td>
|
||||
<td>GPIO</td>
|
||||
<td>Fan</td>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<form class='linkForm' action='add?edit=0'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='✏️'>
|
||||
</form>
|
||||
<form class='linkForm' method='post' action='index.html'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='❌' onclick="return confirmDelete('Tasmota LED1')">
|
||||
</form>
|
||||
</td></tr><tr><td>5</td>
|
||||
<td>Tasmota Dehum</td>
|
||||
<td>Webcall</td>
|
||||
<td>Dehumidifier</td>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<form class='linkForm' action='add?edit=0'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='✏️'>
|
||||
</form>
|
||||
<form class='linkForm' method='post' action='index.html'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input type='submit' value='❌' onclick="return confirmDelete('Tasmota LED1')">
|
||||
</form>
|
||||
</td></tr><tr><td>6</td>
|
||||
<td>Test</td>
|
||||
<td>GPIO</td>
|
||||
<td>Humidifier</td>
|
||||
<td>0</td>
|
||||
<td>
|
||||
<form class='linkForm' action='add?edit=0'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='✏️'>
|
||||
</form>
|
||||
<form class='linkForm' method='post' action='index.html'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='❌' onclick="return confirmDelete('Tasmota LED1')">
|
||||
</form>
|
||||
</td></tr>
|
||||
</table>
|
||||
<div class='footer'><span>Build: 40d0175-esp8266-20241026222209</span></div>
|
||||
</div></body></html>
|
||||
|
|
|
@ -4,27 +4,21 @@
|
|||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||
<title>CanGrow - Amnesia Haze</title>
|
||||
<title>CanGrow - Ruderalis Indica</title>
|
||||
|
||||
<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;
|
||||
font-family: helvetica;
|
||||
}
|
||||
|
||||
.center {
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.centered {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -59,12 +53,6 @@ a:active {
|
|||
width: 42px;
|
||||
}
|
||||
|
||||
.helpbox {
|
||||
font-size: 0.8em;
|
||||
margin-left: 15px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.nav {
|
||||
background: #333;
|
||||
width: 100%;
|
||||
|
@ -75,27 +63,9 @@ a:active {
|
|||
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 {
|
||||
|
@ -103,7 +73,7 @@ a:active {
|
|||
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] {
|
||||
.nav li a , .nav 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;
|
||||
|
@ -113,13 +83,13 @@ a:active {
|
|||
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 {
|
||||
.nav 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 {
|
||||
.nav li a:active {
|
||||
background: #026b45;
|
||||
color: #cae0d0;
|
||||
}
|
||||
|
@ -155,151 +125,73 @@ input[type=text], input[type=date], input[type=number], input[type=password], se
|
|||
border-radius: 3px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1820px) {
|
||||
@media only screen and (min-width: 1280px) {
|
||||
.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;
|
||||
}
|
||||
}
|
||||
/* VPD colors */
|
||||
|
||||
.vpd_danger1 {
|
||||
color: #1a6c9c;
|
||||
}
|
||||
.vpd_earlyveg {
|
||||
color: #22ab9c;
|
||||
}
|
||||
.vpd_lateveg {
|
||||
color: #9cc55b;
|
||||
}
|
||||
.vpd_latebloom {
|
||||
color: #9cc55b;
|
||||
}
|
||||
.vpd_danger2 {
|
||||
color: #1a6c9c;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ul class='nav'><li><a href='/'>🌱 Amnesiaaaa Haze</a></li>
|
||||
<ul class='nav'><li><a href='/'>🌱 Ruderalis Indica</a></li>
|
||||
<li><a href='/growSettings' >🔆 Grow settings</a></li>
|
||||
<li><a href='/systemSettings' class='activeMenu'>⚙ System settings</a></li>
|
||||
<li><a href='/wifiSettings' >📡 WiFi settings</a></li>
|
||||
<li><a href='/help' >❓ Help</a></li>
|
||||
<li><span class='MenuTime'>00:23:10</span></li>
|
||||
<li><a href='https://git.la10cy.net/DeltaLima/CanGrow' target='_blank'>CanGrow v0.1.3-dev</a></li>
|
||||
</ul><div class='center'><h2>⚙ System settings</h2>
|
||||
<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>
|
||||
<p>here you can set which features and sensors you use<br></p><form method='post' action='/systemSettings/save'>
|
||||
<b>Output configuration</b><br>Invert Outputs: <select id='OutputInvert' name='OutputInvert' required>
|
||||
<option value='0'>No</option>
|
||||
<option value='1' selected >Yes</option>
|
||||
</select><br>
|
||||
<p class='helpbox'>When using CanGrow PCB v0.6, set to <b>Yes</b></p>
|
||||
Use relais for LED (disable PWM): <select id='UseLEDrelais' name='UseLEDrelais' required>
|
||||
<option value='0' selected >No</option>
|
||||
<option value='1'>Yes</option>
|
||||
</select><br>
|
||||
Use relais for FAN1 (disable PWM): <select id='UseFANrelais' name='UseFANrelais' required>
|
||||
<option value='0'>No</option>
|
||||
<option value='1' selected >Yes</option>
|
||||
</select><br><br>
|
||||
<b>Sensor configuration</b><br>Soilmoisture sensor: <select id='SelMoistureSensor_Type' name='MoistureSensor_Type' onchange='MoistureSensorType();' required>
|
||||
<option value='1' selected >Analog capacitive</option>
|
||||
<option value='2'>I2C Chirp (0x20)</option>
|
||||
</select><br>
|
||||
Soilmoisture dry: <input type='number' id='iSoilmoistureDry' name='SoilmoistureDry' min='0' value='360' required><br>
|
||||
Soilmoisture wet: <input type='number' id='iSoilmoistureWet' name='SoilmoistureWet' min='0' value='160' required><br>
|
||||
Soilmoisture raw reading: <i><span id='iSoilmoistureRaw'>123</span></i> <input type='button' class='button' value='🔃 Refresh' style='padding: 3px;' onclick='SoilmoistureRefresh();'>
|
||||
<p class='helpbox'>
|
||||
<b>Calibration</b><br>
|
||||
Put your soilmoisture sensor into dry soil and hit Refresh.<br>
|
||||
Adjust the value of 'Soilmoisture dry' if needed according to the reading.<br>
|
||||
Repeat this with wet soil for 'Soilmoisture wet'.
|
||||
</p>
|
||||
<li><span class='MenuTime'>00:03:39</span></li>
|
||||
<li><a href='https://git.la10cy.net/DeltaLima/CanGrow' target='_blank'>CanGrow v0.1</a></li>
|
||||
</ul><div class='center'><h2>⚙ System settings</h2><p>here you can set which features and sensors you use<br></p><form method='post' action='/systemSettings/save'>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Use FAN: </td>
|
||||
<td>
|
||||
<select id='UseFan' name='UseFan' required>
|
||||
<option value='1' selected >Yes</option>
|
||||
<option value='0'>No</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Use PUMP: </td>
|
||||
<td><select id='UsePump' name='UsePump' required>
|
||||
<option value='1' selected >Yes</option>
|
||||
<option value='0'>No</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Use relais for LED:
|
||||
</td>
|
||||
<td>
|
||||
<select id='UseLEDrelais' name='UseLEDrelais' required>
|
||||
<option value='1'>Yes</option>
|
||||
<option value='0' selected >No</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>Use relais for FAN: </td>
|
||||
<select id='UseFANrelais' name='UseFANrelais' required>
|
||||
<option value='1'>Yes</option>
|
||||
<option value='0' selected >No</option>
|
||||
</select>
|
||||
</tr>
|
||||
PUMP ON time: <input class='inputShort' type='number' name='PumpOnTime' min='0' max='255' value='3' required> Seconds<br>
|
||||
Soilmoisture sensor: <select id='MoistureSensor_Type' name='MoistureSensor_Type' required>
|
||||
<option value='1' selected >Analog capacitive</option>
|
||||
<option value='2'>I2C chirp</option>
|
||||
</select><br>
|
||||
Soilmoisture low: <input class='inputShort' type='number' name='SoilmoistureLow' min='0' value='20' required> %<br>
|
||||
Temperature sensor: <select id='TemperatureSensor_Type' name='TemperatureSensor_Type' required>
|
||||
<option value='1' selected >DHT11/22</option>
|
||||
<option value='2'>I2C chirp</option>
|
||||
</select><br>
|
||||
NTP offset: <input class='inputShort' type='number' name='NtpOffset' min='-12' max='14' value='2' required> Hours<br>
|
||||
Maintenance Duration: <input class='inputShort' type='number' name='MaintenanceDuration' min='0' max='900' value='300' required> Seconds<br>
|
||||
<input type='submit' value='Save'>
|
||||
|
||||
<script>
|
||||
|
||||
function loadJSON(callback) {
|
||||
var xobj = new XMLHttpRequest();
|
||||
xobj.overrideMimeType("application/json");
|
||||
xobj.open('GET', '/api/sensors', true);
|
||||
xobj.onreadystatechange = function() {
|
||||
if (xobj.readyState == 4 && xobj.status == "200") {
|
||||
callback(xobj.responseText);
|
||||
}
|
||||
}
|
||||
xobj.send(null);
|
||||
}
|
||||
|
||||
function SoilmoistureRefresh() {
|
||||
loadJSON(function(response) {
|
||||
json = JSON.parse(response);
|
||||
document.getElementById('iSoilmoistureRaw').textContent = json.soilmoistureRaw;
|
||||
console.log(json.soilmoistureRaw);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function MoistureSensorType() {
|
||||
let selVal = document.getElementById('SelMoistureSensor_Type').value;
|
||||
let wet = document.getElementById('iSoilmoistureWet');
|
||||
let dry = document.getElementById('iSoilmoistureDry');
|
||||
switch(selVal) {
|
||||
case '1':
|
||||
wet.value = 160;
|
||||
dry.value = 360;
|
||||
console.log(selVal);
|
||||
break;
|
||||
|
||||
case '2':
|
||||
wet.value = 485;
|
||||
dry.value = 250;
|
||||
console.log(selVal);
|
||||
break;
|
||||
|
||||
default:
|
||||
wet.value = 0;
|
||||
dry.value = 0;
|
||||
console.log(selVal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Temperature sensor: <select id='TemperatureSensor_Type' name='TemperatureSensor_Type' required>
|
||||
<option value='1' selected >I2C BME280 (0x76)</option>
|
||||
<option value='2'>I2C BME280 (0x77)</option>
|
||||
<option value='3'>I2C SHT31 (0x44)</option>
|
||||
<option value='4'>I2C SHT31 (0x45)</option>
|
||||
<option value='5'>I2C Chirp (0x20)</option>
|
||||
</select><br>
|
||||
Humidity sensor: <select id='HumiditySensor_Type' name='HumiditySensor_Type' required>
|
||||
<option value='1' selected >I2C BME280 (0x76)</option>
|
||||
<option value='2'>I2C BME280 (0x77)</option>
|
||||
<option value='3'>I2C SHT31 (0x44)</option>
|
||||
<option value='4'>I2C SHT31 (0x45)</option>
|
||||
</select><br><br>
|
||||
<b>General configuration</b><br>NTP offset/UTC timezone: <input class='inputShort' type='number' name='NtpOffset' min='-12' max='14' value='1' required> Hours<br>
|
||||
Maintenance Duration: <input class='inputShort' type='number' name='MaintenanceDuration' min='0' max='900' value='300' required> Seconds<br>
|
||||
PWM Frequency: <input type='number' name='PWMFrequency' min='0' max='20000' value='13370' required> Hz<br>
|
||||
Display rotation interval: <input class='inputShort' type='number' name='DisplayScreenDuration' min='0' max='255' value='3' required> Seconds<br>
|
||||
<p class='helpbox'><b>0</b> will always show sensor value screen</p>ESP32-Cam IP (optional): <input type='text' name='Esp32CamIP' maxlength='16' value='' ><br><br>
|
||||
<input type='submit' value='💾 Save settings'>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue