Compare commits
145 commits
main
...
firmware_v
Author | SHA1 | Date | |
---|---|---|---|
6d94bb224c | |||
3a55104f55 | |||
270806e5fa | |||
4ecb478354 | |||
13d86ba88f | |||
1624289496 | |||
16f4871306 | |||
df6435f6e1 | |||
156849782b | |||
4b3451c2fa | |||
de0cf2390c | |||
44c5b060d7 | |||
ac707d0367 | |||
0571acc350 | |||
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 |
47 changed files with 4960 additions and 3762 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,4 +3,4 @@ KiCad/CanGrow/CanGrow.kicad_sch-bak
|
||||||
KiCad/CanGrow/fp-info-cache
|
KiCad/CanGrow/fp-info-cache
|
||||||
KiCad/CanGrow/gerber/*.zip
|
KiCad/CanGrow/gerber/*.zip
|
||||||
Arduino/CanGrow/CanGrow.geany
|
Arduino/CanGrow/CanGrow.geany
|
||||||
build/
|
Arduino/CanGrow/build/
|
||||||
|
|
|
@ -28,30 +28,34 @@ long_line_behaviour=1
|
||||||
long_line_column=72
|
long_line_column=72
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
current_page=1
|
current_page=0
|
||||||
FILE_NAME_0=0;Arduino;0;EUTF-8;0;1;0;.%2FCanGrow.ino;0;2
|
FILE_NAME_0=493;Sh;0;EUTF-8;0;1;0;.%2Fcangrow.sh;0;2
|
||||||
FILE_NAME_1=0;C++;0;EUTF-8;0;1;0;.%2FCanGrow_HTML.h;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;.%2FCanGrow_Init.h;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;.%2FCanGrow_Logo.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;.%2FCanGrow_PinAssignments.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;.%2FCanGrow_Sensors.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;.%2FCanGrow_SysFunctions.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;.%2FCanGrow_WebFunctions.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]
|
[build-menu]
|
||||||
C++FT_00_LB=_Compile
|
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=
|
C++FT_00_WD=
|
||||||
filetypes=C++;Arduino;
|
filetypes=C++;Arduino;Sh;
|
||||||
ArduinoFT_00_LB=_Build
|
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_00_WD=
|
||||||
ArduinoFT_01_LB=Build & Upload
|
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=
|
ArduinoFT_01_WD=
|
||||||
C++FT_01_LB=_Build & Upload
|
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=
|
C++FT_01_WD=
|
||||||
|
ShFT_00_LB=Build
|
||||||
[VTE]
|
ShFT_00_CM=./cangrow.sh build
|
||||||
last_dir=~
|
ShFT_00_WD=
|
||||||
|
ShFT_01_LB=Build & Upload
|
||||||
|
ShFT_01_CM=./cangrow.sh upload
|
||||||
|
ShFT_01_WD=
|
||||||
|
|
|
@ -1,419 +1,194 @@
|
||||||
/*
|
/*
|
||||||
* 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
|
#include "Arduino.h"
|
||||||
// https://github.com/arduino/ArduinoCore-avr/tree/master/libraries/SPI
|
|
||||||
#include <SPI.h>
|
// * ESP8266 *
|
||||||
// https://github.com/arduino/ArduinoCore-avr/tree/master/libraries/Wire
|
#ifdef ESP8266
|
||||||
#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 <ESP8266WiFi.h>
|
||||||
#include <WiFiUdp.h>
|
#include <ESPAsyncTCP.h>
|
||||||
// https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer
|
#endif
|
||||||
#include <ESP8266WebServer.h>
|
|
||||||
// OTA update
|
// * ESP32 *
|
||||||
#include <ESP8266HTTPUpdateServer.h>
|
#ifdef ESP32
|
||||||
// https://github.com/adafruit/Adafruit-GFX-Library
|
|
||||||
#include <Adafruit_GFX.h>
|
#include <WiFi.h>
|
||||||
// https://github.com/adafruit/Adafruit_SSD1306
|
#include <AsyncTCP.h>
|
||||||
#include <Adafruit_SSD1306.h>
|
#include <Update.h>
|
||||||
// https://github.com/adafruit/Adafruit_BME280_Library/
|
#endif
|
||||||
#include <Adafruit_Sensor.h>
|
|
||||||
#include <Adafruit_BME280.h>
|
// https://github.com/mathieucarbou/ESPAsyncWebServer
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
// LittleFS filesystem
|
||||||
|
#include "FS.h"
|
||||||
|
// arduino-core for esp8266 and esp32
|
||||||
|
#include "LittleFS.h"
|
||||||
|
|
||||||
|
#include <SPI.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
|
||||||
// https://github.com/bblanchon/ArduinoJson
|
// https://github.com/bblanchon/ArduinoJson
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
// https://github.com/arduino-libraries/NTPClient
|
|
||||||
#include <NTPClient.h>
|
|
||||||
// https://github.com/PaulStoffregen/Time
|
// https://github.com/PaulStoffregen/Time
|
||||||
#include <TimeLib.h>
|
#include <TimeLib.h>
|
||||||
// DHT support dropped
|
|
||||||
// https://github.com/adafruit/DHT-sensor-library
|
|
||||||
// #include "DHT.h"
|
|
||||||
|
|
||||||
/*
|
|
||||||
* CanGrow header files
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "CanGrow_PinAssignments.h"
|
|
||||||
#include "CanGrow_Init.h"
|
|
||||||
#include "CanGrow_Logo.h"
|
|
||||||
#include "CanGrow_Sensors.h"
|
|
||||||
|
|
||||||
#include "CanGrow_Version.h"
|
|
||||||
#include "CanGrow_HTML.h"
|
|
||||||
#include "CanGrow_SysFunctions.h"
|
|
||||||
#include "CanGrow_WebFunctions.h"
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Setup
|
* 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_Sensor.h"
|
||||||
|
#include "include/CanGrow_Webserver.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
|
// define output for onboard LED/WIPE pin
|
||||||
// setup pins
|
|
||||||
pinMode(PinFAN, OUTPUT);
|
|
||||||
//pinMode(PINdht, INPUT);
|
|
||||||
pinMode(PINwaterlevel, OUTPUT);
|
|
||||||
pinMode(PINsoilmoisture, OUTPUT);
|
|
||||||
pinMode(PinLED, OUTPUT);
|
|
||||||
pinMode(PinPUMP, OUTPUT);
|
|
||||||
pinMode(PinWIPE, OUTPUT);
|
pinMode(PinWIPE, OUTPUT);
|
||||||
|
|
||||||
|
|
||||||
// set all OUTPUT to low
|
|
||||||
digitalWrite(PinFAN, HIGH);
|
|
||||||
digitalWrite(PINwaterlevel, LOW);
|
|
||||||
digitalWrite(PinLED, HIGH);
|
|
||||||
digitalWrite(PinPUMP, HIGH);
|
|
||||||
// except PINsoilmoisture
|
|
||||||
// PINsoilmoisture is always HIGH and gets LOW in moment of waterlevel measurement
|
|
||||||
digitalWrite(PINsoilmoisture, LOW);
|
|
||||||
|
|
||||||
// set PWM frequency to 13.37KHz
|
|
||||||
analogWriteFreq(13370);
|
|
||||||
|
|
||||||
// Start EEPROM
|
|
||||||
EEPROM.begin(512);
|
|
||||||
|
|
||||||
// Start Serial
|
// Start Serial
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
|
||||||
// Write a line before doing serious output, because before there is some garbage in serial
|
// Write a line before doing serious output, because before there is some garbage in serial
|
||||||
// whats get the cursor somewhere over the place
|
// whats get the cursor somewhere over the place
|
||||||
Serial.println("420");
|
Serial.println("420");
|
||||||
Serial.print(".:: CanGrow firmware v");
|
Serial.printf(".:: CanGrow firmware v%s build %s starting ::.\n", CANGROW_VER, CANGROW_BUILD);
|
||||||
Serial.print(CanGrowVer);
|
|
||||||
Serial.print(" build ");
|
|
||||||
Serial.print(CanGrowBuild);
|
|
||||||
Serial.println(" starting ::.");
|
|
||||||
|
|
||||||
Serial.println(":: initialise I2C ::");
|
Serial.print("II To format / factory reset LittleFS, pull GPIO ");
|
||||||
// initialise Wire for I2C
|
Serial.print(PinWIPE);
|
||||||
Wire.begin();
|
Serial.print(" (PinWIPE) to ");
|
||||||
Wire.setClockStretchLimit(2500);
|
// we need to invert the default to tell that user the state for an action
|
||||||
Serial.println(":: initialise display ::");
|
Serial.print(1 - PinWIPE_default);
|
||||||
// initialise I2C display
|
Serial.println(" - NOW! (2 seconds left) II");
|
||||||
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Address 0x3C for 128x64
|
|
||||||
display.clearDisplay();
|
|
||||||
display.display();
|
|
||||||
|
|
||||||
// set display settings
|
|
||||||
display.setTextSize(1);
|
|
||||||
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
|
|
||||||
|
|
||||||
// display Logo
|
// blink with the onboard LED on D4/GPIO2 (PinWIPE)
|
||||||
display.drawBitmap(0, 0, bmpCanGrow_Logo, 128, 32, WHITE);
|
|
||||||
display.display();
|
|
||||||
|
|
||||||
Serial.println(":: initialise chirp sensor if present ::");
|
|
||||||
// reset chirp
|
|
||||||
writeI2CRegister8bit(0x20, 6); //TODO: Do only, when configured
|
|
||||||
|
|
||||||
// initialise DHT11
|
|
||||||
// dht support dropped
|
|
||||||
// dht.begin(); //TODO: Do only, when configured
|
|
||||||
|
|
||||||
// initialise BME280
|
|
||||||
Serial.println(":: initialise BME280 sensor ::");
|
|
||||||
// ToDo: let the user configure somewhere the ID of the BME280 sensor
|
|
||||||
if(!bme.begin(0x76)) {
|
|
||||||
Serial.println("!! Cannot find BME280 on I2C bus. Please check connection or ID");
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println("To wipe the EEPROM saved data, set D4 (PinWIPE) to LOW - NOW! (2 seconds left)");
|
|
||||||
// wait a few seconds to let the user pull D4 down to wipe EEPROM
|
|
||||||
// and we can enjoy the boot screen meanwhile :p
|
|
||||||
// meanwhile blink with the led onboad :)
|
|
||||||
// 333 * 6 =~ 2 seconds
|
|
||||||
|
|
||||||
display.fillRect(0,36,128,64-36, 0);
|
|
||||||
display.setCursor(0,36);
|
|
||||||
display.println("To wipe EEPROM pull");
|
|
||||||
display.println("D4 (PinWIPE) to GND");
|
|
||||||
display.display();
|
|
||||||
|
|
||||||
// blink with the onboard LED on D4 (PinWIPE)
|
|
||||||
for(byte i = 0; i <= 6 ; i++) {
|
for(byte i = 0; i <= 6 ; i++) {
|
||||||
if(i % 2) {
|
if(i % 2) {
|
||||||
digitalWrite(PinWIPE, LOW);
|
digitalWrite(PinWIPE, 1 - PinWIPE_default);
|
||||||
} else {
|
} else {
|
||||||
digitalWrite(PinWIPE, HIGH);
|
digitalWrite(PinWIPE, PinWIPE_default);
|
||||||
}
|
}
|
||||||
delay(333);
|
delay(333);
|
||||||
}
|
}
|
||||||
// set back to HIGH because thats the default
|
|
||||||
digitalWrite(PinWIPE, HIGH);
|
// set PinWIPE back to its default
|
||||||
//delay(2000);
|
digitalWrite(PinWIPE, PinWIPE_default);
|
||||||
|
|
||||||
|
|
||||||
// read status from PinWIPE to WIPE
|
// read status from PinWIPE to WIPE
|
||||||
// when PinWIPE is set to LOW, wipe EEPROM
|
// when PinWIPE is set to LOW, format LittleFS
|
||||||
if(digitalRead(PinWIPE) == LOW) {
|
if(digitalRead(PinWIPE) != PinWIPE_default) {
|
||||||
// wipe EEPROM
|
LFS_Format();
|
||||||
wipeEEPROM();
|
Restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* 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();
|
|
||||||
|
|
||||||
|
LFS_Init();
|
||||||
|
LoadConfig();
|
||||||
|
Wifi_Init();
|
||||||
|
Webserver_Init();
|
||||||
|
Serial.printf(":: [SETUP] Usable Pins: %d\n", GPIOindex_length);
|
||||||
|
for(byte i = 1; 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]);
|
||||||
}
|
}
|
||||||
// set web handler
|
|
||||||
WebHandler();
|
|
||||||
// start webserver
|
|
||||||
webserver.begin();
|
|
||||||
|
|
||||||
Serial.println(".:: CanGrow Ready ::.");
|
|
||||||
delay(1000);
|
|
||||||
if(strlen(GrowName) > 0 ) {
|
Serial.println(":: [SETUP] Sensor drivers");
|
||||||
display.clearDisplay();
|
|
||||||
display.display();
|
for(byte i = 1; i <= SensorIndex_length; i++) {
|
||||||
|
Serial.print(":: [SETUP] Sensor Index ");
|
||||||
|
Serial.print(i);
|
||||||
|
Serial.print(", Name '");
|
||||||
|
Serial.print(SensorIndex[i].name);
|
||||||
|
Serial.println("', Readings");
|
||||||
|
|
||||||
|
for(byte j = 0; j < SENSOR_MAX_READING; j++) {
|
||||||
|
if(SensorIndex[i].reading[j] > 0 ) {
|
||||||
|
Serial.print(":: [SETUP] ");
|
||||||
|
Serial.print(j);
|
||||||
|
Serial.print(": ");
|
||||||
|
Serial.print(Sensor_Reading_descr[SensorIndex[i].reading[j]]);
|
||||||
|
Serial.print(" (");
|
||||||
|
Serial.print(SensorIndex[i].reading[j]);
|
||||||
|
Serial.println(")");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
}
|
||||||
*
|
|
||||||
*
|
bool alrdySaved = false;
|
||||||
* Loop
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
void loop() {
|
void loop() {
|
||||||
// var definition
|
unsigned long currentMillis = millis();
|
||||||
unsigned long currentRuntime = millis();
|
|
||||||
|
if(currentMillis - schedulerPrevMillis >= config.system.schedulerInterval) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// first we call webserver handle client
|
if((digitalRead(PinWIPE) != PinWIPE_default) && (alrdySaved == false)) {
|
||||||
webserver.handleClient();
|
Serial.println(":: [LOOP] PinWIPE is triggered");
|
||||||
|
// save config to littlefs as json
|
||||||
|
SaveConfig();
|
||||||
// do every second when everything is configured and grow is started
|
// only print json to serial
|
||||||
if( (configured == true) && (strlen(GrowName) > 0) && (currentRuntime - outputPrevTime >= 1000) ){
|
SaveConfig(true);
|
||||||
|
alrdySaved = true;
|
||||||
// refresh all sensor values
|
} else if( (digitalRead(PinWIPE) != PinWIPE_default) && (alrdySaved == true) ) {
|
||||||
refreshSensors();
|
alrdySaved = true;
|
||||||
|
|
||||||
// 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 {
|
} else {
|
||||||
MaintenanceMode = false;
|
alrdySaved = false;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
controlLED();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
controlPUMP();
|
// if global var doRestart is true, perform a restart
|
||||||
|
if(doRestart == true) {
|
||||||
displayScreens();
|
Restart();
|
||||||
|
|
||||||
// current time gets previous time for new interval
|
|
||||||
outputPrevTime = currentRuntime;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* 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,525 +0,0 @@
|
||||||
|
|
||||||
/*
|
|
||||||
* HTML constants for header, footer, css, ...
|
|
||||||
* Note: I know of the existence of SPIFFS and ESPHtmlTemplateProcessor,
|
|
||||||
* but to keep things simple for compiling and upload for others, I decided
|
|
||||||
* to not use those.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Template: const char HTMLexamplepage[] PROGMEM = R"EOF()EOF";
|
|
||||||
|
|
||||||
// first part of HTML header stuff
|
|
||||||
const char HTMLheaderP1[] PROGMEM = R"EOF(
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset='UTF-8'>
|
|
||||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
|
||||||
)EOF";
|
|
||||||
// here comes the page title in returnHTMLheader()
|
|
||||||
|
|
||||||
// second part of HTML header stuff
|
|
||||||
// Having the whole CSS here ensures it's all the time present
|
|
||||||
const char HTMLheaderP2[] PROGMEM = R"EOF(
|
|
||||||
<link rel='icon' href='data:;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAABcElEQVQ4y42TzU/bQBDFf7Nx1qGuAYVgQSuo2khBggPhyIH//9AiJAQ9tEeLqCKiUD6sxF52OMSEBCdW57aa9968fTsr3V5XWVLPO6sANNL7ZRAMNeU6Ea4T1UEI6pr55kcAwhpMrYOpk2/r/yEQmKWkIonf+TZVgex4Fw0bIEtIAALF3gbZ8U5VwKa3PJ18JT9IpiLvyflBwuhLG5veVUM0/0aoCONPa2hQjWZ8uEVeupJnXSBwO8YOH8iTeAKc2Q4Xt2C1VZL93F7MjbK/bxDnp5Zn7b+So+9pdQ+K/Q5qJlrRj5Ts6DM+rK7Ih7Mr3HaM7jYQVZqXQ6Tb6yqBYdTfomhHiFfUyMI3f+01/z7RHNzTGDyWGThP63SA2d8EEfIkrgQpzmOvH0AV+3M4zegNpUwagAYG8Yp4BS0nl4Kz5Mpf0JXJMby6w/66Aa+M+9uE53/Iexsggq4ESOYWC0jmsBfX8xdXhcJjL4cLc3kBl8uJGQ/CrpAAAAAASUVORK5CYII='>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
color: #cae0d0;
|
|
||||||
background-color: #1d211e;
|
|
||||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center {
|
|
||||||
width: 100%;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.centered {
|
|
||||||
display: block;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3, h4, h5 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
a:link, a:visited {
|
|
||||||
color: #04AA6D;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #64AA6D;
|
|
||||||
}
|
|
||||||
a:active {
|
|
||||||
color: #04AA6D;
|
|
||||||
}
|
|
||||||
.infomsg , .warnmsg {
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 4px;
|
|
||||||
width: fit-content; min-width: 200px; max-width: 420px;
|
|
||||||
margin: auto;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
.infomsg {
|
|
||||||
background: #04AA6D;
|
|
||||||
}
|
|
||||||
.warnmsg {
|
|
||||||
background: #aa4204;
|
|
||||||
}
|
|
||||||
.inputShort {
|
|
||||||
width: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.helpbox {
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
.nav {
|
|
||||||
background: #333;
|
|
||||||
width: 100%;
|
|
||||||
margin: auto;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 0;
|
|
||||||
position: relative;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subnav {
|
|
||||||
text-align: center;
|
|
||||||
display: table;
|
|
||||||
margin: auto;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 0;
|
|
||||||
position: relative;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav li {
|
|
||||||
display: inline-block;
|
|
||||||
list-style: none;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subnav li {
|
|
||||||
background: #026b45;
|
|
||||||
list-style: none;
|
|
||||||
border-radius: 3px;
|
|
||||||
margin-bottom: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav li:first-of-type {
|
|
||||||
background: #026b45;
|
|
||||||
border-top-left-radius: 3px;
|
|
||||||
border-bottom-left-radius: 3px;
|
|
||||||
}
|
|
||||||
.nav li a, .nav span, .subnav li a, .subnav span, .button, .button:link, input[type=button], input[type=submit], input[type=reset] {
|
|
||||||
color: #ddd;
|
|
||||||
display: block;
|
|
||||||
font-family: 'Lucida Sans Unicode', 'Lucida Grande', sans-serif;
|
|
||||||
font-size:0.8em;
|
|
||||||
padding: 10px 20px;
|
|
||||||
text-decoration: none;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav li a:hover, .subnav li a:hover, .activeMenu, .button:link:hover, .button:visited:hover, input[type=button]:hover, input[type=submit]:hover, input[type=reset]:hover {
|
|
||||||
background: #04AA6D;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav li a:active, .subnav li a:active {
|
|
||||||
background: #026b45;
|
|
||||||
color: #cae0d0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.activeMenu {
|
|
||||||
background: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
.MenuTime {
|
|
||||||
background: #292929;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button, .button:link, .button:visited, input[type=button], input[type=submit], input[type=reset] {
|
|
||||||
background: #026b45;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-block;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:link:active, .button:visited:active, input[type=button]:active, input[type=submit]:active, input[type=reset]:active {
|
|
||||||
background: #026b45;
|
|
||||||
color: #cae0d0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=text], input[type=date], input[type=number], input[type=password], select {
|
|
||||||
background: #cae0d0;
|
|
||||||
color: #1d211e;
|
|
||||||
border: 1px solid #026b45;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (min-width: 1820px) {
|
|
||||||
.center, .nav {
|
|
||||||
width: 60%; min-width: 420px;
|
|
||||||
}
|
|
||||||
.subnav li {
|
|
||||||
display: '';
|
|
||||||
margin-bottom: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (min-width: 640px) {
|
|
||||||
.subnav li {
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<ul class='nav'>)EOF";
|
|
||||||
// here comes the menu as unordered List in returnHTMLheader()
|
|
||||||
|
|
||||||
|
|
||||||
const char HTMLfooter[] PROGMEM = R"EOF(
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)EOF";
|
|
||||||
|
|
||||||
|
|
||||||
const char HTMLsuccess[] PROGMEM = R"EOF(
|
|
||||||
<div class='infomsg'>✅ Successfully saved!</div>
|
|
||||||
)EOF";
|
|
||||||
|
|
||||||
const char HTMLneedRestart[] PROGMEM = R"EOF(
|
|
||||||
<div class='warnmsg'>❗ Restart is required to apply new WiFi settings!
|
|
||||||
<form action='/system/restart'>
|
|
||||||
<input type='submit' value='Restart now' />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
)EOF";
|
|
||||||
|
|
||||||
const char HTMLhelp[] PROGMEM = R"EOF(
|
|
||||||
<h2>❓ Help</h2>
|
|
||||||
Here you will get some helpful help.
|
|
||||||
<h3>API</h3>
|
|
||||||
<a href='/api/sensors' target='_blank'>Sensor data</a>: <code>GET /api/sensors</code><br>
|
|
||||||
<a href='/api/debug' target='_blank'>Debug all data:</a> <code>GET /api/debug</code>
|
|
||||||
)EOF";
|
|
||||||
|
|
||||||
|
|
||||||
const char JSconvertDateToEpoch[] PROGMEM = R"EOF(
|
|
||||||
<script>
|
|
||||||
function convertDateToEpoch(src, dst) {
|
|
||||||
var valGrowStart = document.getElementById(src).value ;
|
|
||||||
document.getElementById(dst).value = new Date(valGrowStart).getTime() / 1000;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
)EOF";
|
|
||||||
|
|
||||||
// The gauge meter are based on sathomas' gaugemeter
|
|
||||||
// https://github.com/sathomas/material-gauge
|
|
||||||
|
|
||||||
const char CSSgauge[] PROGMEM = R"EOF(
|
|
||||||
.gauge {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gaugeWrapper {
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gauge__container {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
text-align: center;
|
|
||||||
-webkit-transform: translateX(-50%);
|
|
||||||
-moz-transform: translateX(-50%);
|
|
||||||
-ms-transform: translateX(-50%);
|
|
||||||
-o-transform: translateX(-50%);
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gauge__background {
|
|
||||||
z-index: 0;
|
|
||||||
position: absolute;
|
|
||||||
background-color: #cae0d0;
|
|
||||||
top: 0;
|
|
||||||
border-radius: 300px 300px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gauge__data {
|
|
||||||
z-index: 1;
|
|
||||||
position: absolute;
|
|
||||||
background-color: #04AA6D;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
border-radius: 300px 300px 0 0;
|
|
||||||
-webkit-transform-origin: center bottom;
|
|
||||||
-moz-transform-origin: center bottom;
|
|
||||||
-ms-transform-origin: center bottom;
|
|
||||||
-o-transform-origin: center bottom;
|
|
||||||
transform-origin: center bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gauge__center {
|
|
||||||
z-index: 2;
|
|
||||||
position: absolute;
|
|
||||||
background-color: #1d211e;
|
|
||||||
margin-right: auto;
|
|
||||||
border-radius: 300px 300px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gauge__marker {
|
|
||||||
z-index: 3;
|
|
||||||
background-color: #fff;
|
|
||||||
position: absolute;
|
|
||||||
width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gauge__needle {
|
|
||||||
z-index: 4;
|
|
||||||
background-color: #E91E63;
|
|
||||||
height: 3px;
|
|
||||||
position: absolute;
|
|
||||||
-webkit-transform-origin: left center;
|
|
||||||
-moz-transform-origin: left center;
|
|
||||||
-ms-transform-origin: left center;
|
|
||||||
-o-transform-origin: left center;
|
|
||||||
transform-origin: left center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gauge__labels {
|
|
||||||
display: table;
|
|
||||||
margin: 0 auto;
|
|
||||||
position: relative;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gauge__label--low {
|
|
||||||
display: table-cell;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gauge__label--spacer {
|
|
||||||
display: table-cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gauge__label--high {
|
|
||||||
display: table-cell;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.gauge { height: calc(60px + 3em); }
|
|
||||||
.gauge__container { width: 120px; height: 60px; }
|
|
||||||
.gauge__marker { height: 60px; left: 59.5px; }
|
|
||||||
.gauge__background { width: 120px; height: 60px; }
|
|
||||||
.gauge__center { width: 72px; height: 36px; top: 24px; margin-left: 24px; }
|
|
||||||
.gauge__data { width: 120px; height: 60px; }
|
|
||||||
.gauge__needle { left: 60px; top: 58px; width: 60px; }
|
|
||||||
.gauge__labels { top: 60px; width: 120px; }
|
|
||||||
.gauge__label--low { width: 24px; }
|
|
||||||
.gauge__label--spacer { width: 72px; text-align: center;}
|
|
||||||
.gauge__label--high { width: 24px; }
|
|
||||||
.gaugeLabel { text-align: center; }
|
|
||||||
|
|
||||||
|
|
||||||
@media only screen and (min-width: 720px) {
|
|
||||||
.gauge { height: calc(120px + 4.2em); }
|
|
||||||
.gauge__container { width: 240px; height: 120px; }
|
|
||||||
.gauge__marker { height: 120px; left: 119.5px; }
|
|
||||||
.gauge__background { width: 240px; height: 120px; }
|
|
||||||
.gauge__center { width: 144px; height: 72px; top: 48px; margin-left: 48px; }
|
|
||||||
.gauge__data { width: 240px; height: 120px; }
|
|
||||||
.gauge__needle { left: 120px; top: 117px; width: 120px; }
|
|
||||||
.gauge__labels { top: 120px; width: 240px; }
|
|
||||||
.gauge__label--low { width: 48px; }
|
|
||||||
.gauge__label--spacer { width: 144px; text-align: center;}
|
|
||||||
.gauge__label--high { width: 48px; }
|
|
||||||
.gaugeLabel { font-size: 1.3em; }
|
|
||||||
.gauge__labels { font-size: 2em; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.gauge--liveupdate .gauge__data,
|
|
||||||
.gauge--liveupdate .gauge__needle {
|
|
||||||
-webkit-transition: all 1s ease-in-out;
|
|
||||||
-moz-transition: all 1s ease-in-out;
|
|
||||||
-ms-transition: all 1s ease-in-out;
|
|
||||||
-o-transition: all 1s ease-in-out;
|
|
||||||
transition: all 1s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.gauge__data {
|
|
||||||
-webkit-transform: rotate(-.50turn);
|
|
||||||
-moz-transform: rotate(-.50turn);
|
|
||||||
-ms-transform: rotate(-.50turn);
|
|
||||||
-o-transform: rotate(-.50turn);
|
|
||||||
transform: rotate(-.50turn);
|
|
||||||
}
|
|
||||||
.gauge__needle {
|
|
||||||
-webkit-transform: rotate(-.50turn);
|
|
||||||
-moz-transform: rotate(-.50turn);
|
|
||||||
-ms-transform: rotate(-.50turn);
|
|
||||||
-o-transform: rotate(-.50turn);
|
|
||||||
transform: rotate(-.50turn);
|
|
||||||
}
|
|
||||||
|
|
||||||
)EOF";
|
|
||||||
|
|
||||||
const char JSgauge[] PROGMEM = R"EOF(
|
|
||||||
|
|
||||||
function Gauge(el) {
|
|
||||||
|
|
||||||
var element, // Containing element for the info component
|
|
||||||
data, // `.gauge__data` element
|
|
||||||
needle, // `.gauge__needle` element
|
|
||||||
value = 0.0, // Current gauge value from 0 to 1
|
|
||||||
prop, // Style for transform
|
|
||||||
valueLabel; // `.gauge__label--spacer` element
|
|
||||||
|
|
||||||
var setElement = function(el) {
|
|
||||||
// Keep a reference to the various elements and sub-elements
|
|
||||||
element = el;
|
|
||||||
data = element.querySelector('.gauge__data');
|
|
||||||
needle = element.querySelector('.gauge__needle');
|
|
||||||
valueLabel = element.querySelector('.gauge__label--spacer');
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
var setValue = function(x, max, unit) {
|
|
||||||
percentage = x * 100 / max;
|
|
||||||
value = percentage / 100;
|
|
||||||
var turns = -0.5 + (value * 0.5);
|
|
||||||
data.style[prop] = 'rotate(' + turns + 'turn)';
|
|
||||||
needle.style[prop] = 'rotate(' + turns + 'turn)';
|
|
||||||
valueLabel.textContent = x + unit;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
function exports() { };
|
|
||||||
|
|
||||||
exports.element = function(el) {
|
|
||||||
if (!arguments.length) { return element; }
|
|
||||||
setElement(el);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.value = function(x, max=100, unit='%') {
|
|
||||||
if (!arguments.length) { return value; }
|
|
||||||
setValue(x, max, unit);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
var body = document.getElementsByTagName('body')[0];
|
|
||||||
['webkitTransform', 'mozTransform', 'msTransform', 'oTransform', 'transform'].
|
|
||||||
forEach(function(p) {
|
|
||||||
if (typeof body.style[p] !== 'undefined') { prop = p; }
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (arguments.length) {
|
|
||||||
setElement(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
return exports;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
)EOF";
|
|
||||||
|
|
||||||
const char HTMLgauge[] PROGMEM = R"EOF(
|
|
||||||
|
|
||||||
<div class='gaugeWrapper'>
|
|
||||||
<div class='gauge gauge--liveupdate spacer' id='gaugeTemperature' style='float:left; margin-right: 10px;'>
|
|
||||||
<div class='gaugeLabel'>Temperature</div>
|
|
||||||
<div class='gauge__container'>
|
|
||||||
<div class='gauge__background'></div>
|
|
||||||
<div class='gauge__center'></div>
|
|
||||||
<div class='gauge__data'></div>
|
|
||||||
<div class='gauge__needle'></div>
|
|
||||||
</div>
|
|
||||||
<div class='gauge__labels mdl-typography__headline'>
|
|
||||||
<span class='gauge__label--low'></span>
|
|
||||||
<span class='gauge__label--spacer'></span></span>
|
|
||||||
<span class='gauge__label--high'></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='gauge gauge--liveupdate spacer' id='gaugeHumidity' style='float:left; margin-right: 10px;'>
|
|
||||||
<div class='gaugeLabel'>Humidity</div>
|
|
||||||
<div class='gauge__container'>
|
|
||||||
<div class='gauge__background'></div>
|
|
||||||
<div class='gauge__center'></div>
|
|
||||||
<div class='gauge__data'></div>
|
|
||||||
<div class='gauge__needle'></div>
|
|
||||||
</div>
|
|
||||||
<div class='gauge__labels mdl-typography__headline'>
|
|
||||||
<span class='gauge__label--low'></span>
|
|
||||||
<span class='gauge__label--spacer'></span>
|
|
||||||
<span class='gauge__label--high'></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='gauge gauge--liveupdate' id='gaugeSoilmoisture' style='float:left;'>
|
|
||||||
<div class='gaugeLabel'>Soilmoisture</div>
|
|
||||||
<div class='gauge__container'>
|
|
||||||
<div class='gauge__background'></div>
|
|
||||||
<div class='gauge__center'></div>
|
|
||||||
<div class='gauge__data'></div>
|
|
||||||
<div class='gauge__needle'></div>
|
|
||||||
</div>
|
|
||||||
<div class='gauge__labels mdl-typography__headline'>
|
|
||||||
<span class='gauge__label--low'></span>
|
|
||||||
<span class='gauge__label--spacer'></span>
|
|
||||||
<span class='gauge__label--high'></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src='gauge.js'></script>
|
|
||||||
<script>
|
|
||||||
var gaugeTemperature = new Gauge(document.getElementById('gaugeTemperature'));
|
|
||||||
var gaugeHumidity = new Gauge(document.getElementById('gaugeHumidity'));
|
|
||||||
var gaugeSoilmoisture = new Gauge(document.getElementById('gaugeSoilmoisture'));
|
|
||||||
</script>
|
|
||||||
|
|
||||||
)EOF";
|
|
||||||
|
|
||||||
|
|
||||||
const char HTMLupdate[] PROGMEM = R"EOF(
|
|
||||||
<p>You find the latest CanGrow firmware version on the <a href='https://git.la10cy.net/DeltaLima/CanGrow/releases' target='_blank'>release page</a> of the git repository.</p>
|
|
||||||
<form method='POST' action='/system/applyUpdate' enctype='multipart/form-data' onsubmit="document.getElementById('divUploading').style.display = '';">
|
|
||||||
<b>Select .bin file:</b><br>
|
|
||||||
<input type='file' accept='.bin,.bin.gz' name='firmware' required>
|
|
||||||
<input type='submit' value='Update Firmware'>
|
|
||||||
</form>
|
|
||||||
<div id='divUploading' style='display: none;' class='warnmsg'>🛜 Uploading, please wait...<div>
|
|
||||||
)EOF";
|
|
||||||
|
|
||||||
const char HTMLsystemSubNav[] PROGMEM = R"EOF(
|
|
||||||
<ul class='subnav'>
|
|
||||||
<li><a href='/system/update'>🔄 Firmware update</a></li>
|
|
||||||
<li><a href='/system/restart' >🔁 CanGrow restart</a></li>
|
|
||||||
<li><a href='/system/wipe' >💣 Factory reset</a></li>
|
|
||||||
</ul>
|
|
||||||
)EOF";
|
|
|
@ -1,178 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Constants
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
const char* APssid = "CanGrow-unconfigured";
|
|
||||||
/*
|
|
||||||
* TODO - does not work atm. idk why.
|
|
||||||
* const char* APpass = "CanGrow";
|
|
||||||
const int APchannel = 6;
|
|
||||||
const bool APhidden = false;
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Variables
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// valSoilmoisture - contains the value of getSoilmoisture()
|
|
||||||
unsigned short valSoilmoisture;
|
|
||||||
// valTemperature - contains the value of getTemperature()
|
|
||||||
float valTemperature;
|
|
||||||
// valTemperature - contains the value of getHumidity()
|
|
||||||
float valHumidity;
|
|
||||||
// valWaterlevel - contains the value of getWaterlevel()
|
|
||||||
byte valWaterlevel;
|
|
||||||
// do we need a restart? (e.g. after wifi settings change)
|
|
||||||
bool NeedRestart;
|
|
||||||
bool FirstRun;
|
|
||||||
// which screen should be actually displayed
|
|
||||||
byte ScreenToDisplay = 0;
|
|
||||||
byte DisplayScreenDuration = 3;
|
|
||||||
// how many seconds actual screen got displayed
|
|
||||||
byte ScreenIterationPassed = 0;
|
|
||||||
|
|
||||||
bool MaintenanceMode = false;
|
|
||||||
unsigned long MaintenanceStarted = 0;
|
|
||||||
|
|
||||||
// helper variable to remember how many seconds the pump was
|
|
||||||
// already on within the actual watering cycle
|
|
||||||
byte PumpOnTimePassed = 0;
|
|
||||||
bool PumpOnManual = false;
|
|
||||||
|
|
||||||
// helper variable for pump control with soilmoisture
|
|
||||||
// average of last readings
|
|
||||||
unsigned short valSoilmoistureAvg = 0;
|
|
||||||
unsigned short valSoilmoistureAvg_tmp = 0;
|
|
||||||
byte valSoilmoistureAvg_count = 0;
|
|
||||||
//unsigned short
|
|
||||||
|
|
||||||
/*
|
|
||||||
* millis timer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
unsigned long outputPrevTime = 0;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* EEPROM saved variables
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
|
||||||
// WiFi
|
|
||||||
//
|
|
||||||
|
|
||||||
// if empty, CanGrow start in AccessPoint mode
|
|
||||||
char WIFIssid[32];
|
|
||||||
char WIFIpassword[64];
|
|
||||||
// WIFIuseDHCP - if true, get IP by DHCP
|
|
||||||
bool WIFIuseDHCP;
|
|
||||||
IPAddress WIFIip(192,168,4,20);
|
|
||||||
IPAddress WIFInetmask(255,255,255,0);
|
|
||||||
IPAddress WIFIgateway(192,168,4,254);
|
|
||||||
IPAddress WIFIdns(0,0,0,0);
|
|
||||||
//char WebUiUsername[16] = "cangrow";
|
|
||||||
//char WebUiPassword[32] = "cangrow";
|
|
||||||
|
|
||||||
//
|
|
||||||
// System
|
|
||||||
//
|
|
||||||
// configured - if false, let the user configure system settings first
|
|
||||||
bool configured = false;
|
|
||||||
// NTP Offset
|
|
||||||
short NtpOffset;
|
|
||||||
// MoistureSensor_Type - contains which moisture sensor to use
|
|
||||||
// 1: analog capacitive sensor
|
|
||||||
// 2: I2C chirp sensor from catnip electronics
|
|
||||||
byte MoistureSensor_Type;
|
|
||||||
// SoilmoistureLow - contains the value , when soil moisture is assumed to be low,
|
|
||||||
byte SoilmoistureLow = 80;
|
|
||||||
// UsePump - is the pump used? bool
|
|
||||||
// PumpMode (short) 1: Pump on every n days, 2: Pump on when Soilmoisture <= SoilmoistureLow, 3: Both
|
|
||||||
byte UsePump;
|
|
||||||
// UseFan - is the fan used? bool
|
|
||||||
// PumpOnTime in seconds
|
|
||||||
byte PumpOnTime = 3;
|
|
||||||
byte UseFan;
|
|
||||||
// In case the user uses no 12V LED on the LED output and an relais instead
|
|
||||||
// we have to disable PWM. So we ask here for what kind of light user is going
|
|
||||||
bool UseLEDrelais;
|
|
||||||
bool UseFANrelais;
|
|
||||||
// Which temperature sensor to use?
|
|
||||||
byte TemperatureSensor_Type;
|
|
||||||
unsigned short MaintenanceDuration = 300;
|
|
||||||
char Esp32CamIP[16];
|
|
||||||
// PumpLastOn (long) timestamp
|
|
||||||
unsigned long PumpLastOn;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Grow Stuff
|
|
||||||
//
|
|
||||||
|
|
||||||
// GrowName - contains the name of the grow/plant. Up to 32 byte
|
|
||||||
// if empty, let the user setup grow settings first
|
|
||||||
char GrowName[32];
|
|
||||||
// GrowStart - contains unix timestamp from date where grow starts (00:00)
|
|
||||||
// unsigned long is 8 byte
|
|
||||||
unsigned long GrowStart;
|
|
||||||
// DayOfGrow contains on which day the grow is
|
|
||||||
byte DayOfGrow;
|
|
||||||
// DaysVeg - contains how many days to be in vegetation phase
|
|
||||||
byte DaysVeg = 35;
|
|
||||||
// DaysBloom - contains how many days to be in bloom phase
|
|
||||||
byte DaysBloom = 49;
|
|
||||||
// LighthoursVeg - contains how many hours the Growlight is on in Veg
|
|
||||||
byte LighthoursVeg = 16;
|
|
||||||
// LighthoursBloom - contains how many hours the Growlight is on in Bloom
|
|
||||||
byte LighthoursBloom = 12;
|
|
||||||
// SunriseHour - contains to which hour of day the growlight turns on
|
|
||||||
byte SunriseHour = 7;
|
|
||||||
// SunriseHour - contains to which minute of SunriseHour the growlight turns on
|
|
||||||
byte SunriseMinute = 0;
|
|
||||||
// PinLEDPWM - contains the PWM value for dimming the grow light
|
|
||||||
// default is 255 to ensure it is just on for the case UseLEDrelais is true
|
|
||||||
byte PinLEDPWM = 255;
|
|
||||||
byte PinFANPWM = 255;
|
|
||||||
|
|
||||||
// fade in and out sunrise and sunset?
|
|
||||||
bool SunFade;
|
|
||||||
byte SunFadeDuration = 30;
|
|
||||||
|
|
||||||
// PumpIntervalVeg (int) in days
|
|
||||||
byte PumpIntervalVeg = 5;
|
|
||||||
byte PumpIntervalBloom = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* NTP
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
WiFiUDP ntpUDP;
|
|
||||||
NTPClient timeClient(ntpUDP);
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Webserver
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
ESP8266WebServer webserver(80);
|
|
||||||
ESP8266HTTPUpdateServer webUpdater;
|
|
||||||
|
|
||||||
/* I2C Stuff
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#define WIRE Wire
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Display Stuff
|
|
||||||
*/
|
|
||||||
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &WIRE);
|
|
|
@ -1,30 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Pin assignments
|
|
||||||
*
|
|
||||||
* D0 - MOSFET Pump
|
|
||||||
* D1, D2 - I2C
|
|
||||||
* D3 - DHT11
|
|
||||||
* D4 - PinWIPE
|
|
||||||
* D5 - MOSFET Fan
|
|
||||||
* D6 - MOSFET Grow LED, PWM
|
|
||||||
* D7 - waterlevel (set HIGH to read value)
|
|
||||||
* D8 - analog soil moisture (set HIGH to read value)
|
|
||||||
* A0 - analog input for soil moisture and waterlevel readings
|
|
||||||
*
|
|
||||||
* D4 and D7 cannot be HIGH at the same time!
|
|
||||||
*/
|
|
||||||
|
|
||||||
// D0 is HIGH at boot, no PWM
|
|
||||||
const uint8_t PinPUMP = D0;
|
|
||||||
// If D3 is pulled to LOW, boot fails
|
|
||||||
//const uint8_t PINdht = D3;
|
|
||||||
// D4 is HIGH at boot, boot fail if pulled to LOW
|
|
||||||
// During Start Screen you can pull D4 to LOW to wipe saved data in EEPROM
|
|
||||||
// DO NOT PULL D4 DOWN AT WHEN POWERING ON !!! BOOT WILL FAIL
|
|
||||||
const uint8_t PinWIPE = D4;
|
|
||||||
const uint8_t PinFAN = D5;
|
|
||||||
const uint8_t PinLED = D6; //
|
|
||||||
const uint8_t PINwaterlevel = D7;
|
|
||||||
const uint8_t PINsoilmoisture = D8;
|
|
||||||
const uint8_t PINanalog = A0;
|
|
|
@ -1,205 +0,0 @@
|
||||||
/*
|
|
||||||
* DHT Stuff
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
// DHT support dropped to get a free pin for fan PWM
|
|
||||||
//#define DHTTYPE DHT11
|
|
||||||
//DHT dht(PINdht, DHTTYPE);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* BME280 Stuff
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define SEALEVELPRESSURE_HPA (1013.25)
|
|
||||||
Adafruit_BME280 bme;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Chirp functions
|
|
||||||
*/
|
|
||||||
void writeI2CRegister8bit(int addr, int value) {
|
|
||||||
Wire.beginTransmission(addr);
|
|
||||||
Wire.write(value);
|
|
||||||
Wire.endTransmission();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int readI2CRegister16bit(int addr, int reg) {
|
|
||||||
Wire.beginTransmission(addr);
|
|
||||||
Wire.write(reg);
|
|
||||||
Wire.endTransmission();
|
|
||||||
delay(20);
|
|
||||||
Wire.requestFrom(addr, 2);
|
|
||||||
unsigned int t = Wire.read() << 8;
|
|
||||||
t = t | Wire.read();
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Sensor functions
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
int getWaterlevel(bool returnRAW = false) {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* waterlevelRAW
|
|
||||||
* ===========
|
|
||||||
* 0 - 199 : CRITICAL
|
|
||||||
* 200 - 399 : WARNING
|
|
||||||
* >400 : OK
|
|
||||||
*
|
|
||||||
* waterlevel
|
|
||||||
* ==========
|
|
||||||
* 2 : CRITICAL
|
|
||||||
* 1 : WARNING
|
|
||||||
* 0 : OK
|
|
||||||
*/
|
|
||||||
|
|
||||||
short waterlevelWARN = 200;
|
|
||||||
short waterlevelOK = 400;
|
|
||||||
short waterlevelRAW = 0;
|
|
||||||
byte waterlevel = 0;
|
|
||||||
|
|
||||||
// disable first PINsoilmoisture
|
|
||||||
digitalWrite(PINsoilmoisture, LOW);
|
|
||||||
// enable Vcc for water level sensor
|
|
||||||
digitalWrite(PINwaterlevel, HIGH);
|
|
||||||
// wait a bit to let the circuit stabilize
|
|
||||||
// TODO: replace delay() with millis()
|
|
||||||
delay(100);
|
|
||||||
// get the value
|
|
||||||
|
|
||||||
for(byte i = 0; i < 10 ; i++) {
|
|
||||||
waterlevelRAW = waterlevelRAW + analogRead(PINanalog);
|
|
||||||
}
|
|
||||||
waterlevelRAW = waterlevelRAW / 10;
|
|
||||||
|
|
||||||
// disable Vcc for the sensor to prevent electrolysis effect and release analog pin
|
|
||||||
digitalWrite(PINwaterlevel, LOW);
|
|
||||||
// and turn soilmoisture back on
|
|
||||||
digitalWrite(PINsoilmoisture, HIGH);
|
|
||||||
if( waterlevelRAW >= waterlevelOK) {
|
|
||||||
waterlevel = 0;
|
|
||||||
} else if( waterlevelRAW >= waterlevelWARN) {
|
|
||||||
waterlevel = 1;
|
|
||||||
} else {
|
|
||||||
waterlevel = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return waterlevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
float getTemperature(byte tempSensor) {
|
|
||||||
/*
|
|
||||||
* tempSensor
|
|
||||||
* ==========
|
|
||||||
* 1 : DHT11 temp sensor
|
|
||||||
* 2 : chirp I2C temp sensor
|
|
||||||
*/
|
|
||||||
|
|
||||||
float temperature = 0;
|
|
||||||
|
|
||||||
switch(tempSensor) {
|
|
||||||
case 1:
|
|
||||||
// read temperature from BME280
|
|
||||||
temperature = bme.readTemperature();
|
|
||||||
// read temperature from DHT11
|
|
||||||
// dht support dropped
|
|
||||||
// temperature = dht.readTemperature();
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
// read temperature from chrip I2C
|
|
||||||
temperature = readI2CRegister16bit(0x20, 5) * 0.10 ;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// if sensor type is not recognized, return 99
|
|
||||||
temperature = 99.99;
|
|
||||||
}
|
|
||||||
|
|
||||||
return temperature;
|
|
||||||
}
|
|
||||||
|
|
||||||
float getHumidity() {
|
|
||||||
// dht support dropped
|
|
||||||
// return dht.readHumidity();
|
|
||||||
return bme.readHumidity();
|
|
||||||
}
|
|
||||||
|
|
||||||
int getSoilmoisture(byte moistureSensor, bool returnRAW = false) {
|
|
||||||
/*
|
|
||||||
* moistureSensor
|
|
||||||
* ==============
|
|
||||||
* 1 : analog capacitive moisture sensor
|
|
||||||
* 2 : chirp I2C moisture sensor
|
|
||||||
*/
|
|
||||||
|
|
||||||
// value to return
|
|
||||||
int soilmoisture = 0;
|
|
||||||
// value for wet
|
|
||||||
int wet;
|
|
||||||
// value for dry
|
|
||||||
int dry;
|
|
||||||
|
|
||||||
switch(moistureSensor) {
|
|
||||||
case 1:
|
|
||||||
// read analog value from analog moisture sensor
|
|
||||||
wet = 180;
|
|
||||||
// this value was measured in air, without contact to anything
|
|
||||||
//dry= 590;
|
|
||||||
|
|
||||||
// was measured in dry soil, not bone dry but really dry (6 days no watering)
|
|
||||||
dry = 360;
|
|
||||||
|
|
||||||
digitalWrite(PINsoilmoisture, HIGH);
|
|
||||||
// wait a bit to let the circuit stabilize
|
|
||||||
delay(50);
|
|
||||||
|
|
||||||
// get analog input value
|
|
||||||
// get values 10 times and get the middle for more precise data
|
|
||||||
for(byte i = 0; i < 10 ; i++) {
|
|
||||||
soilmoisture = soilmoisture + analogRead(PINanalog);
|
|
||||||
}
|
|
||||||
soilmoisture = soilmoisture / 10;
|
|
||||||
|
|
||||||
// disable Vcc for the sensor to release analog pin
|
|
||||||
digitalWrite(PINsoilmoisture, LOW);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
// read soil moisture from chrip I2C
|
|
||||||
// this value was measured in water
|
|
||||||
// wet = 560;
|
|
||||||
|
|
||||||
// measured in fresh watered soil
|
|
||||||
wet = 485;
|
|
||||||
dry= 250;
|
|
||||||
|
|
||||||
// get raw value from I2C chirp sensor
|
|
||||||
soilmoisture = readI2CRegister16bit(0x20, 0);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
wet = 0;
|
|
||||||
dry = 1;
|
|
||||||
soilmoisture = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(returnRAW == true) {
|
|
||||||
return soilmoisture;
|
|
||||||
} else {
|
|
||||||
short soilmoistureP = map(soilmoisture, wet, dry, 100, 0);
|
|
||||||
// dont return negative percentage values
|
|
||||||
if(soilmoistureP < 0) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return soilmoistureP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int getLightchirp() {
|
|
||||||
// get the "light value" from I2C chirp module
|
|
||||||
writeI2CRegister8bit(0x20, 3); //request light measurement
|
|
||||||
int lightchirp = readI2CRegister16bit(0x20, 4);
|
|
||||||
return lightchirp;
|
|
||||||
}
|
|
|
@ -1,752 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* System Functions
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
void wipeEEPROM() {
|
|
||||||
Serial.println(":: wipe EEPROM ::");
|
|
||||||
|
|
||||||
// wipeMsg is helper variable to know if the Serial.print Message was
|
|
||||||
// already sent
|
|
||||||
byte wipeMsg = 0;
|
|
||||||
while(digitalRead(PinWIPE) == LOW ) {
|
|
||||||
// only show the Serial message once
|
|
||||||
if(wipeMsg == 0) {
|
|
||||||
Serial.println("Please release PinWIPE to erase all data saved in EEPROM");
|
|
||||||
Serial.println("LAST CHANCE TO KEEP THE DATA BY RESETTING NOW!!");
|
|
||||||
|
|
||||||
display.clearDisplay();
|
|
||||||
display.setCursor(0,0);
|
|
||||||
display.println("!!!!!!!!!!!!!!!!!!!!!");
|
|
||||||
display.println("");
|
|
||||||
display.println("RELEASE PinWIPE");
|
|
||||||
display.println("TO WIPE EEPROM");
|
|
||||||
display.display();
|
|
||||||
|
|
||||||
// increase i to show the serial message only once
|
|
||||||
wipeMsg = 1;
|
|
||||||
}
|
|
||||||
delay(500);
|
|
||||||
}
|
|
||||||
|
|
||||||
// write a 0 to all 512 bytes of the EEPROM
|
|
||||||
Serial.print("wiping EEPROM... ");
|
|
||||||
display.println("Wiping EEPROM...");
|
|
||||||
display.println("Will restart in 3s");
|
|
||||||
display.display();
|
|
||||||
for (int i = 0; i < 512; i++) { EEPROM.write(i, 0); }
|
|
||||||
|
|
||||||
// commit everything to EEPROM and end here
|
|
||||||
EEPROM.end();
|
|
||||||
|
|
||||||
Serial.println("DONE");
|
|
||||||
|
|
||||||
// set D4 PinWIPE internal LED to Output to give feedback WIPE
|
|
||||||
// was done
|
|
||||||
pinMode(PinWIPE, OUTPUT);
|
|
||||||
|
|
||||||
Serial.println("!! Device will restart in 3 seconds !!");
|
|
||||||
|
|
||||||
// let the internal led blink fast to signalize wipe is done
|
|
||||||
for(byte i = 0; i <= 24 ; i++) {
|
|
||||||
if(i % 2) {
|
|
||||||
digitalWrite(PinWIPE, LOW);
|
|
||||||
} else {
|
|
||||||
digitalWrite(PinWIPE, HIGH);
|
|
||||||
}
|
|
||||||
delay(125);
|
|
||||||
}
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool loadEEPROM() {
|
|
||||||
/*
|
|
||||||
* EEPROM Save table
|
|
||||||
*
|
|
||||||
* 0 WIFIssid
|
|
||||||
* 32 WIFIpassword
|
|
||||||
* 96 WIFIip
|
|
||||||
* 112 WIFInetmask
|
|
||||||
* 128 WIFIgateway
|
|
||||||
* 144 WIFIdns
|
|
||||||
* 160 WIFIuseDHCP
|
|
||||||
*
|
|
||||||
* 161 configured
|
|
||||||
* 162 UseFan
|
|
||||||
* 163 UsePump
|
|
||||||
* 164 PumpOnTime
|
|
||||||
* 165 MoistureSensor_Type
|
|
||||||
* 166 SoilmoistureLow
|
|
||||||
* 167 NtpOffset
|
|
||||||
* 169 UseLEDrelais
|
|
||||||
*
|
|
||||||
* 170 GrowName
|
|
||||||
* 202 GrowStart
|
|
||||||
* 206 DaysVeg
|
|
||||||
* 207 DaysBloom
|
|
||||||
* 208 LighthoursVet
|
|
||||||
* 209 LighthoursBloom
|
|
||||||
* 210 SunriseHour
|
|
||||||
* 211 SunriseMinute
|
|
||||||
* 212 DayOfGrow
|
|
||||||
*
|
|
||||||
* -- afterwards added, need to sort --
|
|
||||||
*
|
|
||||||
* 213 PinLEDPWM
|
|
||||||
* 214 TemperatureSensor_Type
|
|
||||||
* 215 UseFANrelais
|
|
||||||
* 216 PinFANPWM
|
|
||||||
* 217 SunFade
|
|
||||||
* 218 SunFadeDuration
|
|
||||||
* 219 MaintenanceDuration (2 byte)
|
|
||||||
* 221 Esp32CamIP (16 byte)
|
|
||||||
* 237 PumpLastOn (4 byte)
|
|
||||||
* 241 PumpIntervalVeg (1 byte)
|
|
||||||
* 242 PumpIntervalBloom (1 byte)
|
|
||||||
* 243 ...
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
Serial.println(":: loading EEPROM ::");
|
|
||||||
|
|
||||||
display.setCursor(0,36);
|
|
||||||
display.fillRect(0,36,128,64-36, 0);
|
|
||||||
display.println("loading EEPROM");
|
|
||||||
display.display();
|
|
||||||
// read var WIFIssid from address 0, 32 byte long
|
|
||||||
// read this first, because we decide on the ssid length (>0?) if
|
|
||||||
// we run in unconfigured AP mode, nor not
|
|
||||||
EEPROM.get(0, WIFIssid);
|
|
||||||
|
|
||||||
// when length is > 0 then read furter EEPROM config data
|
|
||||||
if(strlen(WIFIssid)) {
|
|
||||||
/*
|
|
||||||
* WIFI settings
|
|
||||||
*/
|
|
||||||
|
|
||||||
// read var WIFIpassword from address 32, 64 byte long
|
|
||||||
EEPROM.get(32, WIFIpassword);
|
|
||||||
// read var WIFIip from address 96, 16 byte long
|
|
||||||
EEPROM.get(96, WIFIip);
|
|
||||||
// read var WIFInetmask from address 112, 16 byte long
|
|
||||||
EEPROM.get(112, WIFInetmask);
|
|
||||||
// read var WIFIgateway from address 128, 16 byte long
|
|
||||||
EEPROM.get(128, WIFIgateway);
|
|
||||||
// read var WIFIgateway from address 128, 16 byte long
|
|
||||||
EEPROM.get(144, WIFIdns);
|
|
||||||
// read var WIFIuseDHCP from Address 160, 1 byte long
|
|
||||||
EEPROM.get(160, WIFIuseDHCP);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* System settings
|
|
||||||
*/
|
|
||||||
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(161, configured);
|
|
||||||
if(configured == true) {
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(162, UseFan);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(163, UsePump);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(164, PumpOnTime);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(165, MoistureSensor_Type);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(166, SoilmoistureLow);
|
|
||||||
// size is 2 byte
|
|
||||||
EEPROM.get(167, NtpOffset);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(169, UseLEDrelais);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(214, TemperatureSensor_Type);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(215, UseFANrelais);
|
|
||||||
// size is 2 byte
|
|
||||||
EEPROM.get(219, MaintenanceDuration);
|
|
||||||
// size is 16 byte
|
|
||||||
EEPROM.get(221, Esp32CamIP);
|
|
||||||
// size is 4 byte
|
|
||||||
EEPROM.get(237, PumpLastOn);
|
|
||||||
|
|
||||||
}
|
|
||||||
// TODO auth does not work atm
|
|
||||||
// EEPROM.get(160, WebUiUsername);
|
|
||||||
// EEPROM.get(176, WebUiPassword);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Grow settings
|
|
||||||
*/
|
|
||||||
|
|
||||||
// size is 32 byte
|
|
||||||
EEPROM.get(170, GrowName);
|
|
||||||
if(strlen(GrowName) > 0) {
|
|
||||||
// size is 4 byte
|
|
||||||
EEPROM.get(202, GrowStart);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(206, DaysVeg);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(207, DaysBloom);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(208, LighthoursVeg);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(209, LighthoursBloom);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(210, SunriseHour);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(211, SunriseMinute);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(212, DayOfGrow);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(213, PinLEDPWM);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(216, PinFANPWM);
|
|
||||||
EEPROM.get(217, SunFade);
|
|
||||||
EEPROM.get(218, SunFadeDuration);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(241, PumpIntervalVeg);
|
|
||||||
// size is 1 byte
|
|
||||||
EEPROM.get(242, PumpIntervalBloom);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// print values to Serial output
|
|
||||||
Serial.println("---- WiFi values ----");
|
|
||||||
Serial.print("WIFIssid: ");
|
|
||||||
Serial.println(WIFIssid);
|
|
||||||
Serial.print("WIFIpassword: ");
|
|
||||||
Serial.println(WIFIpassword);
|
|
||||||
Serial.print("Use DHCP: ");
|
|
||||||
Serial.println(WIFIuseDHCP);
|
|
||||||
|
|
||||||
Serial.println("---- System values ----");
|
|
||||||
Serial.print("configured: ");
|
|
||||||
Serial.println(configured);
|
|
||||||
Serial.print("UseFan: ");
|
|
||||||
Serial.println(UseFan);
|
|
||||||
Serial.print("UsePump: ");
|
|
||||||
Serial.println(UsePump);
|
|
||||||
Serial.print("PumpOnTime: ");
|
|
||||||
Serial.println(PumpOnTime);
|
|
||||||
Serial.print("MoistureSensor_Type: ");
|
|
||||||
Serial.println(MoistureSensor_Type);
|
|
||||||
Serial.print("TemperatureSensor_Type: ");
|
|
||||||
Serial.println(TemperatureSensor_Type);
|
|
||||||
Serial.print("SoilmoistureLow: ");
|
|
||||||
Serial.println(SoilmoistureLow);
|
|
||||||
Serial.print("NtpOffset: ");
|
|
||||||
Serial.println(NtpOffset);
|
|
||||||
Serial.print("UseLEDrelais: ");
|
|
||||||
Serial.println(UseLEDrelais);
|
|
||||||
Serial.print("UseFANrelais: ");
|
|
||||||
Serial.println(UseFANrelais);
|
|
||||||
Serial.print("MaintenanceDuration: ");
|
|
||||||
Serial.println(MaintenanceDuration);
|
|
||||||
|
|
||||||
Serial.println("---- Grow values ----");
|
|
||||||
Serial.print("GrowName: ");
|
|
||||||
Serial.println(GrowName);
|
|
||||||
Serial.print("GrowStart: ");
|
|
||||||
Serial.println(GrowStart);
|
|
||||||
Serial.print("DaysVeg: ");
|
|
||||||
Serial.println(DaysVeg);
|
|
||||||
Serial.print("DaysBloom: ");
|
|
||||||
Serial.println(DaysBloom);
|
|
||||||
Serial.print("LighthoursVeg: ");
|
|
||||||
Serial.println(LighthoursVeg);
|
|
||||||
Serial.print("LighthoursBloom: ");
|
|
||||||
Serial.println(LighthoursBloom);
|
|
||||||
Serial.print("SunriseHour: ");
|
|
||||||
Serial.println(SunriseHour);
|
|
||||||
Serial.print("SunriseMinute: ");
|
|
||||||
Serial.println(SunriseMinute);
|
|
||||||
Serial.print("DayOfGrow: ");
|
|
||||||
Serial.println(DayOfGrow);
|
|
||||||
Serial.print("PinLEDPWM: ");
|
|
||||||
Serial.println(PinLEDPWM);
|
|
||||||
Serial.print("PinFANPWM: ");
|
|
||||||
Serial.println(PinFANPWM);
|
|
||||||
Serial.print("SunFade: ");
|
|
||||||
Serial.println(SunFade);
|
|
||||||
Serial.print("SunFadeDuration: ");
|
|
||||||
Serial.println(SunFadeDuration);
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Serial.println("EEPROM value WIFIssid is empty");
|
|
||||||
}
|
|
||||||
Serial.println(":: EEPROM loaded ::");
|
|
||||||
|
|
||||||
display.setCursor(0,42);
|
|
||||||
display.println("EEPROM loaded");
|
|
||||||
display.display();
|
|
||||||
|
|
||||||
return(strlen(WIFIssid));
|
|
||||||
}
|
|
||||||
|
|
||||||
void wifiConnect() {
|
|
||||||
Serial.println(":: Connecting to WiFi ::");
|
|
||||||
FirstRun = false;
|
|
||||||
Serial.print("SSID: ");
|
|
||||||
Serial.println(WIFIssid);
|
|
||||||
|
|
||||||
display.fillRect(0,36,128,64-36, 0);
|
|
||||||
display.setCursor(0,36);
|
|
||||||
display.println("Connecting to WiFi");
|
|
||||||
display.println(WIFIssid);
|
|
||||||
display.display();
|
|
||||||
|
|
||||||
// Start WiFi connection
|
|
||||||
WiFi.begin(WIFIssid, WIFIpassword);
|
|
||||||
if(WIFIuseDHCP == false) {
|
|
||||||
WiFi.config(WIFIip, WIFIdns, WIFIgateway, WIFInetmask);
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait until WiFi connection is established
|
|
||||||
while (WiFi.status() != WL_CONNECTED) {
|
|
||||||
delay(500);
|
|
||||||
Serial.print(".");
|
|
||||||
}
|
|
||||||
Serial.println(" CONNECTED!");
|
|
||||||
Serial.print("IP: ");
|
|
||||||
Serial.println(WiFi.localIP());
|
|
||||||
|
|
||||||
Serial.println(":: Getting time from NTP ::");
|
|
||||||
display.fillRect(0,36,128,64-36, 0);
|
|
||||||
display.setCursor(0,36);
|
|
||||||
display.println("Getting NTP time");
|
|
||||||
display.display();
|
|
||||||
|
|
||||||
timeClient.begin();
|
|
||||||
timeClient.setTimeOffset(NtpOffset * 60 * 60);
|
|
||||||
timeClient.update();
|
|
||||||
while ( ! timeClient.isTimeSet()) {
|
|
||||||
timeClient.update();
|
|
||||||
delay(500);
|
|
||||||
Serial.print(".");
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println(timeClient.getFormattedTime());
|
|
||||||
Serial.println(timeClient.getEpochTime());
|
|
||||||
display.println(timeClient.getFormattedTime());
|
|
||||||
display.display();
|
|
||||||
display.print("IP: ");
|
|
||||||
display.print(WiFi.localIP());
|
|
||||||
display.display();
|
|
||||||
}
|
|
||||||
|
|
||||||
void wifiAp() {
|
|
||||||
Serial.println(":: Creating Accesspoint ::");
|
|
||||||
|
|
||||||
display.fillRect(0,36,128,64-36, 0);
|
|
||||||
display.setCursor(0,36);
|
|
||||||
display.println("Creating AccessPoint");
|
|
||||||
display.println(APssid);
|
|
||||||
display.display();
|
|
||||||
|
|
||||||
FirstRun = true;
|
|
||||||
// configure WiFi Access Point
|
|
||||||
WiFi.softAPConfig(WIFIip, WIFIgateway, WIFInetmask);
|
|
||||||
// start Access Point
|
|
||||||
// TODO make AP with password - does not work atm. idk why.
|
|
||||||
WiFi.softAP(APssid);
|
|
||||||
Serial.print("SSID: ");
|
|
||||||
Serial.println(APssid);
|
|
||||||
Serial.print("CanGrow IP address: ");
|
|
||||||
Serial.println(WiFi.softAPIP());
|
|
||||||
|
|
||||||
display.print("IP: ");
|
|
||||||
display.println(WiFi.softAPIP());
|
|
||||||
display.display();
|
|
||||||
// TODO does not work atm, idk why
|
|
||||||
//Serial.println("The login credentials for the WebUI are 'cangrow' for username and password");
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned short growState() {
|
|
||||||
/*
|
|
||||||
* growState()
|
|
||||||
*
|
|
||||||
* returns growState as short
|
|
||||||
*
|
|
||||||
* 1 - vegetation
|
|
||||||
* 2 - bloom
|
|
||||||
* 3 - harvest
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
unsigned short state;
|
|
||||||
|
|
||||||
if(DayOfGrow > (DaysVeg + DaysBloom ) ) {
|
|
||||||
state = 3;
|
|
||||||
} else if(DayOfGrow > DaysVeg ) {
|
|
||||||
state = 2;
|
|
||||||
} else {
|
|
||||||
state = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setOutput(byte Output, byte OutputState) {
|
|
||||||
/*
|
|
||||||
* Pin assignments
|
|
||||||
*
|
|
||||||
* 1 - LED
|
|
||||||
* 2 - FAN
|
|
||||||
* 3 - PUMP
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
bool UseRelais = true;
|
|
||||||
byte OutputPin;
|
|
||||||
|
|
||||||
switch(Output) {
|
|
||||||
case 1:
|
|
||||||
OutputPin = PinLED;
|
|
||||||
if(UseLEDrelais == true) {
|
|
||||||
UseRelais = true;
|
|
||||||
} else {
|
|
||||||
UseRelais = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
OutputPin = PinFAN;
|
|
||||||
if(UseFANrelais == true) {
|
|
||||||
UseRelais = true;
|
|
||||||
} else {
|
|
||||||
UseRelais = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// PUMP Pin (D0) does not support PWM, so we do not need to care about
|
|
||||||
case 3:
|
|
||||||
OutputPin = PinPUMP;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~ Serial.print("Output: ");
|
|
||||||
//~ Serial.println(Output);
|
|
||||||
//~ Serial.print("OutputPin: ");
|
|
||||||
//~ Serial.println(OutputPin);
|
|
||||||
//~ Serial.print("OutputState: ");
|
|
||||||
//~ Serial.println(OutputState);
|
|
||||||
//~ Serial.print("UseRelais: ");
|
|
||||||
//~ Serial.println(UseRelais);
|
|
||||||
|
|
||||||
if( (UseRelais == true) || (OutputPin == PinPUMP) ) {
|
|
||||||
digitalWrite(OutputPin, 1 - OutputState);
|
|
||||||
} else {
|
|
||||||
analogWrite(OutputPin, 255 - OutputState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void controlLED() {
|
|
||||||
byte lightHours;
|
|
||||||
byte PinLEDPWM_tmp;
|
|
||||||
unsigned int secondsSunrise = (SunriseHour * 60 * 60) + (SunriseMinute * 60);
|
|
||||||
unsigned int secondsToday = (timeClient.getHours() * 60 * 60) + (timeClient.getMinutes() * 60) + timeClient.getSeconds();
|
|
||||||
|
|
||||||
switch(growState()) {
|
|
||||||
case 1:
|
|
||||||
lightHours = LighthoursVeg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
lightHours = LighthoursBloom;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
lightHours = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if secondsToday is larger then secondsSunrise time AND if
|
|
||||||
// secondsToday is smaller then the sum of secondsSunrise + seconds of lightHours
|
|
||||||
if( ((secondsToday >= secondsSunrise) && (secondsToday <= ( secondsSunrise + (lightHours * 60 * 60))) ) && (growState() < 3) ){
|
|
||||||
//Serial.println("light on time");
|
|
||||||
|
|
||||||
// when SunFade is true, fade LED light. Otherwise just turn on or off
|
|
||||||
if( (SunFade == true) && (UseLEDrelais == false) && (secondsSunrise + SunFadeDuration * 60 >= secondsToday) ) {
|
|
||||||
// in the first n minutes of lighting (SunFadeDuration), we want
|
|
||||||
// to raise the light slowly to prevent stress from the plant
|
|
||||||
// convert progress sunrise to PWM value
|
|
||||||
PinLEDPWM_tmp = (SunFadeDuration * 60 - ((secondsSunrise + SunFadeDuration * 60) - secondsToday)) * PinLEDPWM / (SunFadeDuration * 60);
|
|
||||||
setOutput(1, PinLEDPWM_tmp);
|
|
||||||
//Serial.print("sunrise PWM; ");
|
|
||||||
//Serial.println(PinLEDPWM_tmp);
|
|
||||||
|
|
||||||
} else if( (SunFade == true) && (UseLEDrelais == false) && (secondsToday >= ((secondsSunrise + lightHours * 60 * 60) - SunFadeDuration * 60) ) ) {
|
|
||||||
// calculate progress sunset to PWM value
|
|
||||||
PinLEDPWM_tmp = (secondsSunrise + (lightHours * 60 * 60) - secondsToday) * PinLEDPWM / (SunFadeDuration * 60);
|
|
||||||
setOutput(1, PinLEDPWM_tmp);
|
|
||||||
//Serial.print("sunset PWM: ");
|
|
||||||
//Serial.println(PinLEDPWM_tmp);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
//Serial.println("just turn on the light");
|
|
||||||
// no sunrise or sunset, just keep the LED turned on
|
|
||||||
setOutput(1, PinLEDPWM);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
//Serial.println("good night time");
|
|
||||||
// turn off
|
|
||||||
setOutput(1, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void refreshSensors() {
|
|
||||||
byte soilmoistureAvgSampleCount = 5;
|
|
||||||
|
|
||||||
valSoilmoisture = getSoilmoisture(MoistureSensor_Type);
|
|
||||||
valHumidity = getHumidity();
|
|
||||||
valTemperature = getTemperature(TemperatureSensor_Type);
|
|
||||||
valWaterlevel = getWaterlevel();
|
|
||||||
|
|
||||||
// get average of 5 readings for valSoilmoisture
|
|
||||||
valSoilmoistureAvg_tmp = valSoilmoistureAvg_tmp + valSoilmoisture;
|
|
||||||
if(valSoilmoistureAvg_count < soilmoistureAvgSampleCount - 1) {
|
|
||||||
valSoilmoistureAvg_count++;
|
|
||||||
} else {
|
|
||||||
// build average
|
|
||||||
valSoilmoistureAvg = valSoilmoistureAvg_tmp / soilmoistureAvgSampleCount;
|
|
||||||
// reset everything
|
|
||||||
valSoilmoistureAvg_tmp = 0;
|
|
||||||
valSoilmoistureAvg_count = 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void displayScreens() {
|
|
||||||
/*
|
|
||||||
* which screen to display
|
|
||||||
* interate through different screens
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
if(ScreenIterationPassed > DisplayScreenDuration){
|
|
||||||
ScreenIterationPassed = 0;
|
|
||||||
// helper variable, maybe i find a better way in future
|
|
||||||
byte LastScreen = 2;
|
|
||||||
// when the next screen gets displayed, clear display
|
|
||||||
display.clearDisplay();
|
|
||||||
display.display();
|
|
||||||
// when ScreenToDisplay has reach last number of screens, reset to first (0)
|
|
||||||
if(ScreenToDisplay >= LastScreen) {
|
|
||||||
ScreenToDisplay = 0;
|
|
||||||
} else {
|
|
||||||
ScreenToDisplay++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
display.setCursor(0,0);
|
|
||||||
|
|
||||||
if(MaintenanceMode == true) {
|
|
||||||
display.drawBitmap(0, 0, bmpCanGrow_Logo, 128, 32, WHITE);
|
|
||||||
display.display();
|
|
||||||
display.setCursor(0,32);
|
|
||||||
display.println("Maintenance mode active");
|
|
||||||
display.print("Time left: ");
|
|
||||||
display.print(MaintenanceDuration - ((millis() - MaintenanceStarted) / 1000));
|
|
||||||
display.println("s");
|
|
||||||
} else {
|
|
||||||
// in this switch case the single screens gets defined
|
|
||||||
//switch(ScreenToDisplay) {
|
|
||||||
switch(0) {
|
|
||||||
case 0:
|
|
||||||
display.print("Humidity: ");
|
|
||||||
display.print(valHumidity);
|
|
||||||
display.println(" %");
|
|
||||||
display.println("");
|
|
||||||
display.print("Temperature: ");
|
|
||||||
display.print(valTemperature);
|
|
||||||
display.println(" C");
|
|
||||||
display.println("");
|
|
||||||
display.print("Moisture: ");
|
|
||||||
display.print(valSoilmoisture);
|
|
||||||
display.print(" % ");
|
|
||||||
display.println(valSoilmoistureAvg);
|
|
||||||
display.println("");
|
|
||||||
if(UsePump > 0) {
|
|
||||||
display.print("Pump Waterlvl: ");
|
|
||||||
switch(valWaterlevel) {
|
|
||||||
case 0:
|
|
||||||
display.println("OK");
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
display.println("Warn");
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
display.println("Crit");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
display.print("LED: ");
|
|
||||||
display.print(PinLEDPWM * 100 / 255);
|
|
||||||
display.println(" %");
|
|
||||||
display.print("State: ");
|
|
||||||
display.println(digitalRead(PinLED));
|
|
||||||
display.println("");
|
|
||||||
display.print("FAN: ");
|
|
||||||
display.print(PinFANPWM * 100 / 255);
|
|
||||||
display.println(" %");
|
|
||||||
display.print("State: ");
|
|
||||||
display.println(digitalRead(PinFAN));
|
|
||||||
display.println("");
|
|
||||||
display.print("Pump state: ");
|
|
||||||
display.println(digitalRead(PinPUMP));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
// display Logo
|
|
||||||
display.drawBitmap(0, 0, bmpCanGrow_Logo, 128, 32, WHITE);
|
|
||||||
display.display();
|
|
||||||
display.setCursor(0,32);
|
|
||||||
display.println(GrowName);
|
|
||||||
display.print("DoG: ");
|
|
||||||
display.print(DayOfGrow);
|
|
||||||
display.print(", ");
|
|
||||||
display.println(timeClient.getFormattedTime());
|
|
||||||
display.print("IP: ");
|
|
||||||
display.println(WiFi.localIP());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ScreenIterationPassed++;
|
|
||||||
display.display();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Pump control
|
|
||||||
*
|
|
||||||
* Vars:
|
|
||||||
* - UsePump (byte)
|
|
||||||
* - PumpOnTime (byte) in sec
|
|
||||||
* - SoilmoistureLow (byte) in %
|
|
||||||
* - PumpLastOn (long) timestamp
|
|
||||||
* - PumpMode (byte) 1: Pump on every n days, 2: Pump on when Soilmoisture <= SoilmoistureLow, 3: Both
|
|
||||||
*
|
|
||||||
* - PumpIntervalVeg (byte) in days
|
|
||||||
* - PumpIntervalBloom (byte) in days
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
void controlPUMP() {
|
|
||||||
byte PumpInterval;
|
|
||||||
|
|
||||||
// UsePump true and not in harvest state?
|
|
||||||
// dont water within the first 15 sec after startup
|
|
||||||
if ( (UsePump > 0) && (growState() < 3) && (millis() > 15000) ) {
|
|
||||||
switch(growState()) {
|
|
||||||
case 1:
|
|
||||||
PumpInterval = PumpIntervalVeg;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
PumpInterval = PumpIntervalBloom;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
PumpInterval = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// when PumpOnManuel is true, turn pump on for PumpOnTime seconds
|
|
||||||
if(PumpOnManual == true) {
|
|
||||||
if(PumpOnTimePassed < PumpOnTime) {
|
|
||||||
setOutput(3, 1);
|
|
||||||
//digitalWrite(PinPUMP, HIGH);
|
|
||||||
PumpOnTimePassed++;
|
|
||||||
} else {
|
|
||||||
PumpOnManual = false;
|
|
||||||
setOutput(3, 0);
|
|
||||||
//digitalWrite(PinPUMP, LOW);
|
|
||||||
EEPROM.put(237, PumpLastOn);
|
|
||||||
PumpOnTimePassed = 0;
|
|
||||||
}
|
|
||||||
// otherwise check which PumpMode to use
|
|
||||||
} else {
|
|
||||||
switch(UsePump) {
|
|
||||||
case 1:
|
|
||||||
// when diff of time now and time pumpLastOn is greater then PumpInterval, do some watering (Or manual watering)
|
|
||||||
if( (timeClient.getEpochTime() - PumpLastOn) >= (PumpInterval) ) { // TODO: * 24 * 60 * 60 PumpInterval
|
|
||||||
// only water as long PumpOnTime
|
|
||||||
if(PumpOnTimePassed < PumpOnTime) {
|
|
||||||
setOutput(3, 1);
|
|
||||||
//digitalWrite(PinPUMP, HIGH);
|
|
||||||
PumpOnTimePassed++;
|
|
||||||
} else {
|
|
||||||
setOutput(3, 0);
|
|
||||||
//digitalWrite(PinPUMP, LOW);
|
|
||||||
PumpLastOn = timeClient.getEpochTime();
|
|
||||||
// write the value to EEPROM for the case ESP gets restarted
|
|
||||||
EEPROM.put(237, PumpLastOn);
|
|
||||||
PumpOnTimePassed = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setOutput(3, 0);
|
|
||||||
//digitalWrite(PinPUMP, LOW);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
// when valSoilmoistureAvg is lower then SoilMoisture low do some watering
|
|
||||||
if( (valSoilmoistureAvg < SoilmoistureLow) || ( (valSoilmoistureAvg >= SoilmoistureLow) && ( (PumpOnTimePassed > 0) && (PumpOnTimePassed <= PumpOnTime) ) ) ) {
|
|
||||||
// check if we alerady exceeded max PumpOnTime
|
|
||||||
if(PumpOnTimePassed < PumpOnTime) {
|
|
||||||
setOutput(3, 1);
|
|
||||||
//digitalWrite(PinPUMP, HIGH);
|
|
||||||
PumpOnTimePassed++;
|
|
||||||
} else {
|
|
||||||
setOutput(3, 0);
|
|
||||||
//digitalWrite(PinPUMP, LOW);
|
|
||||||
PumpLastOn = timeClient.getEpochTime();
|
|
||||||
PumpOnTimePassed = 0;
|
|
||||||
}
|
|
||||||
// when valSoilmoistureAvg is greater then the Low value,
|
|
||||||
} else {
|
|
||||||
setOutput(3, 0);
|
|
||||||
//digitalWrite(PinPUMP, LOW);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
//
|
|
||||||
case 3:
|
|
||||||
if( ( (timeClient.getEpochTime() - PumpLastOn) >= (PumpInterval) ) && //TODO calculate PumpInterval into days as well here
|
|
||||||
( (valSoilmoistureAvg < SoilmoistureLow) ||
|
|
||||||
( (valSoilmoistureAvg >= SoilmoistureLow) && ( (PumpOnTimePassed > 0) && (PumpOnTimePassed <= PumpOnTime) ) )
|
|
||||||
) ) {
|
|
||||||
// check if we alerady exceeded max PumpOnTime
|
|
||||||
if(PumpOnTimePassed < PumpOnTime) {
|
|
||||||
setOutput(3, 1);
|
|
||||||
//digitalWrite(PinPUMP, HIGH);
|
|
||||||
PumpOnTimePassed++;
|
|
||||||
} else {
|
|
||||||
setOutput(3, 0);
|
|
||||||
//digitalWrite(PinPUMP, LOW);
|
|
||||||
PumpLastOn = timeClient.getEpochTime();
|
|
||||||
EEPROM.put(237, PumpLastOn);
|
|
||||||
PumpOnTimePassed = 0;
|
|
||||||
}
|
|
||||||
// when valSoilmoistureAvg is greater then the Low value,
|
|
||||||
} else {
|
|
||||||
setOutput(3, 0);
|
|
||||||
//digitalWrite(PinPUMP, LOW);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// ensure pump is off when it should be off
|
|
||||||
setOutput(3, 0);
|
|
||||||
//digitalWrite(PinPUMP, LOW);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
/* CanGrow_Version.h gets generated from cangrow.sh */
|
|
||||||
|
|
||||||
const char* CanGrowVer = "0.1-dev";
|
|
||||||
const char* CanGrowBuild = "d2e264d-20240919022041";
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,10 +1,50 @@
|
||||||
# CanGrow - Arduino Code
|
# CanGrow - An OpenSource grow controller firmware
|
||||||
|
|
||||||
Here the Code for CanGrow is contained.
|
## Build environment
|
||||||
A ESP8266 with Arduino Firmware is used (should most times be the standard already).
|
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
|
**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.
|
in Project -> Settings -> Create/Make.
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
board_manager:
|
board_manager:
|
||||||
additional_urls:
|
additional_urls:
|
||||||
- http://arduino.esp8266.com/stable/package_esp8266com_index.json
|
- 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
|
300
Arduino/CanGrow/include/CanGrow.h
Normal file
300
Arduino/CanGrow/include/CanGrow.h
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Config
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// define Max limits for outputs and sensors
|
||||||
|
const byte Max_Outputs = 16;
|
||||||
|
const byte Max_Sensors = 16;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* - 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];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Config_System_Sensor {
|
||||||
|
/*
|
||||||
|
* Config System Sensor
|
||||||
|
* - type: Index ID of SensorIndex, which Sensor to use (ADC, BME280, Chirp, ...)
|
||||||
|
* - name: nice name
|
||||||
|
* - gpio: gpio to use for RPM reading, builtin ADC, OneWire
|
||||||
|
*/
|
||||||
|
|
||||||
|
byte type[Max_Sensors];
|
||||||
|
char name[Max_Sensors][32];
|
||||||
|
char i2c_address[Max_Sensors][5];
|
||||||
|
byte gpio[Max_Sensors];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* 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_System_Sensor sensor;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
156
Arduino/CanGrow/include/CanGrow_Core.h
Normal file
156
Arduino/CanGrow/include/CanGrow_Core.h
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte Give_Free_SensorId() {
|
||||||
|
byte sensorId_free;
|
||||||
|
for(byte i=0; i < Max_Sensors; i++) {
|
||||||
|
if(config.system.sensor.type[i] > 0) {
|
||||||
|
// here i define that 255 stands for "no more free outputs"
|
||||||
|
sensorId_free = 255;
|
||||||
|
} else {
|
||||||
|
sensorId_free = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifndef DEBUG
|
||||||
|
Serial.printf("DB [Core:Give_Free_SensorId] next free output id: %d\n", sensorId_free);
|
||||||
|
#endif
|
||||||
|
return sensorId_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(used == false) {
|
||||||
|
for(byte i=0; i < Max_Sensors; i++) {
|
||||||
|
#ifndef DEBUG
|
||||||
|
//Serial.printf("DB [Core:Check_GPIOindex_Used] OutputId: %d , type: %d\n", i, config.system.output.type[i]);
|
||||||
|
#endif
|
||||||
|
// check if sensor type is gpio
|
||||||
|
if(config.system.sensor.gpio[i] > 0) {
|
||||||
|
#ifndef DEBUG
|
||||||
|
Serial.printf("DB [Core:Check_GPIOindex_Used] SensorId: %d is GPIO (type %d)\n", i, config.system.sensor.gpio[i]);
|
||||||
|
#endif
|
||||||
|
// check if gpio id is already in use
|
||||||
|
if(config.system.sensor.gpio[i] == gpio) {
|
||||||
|
#ifndef DEBUG
|
||||||
|
Serial.printf("DB [Core:Check_GPIOindex_Used] sensor.gpio[%d](%d) == GPIO %d\n", i, config.system.sensor.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;
|
||||||
|
}
|
88
Arduino/CanGrow/include/CanGrow_ESP32.h
Normal file
88
Arduino/CanGrow/include/CanGrow_ESP32.h
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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[] = {{ 255, 255 },
|
||||||
|
{ 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
|
58
Arduino/CanGrow/include/CanGrow_ESP8266.h
Normal file
58
Arduino/CanGrow/include/CanGrow_ESP8266.h
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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[] = {{ 255, 255 },
|
||||||
|
{ 0, BOOTFAILS_LOW },
|
||||||
|
{ 12 },
|
||||||
|
{ 13 },
|
||||||
|
{ 14 },
|
||||||
|
{ 15, BOOTFAILS_HIGH },
|
||||||
|
{ 16, NO_PWM } };
|
||||||
|
|
||||||
|
#endif
|
610
Arduino/CanGrow/include/CanGrow_LittleFS.h
Normal file
610
Arduino/CanGrow/include/CanGrow_LittleFS.h
Normal file
|
@ -0,0 +1,610 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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"];
|
||||||
|
|
||||||
|
|
||||||
|
/* System Outputs */
|
||||||
|
JsonObject objSystemOutput = objSystem["output"][0];
|
||||||
|
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]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* System Sensors */
|
||||||
|
JsonObject objSystemSensor = objSystem["sensor"][0];
|
||||||
|
for(byte i=0; i < Max_Sensors; i++) {
|
||||||
|
if(objSystemSensor["type"][i] > 0) {
|
||||||
|
config.system.sensor.type[i] = objSystemSensor["type"][i];
|
||||||
|
strlcpy(config.system.sensor.name[i], objSystemSensor["name"][i], sizeof(config.system.sensor.name[i]));
|
||||||
|
strlcpy(config.system.sensor.i2c_address[i], objSystemSensor["i2c_address"][i], sizeof(config.system.sensor.i2c_address[i]));
|
||||||
|
// gpio
|
||||||
|
config.system.sensor.gpio[i] = objSystemSensor["gpio"][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];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* System Sensors */
|
||||||
|
JsonObject objSystemSensor = objSystem["sensor"].add<JsonObject>();
|
||||||
|
for(byte i=0; i < Max_Sensors; i++) {
|
||||||
|
if(config.system.sensor.type[i] > 0) {
|
||||||
|
objSystemSensor["type"][i] = config.system.sensor.type[i];
|
||||||
|
objSystemSensor["name"][i] = config.system.sensor.name[i];
|
||||||
|
objSystemSensor["i2c_address"][i] = config.system.sensor.i2c_address[i];
|
||||||
|
objSystemSensor["gpio"][i] = config.system.sensor.gpio[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
|
80
Arduino/CanGrow/include/CanGrow_Sensor.h
Normal file
80
Arduino/CanGrow/include/CanGrow_Sensor.h
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* include/CanGrow_Sensor.h - sensor 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 <Adafruit_Sensor.h>
|
||||||
|
|
||||||
|
#include "Sensor/Common.h"
|
||||||
|
#include "Sensor/01_ADC_builtin.h"
|
||||||
|
#include "Sensor/02_BME280.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct Sensor_Index {
|
||||||
|
/*
|
||||||
|
* Sensor Index
|
||||||
|
* - name
|
||||||
|
* - readings (array, up to 8 entries)
|
||||||
|
* - 0 unset
|
||||||
|
* - 1 Raw
|
||||||
|
* - 2 Temp
|
||||||
|
* - 3 Humidity
|
||||||
|
* - 4 Moisture
|
||||||
|
* - 5 Pressure
|
||||||
|
* - 6 Gas restistance
|
||||||
|
* - max units
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const char name[32];
|
||||||
|
const byte reading[SENSOR_MAX_READING];
|
||||||
|
//byte max;
|
||||||
|
};
|
||||||
|
|
||||||
|
const byte SensorIndex_length = 2;
|
||||||
|
Sensor_Index SensorIndex[] {
|
||||||
|
// 0 is for unset in config
|
||||||
|
{ "unset", {
|
||||||
|
{},
|
||||||
|
}},
|
||||||
|
|
||||||
|
// 1 - internal ADC
|
||||||
|
{ SENSOR_01_NAME,
|
||||||
|
{
|
||||||
|
SENSOR_READING_TEMP
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// 2 - BME280
|
||||||
|
{ SENSOR_02_NAME,
|
||||||
|
{
|
||||||
|
SENSOR_READING_TEMP,
|
||||||
|
SENSOR_READING_HUMIDITY,
|
||||||
|
SENSOR_READING_PRESSURE
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
146
Arduino/CanGrow/include/CanGrow_Webserver.h
Normal file
146
Arduino/CanGrow/include/CanGrow_Webserver.h
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
webserver.on("/system/sensor/", HTTP_GET, WebPage_system_sensor);
|
||||||
|
webserver.on("/system/sensor/", HTTP_POST, WebPage_system_sensor);
|
||||||
|
|
||||||
|
webserver.on("/system/sensor/add", HTTP_GET, WebPage_system_sensor_add);
|
||||||
|
webserver.on("/system/sensor/add", HTTP_POST, WebPage_system_sensor_add);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// this activates the middleware
|
||||||
|
if(config.system.httpLogSerial == true) {
|
||||||
|
requestLogger.setOutput(Serial);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
32
Arduino/CanGrow/include/Sensor/01_ADC_builtin.h
Normal file
32
Arduino/CanGrow/include/Sensor/01_ADC_builtin.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* include/Sensor/00_ADC_builtin.h - sensor header for builtin ADC
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* 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 SENSOR_01_NAME "ADC builtin"
|
||||||
|
#define ADC_BUILTIN_MAX 0
|
||||||
|
|
42
Arduino/CanGrow/include/Sensor/02_BME280.h
Normal file
42
Arduino/CanGrow/include/Sensor/02_BME280.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* include/Sensor/00_ADC_builtin.h - sensor header for BME280 I2C sensor
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* 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 <Adafruit_BME280.h>
|
||||||
|
|
||||||
|
|
||||||
|
#define SENSOR_02_NAME "BME280"
|
||||||
|
#define BME280_MAX 2
|
||||||
|
|
||||||
|
|
||||||
|
Adafruit_BME280 BME280[BME280_MAX];
|
||||||
|
|
||||||
|
void Sensor_Bme280_Init(byte sensorId) {
|
||||||
|
|
||||||
|
}
|
64
Arduino/CanGrow/include/Sensor/Common.h
Normal file
64
Arduino/CanGrow/include/Sensor/Common.h
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* include/Sensor/Common.h - common sensor 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Common used consts and variables, used within the Sensor header for example
|
||||||
|
*/
|
||||||
|
|
||||||
|
// for bme280 and bme680
|
||||||
|
#define SEALEVELPRESSURE_HPA (1013.25)
|
||||||
|
|
||||||
|
// How much values can a sensor return
|
||||||
|
const byte SENSOR_MAX_READING = 8;
|
||||||
|
|
||||||
|
const byte SENSOR_READING_RAW = 1;
|
||||||
|
const byte SENSOR_READING_TEMP = 2;
|
||||||
|
const byte SENSOR_READING_HUMIDITY = 3;
|
||||||
|
const byte SENSOR_READING_MOISTURE = 4;
|
||||||
|
const byte SENSOR_READING_PRESSURE = 5;
|
||||||
|
const byte SENSOR_READING_GAS_RESISTANCE = 6;
|
||||||
|
|
||||||
|
const char SENSOR_READING_RAW_descr[] = "Raw Analog";
|
||||||
|
const char SENSOR_READING_TEMP_descr[] = "Temperature °C";
|
||||||
|
const char SENSOR_READING_HUMIDITY_descr[] = "Humidity %";
|
||||||
|
const char SENSOR_READING_MOISTURE_descr[] = "Moisture %";
|
||||||
|
const char SENSOR_READING_PRESSURE_descr[] = "Pressure Pa";
|
||||||
|
const char SENSOR_READING_GAS_RESISTANCE_descr[] = "Gas resistance KOhm";
|
||||||
|
|
||||||
|
const char * Sensor_Reading_descr[] = {
|
||||||
|
NULL, // 0 is unset
|
||||||
|
SENSOR_READING_RAW_descr,
|
||||||
|
SENSOR_READING_TEMP_descr,
|
||||||
|
SENSOR_READING_HUMIDITY_descr,
|
||||||
|
SENSOR_READING_MOISTURE_descr,
|
||||||
|
SENSOR_READING_PRESSURE_descr,
|
||||||
|
SENSOR_READING_GAS_RESISTANCE_descr,
|
||||||
|
};
|
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 = 1; 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);
|
||||||
|
}
|
30
Arduino/CanGrow/include/Webserver/Footer.h
Normal file
30
Arduino/CanGrow/include/Webserver/Footer.h
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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%)";
|
1167
Arduino/CanGrow/include/Webserver/Page_system.h
Normal file
1167
Arduino/CanGrow/include/Webserver/Page_system.h
Normal file
File diff suppressed because it is too large
Load diff
314
Arduino/CanGrow/include/Webserver/Page_system_HTML.h
Normal file
314
Arduino/CanGrow/include/Webserver/Page_system_HTML.h
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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='%ACTIVE_SUBNAV_SENSOR%' 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%)";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Subpage sensor
|
||||||
|
*/
|
||||||
|
const char* Page_system_sensor_HTML PROGMEM = R"(%HEADER%
|
||||||
|
%SUBNAV%
|
||||||
|
%SAVE_MSG%
|
||||||
|
<a class='button' href='/system/sensor/add'>➕ Add sensor</a>
|
||||||
|
<table class='centered'>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
</tr>
|
||||||
|
%OUTPUT_TR_TD%
|
||||||
|
</table>
|
||||||
|
%FOOTER%)";
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Subpage sensor add
|
||||||
|
*/
|
||||||
|
const char* Page_system_sensor_add_HTML PROGMEM = R"(%HEADER%
|
||||||
|
%SUBNAV%
|
||||||
|
<h3>%ACTION% sensor ID %SENSOR_ID%</h3>
|
||||||
|
%SAVE_MSG%
|
||||||
|
|
||||||
|
<p>Add/Edit CanGrow sensor.</p>
|
||||||
|
<form method='post' action='/system/sensor/add'>
|
||||||
|
<input type='hidden' name='sensorId' value='%SENSOR_ID%' />
|
||||||
|
<u>Type</u>:<br>
|
||||||
|
<select id='type_sel' name='type' onchange="showSelect('type_sel', 'type_', 'hidden');" required>
|
||||||
|
<option disabled value='' selected hidden>---</option>
|
||||||
|
%SENSOR_TYPE%
|
||||||
|
</select><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<u>Name</u>:<br>
|
||||||
|
<input type='text' name='name' maxlength='32' value='%SENSOR_NAME%' required><br>
|
||||||
|
|
||||||
|
<u>I2C address</u>:<br>
|
||||||
|
<p>leave empty for default</p>
|
||||||
|
<input type='text' name='i2c_address' maxlength='5' value='%SENSOR_I2C_ADDRESS%' ><br>
|
||||||
|
|
||||||
|
<u>GPIO</u>:<br>
|
||||||
|
<select name='gpio'>
|
||||||
|
<option value='' selected >---</option>
|
||||||
|
%GPIO_INDEX%
|
||||||
|
</select><br>
|
||||||
|
|
||||||
|
<div class='hidden %CLASS_TYPE_1%' id='type_1'>
|
||||||
|
<span>Special 1</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='hidden %CLASS_TYPE_2%' id='type_2'>
|
||||||
|
<span>Special 2</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='hidden %CLASS_TYPE_3% %CLASS_TYPE_1%' id='type_3'>
|
||||||
|
<span>Special 3</span>
|
||||||
|
</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>)";
|
||||||
|
|
||||||
|
|
103
cangrow.sh
103
cangrow.sh
|
@ -1,103 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
|
|
||||||
test -z $TTY && TTY="/dev/ttyUSB0"
|
|
||||||
test -z $IP && IP="192.168.30.212"
|
|
||||||
test -z $VER && VER="0.1-dev"
|
|
||||||
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" )
|
|
||||||
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
|
|
||||||
echo "/* CanGrow_Version.h gets generated from cangrow.sh */
|
|
||||||
|
|
||||||
const char* CanGrowVer = \"${VER}\";
|
|
||||||
const char* CanGrowBuild = \"${BUILD}\";
|
|
||||||
" > Arduino/CanGrow/CanGrow_Version.h
|
|
||||||
${ACLI_CMD} --no-color compile -b ${BOARD} "Arduino/CanGrow/CanGrow.ino" --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>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset='UTF-8'>
|
<meta charset='UTF-8'>
|
||||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||||
<title>CanGrow - Amnesia Haze</title>
|
<title>CanGrow - CanGrow v0.2-dev</title>
|
||||||
|
<link rel='stylesheet' href='cangrow.css'>
|
||||||
<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='>
|
<script type='text/javascript' src='cangrow.js'></script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
.linkForm {
|
||||||
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;
|
display: inline-block;
|
||||||
list-style: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav li:first-of-type {
|
.linkForm input[type=submit] {
|
||||||
background: #026b45;
|
background: #262B27;
|
||||||
border-top-left-radius: 3px;
|
padding: 5px;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<ul class='nav'><li><a href='/'>🌱 Amnesia Haze</a></li>
|
<ul class='nav'><li><a href='/'>🌱 CanGrow</a></li>
|
||||||
<li><a href='/growSettings' >🔆 Grow settings</a></li>
|
<li><a class='' href='/grow/' >🔆 Grow settings</a></li>
|
||||||
<li><a href='/systemSettings' >⚙ System settings</a></li>
|
<li><a class='activeNav' href='/system/' >⚙ System settings</a></li>
|
||||||
<li><a href='/wifiSettings' >📡 WiFi settings</a></li>
|
<li><a class='' href='/wifi/' >📡 WiFi settings</a></li>
|
||||||
<li><a href='/help' >❓ Help</a></li>
|
<li><a class='' href='/help' >❓ Help</a></li>
|
||||||
<li><span class='MenuTime'>09:48:27</span></li>
|
<li><span class='navTime'>04:20:23</span></li>
|
||||||
<li><a href='https://git.la10cy.net/DeltaLima/CanGrow' target='_blank'>CanGrow v0.1</a></li>
|
<li><a href='https://git.la10cy.net/DeltaLima/CanGrow' target='_blank'>CanGrow v0.2-dev</a></li>
|
||||||
</ul><div class='center'><h2>🔄 Firmware update</h2>Version: 0.1<br>Build: 4ad16c9
|
</ul>
|
||||||
<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>
|
<div class='center'>
|
||||||
<form method='GET' action='' enctype='multipart/form-data'>
|
|
||||||
Firmware .bin file:<br>
|
|
||||||
<input type='file' accept='.bin,.bin.gz' name='firmware'>
|
|
||||||
|
|
||||||
|
<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>
|
||||||
<button onclick="document.getElementById('divUploading').style.display = ''; window.alert('click');">asd</button>
|
<form class='linkForm' method='post' action='index.html'>
|
||||||
<div id='divUploading' style='display: none;' class='warnmsg'>Uploading, please wait...<div>
|
<input type='hidden' name='del' value='0'>
|
||||||
|
<input class='linkForm' type='submit' value='❌' onclick="return confirmDelete('Tasmota LED1')">
|
||||||
</div>
|
</form>
|
||||||
</body>
|
</td></tr><tr><td>1</td>
|
||||||
</html>
|
<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>
|
||||||
|
|
Loading…
Reference in a new issue