Compare commits
132 commits
main
...
espasyncwe
Author | SHA1 | Date | |
---|---|---|---|
84c4840993 | |||
92724fa1f4 | |||
cf824c1c61 | |||
b28c71c9a4 | |||
790b9bb9c9 | |||
735cff463e | |||
9af343bd3e | |||
1419d625a4 | |||
fba2210bbd | |||
aaf9688d1a | |||
8955824884 | |||
40d0175564 | |||
b7d21ca868 | |||
c466f1396f | |||
54b6d48e1e | |||
a983129085 | |||
c8247268cc | |||
22c316edbb | |||
bc15bbab46 | |||
b0f3d05576 | |||
f7f5fe073b | |||
55997e82eb | |||
bba1687022 | |||
f4624f860e | |||
10a0906a93 | |||
f7c4739f0d | |||
9c51386be9 | |||
dadaf09232 | |||
98ef3de395 | |||
945c208ba4 | |||
3517f6abf4 | |||
d679d896b2 | |||
ca4eb8cfd6 | |||
8e5268df22 | |||
0d0efbb2c1 | |||
4808af281c | |||
7da5bc38d7 | |||
1765748422 | |||
bafb623392 | |||
e3c739e745 | |||
2ad9c4c03d | |||
00a4acfd13 | |||
dc8ba42909 | |||
5c019f8df6 | |||
458077442a | |||
99d598e05d | |||
bf1e25d04c | |||
d68d0fbf79 | |||
4c9c280d45 | |||
94e79b8fa2 | |||
4f42b64c7e | |||
13087c0cc1 | |||
0227427b6f | |||
8861393e80 | |||
2dfa2b0c13 | |||
f91b2de002 | |||
c3b9f0f8a6 | |||
f1a1cb3aa9 | |||
947af0dd4d | |||
4a14629e48 | |||
44ca2c6a8e | |||
5af5bcd94d | |||
22fdfc57c2 | |||
e7bb42f72b | |||
f093ac843b | |||
de26abaf05 | |||
827c8cd184 | |||
825f2e8e19 | |||
ae7f6cd3f7 | |||
c2d6d508a2 | |||
e843df1a28 | |||
979b214d43 | |||
6e4127398b | |||
b5991a576b | |||
5b54ae3658 | |||
57fa57fc94 | |||
187ade247f | |||
2252fe0142 | |||
0ef3656bf6 | |||
2c2adc9678 | |||
7d0880343b | |||
a0735829ae | |||
45ea8eabaa | |||
8e9a07a65b | |||
6ac7b31602 | |||
a044c65503 | |||
d4e1108759 | |||
52cd7d469f | |||
219586de93 | |||
603118ad0c | |||
f4482ee37b | |||
f918529e57 | |||
42d5939dbc | |||
16074669c3 | |||
f7224a1588 | |||
9d2faf4e4c | |||
b07696a1c4 | |||
ac10feccf7 | |||
a3fef36ecb | |||
f3f1629001 | |||
1d44b6736d | |||
1d55f36387 | |||
f347a375ce | |||
764cf45b80 | |||
717b201889 | |||
06d4ba73c0 | |||
36b2da7802 | |||
f130700cf0 | |||
985b2cac5d | |||
4595bea4e5 | |||
c4dea65157 | |||
d97a220f42 | |||
26f0939cc6 | |||
7dcfc375bb | |||
989fefd1e2 | |||
a330268960 | |||
04e8de0fde | |||
73f017cd2f | |||
e82046d297 | |||
7b43171231 | |||
c4155e1f9b | |||
4c57546657 | |||
3f8b060c66 | |||
3a29ebd316 | |||
11b7217f57 | |||
529bd7e556 | |||
cf58ef292b | |||
ccca8b10ab | |||
8fb27a1720 | |||
8940a9a136 | |||
fec057b8db | |||
a6f5a6539b |
43 changed files with 4108 additions and 3764 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,4 +3,4 @@ KiCad/CanGrow/CanGrow.kicad_sch-bak
|
|||
KiCad/CanGrow/fp-info-cache
|
||||
KiCad/CanGrow/gerber/*.zip
|
||||
Arduino/CanGrow/CanGrow.geany
|
||||
build/
|
||||
Arduino/CanGrow/build/
|
||||
|
|
|
@ -28,30 +28,34 @@ long_line_behaviour=1
|
|||
long_line_column=72
|
||||
|
||||
[files]
|
||||
current_page=1
|
||||
FILE_NAME_0=0;Arduino;0;EUTF-8;0;1;0;.%2FCanGrow.ino;0;2
|
||||
FILE_NAME_1=0;C++;0;EUTF-8;0;1;0;.%2FCanGrow_HTML.h;0;2
|
||||
FILE_NAME_2=0;C++;0;EUTF-8;0;1;0;.%2FCanGrow_Init.h;0;2
|
||||
FILE_NAME_3=0;C++;0;EUTF-8;0;1;0;.%2FCanGrow_Logo.h;0;2
|
||||
FILE_NAME_4=0;C++;0;EUTF-8;0;1;0;.%2FCanGrow_PinAssignments.h;0;2
|
||||
FILE_NAME_5=0;C++;0;EUTF-8;0;1;0;.%2FCanGrow_Sensors.h;0;2
|
||||
FILE_NAME_6=0;C++;0;EUTF-8;0;1;0;.%2FCanGrow_SysFunctions.h;0;2
|
||||
FILE_NAME_7=0;C++;0;EUTF-8;0;1;0;.%2FCanGrow_WebFunctions.h;0;2
|
||||
current_page=0
|
||||
FILE_NAME_0=493;Sh;0;EUTF-8;0;1;0;.%2Fcangrow.sh;0;2
|
||||
FILE_NAME_1=0;Arduino;0;EUTF-8;0;1;0;.%2FCanGrow.ino;0;2
|
||||
FILE_NAME_2=0;C++;0;EUTF-8;0;1;0;.%2Finclude%2FCanGrow.h;0;2
|
||||
FILE_NAME_3=0;C++;0;EUTF-8;0;1;0;.%2Finclude%2FCanGrow_Core.h;0;2
|
||||
FILE_NAME_4=0;C++;0;EUTF-8;0;1;0;.%2Finclude%2FCanGrow_ESP32.h;0;2
|
||||
FILE_NAME_5=0;C++;0;EUTF-8;0;1;0;.%2Finclude%2FCanGrow_ESP8266.h;0;2
|
||||
FILE_NAME_6=0;C++;0;EUTF-8;0;1;0;.%2Finclude%2FCanGrow_LittleFS.h;0;2
|
||||
FILE_NAME_7=0;C++;0;EUTF-8;0;1;0;.%2Finclude%2FCanGrow_Logo.h;0;2
|
||||
FILE_NAME_8=0;C++;0;EUTF-8;0;1;0;.%2Finclude%2FCanGrow_Version.h;0;2
|
||||
|
||||
[build-menu]
|
||||
C++FT_00_LB=_Compile
|
||||
C++FT_00_CM=~/.local/bin/arduino-cli --no-color compile -b esp8266:esp8266:d1_mini_clone "%d/CanGrow.ino"
|
||||
C++FT_00_CM=cd .. ; ./cangrow.sh build
|
||||
C++FT_00_WD=
|
||||
filetypes=C++;Arduino;
|
||||
filetypes=C++;Arduino;Sh;
|
||||
ArduinoFT_00_LB=_Build
|
||||
ArduinoFT_00_CM=~/.local/bin/arduino-cli --no-color compile -b esp8266:esp8266:d1_mini_clone "%d/CanGrow.ino"
|
||||
ArduinoFT_00_CM=./cangrow.sh build
|
||||
ArduinoFT_00_WD=
|
||||
ArduinoFT_01_LB=Build & Upload
|
||||
ArduinoFT_01_CM=~/.local/bin/arduino-cli --no-color compile -v -b esp8266:esp8266:d1_mini_clone -u -p /dev/ttyUSB0 "%d/CanGrow.ino"
|
||||
ArduinoFT_01_CM=./cangrow.sh upload
|
||||
ArduinoFT_01_WD=
|
||||
C++FT_01_LB=_Build & Upload
|
||||
C++FT_01_CM=~/.local/bin/arduino-cli --no-color compile -v -b esp8266:esp8266:d1_mini_clone -u -p /dev/ttyUSB0 "%d/CanGrow.ino"
|
||||
C++FT_01_CM=cd .. ; ./cangrow.sh upload
|
||||
C++FT_01_WD=
|
||||
|
||||
[VTE]
|
||||
last_dir=~
|
||||
ShFT_00_LB=Build
|
||||
ShFT_00_CM=./cangrow.sh build
|
||||
ShFT_00_WD=
|
||||
ShFT_01_LB=Build & Upload
|
||||
ShFT_01_CM=./cangrow.sh upload
|
||||
ShFT_01_WD=
|
||||
|
|
|
@ -1,419 +1,169 @@
|
|||
/*
|
||||
* CanGrow - simply DIY automatic plant grow system (for cannabis).
|
||||
*
|
||||
* CanGrow - an OpenSource growcontroller firmware (for cannabis)
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Includes
|
||||
*
|
||||
* Libraries include
|
||||
*/
|
||||
|
||||
// Libraries
|
||||
#include "Arduino.h"
|
||||
|
||||
// * ESP8266 *
|
||||
#ifdef ESP8266
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
|
||||
// * ESP32 *
|
||||
#ifdef ESP32
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include <Update.h>
|
||||
#endif
|
||||
|
||||
// https://github.com/mathieucarbou/ESPAsyncWebServer
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
// LittleFS filesystem
|
||||
#include "FS.h"
|
||||
// arduino-core for esp8266 and esp32
|
||||
#include "LittleFS.h"
|
||||
|
||||
// https://github.com/arduino/ArduinoCore-avr/tree/master/libraries/SPI
|
||||
#include <SPI.h>
|
||||
// https://github.com/arduino/ArduinoCore-avr/tree/master/libraries/Wire
|
||||
#include <Wire.h>
|
||||
// https://github.com/arduino/ArduinoCore-avr/tree/master/libraries/EEPROM
|
||||
#include <EEPROM.h>
|
||||
// https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WiFi
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <WiFiUdp.h>
|
||||
// https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer
|
||||
#include <ESP8266WebServer.h>
|
||||
// OTA update
|
||||
#include <ESP8266HTTPUpdateServer.h>
|
||||
// https://github.com/adafruit/Adafruit-GFX-Library
|
||||
#include <Adafruit_GFX.h>
|
||||
// https://github.com/adafruit/Adafruit_SSD1306
|
||||
#include <Adafruit_SSD1306.h>
|
||||
// https://github.com/adafruit/Adafruit_BME280_Library/
|
||||
#include <Adafruit_Sensor.h>
|
||||
#include <Adafruit_BME280.h>
|
||||
|
||||
// https://github.com/bblanchon/ArduinoJson
|
||||
#include <ArduinoJson.h>
|
||||
// https://github.com/arduino-libraries/NTPClient
|
||||
#include <NTPClient.h>
|
||||
// https://github.com/PaulStoffregen/Time
|
||||
#include <TimeLib.h>
|
||||
// DHT support dropped
|
||||
// https://github.com/adafruit/DHT-sensor-library
|
||||
// #include "DHT.h"
|
||||
|
||||
/*
|
||||
* CanGrow header files
|
||||
*/
|
||||
|
||||
#include "CanGrow_PinAssignments.h"
|
||||
#include "CanGrow_Init.h"
|
||||
#include "CanGrow_Logo.h"
|
||||
#include "CanGrow_Sensors.h"
|
||||
|
||||
#include "CanGrow_Version.h"
|
||||
#include "CanGrow_HTML.h"
|
||||
#include "CanGrow_SysFunctions.h"
|
||||
#include "CanGrow_WebFunctions.h"
|
||||
|
||||
|
||||
/*
|
||||
* Setup
|
||||
*
|
||||
* CanGrow includes
|
||||
*/
|
||||
|
||||
/* main header file, where all variables, consts and structs get defined */
|
||||
#include "include/CanGrow.h"
|
||||
/* CanGrow platform specific includes */
|
||||
#include "include/CanGrow_ESP8266.h"
|
||||
#include "include/CanGrow_ESP32.h"
|
||||
/* CanGrow header with all functions */
|
||||
#include "include/CanGrow_Core.h"
|
||||
#include "include/CanGrow_Wifi.h"
|
||||
#include "include/CanGrow_LittleFS.h"
|
||||
#include "include/CanGrow_Webserver.h"
|
||||
|
||||
|
||||
void setup() {
|
||||
|
||||
// setup pins
|
||||
pinMode(PinFAN, OUTPUT);
|
||||
//pinMode(PINdht, INPUT);
|
||||
pinMode(PINwaterlevel, OUTPUT);
|
||||
pinMode(PINsoilmoisture, OUTPUT);
|
||||
pinMode(PinLED, OUTPUT);
|
||||
pinMode(PinPUMP, OUTPUT);
|
||||
// define output for onboard LED/WIPE pin
|
||||
pinMode(PinWIPE, OUTPUT);
|
||||
|
||||
|
||||
// set all OUTPUT to low
|
||||
digitalWrite(PinFAN, HIGH);
|
||||
digitalWrite(PINwaterlevel, LOW);
|
||||
digitalWrite(PinLED, HIGH);
|
||||
digitalWrite(PinPUMP, HIGH);
|
||||
// except PINsoilmoisture
|
||||
// PINsoilmoisture is always HIGH and gets LOW in moment of waterlevel measurement
|
||||
digitalWrite(PINsoilmoisture, LOW);
|
||||
|
||||
// set PWM frequency to 13.37KHz
|
||||
analogWriteFreq(13370);
|
||||
|
||||
// Start EEPROM
|
||||
EEPROM.begin(512);
|
||||
|
||||
// Start Serial
|
||||
Serial.begin(115200);
|
||||
|
||||
// Write a line before doing serious output, because before there is some garbage in serial
|
||||
// whats get the cursor somewhere over the place
|
||||
Serial.println("420");
|
||||
Serial.print(".:: CanGrow firmware v");
|
||||
Serial.print(CanGrowVer);
|
||||
Serial.print(" build ");
|
||||
Serial.print(CanGrowBuild);
|
||||
Serial.println(" starting ::.");
|
||||
Serial.printf(".:: CanGrow firmware v%s build %s starting ::.\n", CANGROW_VER, CANGROW_BUILD);
|
||||
|
||||
Serial.println(":: initialise I2C ::");
|
||||
// initialise Wire for I2C
|
||||
Wire.begin();
|
||||
Serial.print("II To format / factory reset LittleFS, pull GPIO ");
|
||||
Serial.print(PinWIPE);
|
||||
Serial.print(" (PinWIPE) to ");
|
||||
// we need to invert the default to tell that user the state for an action
|
||||
Serial.print(1 - PinWIPE_default);
|
||||
Serial.println(" - NOW! (2 seconds left) II");
|
||||
|
||||
Serial.println(":: initialise display ::");
|
||||
// initialise I2C display
|
||||
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Address 0x3C for 128x64
|
||||
display.clearDisplay();
|
||||
display.display();
|
||||
|
||||
// set display settings
|
||||
display.setTextSize(1);
|
||||
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
|
||||
|
||||
// display Logo
|
||||
display.drawBitmap(0, 0, bmpCanGrow_Logo, 128, 32, WHITE);
|
||||
display.display();
|
||||
|
||||
Serial.println(":: initialise chirp sensor if present ::");
|
||||
// reset chirp
|
||||
writeI2CRegister8bit(0x20, 6); //TODO: Do only, when configured
|
||||
|
||||
// initialise DHT11
|
||||
// dht support dropped
|
||||
// dht.begin(); //TODO: Do only, when configured
|
||||
|
||||
// initialise BME280
|
||||
Serial.println(":: initialise BME280 sensor ::");
|
||||
// ToDo: let the user configure somewhere the ID of the BME280 sensor
|
||||
if(!bme.begin(0x76)) {
|
||||
Serial.println("!! Cannot find BME280 on I2C bus. Please check connection or ID");
|
||||
}
|
||||
|
||||
Serial.println("To wipe the EEPROM saved data, set D4 (PinWIPE) to LOW - NOW! (2 seconds left)");
|
||||
// wait a few seconds to let the user pull D4 down to wipe EEPROM
|
||||
// and we can enjoy the boot screen meanwhile :p
|
||||
// meanwhile blink with the led onboad :)
|
||||
// 333 * 6 =~ 2 seconds
|
||||
|
||||
display.fillRect(0,36,128,64-36, 0);
|
||||
display.setCursor(0,36);
|
||||
display.println("To wipe EEPROM pull");
|
||||
display.println("D4 (PinWIPE) to GND");
|
||||
display.display();
|
||||
|
||||
// blink with the onboard LED on D4 (PinWIPE)
|
||||
// blink with the onboard LED on D4/GPIO2 (PinWIPE)
|
||||
for(byte i = 0; i <= 6 ; i++) {
|
||||
if(i % 2) {
|
||||
digitalWrite(PinWIPE, LOW);
|
||||
digitalWrite(PinWIPE, 1 - PinWIPE_default);
|
||||
} else {
|
||||
digitalWrite(PinWIPE, HIGH);
|
||||
digitalWrite(PinWIPE, PinWIPE_default);
|
||||
}
|
||||
delay(333);
|
||||
}
|
||||
// set back to HIGH because thats the default
|
||||
digitalWrite(PinWIPE, HIGH);
|
||||
//delay(2000);
|
||||
|
||||
// set PinWIPE back to its default
|
||||
digitalWrite(PinWIPE, PinWIPE_default);
|
||||
|
||||
|
||||
// read status from PinWIPE to WIPE
|
||||
// when PinWIPE is set to LOW, wipe EEPROM
|
||||
if(digitalRead(PinWIPE) == LOW) {
|
||||
// wipe EEPROM
|
||||
wipeEEPROM();
|
||||
// when PinWIPE is set to LOW, format LittleFS
|
||||
if(digitalRead(PinWIPE) != PinWIPE_default) {
|
||||
LFS_Format();
|
||||
Restart();
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 = 0; i < GPIOindex_length; i++) {
|
||||
Serial.printf(":: [SETUP] Pin Index: %d, GPIO: %d, Notes: ", i, GPIOindex[i].gpio);
|
||||
Serial.println(GPIO_Index_note_descr[GPIOindex[i].note]);
|
||||
}
|
||||
// set web handler
|
||||
WebHandler();
|
||||
// start webserver
|
||||
webserver.begin();
|
||||
|
||||
Serial.println(".:: CanGrow Ready ::.");
|
||||
delay(1000);
|
||||
if(strlen(GrowName) > 0 ) {
|
||||
display.clearDisplay();
|
||||
display.display();
|
||||
}
|
||||
}
|
||||
|
||||
bool alrdySaved = false;
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
* Loop
|
||||
*
|
||||
*
|
||||
*/
|
||||
void loop() {
|
||||
// var definition
|
||||
unsigned long currentRuntime = millis();
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
if(currentMillis - schedulerPrevMillis >= config.system.schedulerInterval) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// first we call webserver handle client
|
||||
webserver.handleClient();
|
||||
if((digitalRead(PinWIPE) != PinWIPE_default) && (alrdySaved == false)) {
|
||||
Serial.println(":: [LOOP] PinWIPE is triggered");
|
||||
// save config to littlefs as json
|
||||
SaveConfig();
|
||||
// only print json to serial
|
||||
SaveConfig(true);
|
||||
alrdySaved = true;
|
||||
} else if( (digitalRead(PinWIPE) != PinWIPE_default) && (alrdySaved == true) ) {
|
||||
alrdySaved = true;
|
||||
} else {
|
||||
alrdySaved = false;
|
||||
}
|
||||
|
||||
|
||||
// do every second when everything is configured and grow is started
|
||||
if( (configured == true) && (strlen(GrowName) > 0) && (currentRuntime - outputPrevTime >= 1000) ){
|
||||
|
||||
// refresh all sensor values
|
||||
refreshSensors();
|
||||
|
||||
// calculate acutal DayOfGrow
|
||||
DayOfGrow = int(ceil(float((timeClient.getEpochTime() - GrowStart) / 60 / 60 / 24)));
|
||||
// decide if we are in Veg or Bloom phase of grow
|
||||
// when DayOfGrow is larger then DaysVeg we must be in Bloom
|
||||
|
||||
// set the actual state of the Grow LED
|
||||
// when being in Maintenance Mode and UseRelaisLED not true,
|
||||
// dimm the light
|
||||
if(MaintenanceMode == true) {
|
||||
if((currentRuntime - MaintenanceStarted <= MaintenanceDuration * 1000 ) && (UseLEDrelais == false)) {
|
||||
// in case of being in Maintenance Mode , dimm the grow light when not a relais is used
|
||||
setOutput(1, 15);
|
||||
} else {
|
||||
MaintenanceMode = false;
|
||||
}
|
||||
} else {
|
||||
controlLED();
|
||||
}
|
||||
|
||||
controlPUMP();
|
||||
|
||||
displayScreens();
|
||||
|
||||
// current time gets previous time for new interval
|
||||
outputPrevTime = currentRuntime;
|
||||
// if global var doRestart is true, perform a restart
|
||||
if(doRestart == true) {
|
||||
Restart();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* TODO LIST / NOTES
|
||||
*
|
||||
*
|
||||
* - when PWM for fan is set, set fan speed to regulate humidity and
|
||||
* temperature, depending on which phase of grow the plant is
|
||||
* (https://www.royalqueenseeds.de/blog-cannabisanbau-im-grow-room-relative-luftfeuchtigkeit-und-temperaturen-n243)
|
||||
* - re-organize EEPROM saved values.
|
||||
* - prevent GrowStart to be in the future
|
||||
* - maybe let the user configure some screens to display.
|
||||
* - put EEPROM adresses into DEFINEs
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Fan control
|
||||
*
|
||||
* Vars:
|
||||
* - FanVent (byte) Fan1 or Fan2
|
||||
* - FanExhaust (byte) Fan1 or Fan2
|
||||
* -
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
* PLAYGROUND / TRASH
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
unsigned long currentTime = millis();
|
||||
|
||||
int valSoilmoisture0 = getSoilmoisture(0);
|
||||
int valSoilmoisture1 = getSoilmoisture(1);
|
||||
|
||||
float valTemperature0 = getTemperature(0);
|
||||
float valTemperature1 = getTemperature(1);
|
||||
|
||||
float valHumidity = getHumidity();
|
||||
|
||||
int valWaterlevel = getWaterlevel();
|
||||
|
||||
switch(valWaterlevel) {
|
||||
|
||||
case 0:
|
||||
digitalWrite(PinLED, HIGH);
|
||||
digitalWrite(PinPUMP, LOW);
|
||||
digitalWrite(PinFAN, LOW);
|
||||
break;
|
||||
case 1:
|
||||
digitalWrite(PinLED, LOW);
|
||||
digitalWrite(PinPUMP, HIGH);
|
||||
digitalWrite(PinFAN, LOW);
|
||||
break;
|
||||
case 2:
|
||||
digitalWrite(PinLED, LOW);
|
||||
digitalWrite(PinPUMP, LOW);
|
||||
digitalWrite(PinFAN, HIGH);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// OUTPUT
|
||||
if(currentTime - outputPrevTime >= 1000) {
|
||||
|
||||
|
||||
// set display cursor to top left
|
||||
display.setCursor(0,0);
|
||||
// display text
|
||||
display.print("I2C: ");
|
||||
display.print(valSoilmoisture1);
|
||||
display.print(", ");
|
||||
display.println(valTemperature1);
|
||||
|
||||
Serial.print("I2C: ");
|
||||
Serial.print(valSoilmoisture1);
|
||||
Serial.print(", ");
|
||||
Serial.println(valTemperature1);
|
||||
|
||||
|
||||
display.print("DHT11: ");
|
||||
display.print(valTemperature0);
|
||||
display.print(", ");
|
||||
display.println(valHumidity);
|
||||
|
||||
Serial.print("DHT11: ");
|
||||
Serial.print(valTemperature0);
|
||||
Serial.print(", ");
|
||||
Serial.println(valHumidity);
|
||||
|
||||
|
||||
display.print("Water Status: ");
|
||||
display.println(valWaterlevel);
|
||||
|
||||
Serial.print("Water Status: ");
|
||||
Serial.println(valWaterlevel);
|
||||
|
||||
display.print("ASM: ");
|
||||
display.print(valSoilmoisture0);
|
||||
display.println(", ");
|
||||
|
||||
Serial.print("ASM: ");
|
||||
Serial.println(valSoilmoisture0);
|
||||
|
||||
// print everything on the display
|
||||
display.display();
|
||||
|
||||
Serial.println("Test");
|
||||
|
||||
outputPrevTime = currentTime;
|
||||
*/
|
||||
|
||||
|
||||
/* if(D6status == true) {
|
||||
digitalWrite(PinLED, LOW);
|
||||
digitalWrite(PinPUMP, LOW);
|
||||
digitalWrite(PinFAN, LOW);
|
||||
D6status = false;
|
||||
Serial.println("D6 is off now");
|
||||
} else {
|
||||
digitalWrite(PinLED, HIGH);
|
||||
digitalWrite(PinPUMP, HIGH);
|
||||
digitalWrite(PinFAN, HIGH);
|
||||
D6status = true;
|
||||
Serial.println("D6 is ON now");
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
for(int dutyCycle = 0; dutyCycle < 255; dutyCycle++){
|
||||
// changing the LED brightness with PWM
|
||||
analogWrite(PinLED, dutyCycle);
|
||||
delay(1);
|
||||
}
|
||||
|
||||
// decrease the LED brightness
|
||||
for(int dutyCycle = 255; dutyCycle > 0; dutyCycle--){
|
||||
// changing the LED brightness with PWM
|
||||
analogWrite(PinLED, dutyCycle);
|
||||
delay(1);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
@ -1,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.
|
||||
A ESP8266 with Arduino Firmware is used (should most times be the standard already).
|
||||
## Build environment
|
||||
The helper script `cangrow.sh` is written for a Debian 12 system.
|
||||
|
||||
To install all dependencies you need for building the firmware, run the cangrow.sh setup:
|
||||
|
||||
```sh
|
||||
$ ./cangrow.sh help
|
||||
./cangrow.sh [setup|build|upload|webupload|monitor]
|
||||
setup: setup build environment, download arduino-cli, install all dependencies for arduino ide
|
||||
build: build firmware binary. will be saved into build/
|
||||
upload: upload firmware by serial connection /dev/ttyUSB0
|
||||
webupload: upload firmware with webupload to 192.168.4.20
|
||||
monitor: serial monitor /dev/ttyUSB0
|
||||
|
||||
# Install all dependencies for build environment
|
||||
$ ./cangrow.sh setup
|
||||
```
|
||||
|
||||
The script installs [arduino-cli](https://github.com/arduino/arduino-cli) to `~/.local/bin/arduino-cli`.
|
||||
|
||||
## Compile
|
||||
|
||||
```sh
|
||||
# compile and output to build/CanGrow_v0.2...bin
|
||||
# Default Target is ESP8266 D1 Mini
|
||||
$ ./cangrow.sh build
|
||||
|
||||
# Compile for ESP32 D1 Mini
|
||||
$ export BOARD="esp32:esp32:d1_mini32"
|
||||
$ ./cangrow.sh build
|
||||
|
||||
# Build and webupload to IP
|
||||
$ export IP="192.168.4.69"
|
||||
$ ./cangrow.sh build # need to make .bin first
|
||||
$ ./cangrow.sh webupload # upload
|
||||
|
||||
# listen to serial monitor on /dev/ttyUSB2
|
||||
$ export TTY="/dev/ttyUSB2"
|
||||
./cangrow.sh monitor
|
||||
```
|
||||
|
||||
I wrote this project using [Geany IDE. ](https://www.geany.org/). The Geany Projectfile is also included, just run
|
||||
```sh
|
||||
$ geany CanGrow.geany
|
||||
```
|
||||
|
||||
I wrote this project using [Geany IDE. ](https://www.geany.org/). The Geany Projectfile is also included.
|
||||
Compiling is done with [arduino-cli](https://github.com/arduino/arduino-cli). It is supposed to be in
|
||||
`~/.local/bin/arduino-cli`.
|
||||
**F8 compiles** the project, **F9 uploads** firmware to /dev/ttyUSB0. You can change these settings for .ino and .h files
|
||||
in Project -> Settings -> Create/Make.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
board_manager:
|
||||
additional_urls:
|
||||
- http://arduino.esp8266.com/stable/package_esp8266com_index.json
|
||||
- https://espressif.github.io/arduino-esp32/package_esp32_index.json
|
145
Arduino/CanGrow/cangrow.sh
Executable file
145
Arduino/CanGrow/cangrow.sh
Executable file
|
@ -0,0 +1,145 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
|
||||
test -z $TTY && TTY="/dev/ttyUSB0"
|
||||
test -z $IP && IP="192.168.4.20"
|
||||
test -z $VER && VER="0.2-dev"
|
||||
test -z $BOARD && BOARD="esp8266:esp8266:d1_mini_clone"
|
||||
#test -z $BOARD && BOARD="esp32:esp32:d1_mini32"
|
||||
|
||||
BUILD="$(git rev-parse --short HEAD)-$(echo $BOARD | cut -d : -f1)-$(date '+%Y%m%d%H%M%S')"
|
||||
|
||||
ACLI="$HOME/.local/bin/arduino-cli"
|
||||
ACLI_CMD="$ACLI --config-file arduino-cli.yml"
|
||||
test -z $BUILDDIR && BUILDDIR="build"
|
||||
|
||||
|
||||
function help() {
|
||||
echo "$0 [setup|build|upload|webupload|monitor]"
|
||||
echo "setup: setup build environment, download arduino-cli, install all dependencies for arduino ide"
|
||||
echo "build: build firmware binary. will be saved into ${BUILDDIR}/"
|
||||
echo "upload: upload firmware by serial connection $TTY"
|
||||
echo "webupload: upload firmware with webupload to $IP"
|
||||
echo "monitor: serial monitor $TTY"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function check_acli() {
|
||||
if [ ! -x $ACLI ]
|
||||
then
|
||||
echo "$ACLI does not exist nor is executable. Please run '$0 setup' first"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
test -z $1 && help
|
||||
|
||||
case $1 in
|
||||
s|setup)
|
||||
ACLI_DIR="$(dirname $ACLI)"
|
||||
declare -a CORES=(
|
||||
"esp8266:esp8266@3.1.2"
|
||||
"esp32:esp32@3.0.5"
|
||||
)
|
||||
declare -a LIBS=(
|
||||
"Adafruit SSD1306@2.5.12"
|
||||
"Adafruit BME280 Library@2.2.4"
|
||||
"ArduinoJson@7.2.0"
|
||||
"NTPClient@3.2.1"
|
||||
"Time@1.6.1"
|
||||
"ESP Async WebServer@3.3.17"
|
||||
"Async TCP@3.2.10"
|
||||
"ESPAsyncTCP@1.2.4"
|
||||
)
|
||||
echo ":: Setting up build environment for CanGrow Firmware."
|
||||
echo " This will download the binary for arduino-cli and install"
|
||||
echo " the packages for the arduino ide from the debian repository."
|
||||
echo " !! This script is meant to be executed on a Debian stable (bookworm) system !!"
|
||||
echo ""
|
||||
echo ":: Press Enter to continue"
|
||||
read
|
||||
echo ""
|
||||
echo ":: Installing Arduino IDE packages with apt, please enter sudo password:"
|
||||
sudo apt update || exit 1
|
||||
sudo apt install arduino python3 python3-serial wget curl xxd || exit 1
|
||||
echo ":: Ensure directory ${ACLI_DIR} is present"
|
||||
test -d ${ACLI_DIR} || mkdir -p ${ACLI_DIR}
|
||||
echo ":: Please ensure ${ACLI_DIR} is in your \$PATH, I wont do it."
|
||||
echo ""
|
||||
echo ":: Downloading arduino-cli 1.0.0 into ${ACLI_DIR}/"
|
||||
wget -O - "https://github.com/arduino/arduino-cli/releases/download/v1.0.4/arduino-cli_1.0.4_Linux_64bit.tar.gz" | tar -C ${ACLI_DIR} -zxvf - arduino-cli
|
||||
chmod +x ${ACLI}
|
||||
echo ""
|
||||
echo ":: Installing ESP8266 and ESP32 cores for Arduino"
|
||||
for core in ${!CORES[@]}
|
||||
do
|
||||
${ACLI_CMD} core install ${CORES[$core]}
|
||||
done
|
||||
echo ":: Installing Arduino libraries"
|
||||
${ACLI_CMD} lib update-index || exit 1
|
||||
for lib in ${!LIBS[@]}
|
||||
do
|
||||
echo " - ${LIBS[$lib]}"
|
||||
done
|
||||
|
||||
for lib in ${!LIBS[@]}
|
||||
do
|
||||
${ACLI_CMD} lib install "${LIBS[$lib]}" || exit 1
|
||||
done
|
||||
echo ""
|
||||
echo ":: Setup build environment done! You can now build the firmware"
|
||||
echo " with: $0 build"
|
||||
|
||||
;;
|
||||
b|build)
|
||||
check_acli
|
||||
ACLI_CMD="${ACLI_CMD} --output-dir ${BUILDDIR}"
|
||||
echo ":: Building firmware $VER $BUILD, target dir: ${BUILDDIR}/"
|
||||
|
||||
test -d ${BUILDDIR} || mkdir ${BUILDDIR}
|
||||
|
||||
|
||||
# esp8266 and esp32 compiler have to use different compile flags for VER and BUILD
|
||||
if [ "$(echo $BOARD | cut -d : -f1)" == "esp8266" ]
|
||||
then
|
||||
${ACLI_CMD} --no-color compile -b ${BOARD} --build-property "build.extra_flags=-DCANGROW_VER=\"${VER}\" -DCANGROW_BUILD=\"${BUILD}\"" "CanGrow.ino" || exit 1
|
||||
elif [ "$(echo $BOARD | cut -d : -f1)" == "esp32" ]
|
||||
then
|
||||
${ACLI_CMD} --no-color compile -b ${BOARD} --build-property "build.defines=-DCANGROW_VER=\"${VER}\" -DCANGROW_BUILD=\"${BUILD}\"" "CanGrow.ino" || exit 1
|
||||
fi
|
||||
|
||||
cp ${BUILDDIR}/CanGrow.ino.bin ${BUILDDIR}/CanGrow_v${VER}_${BUILD}.bin
|
||||
;;
|
||||
u|upload)
|
||||
check_acli
|
||||
echo ":: Build and upload firmware $VER $BUILD to $TTY"
|
||||
|
||||
test -d build || mkdir build
|
||||
|
||||
# esp8266 and esp32 compiler have to use different compile flags for VER and BUILD
|
||||
if [ "$(echo $BOARD | cut -d : -f1)" == "esp8266" ]
|
||||
then
|
||||
${ACLI_CMD} --no-color compile -b ${BOARD} --build-property "build.extra_flags=-DCANGROW_VER=\"${VER}\" -DCANGROW_BUILD=\"${BUILD}\"" ${ACLI_BUILD_OPTS} -u -p $TTY "CanGrow.ino"
|
||||
elif [ "$(echo $BOARD | cut -d : -f1)" == "esp32" ]
|
||||
then
|
||||
${ACLI_CMD} --no-color compile -b ${BOARD} --build-property "build.defines=-DCANGROW_VER=\"${VER}\" -DCANGROW_BUILD=\"${BUILD}\"" ${ACLI_BUILD_OPTS} -u -p $TTY "CanGrow.ino"
|
||||
fi
|
||||
|
||||
;;
|
||||
w|webupload)
|
||||
test -z "$2" && UPLOAD_FILE="${BUILDDIR}/CanGrow.ino.bin"
|
||||
test -n "$2" && UPLOAD_FILE="$2"
|
||||
|
||||
echo ":: Uploading $UPLOAD_FILE to $IP"
|
||||
curl -v http://$IP/system/update -X POST -H 'Content-Type: multipart/form-data' -F "firmware=@${UPLOAD_FILE}"
|
||||
echo
|
||||
;;
|
||||
m|mon|monitor)
|
||||
check_acli
|
||||
echo ":: Open serial monitor $TTY"
|
||||
${ACLI_CMD} monitor -c baudrate=115200 -b ${BOARD} -p $TTY
|
||||
;;
|
||||
*)
|
||||
help
|
||||
;;
|
||||
esac
|
282
Arduino/CanGrow/include/CanGrow.h
Normal file
282
Arduino/CanGrow/include/CanGrow.h
Normal file
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow.h - main header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
//#include "CanGrow_Version.h"
|
||||
|
||||
/* ensure the code will also compile when CANGROW_VER and CANGROW_BUILD
|
||||
* are not defined by the compiler arguments
|
||||
* like -DCANGROW_VER="0.x-dev" or -DCANGROW_BUILD="commitid-core-timestamp"
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
* Constants
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CANGROW_VER
|
||||
#define CANGROW_VER "0.x-dev"
|
||||
#endif
|
||||
#ifndef CANGROW_BUILD
|
||||
#define CANGROW_BUILD "0000"
|
||||
#endif
|
||||
|
||||
#define CANGROW_SSID "CanGrow-unconfigured"
|
||||
|
||||
/* actual structure initialization for GPIO_Index is done within the header files
|
||||
* for ESP32 and ESP8266
|
||||
*
|
||||
* GPIO_Index.note explenation:
|
||||
* 1 - BOOTFAILS_LOW: BootFails when LOW
|
||||
* 2 - BOOTFAILS_HIGH: BootFails when HIGH
|
||||
* 3 - FLASHMODE_LOW: FlashMode needs LOW to enter
|
||||
* 4 - INPUT_ONLY: Input Only
|
||||
* 5 - NO_PWM: No PWM output
|
||||
* 6 - PWM_BOOT: PWM at boot time
|
||||
*/
|
||||
const byte BOOTFAILS_LOW = 1;
|
||||
const byte BOOTFAILS_HIGH = 2;
|
||||
const byte FLASHMODE_LOW = 3;
|
||||
const byte INPUT_ONLY = 4;
|
||||
const byte NO_PWM = 5;
|
||||
const byte HIGH_BOOT = 6;
|
||||
|
||||
char BOOTFAILS_LOW_descr[] = "BF_LOW";
|
||||
char BOOTFAILS_HIGH_descr[] = "BF_HIGH";
|
||||
char FLASMODE_LOW_descr[] = "FM_LOW";
|
||||
char INPUT_ONLY_descr[] = "IN_ONLY";
|
||||
char NO_PWM_descr[] = "NO_PWM";
|
||||
char HIGH_BOOT_descr[] = "B_HIGH";
|
||||
|
||||
const char * GPIO_Index_note_descr[] = {
|
||||
NULL, // 0 - no note
|
||||
BOOTFAILS_LOW_descr, // 1
|
||||
BOOTFAILS_HIGH_descr, // 2
|
||||
FLASMODE_LOW_descr, // 3
|
||||
INPUT_ONLY_descr, // 4
|
||||
NO_PWM_descr, // 5
|
||||
HIGH_BOOT_descr, // 6
|
||||
};
|
||||
|
||||
/*
|
||||
* Output Type
|
||||
*/
|
||||
|
||||
// 0 is unconfigured
|
||||
const byte Output_Type_total = 4;
|
||||
|
||||
const byte OUTPUT_TYPE_GPIO = 1;
|
||||
const byte OUTPUT_TYPE_I2C = 2;
|
||||
const byte OUTPUT_TYPE_WEB = 3;
|
||||
|
||||
char OUTPUT_TYPE_GPIO_descr[] = "GPIO";
|
||||
char OUTPUT_TYPE_I2C_descr[] = "I2C";
|
||||
char OUTPUT_TYPE_WEB_descr[] = "Webcall";
|
||||
|
||||
const char * Output_Type_descr[] = {
|
||||
NULL, // 0 - no description because 0 means unconfigured
|
||||
OUTPUT_TYPE_GPIO_descr,
|
||||
OUTPUT_TYPE_I2C_descr,
|
||||
OUTPUT_TYPE_WEB_descr,
|
||||
};
|
||||
|
||||
/*
|
||||
* Output Device
|
||||
*/
|
||||
// 0 is unconfigured
|
||||
const byte Output_Device_total = 7;
|
||||
|
||||
const byte OUTPUT_DEVICE_LIGHT = 1;
|
||||
const byte OUTPUT_DEVICE_FAN = 2;
|
||||
const byte OUTPUT_DEVICE_PUMP = 3;
|
||||
const byte OUTPUT_DEVICE_HUMIDIFIER = 4;
|
||||
const byte OUTPUT_DEVICE_DEHUMIDIFIER = 5;
|
||||
const byte OUTPUT_DEVICE_HEATING = 6;
|
||||
|
||||
char OUTPUT_DEVICE_LIGHT_descr[] = "Light";
|
||||
char OUTPUT_DEVICE_FAN_descr[] = "Fan";
|
||||
char OUTPUT_DEVICE_PUMP_descr[] = "Pump";
|
||||
char OUTPUT_DEVICE_HUMIDIFIER_descr[] = "Humidifier";
|
||||
char OUTPUT_DEVICE_DEHUMIDIFIER_descr[] = "Dehumidifier";
|
||||
char OUTPUT_DEVICE_HEATING_descr[] = "Heating";
|
||||
|
||||
|
||||
const char * Output_Device_descr[] = {
|
||||
NULL, // 0 - no description because 0 means unconfigured
|
||||
OUTPUT_DEVICE_LIGHT_descr,
|
||||
OUTPUT_DEVICE_FAN_descr,
|
||||
OUTPUT_DEVICE_PUMP_descr,
|
||||
OUTPUT_DEVICE_HUMIDIFIER_descr,
|
||||
OUTPUT_DEVICE_DEHUMIDIFIER_descr,
|
||||
OUTPUT_DEVICE_HEATING_descr,
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct GPIO_Index {
|
||||
const byte gpio;
|
||||
const byte note;
|
||||
};
|
||||
|
||||
const byte Max_Outputs = 16;
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* Config
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Config WiFi
|
||||
*/
|
||||
struct Config_WiFi {
|
||||
char ssid[32];
|
||||
char password[64];
|
||||
bool dhcp;
|
||||
byte ip[4] = {192,168,4,20};
|
||||
byte netmask[4] = {255,255,255,0};
|
||||
byte gateway[4] = {0,0,0,0};
|
||||
byte dns[4] = {0,0,0,0};
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Config System
|
||||
*/
|
||||
|
||||
struct Config_System_Output {
|
||||
|
||||
/*
|
||||
* Config System Output
|
||||
*
|
||||
* - output_type: output type like GPIO, I2C, URL
|
||||
* 1 - GPIO
|
||||
* 2 - I2C
|
||||
* 3 - Web
|
||||
* - device: what this output is connected to
|
||||
* 1 - Light
|
||||
* 2 - Fan
|
||||
* 3 - Pump
|
||||
* 4 - Humudifier
|
||||
* 5 - Dehumidifier
|
||||
* 6 - Heating
|
||||
* - name: name of output
|
||||
* - enabled: enable output
|
||||
* - gpio: which gpio is used
|
||||
* - gpio_invert: invert gpio output
|
||||
* - gpio_pwm: enable pwm for output
|
||||
* - i2c:
|
||||
* - webcall_host: ip to smart plug (tasmota e.g.)
|
||||
* - webcall_path_on: GET request path to turn ON
|
||||
* - webcall_path_off: GET request path to turn OFF
|
||||
|
||||
*
|
||||
*/
|
||||
byte type[Max_Outputs];
|
||||
byte device[Max_Outputs];
|
||||
char name[Max_Outputs][32];
|
||||
bool enabled[Max_Outputs];
|
||||
byte gpio[Max_Outputs];
|
||||
bool gpio_pwm[Max_Outputs];
|
||||
bool gpio_invert[Max_Outputs];
|
||||
char i2c[Max_Outputs][8];
|
||||
char webcall_host[Max_Outputs][32];
|
||||
char webcall_path_on[Max_Outputs][32];
|
||||
char webcall_path_off[Max_Outputs][32];
|
||||
};
|
||||
|
||||
/* main System struct */
|
||||
struct Config_System {
|
||||
byte ntpOffset;
|
||||
unsigned short maintenanceDuration;
|
||||
char esp32camIp[16];
|
||||
char httpUser[32];
|
||||
char httpPass[32];
|
||||
bool httpLogSerial;
|
||||
unsigned short schedulerInterval = 1000;
|
||||
Config_System_Output output;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Config Grow
|
||||
*/
|
||||
struct Config_Grow {
|
||||
char growName[64] = "CanGrow";
|
||||
unsigned short dayOfGrow;
|
||||
byte daysSeed;
|
||||
byte daysVeg;
|
||||
byte daysBloom;
|
||||
byte lightHoursVeg;
|
||||
byte lightHoursBloom;
|
||||
byte sunriseHour;
|
||||
byte sunriseMinute;
|
||||
bool sunFade;
|
||||
byte sunFadeDuration;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* main Config struct
|
||||
*/
|
||||
struct Config {
|
||||
char test[16] = "123";
|
||||
Config_WiFi wifi;
|
||||
Config_System system;
|
||||
Config_Grow grow;
|
||||
|
||||
|
||||
};
|
||||
|
||||
Config config;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
* Global Runtime variables
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// do we need a restart? (e.g. after wifi settings change)
|
||||
bool needRestart = false;
|
||||
// this triggers Restart() from the main loop
|
||||
bool doRestart = false;
|
||||
// previous value of millis within the scheduler loop
|
||||
unsigned long schedulerPrevMillis = 0;
|
114
Arduino/CanGrow/include/CanGrow_Core.h
Normal file
114
Arduino/CanGrow/include/CanGrow_Core.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_Core.h - core stuff header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// blink fast with the built in LED in an infinite loop
|
||||
void Restart() {
|
||||
Serial.println(":: [Core:Restart] got triggered, restarting in 2 seconds");
|
||||
byte i = 0;
|
||||
while(i <= 16) {
|
||||
if(i % 2) {
|
||||
digitalWrite(PinWIPE, 1 - PinWIPE_default);
|
||||
|
||||
} else {
|
||||
digitalWrite(PinWIPE, PinWIPE_default);
|
||||
|
||||
}
|
||||
i++;
|
||||
delay(125);
|
||||
}
|
||||
ESP.restart();
|
||||
|
||||
}
|
||||
|
||||
|
||||
// IP2Char helper function to convert ip arrarys to char arrays
|
||||
char* IP2Char(IPAddress ipaddr){
|
||||
// https://forum.arduino.cc/t/trouble-returning-char-array-string/473246/6
|
||||
static char buffer[18];
|
||||
sprintf(buffer, "%d.%d.%d.%d", ipaddr[0], ipaddr[1], ipaddr[2], ipaddr[3] );
|
||||
return buffer;
|
||||
}
|
||||
|
||||
byte Give_Free_OutputId() {
|
||||
byte outputId_free;
|
||||
for(byte i=0; i < Max_Outputs; i++) {
|
||||
if(config.system.output.type[i] > 0) {
|
||||
// here i define that 255 stands for "no more free outputs"
|
||||
outputId_free = 255;
|
||||
} else {
|
||||
outputId_free = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#ifndef DEBUG
|
||||
Serial.printf("DB [Core:Give_Free_OutputId] next free output id: %d\n", outputId_free);
|
||||
#endif
|
||||
return outputId_free;
|
||||
}
|
||||
|
||||
//bool Check_OutputId_Used(byte outputId) {
|
||||
|
||||
//}
|
||||
|
||||
// checks if GPIO is already in use by output or sensor
|
||||
bool Check_GPIOindex_Used(byte gpio) {
|
||||
bool used;
|
||||
#ifndef DEBUG
|
||||
Serial.printf("DB [Core:Check_GPIOindex_Used] check GPIO: %d\n", gpio);
|
||||
#endif
|
||||
// go through each outputid
|
||||
for(byte i=0; i < Max_Outputs; i++) {
|
||||
#ifndef DEBUG
|
||||
//Serial.printf("DB [Core:Check_GPIOindex_Used] OutputId: %d , type: %d\n", i, config.system.output.type[i]);
|
||||
#endif
|
||||
// check if output type is gpio
|
||||
if(config.system.output.type[i] == OUTPUT_TYPE_GPIO) {
|
||||
#ifndef DEBUG
|
||||
Serial.printf("DB [Core:Check_GPIOindex_Used] OutputId: %d is GPIO (type %d)\n", i, config.system.output.type[i]);
|
||||
#endif
|
||||
// check if gpio id is already in use
|
||||
if(config.system.output.gpio[i] == gpio) {
|
||||
#ifndef DEBUG
|
||||
Serial.printf("DB [Core:Check_GPIOindex_Used] output.gpio[%d](%d) == GPIO %d\n", i, config.system.output.gpio[i], gpio);
|
||||
#endif
|
||||
used = true;
|
||||
break;
|
||||
} else {
|
||||
used = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifndef DEBUG
|
||||
Serial.printf("DB [Core:Check_GPIOindex_Used] GPIO: %d, used: %d\n", gpio, used);
|
||||
#endif
|
||||
// check sensors
|
||||
|
||||
return used;
|
||||
}
|
87
Arduino/CanGrow/include/CanGrow_ESP32.h
Normal file
87
Arduino/CanGrow/include/CanGrow_ESP32.h
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_ESP32.h - ESP32 specific header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
#ifdef ESP32
|
||||
|
||||
#define PinWIPE 2
|
||||
#define PinWIPE_default LOW
|
||||
#define Pin_I2C_SCL = 22
|
||||
#define Pin_I2C_SDA = 21
|
||||
|
||||
/* https://randomnerdtutorials.com/esp32-pinout-reference-gpios/
|
||||
*
|
||||
* free usable pins
|
||||
* - GPIO 0 PU OK outputs PWM signal at boot, must be LOW to enter flashing mode
|
||||
* - GPIO 4 OK OK
|
||||
* - GPIO 5 OK OK outputs PWM signal at boot, strapping pin
|
||||
* - GPIO 12 OK OK boot fails if pulled high, strapping pin
|
||||
* - GPIO 13 OK OK
|
||||
* - GPIO 14 OK OK outputs PWM signal at boot
|
||||
* - GPIO 15 OK OK outputs PWM signal at boot, strapping pin
|
||||
* - GPIO 16 OK OK
|
||||
* - GPIO 17 OK OK
|
||||
* - GPIO 18 OK OK
|
||||
* - GPIO 19 OK OK
|
||||
* - GPIO 23 OK OK
|
||||
* - GPIO 25 OK OK
|
||||
* - GPIO 26 OK OK
|
||||
* - GPIO 27 OK OK
|
||||
* - GPIO 32 OK OK
|
||||
* - GPIO 33 OK OK
|
||||
* - GPIO 34 OK input only
|
||||
* - GPIO 35 OK input only
|
||||
* - GPIO 36 OK input only
|
||||
* - GPIO 39 OK input only
|
||||
*/
|
||||
|
||||
|
||||
//
|
||||
const byte GPIOindex_length = 21;
|
||||
// initialize pinIndex with all usable GPIOs
|
||||
GPIO_Index GPIOindex[] = { { 0, FLASHMODE_LOW },
|
||||
{ 4 },
|
||||
{ 5 },
|
||||
{ 12, BOOTFAILS_HIGH },
|
||||
{ 13 },
|
||||
{ 14 },
|
||||
{ 15 },
|
||||
{ 16 },
|
||||
{ 17 },
|
||||
{ 18 },
|
||||
{ 19 },
|
||||
{ 23 },
|
||||
{ 25 },
|
||||
{ 26 },
|
||||
{ 27 },
|
||||
{ 32 },
|
||||
{ 33 },
|
||||
{ 34, INPUT_ONLY },
|
||||
{ 35, INPUT_ONLY },
|
||||
{ 36, INPUT_ONLY },
|
||||
{ 39, INPUT_ONLY } };
|
||||
#endif
|
57
Arduino/CanGrow/include/CanGrow_ESP8266.h
Normal file
57
Arduino/CanGrow/include/CanGrow_ESP8266.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_ESP8266.h - ESP8266 specific header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
#ifdef ESP8266
|
||||
|
||||
// GPIO 2 Boot fails if pulled to LOW
|
||||
#define PinWIPE 2
|
||||
#define PinWIPE_default HIGH
|
||||
#define Pin_I2C_SCL = 5
|
||||
#define Pin_I2C_SDA = 4
|
||||
|
||||
/* https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/
|
||||
*
|
||||
* free usable pins
|
||||
* - GPIO 0 / D3 boot fails if pulled LOW
|
||||
* - GPIO 12 / D6
|
||||
* - GPIO 13 / D7
|
||||
* - GPIO 14 / D5
|
||||
* - GPIO 15 / D8 Boot fails if pulled HIGH
|
||||
* - GPIO 16 / D0
|
||||
*/
|
||||
|
||||
const byte GPIOindex_length = 6;
|
||||
// initialize pinIndex with all usable GPIOs
|
||||
GPIO_Index GPIOindex[] = { { 0, BOOTFAILS_LOW },
|
||||
{ 12 },
|
||||
{ 13 },
|
||||
{ 14 },
|
||||
{ 15, BOOTFAILS_HIGH },
|
||||
{ 16, NO_PWM } };
|
||||
|
||||
#endif
|
583
Arduino/CanGrow/include/CanGrow_LittleFS.h
Normal file
583
Arduino/CanGrow/include/CanGrow_LittleFS.h
Normal file
|
@ -0,0 +1,583 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_LittleFS.h - LittleFS handling header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#define CANGROW_CFG "/config.json"
|
||||
|
||||
// LittleFS auto format
|
||||
#define FORMAT_LITTLEFS_IF_FAILED true
|
||||
|
||||
void LFS_Init() {
|
||||
Serial.println(":: [LittleFS] initializing");
|
||||
// ESP8266 crashes with first argument set
|
||||
#ifdef ESP8266
|
||||
if(!LittleFS.begin()) {
|
||||
#endif
|
||||
// ESP32 works, do autoformat if mount fails
|
||||
#ifdef ESP32
|
||||
if(!LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED)) {
|
||||
#endif
|
||||
Serial.println("!! [LittleFS] FAILED initializing. You have to format LittleFS manually. Will now restart.");
|
||||
Restart();
|
||||
}
|
||||
}
|
||||
|
||||
void LFS_Format() {
|
||||
Serial.println(":: [LittleFS] formatting...");
|
||||
// ESP32 LittleFS needs begin() first, otherwise it would crash
|
||||
// ESP8266 does not need it, so we leave it
|
||||
#ifdef ESP32
|
||||
LittleFS.begin();
|
||||
#endif
|
||||
if(LittleFS.format()) {
|
||||
Serial.println(":: [LittleFS] done formatting");
|
||||
} else {
|
||||
Serial.println("!! [LittleFS] FAILED formatting");
|
||||
}
|
||||
}
|
||||
|
||||
bool existFile(const char *path) {
|
||||
#ifdef ESP8266
|
||||
File file = LittleFS.open(path, "r");
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(path);
|
||||
#endif
|
||||
|
||||
if (!file) {
|
||||
Serial.printf(":: [LittleFS] file does not exist: %s\n", path);
|
||||
file.close();
|
||||
return false;
|
||||
} else {
|
||||
Serial.printf(":: [LittleFS] file does exist: %s\n", path);
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void readFile(const char *path) {
|
||||
#ifdef ESP8266
|
||||
File file = LittleFS.open(path, "r");
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(path);
|
||||
#endif
|
||||
|
||||
if (!file) {
|
||||
Serial.printf(":: [LittleFS] FAILED to open file for reading: %s\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.printf(":: [LittleFS] file content: %s\n", path);
|
||||
Serial.println("----");
|
||||
while (file.available()) { Serial.write(file.read()); }
|
||||
Serial.println("\n----");
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
void writeFile(const char *path, const char *message) {
|
||||
#ifdef ESP8266
|
||||
File file = LittleFS.open(path, "w");
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(path, FILE_WRITE);
|
||||
#endif
|
||||
|
||||
if (!file) {
|
||||
Serial.printf(":: [LittleFS] FAILED to open file for writing: %s\n", path);
|
||||
return;
|
||||
}
|
||||
if (file.print(message)) {
|
||||
Serial.printf(":: [LittleFS] file written: %s\n", path);
|
||||
} else {
|
||||
Serial.printf(":: [LittleFS] writing file FAILED: %s\n", path);
|
||||
}
|
||||
//delay(2000); // Make sure the CREATE and LASTWRITE times are different
|
||||
file.close();
|
||||
}
|
||||
|
||||
void deleteFile(const char *path) {
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(path, FILE_WRITE);
|
||||
#endif
|
||||
|
||||
Serial.printf(":: [LittleFS] deleting file: %s\n", path);
|
||||
#ifdef ESP8266
|
||||
if (LittleFS.remove(path)) {
|
||||
#endif
|
||||
#ifdef ESP32
|
||||
if (fs.remove(path)) {
|
||||
#endif
|
||||
Serial.printf(":: [LittleFS] deleted file: %s\n", path);
|
||||
} else {
|
||||
Serial.printf(":: [LittleFS] deleting file FAILED: %s\n", path);
|
||||
}
|
||||
}
|
||||
|
||||
// https://arduinojson.org/v7/example/config/
|
||||
// https://arduinojson.org/v7/assistant/
|
||||
bool LoadConfig() {
|
||||
#ifdef ESP8266
|
||||
File file = LittleFS.open(CANGROW_CFG, "r");
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(CANGROW_CFG);
|
||||
#endif
|
||||
|
||||
Serial.printf(":: [LittleFS:LoadConfig] loading config from: %s\n", CANGROW_CFG);
|
||||
|
||||
JsonDocument doc;
|
||||
// Deserialize the JSON document
|
||||
DeserializationError error = deserializeJson(doc, file);
|
||||
if(error) {
|
||||
Serial.printf(":: [LittleFS:LoadConfig] FAILED to load config: %s\n", CANGROW_CFG);
|
||||
if (existFile(CANGROW_CFG)) {
|
||||
readFile(CANGROW_CFG);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* put json values into config structs
|
||||
*/
|
||||
|
||||
// Copy strings from the JsonDocument to the Config struct as char
|
||||
strlcpy(config.test, doc["test"], sizeof(config.test));
|
||||
|
||||
/* WiFi */
|
||||
JsonObject objWifi = doc["wifi"][0];
|
||||
strlcpy(config.wifi.ssid, objWifi["ssid"], sizeof(config.wifi.ssid));
|
||||
strlcpy(config.wifi.password, objWifi["password"], sizeof(config.wifi.password));
|
||||
// Copy bool / int directly into struct
|
||||
config.wifi.dhcp = objWifi["dhcp"];
|
||||
// load the ip addresses as array
|
||||
|
||||
for(byte i=0; i < 4 ; i++) {
|
||||
config.wifi.ip[i] = objWifi["ip"][i];
|
||||
config.wifi.netmask[i] = objWifi["netmask"][i];
|
||||
config.wifi.gateway[i] = objWifi["gateway"][i];
|
||||
config.wifi.dns[i] = objWifi["dns"][i];
|
||||
}
|
||||
|
||||
|
||||
/* System */
|
||||
JsonObject objSystem = doc["system"][0];
|
||||
config.system.ntpOffset = objSystem["ntpOffset"];
|
||||
config.system.maintenanceDuration = objSystem["maintenanceDuration"];
|
||||
strlcpy(config.system.esp32camIp, objSystem["esp32camIp"], sizeof(config.system.esp32camIp));
|
||||
strlcpy(config.system.httpUser, objSystem["httpUser"], sizeof(config.system.httpUser));
|
||||
strlcpy(config.system.httpPass, objSystem["httpPass"], sizeof(config.system.httpPass));
|
||||
config.system.httpLogSerial = objSystem["httpLogSerial"];
|
||||
config.system.schedulerInterval = objSystem["schedulerInterval"];
|
||||
|
||||
JsonObject objSystemOutput = objSystem["output"][0];
|
||||
|
||||
/* System Outputs */
|
||||
for(byte i=0; i < Max_Outputs; i++) {
|
||||
if(objSystemOutput["type"][i] > 0) {
|
||||
config.system.output.type[i] = objSystemOutput["type"][i];
|
||||
config.system.output.device[i] = objSystemOutput["device"][i];
|
||||
strlcpy(config.system.output.name[i], objSystemOutput["name"][i], sizeof(config.system.output.name[i]));
|
||||
config.system.output.enabled[i] = objSystemOutput["enabled"][i];
|
||||
// gpio
|
||||
config.system.output.gpio[i] = objSystemOutput["gpio"][i];
|
||||
config.system.output.gpio_invert[i] = objSystemOutput["gpio_invert"][i];
|
||||
config.system.output.gpio_pwm[i] = objSystemOutput["gpio_pwm"][i];
|
||||
// i2c
|
||||
strlcpy(config.system.output.i2c[i], objSystemOutput["i2c"][i], sizeof(config.system.output.i2c[i]));
|
||||
// web
|
||||
strlcpy(config.system.output.webcall_host[i], objSystemOutput["webcall_host"][i], sizeof(config.system.output.webcall_host[i]));
|
||||
strlcpy(config.system.output.webcall_path_on[i], objSystemOutput["webcall_path_on"][i], sizeof(config.system.output.webcall_path_on[i]));
|
||||
strlcpy(config.system.output.webcall_path_off[i], objSystemOutput["webcall_path_off"][i], sizeof(config.system.output.webcall_path_off[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/* Grow */
|
||||
JsonObject objGrow = doc["grow"][0];
|
||||
strlcpy(config.grow.growName, objGrow["growName"], sizeof(config.grow.growName));
|
||||
config.grow.dayOfGrow = objGrow["dayOfGrow"];
|
||||
config.grow.daysSeed = objGrow["daysSeed"];
|
||||
config.grow.daysVeg = objGrow["daysVeg"];
|
||||
config.grow.daysBloom = objGrow["daysBloom"];
|
||||
config.grow.lightHoursVeg = objGrow["lightHoursVeg"];
|
||||
config.grow.lightHoursBloom = objGrow["lightHoursBloom"];
|
||||
config.grow.sunriseHour = objGrow["sunriseHour"];
|
||||
config.grow.sunriseMinute = objGrow["sunriseMinute"];
|
||||
config.grow.sunFade = objGrow["sunFade"];
|
||||
config.grow.sunFadeDuration = objGrow["sunFadeDuration"];
|
||||
|
||||
|
||||
|
||||
// Close the file (Curiously, File's destructor doesn't close the file)
|
||||
file.close();
|
||||
Serial.println(":: [LittleFS:LoadConfig] config successfully loaded");
|
||||
Serial.println(":: [LittleFS:LoadConfig] --- runtime config ---");
|
||||
serializeJsonPretty(doc, Serial);
|
||||
Serial.println("");
|
||||
Serial.println(":: [LittleFS:LoadConfig] ----------------------");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SaveConfig(bool writeToSerial = false) {
|
||||
/*
|
||||
* Building config.json here
|
||||
*/
|
||||
JsonDocument doc;
|
||||
|
||||
/* Root */
|
||||
doc["test"] = config.test;
|
||||
|
||||
/* WiFi */
|
||||
JsonObject objWifi = doc["wifi"].add<JsonObject>();
|
||||
objWifi["ssid"] = config.wifi.ssid;
|
||||
objWifi["password"] = config.wifi.password;
|
||||
// save the ip addressess as array
|
||||
int i;
|
||||
for(i=0; i <4 ; i++) {
|
||||
objWifi["ip"][i] = config.wifi.ip[i];
|
||||
objWifi["netmask"][i] = config.wifi.netmask[i];
|
||||
objWifi["gateway"][i] = config.wifi.gateway[i];
|
||||
objWifi["dns"][i] = config.wifi.dns[i];
|
||||
}
|
||||
objWifi["dhcp"] = config.wifi.dhcp;
|
||||
|
||||
/* System */
|
||||
JsonObject objSystem = doc["system"].add<JsonObject>();
|
||||
objSystem["ntpOffset"] = config.system.ntpOffset;
|
||||
objSystem["maintenanceDuration"] = config.system.maintenanceDuration;
|
||||
objSystem["esp32camIp"] = config.system.esp32camIp;
|
||||
objSystem["httpUser"] = config.system.httpUser;
|
||||
objSystem["httpPass"] = config.system.httpPass;
|
||||
objSystem["httpLogSerial"] = config.system.httpLogSerial;
|
||||
objSystem["schedulerInterval"] = config.system.schedulerInterval;
|
||||
|
||||
/* System Outputs */
|
||||
JsonObject objSystemOutput = objSystem["output"].add<JsonObject>();
|
||||
for(byte i=0; i < Max_Outputs; i++) {
|
||||
if(config.system.output.type[i] > 0) {
|
||||
objSystemOutput["type"][i] = config.system.output.type[i];
|
||||
objSystemOutput["device"][i] = config.system.output.device[i];
|
||||
objSystemOutput["name"][i] = config.system.output.name[i];
|
||||
objSystemOutput["enabled"][i] = config.system.output.enabled[i];
|
||||
// gpio
|
||||
objSystemOutput["gpio"][i] = config.system.output.gpio[i];
|
||||
objSystemOutput["gpio_invert"][i] = config.system.output.gpio_invert[i];
|
||||
objSystemOutput["gpio_pwm"][i] = config.system.output.gpio_pwm[i];
|
||||
// i2c
|
||||
objSystemOutput["i2c"][i] = config.system.output.i2c[i];
|
||||
// web
|
||||
objSystemOutput["webcall_host"][i] = config.system.output.webcall_host[i];
|
||||
objSystemOutput["webcall_path_on"][i] = config.system.output.webcall_path_on[i];
|
||||
objSystemOutput["webcall_path_off"][i] = config.system.output.webcall_path_off[i];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Grow */
|
||||
JsonObject objGrow = doc["grow"].add<JsonObject>();
|
||||
objGrow["growName"] = config.grow.growName;
|
||||
objGrow["dayOfGrow"] = config.grow.dayOfGrow;
|
||||
objGrow["daysSeed"] = config.grow.daysSeed;
|
||||
objGrow["daysVeg"] = config.grow.daysVeg;
|
||||
objGrow["daysBloom"] = config.grow.daysBloom;
|
||||
objGrow["lightHoursVeg"] = config.grow.lightHoursVeg;
|
||||
objGrow["lightHoursBloom"] = config.grow.lightHoursBloom;
|
||||
objGrow["sunriseHour"] = config.grow.sunriseHour;
|
||||
objGrow["sunriseMinute"] = config.grow.sunriseMinute;
|
||||
objGrow["sunFade"] = config.grow.sunFade;
|
||||
objGrow["sunFadeDuration"] = config.grow.sunFadeDuration;
|
||||
|
||||
/*
|
||||
* END Building config.json here
|
||||
*/
|
||||
|
||||
// if writeToSerial is true, output json to serial, but do not write to LittleFS
|
||||
if(writeToSerial == false) {
|
||||
#ifdef ESP8266
|
||||
File file = LittleFS.open(CANGROW_CFG, "w");
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(CANGROW_CFG, FILE_WRITE);
|
||||
#endif
|
||||
|
||||
if (!file) {
|
||||
Serial.printf("!! [LittleFS:SaveConfig] FAILED to open configfile for writing: %s\n", CANGROW_CFG);
|
||||
return false;
|
||||
} else {
|
||||
Serial.printf(":: [LittleFS:SaveConfig] opened for writing %s\n", CANGROW_CFG);
|
||||
}
|
||||
// Serialize JSON to file
|
||||
if (serializeJson(doc, file) == 0) {
|
||||
Serial.printf("!! [LittleFS:SaveConfig] FAILED to write configfile: %s\n", CANGROW_CFG);
|
||||
} else {
|
||||
Serial.printf(":: [LittleFS:SaveConfig] successfully written %s\n", CANGROW_CFG);
|
||||
}
|
||||
file.close();
|
||||
} else {
|
||||
Serial.printf(":: [LittleFS:SaveConfig] --- %s ---\n", CANGROW_CFG);
|
||||
serializeJsonPretty(doc, Serial);
|
||||
Serial.println("");
|
||||
Serial.printf(":: [LittleFS:SaveConfig] ----------------------\n", CANGROW_CFG);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
///*
|
||||
//* ESP8266 functions
|
||||
//*/
|
||||
|
||||
///*functions from https://github.com/esp8266/Arduino/blob/master/libraries/LittleFS/examples/LittleFS_Timestamp/LittleFS_Timestamp.ino*/
|
||||
//#ifdef ESP8266
|
||||
//void listDir(const char *dirname) {
|
||||
//Serial.printf("Listing directory: %s\n", dirname);
|
||||
|
||||
//Dir root = LittleFS.openDir(dirname);
|
||||
|
||||
//while (root.next()) {
|
||||
//File file = root.openFile("r");
|
||||
//Serial.print(" FILE: ");
|
||||
//Serial.print(root.fileName());
|
||||
//Serial.print(" SIZE: ");
|
||||
//Serial.print(file.size());
|
||||
//time_t cr = file.getCreationTime();
|
||||
//time_t lw = file.getLastWrite();
|
||||
//file.close();
|
||||
//struct tm *tmstruct = localtime(&cr);
|
||||
//Serial.printf(" CREATION: %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec);
|
||||
//tmstruct = localtime(&lw);
|
||||
//Serial.printf(" LAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec);
|
||||
//}
|
||||
//}
|
||||
|
||||
|
||||
//void readFile(const char *path) {
|
||||
//Serial.printf("Reading file: %s\n", path);
|
||||
|
||||
//File file = LittleFS.open(path, "r");
|
||||
//if (!file) {
|
||||
//Serial.println("Failed to open file for reading");
|
||||
//return;
|
||||
//}
|
||||
|
||||
//Serial.print("Read from file: ");
|
||||
//while (file.available()) { Serial.write(file.read()); }
|
||||
//file.close();
|
||||
//}
|
||||
|
||||
//void writeFile(const char *path, const char *message) {
|
||||
//Serial.printf("Writing file: %s\n", path);
|
||||
|
||||
//File file = LittleFS.open(path, "w");
|
||||
//if (!file) {
|
||||
//Serial.println("Failed to open file for writing");
|
||||
//return;
|
||||
//}
|
||||
//if (file.print(message)) {
|
||||
//Serial.println("File written");
|
||||
//} else {
|
||||
//Serial.println("Write failed");
|
||||
//}
|
||||
//delay(2000); // Make sure the CREATE and LASTWRITE times are different
|
||||
//file.close();
|
||||
//}
|
||||
|
||||
//void appendFile(const char *path, const char *message) {
|
||||
//Serial.printf("Appending to file: %s\n", path);
|
||||
|
||||
//File file = LittleFS.open(path, "a");
|
||||
//if (!file) {
|
||||
//Serial.println("Failed to open file for appending");
|
||||
//return;
|
||||
//}
|
||||
//if (file.print(message)) {
|
||||
//Serial.println("Message appended");
|
||||
//} else {
|
||||
//Serial.println("Append failed");
|
||||
//}
|
||||
//file.close();
|
||||
//}
|
||||
|
||||
//void renameFile(const char *path1, const char *path2) {
|
||||
//Serial.printf("Renaming file %s to %s\n", path1, path2);
|
||||
//if (LittleFS.rename(path1, path2)) {
|
||||
//Serial.println("File renamed");
|
||||
//} else {
|
||||
//Serial.println("Rename failed");
|
||||
//}
|
||||
//}
|
||||
|
||||
//void deleteFile(const char *path) {
|
||||
//Serial.printf("Deleting file: %s\n", path);
|
||||
//if (LittleFS.remove(path)) {
|
||||
//Serial.println("File deleted");
|
||||
//} else {
|
||||
//Serial.println("Delete failed");
|
||||
//}
|
||||
//}
|
||||
//#endif
|
||||
|
||||
|
||||
///*
|
||||
//* ESP32 functions
|
||||
//*/
|
||||
|
||||
///*functions from https://github.com/espressif/arduino-esp32/blob/master/libraries/LittleFS/examples/LITTLEFS_time/LITTLEFS_time.ino*/
|
||||
//#ifdef ESP32
|
||||
//void listDir(fs::FS &fs, const char *dirname, uint8_t levels) {
|
||||
//Serial.printf("Listing directory: %s\n", dirname);
|
||||
|
||||
//File root = fs.open(dirname);
|
||||
//if (!root) {
|
||||
//Serial.println("Failed to open directory");
|
||||
//return;
|
||||
//}
|
||||
//if (!root.isDirectory()) {
|
||||
//Serial.println("Not a directory");
|
||||
//return;
|
||||
//}
|
||||
|
||||
//File file = root.openNextFile();
|
||||
//while (file) {
|
||||
//if (file.isDirectory()) {
|
||||
//Serial.print(" DIR : ");
|
||||
//Serial.print(file.name());
|
||||
//time_t t = file.getLastWrite();
|
||||
//struct tm *tmstruct = localtime(&t);
|
||||
//Serial.printf(
|
||||
//" LAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour,
|
||||
//tmstruct->tm_min, tmstruct->tm_sec
|
||||
//);
|
||||
//if (levels) {
|
||||
//listDir(fs, file.path(), levels - 1);
|
||||
//}
|
||||
//} else {
|
||||
//Serial.print(" FILE: ");
|
||||
//Serial.print(file.name());
|
||||
//Serial.print(" SIZE: ");
|
||||
//Serial.print(file.size());
|
||||
//time_t t = file.getLastWrite();
|
||||
//struct tm *tmstruct = localtime(&t);
|
||||
//Serial.printf(
|
||||
//" LAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour,
|
||||
//tmstruct->tm_min, tmstruct->tm_sec
|
||||
//);
|
||||
//}
|
||||
//file = root.openNextFile();
|
||||
//}
|
||||
//}
|
||||
|
||||
//void removeDir(fs::FS &fs, const char *path) {
|
||||
//Serial.printf("Removing Dir: %s\n", path);
|
||||
//if (fs.rmdir(path)) {
|
||||
//Serial.println("Dir removed");
|
||||
//} else {
|
||||
//Serial.println("rmdir failed");
|
||||
//}
|
||||
//}
|
||||
|
||||
//void readFile(fs::FS &fs, const char *path) {
|
||||
//Serial.printf("Reading file: %s\n", path);
|
||||
|
||||
//File file = fs.open(path);
|
||||
//if (!file) {
|
||||
//Serial.println("Failed to open file for reading");
|
||||
//return;
|
||||
//}
|
||||
|
||||
//Serial.print("Read from file: ");
|
||||
//while (file.available()) {
|
||||
//Serial.write(file.read());
|
||||
//}
|
||||
//file.close();
|
||||
//}
|
||||
|
||||
//void writeFile(fs::FS &fs, const char *path, const char *message) {
|
||||
//Serial.printf("Writing file: %s\n", path);
|
||||
|
||||
//File file = fs.open(path, FILE_WRITE);
|
||||
//if (!file) {
|
||||
//Serial.println("Failed to open file for writing");
|
||||
//return;
|
||||
//}
|
||||
//if (file.print(message)) {
|
||||
//Serial.println("File written");
|
||||
//} else {
|
||||
//Serial.println("Write failed");
|
||||
//}
|
||||
//file.close();
|
||||
//}
|
||||
|
||||
//void appendFile(fs::FS &fs, const char *path, const char *message) {
|
||||
//Serial.printf("Appending to file: %s\n", path);
|
||||
|
||||
//File file = fs.open(path, FILE_APPEND);
|
||||
//if (!file) {
|
||||
//Serial.println("Failed to open file for appending");
|
||||
//return;
|
||||
//}
|
||||
//if (file.print(message)) {
|
||||
//Serial.println("Message appended");
|
||||
//} else {
|
||||
//Serial.println("Append failed");
|
||||
//}
|
||||
//file.close();
|
||||
//}
|
||||
|
||||
//void renameFile(fs::FS &fs, const char *path1, const char *path2) {
|
||||
//Serial.printf("Renaming file %s to %s\n", path1, path2);
|
||||
//if (fs.rename(path1, path2)) {
|
||||
//Serial.println("File renamed");
|
||||
//} else {
|
||||
//Serial.println("Rename failed");
|
||||
//}
|
||||
//}
|
||||
|
||||
//void deleteFile(fs::FS &fs, const char *path) {
|
||||
//Serial.printf("Deleting file: %s\n", path);
|
||||
//if (fs.remove(path)) {
|
||||
//Serial.println("File deleted");
|
||||
//} else {
|
||||
//Serial.println("Delete failed");
|
||||
//}
|
||||
//}
|
||||
//#endif
|
138
Arduino/CanGrow/include/CanGrow_Webserver.h
Normal file
138
Arduino/CanGrow/include/CanGrow_Webserver.h
Normal file
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_Webserver.h - webserver header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* include static files files
|
||||
*/
|
||||
#include "Webserver/File_cangrow_CSS.h"
|
||||
#include "Webserver/File_cangrow_JS.h"
|
||||
#include "Webserver/File_favicon_ico.h"
|
||||
|
||||
/*
|
||||
* include webpages header files
|
||||
*/
|
||||
#include "Webserver/Header.h"
|
||||
#include "Webserver/Footer.h"
|
||||
#include "Webserver/Common.h"
|
||||
|
||||
#include "Webserver/Page_root.h"
|
||||
|
||||
#include "Webserver/Page_wifi.h"
|
||||
|
||||
#include "Webserver/Page_system.h"
|
||||
|
||||
|
||||
AsyncWebServer webserver(80);
|
||||
// load requestLogger middleware
|
||||
LoggingMiddleware requestLogger;
|
||||
|
||||
|
||||
/*
|
||||
* 404 error page begins
|
||||
*/
|
||||
|
||||
// 404 page is a good page template btw
|
||||
const char* Page_404_HTML PROGMEM = R"(%HEADER%
|
||||
<div class='warnmsg'><h1>❗ ️ 404 - not found</h1></div>
|
||||
%FOOTER%)";
|
||||
|
||||
/* processor */
|
||||
String Proc_WebPage_404(const String& var) {
|
||||
return AddHeaderFooter(var);
|
||||
}
|
||||
|
||||
// https://github.com/mathieucarbou/ESPAsyncWebServer/blob/main/examples/SimpleServer/SimpleServer.ino
|
||||
void WebserverNotFound(AsyncWebServerRequest* request) {
|
||||
request->send(404, "text/html", Page_404_HTML, Proc_WebPage_404);
|
||||
}
|
||||
|
||||
/*
|
||||
* 404 error page ends
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* setup all the webhandlers
|
||||
*/
|
||||
void Webserver_Init() {
|
||||
Serial.println(":: [Webserver] initializing");
|
||||
|
||||
/* url handler definition */
|
||||
webserver.on("/", HTTP_GET, WebPage_root);
|
||||
webserver.on("/cangrow.css", HTTP_GET, WebFile_cangrow_CSS);
|
||||
webserver.on("/cangrow.js", HTTP_GET, WebFile_cangrow_JS);
|
||||
webserver.on("/favicon.ico", HTTP_GET, WebFile_favicon_ico);
|
||||
|
||||
webserver.on("/wifi/", HTTP_GET, WebPage_wifi);
|
||||
webserver.on("/wifi/", HTTP_POST, WebPage_wifi);
|
||||
webserver.on("/system/", HTTP_GET, WebPage_system);
|
||||
webserver.on("/system/", HTTP_POST, WebPage_system);
|
||||
|
||||
webserver.on("/system/update", HTTP_GET, WebPage_system_update);
|
||||
webserver.on("/system/update", HTTP_POST, WebPage_system_update, WebPage_system_update_ApplyUpdate);
|
||||
|
||||
webserver.on("/system/restart", HTTP_GET, WebPage_system_restart);
|
||||
webserver.on("/system/restart", HTTP_POST, WebPage_system_restart);
|
||||
|
||||
webserver.on("/system/wipe", HTTP_GET, WebPage_system_wipe);
|
||||
webserver.on("/system/wipe", HTTP_POST, WebPage_system_wipe);
|
||||
|
||||
webserver.on("/system/output/", HTTP_GET, WebPage_system_output);
|
||||
webserver.on("/system/output/", HTTP_POST, WebPage_system_output);
|
||||
|
||||
webserver.on("/system/output/add", HTTP_GET, WebPage_system_output_add);
|
||||
webserver.on("/system/output/add", HTTP_POST, WebPage_system_output_add);
|
||||
|
||||
requestLogger.setOutput(Serial);
|
||||
// this activates the middleware
|
||||
if(config.system.httpLogSerial == true) {
|
||||
Serial.println(":: [Webserver] serial logging: enabled");
|
||||
webserver.addMiddleware(&requestLogger);
|
||||
} else {
|
||||
Serial.println(":: [Webserver] serial logging: disabled");
|
||||
}
|
||||
|
||||
webserver.onNotFound(WebserverNotFound);
|
||||
|
||||
// Workaround, see comment at
|
||||
// https://github.com/mathieucarbou/ESPAsyncWebServer/blob/main/docs/index.md#scanning-for-available-wifi-networks
|
||||
// call the network scan once, so there are some values at the first call
|
||||
// of the wifi settings page. otherwise the first call of the wifi scan would return
|
||||
// an empty list of networks
|
||||
Serial.println(":: [Webserver] call [wifi:ScanNetworks] to workaround empty scan results bug");
|
||||
WebPage_wifi_ScanNetworks();
|
||||
|
||||
webserver.begin();
|
||||
Serial.println(":: [Webserver] ready to serve");
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
81
Arduino/CanGrow/include/CanGrow_Wifi.h
Normal file
81
Arduino/CanGrow/include/CanGrow_Wifi.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_Wifi.h - Wifi stuff header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
void Wifi_Connect() {
|
||||
Serial.printf(":: [WiFi] connecting to SSID: %s\n", config.wifi.ssid);
|
||||
WiFi.begin(config.wifi.ssid, config.wifi.password);
|
||||
if(config.wifi.dhcp == false) {
|
||||
Serial.println(":: [WiFi] using static ip configuration:");
|
||||
Serial.printf(":: [WiFi] IP : %s\n", IP2Char(config.wifi.ip));
|
||||
Serial.printf(":: [WiFi] Netmask: %s\n", IP2Char(config.wifi.netmask));
|
||||
Serial.printf(":: [WiFi] Gateway: %s\n", IP2Char(config.wifi.gateway));
|
||||
Serial.printf(":: [WiFi] DNS : %s\n", IP2Char(config.wifi.dns));
|
||||
|
||||
WiFi.config(config.wifi.ip, config.wifi.dns, config.wifi.gateway, config.wifi.netmask);
|
||||
} else {
|
||||
Serial.println(":: [WiFi] using DHCP for ip configuration");
|
||||
}
|
||||
|
||||
Serial.print(":: [WiFi] ");
|
||||
// wait until WiFi connection is established
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("CONNECTED!");
|
||||
|
||||
if(config.wifi.dhcp == true) {
|
||||
Serial.println(":: [WiFi] DHCP offered ip configuration:");
|
||||
Serial.printf(":: [WiFi] IP : %s\n", IP2Char(WiFi.localIP()));
|
||||
Serial.printf(":: [WiFi] Netmask: %s\n", IP2Char(WiFi.subnetMask()));
|
||||
Serial.printf(":: [WiFi] Gateway: %s\n", IP2Char(WiFi.gatewayIP()));
|
||||
Serial.printf(":: [WiFi] DNS : %s\n", IP2Char(WiFi.dnsIP()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Wifi_AP() {
|
||||
Serial.printf(":: [WiFi] create access point: %s\n", CANGROW_SSID);
|
||||
WiFi.softAPConfig(config.wifi.ip, config.wifi.gateway, config.wifi.netmask);
|
||||
WiFi.softAP(CANGROW_SSID);
|
||||
Serial.println(":: [WiFi] access point started:");
|
||||
Serial.printf(":: [WiFi] IP : %s\n", IP2Char(config.wifi.ip));
|
||||
Serial.printf(":: [WiFi] Netmask: %s\n", IP2Char(config.wifi.netmask));
|
||||
}
|
||||
|
||||
void Wifi_Init() {
|
||||
Serial.println(":: [WiFi] initializing");
|
||||
if(strlen(config.wifi.ssid) == 0) {
|
||||
Serial.println(":: [WiFi] config.wifi.ssid is unset");
|
||||
Wifi_AP();
|
||||
} else {
|
||||
Wifi_Connect();
|
||||
}
|
||||
}
|
169
Arduino/CanGrow/include/Webserver/Common.h
Normal file
169
Arduino/CanGrow/include/Webserver/Common.h
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Common.h - header file with common webserver functions
|
||||
* HTML header or footer to a String()
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Common_HTML.h"
|
||||
|
||||
/*
|
||||
* TestHeaderFooter - checks if the given var from the webserver processor
|
||||
* is actual a template variable from header or footer.
|
||||
*/
|
||||
bool TestHeaderFooter(const String& var) {
|
||||
#ifdef DEBUG
|
||||
Serial.print(":: [Webserver:Page:root:proc:hf] var: ");
|
||||
Serial.println(var);
|
||||
#endif
|
||||
|
||||
if(
|
||||
(var == "HEADER") ||
|
||||
(var == "FOOTER") ||
|
||||
(var == "CGVER") ||
|
||||
(var == "CGBUILD") ||
|
||||
(var == "GROWNAME") ||
|
||||
(var == "CANGROW_CSS") ||
|
||||
(var == "NEED_RESTART") ||
|
||||
(var == "ACTIVE_NAV_GROW") ||
|
||||
(var == "ACTIVE_NAV_SYSTEM") ||
|
||||
(var == "ACTIVE_NAV_WIFI") ||
|
||||
(var == "ACTIVE_NAV_HELP") ||
|
||||
(var == "PLACEHOLDER")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AddHeaderFooter - processor for header and footer template variables
|
||||
*
|
||||
* String& var:
|
||||
* the string we receive from the processor is the actual
|
||||
* variable name we replace here.
|
||||
* byte activeNav:
|
||||
* contains the number representing which page is active
|
||||
* 1 - grow settings
|
||||
* 2 - system settings
|
||||
* 3 - wifi settings
|
||||
* 4 - help page
|
||||
*/
|
||||
String AddHeaderFooter(const String& var, byte activeNav = 0) {
|
||||
String activeNav_ClassName = "activeNav";
|
||||
if(var == "HEADER") {
|
||||
return String(Header_HTML);
|
||||
} else if(var == "FOOTER") {
|
||||
return String(Footer_HTML);
|
||||
} else if(var == "CGVER") {
|
||||
return String(CANGROW_VER);
|
||||
} else if(var == "CGBUILD") {
|
||||
return String(CANGROW_BUILD);
|
||||
} else if(var == "GROWNAME") {
|
||||
return String(config.grow.growName);
|
||||
} else if(var == "CANGROW_CSS") {
|
||||
return String(File_cangrow_CSS);
|
||||
} else if((var == "ACTIVE_NAV_GROW") && (activeNav == 1)) {
|
||||
return activeNav_ClassName;
|
||||
} else if((var == "ACTIVE_NAV_SYSTEM") && (activeNav == 2)) {
|
||||
return activeNav_ClassName;
|
||||
} else if((var == "ACTIVE_NAV_WIFI") && (activeNav == 3)) {
|
||||
return activeNav_ClassName;
|
||||
} else if((var == "ACTIVE_NAV_HELP") && (activeNav == 4)) {
|
||||
return activeNav_ClassName;
|
||||
} else if(var == "NEED_RESTART") {
|
||||
if(needRestart == true) {
|
||||
return String(Common_HTML_NEED_RESTART);
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Html_SelectOpt_GPIOindex
|
||||
*
|
||||
* returns <option> list as string with available gpios
|
||||
*/
|
||||
|
||||
String Html_SelectOpt_GPIOindex(byte selectId = 255) {
|
||||
|
||||
String gpioIndex_html;
|
||||
// iterate through through all available GPIOs in index
|
||||
for(byte i = 0; i < GPIOindex_length; i++) {
|
||||
bool gpioUsed = Check_GPIOindex_Used(i);
|
||||
|
||||
gpioIndex_html += "<option value='";
|
||||
gpioIndex_html += i;
|
||||
gpioIndex_html += "'";
|
||||
// set disabled option for gpio which are already in use or incompatible
|
||||
if( (((gpioUsed == true) && (i != selectId)) || (GPIOindex[i].note == INPUT_ONLY)) ) {
|
||||
gpioIndex_html += " disabled";
|
||||
}
|
||||
|
||||
if(i == selectId) {
|
||||
gpioIndex_html += " selected";
|
||||
}
|
||||
gpioIndex_html += ">GPIO ";
|
||||
gpioIndex_html += GPIOindex[i].gpio;
|
||||
//add gpio note if there is some
|
||||
//if(GPIOindex[i].note > 0) {
|
||||
gpioIndex_html += " ";
|
||||
gpioIndex_html += GPIO_Index_note_descr[GPIOindex[i].note];
|
||||
|
||||
// disable output incompatible gpio
|
||||
if(GPIOindex[i].note == INPUT_ONLY) {
|
||||
gpioIndex_html += " (N/A)";
|
||||
// add USED if gpio is already in use
|
||||
} else if((gpioUsed == true) && (i != selectId)) {
|
||||
gpioIndex_html += " (used)";
|
||||
}
|
||||
gpioIndex_html += "</option>";
|
||||
}
|
||||
return gpioIndex_html;
|
||||
}
|
||||
|
||||
|
||||
String Html_SelectOpt_bool(byte selectVal = 255, String trueStr = "Yes", String falseStr = "No") {
|
||||
String html;
|
||||
html += "<option value='1'";
|
||||
html += (selectVal == true) ? " selected" : "";
|
||||
html += ">";
|
||||
html += trueStr;
|
||||
html += "</option>";
|
||||
|
||||
html += "<option value='0'";
|
||||
html += (selectVal == false) ? " selected" : "";
|
||||
html += ">";
|
||||
html += falseStr;
|
||||
html += "</option>";
|
||||
return html;
|
||||
}
|
47
Arduino/CanGrow/include/Webserver/Common_HTML.h
Normal file
47
Arduino/CanGrow/include/Webserver/Common_HTML.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Common_HTML.h - header file with common HTML snippets
|
||||
* HTML header or footer to a String()
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// double div to force a linebreak. infomsg , warnmsg are inline-block
|
||||
const char Common_HTML_SAVE_MSG[] PROGMEM = R"EOF(
|
||||
<div><div class='infomsg'>✅ Successfully saved!</div></div>
|
||||
)EOF";
|
||||
|
||||
const char Common_HTML_SAVE_MSG_ERR[] PROGMEM = R"EOF(
|
||||
<div><div class='infomsg'>!! ERROR saving!</div></div>
|
||||
)EOF";
|
||||
|
||||
const char Common_HTML_NEED_RESTART[] PROGMEM = R"EOF(
|
||||
<div><div class='warnmsg'>❗ Restart is required to apply new settings!
|
||||
<form action='/system/restart' method='post'><input type='hidden' name='confirmed' value='true' />
|
||||
<input type='submit' value='Restart now' />
|
||||
</form>
|
||||
</div></div>
|
||||
)EOF";
|
247
Arduino/CanGrow/include/Webserver/File_cangrow_CSS.h
Normal file
247
Arduino/CanGrow/include/Webserver/File_cangrow_CSS.h
Normal file
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/File_cangrow_CSS.h - /cangrow.css header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
const char* File_cangrow_CSS PROGMEM = R"(body {
|
||||
color: #cae0d0;
|
||||
background-color: #1d211e;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer {
|
||||
color: #343B35;
|
||||
}
|
||||
|
||||
.center {
|
||||
/*width: 100; */
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.centered {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
border-bottom: 1px solid #262B27;
|
||||
}
|
||||
|
||||
a:link, a:visited {
|
||||
color: #04AA6D;
|
||||
}
|
||||
a:hover {
|
||||
color: #64AA6D;
|
||||
}
|
||||
a:active {
|
||||
color: #04AA6D;
|
||||
}
|
||||
.infomsg , .warnmsg {
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
padding: 4px;
|
||||
/*width: fit-content; min-width: 200px; max-width: 420px;*/
|
||||
display: inline-block;
|
||||
margin: auto;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
/*text-align: center;*/
|
||||
text-decoration: none;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.infomsg {
|
||||
background: #04AA6D;
|
||||
}
|
||||
.warnmsg {
|
||||
background: #aa4204;
|
||||
}
|
||||
.inputShort {
|
||||
width: 42px;
|
||||
}
|
||||
|
||||
.helpbox {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.nav {
|
||||
background: #333;
|
||||
/*width: 100; */
|
||||
margin: auto;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.subnav {
|
||||
/*text-align: center;*/
|
||||
margin: auto;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.subnavTitle {
|
||||
font-size: 1em;
|
||||
/*font-weight: bold;*/
|
||||
margin-top: -10px;
|
||||
margin-bottom: 10px;
|
||||
/*text-align: center;*/
|
||||
}
|
||||
.nav li {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.subnav li {
|
||||
background: #262B27;
|
||||
list-style: none;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 3px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.nav li:first-of-type {
|
||||
background: #026b45;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
.nav li a, .nav span, .subnav li a, .subnav span, .button, .button:link, input[type=button], input[type=submit],
|
||||
input[type=reset], .linkForm input[type=submit] {
|
||||
color: #ddd;
|
||||
display: block;
|
||||
font-family: 'Lucida Sans Unicode', 'Lucida Grande', sans-serif;
|
||||
font-size:0.8em;
|
||||
padding: 10px 20px;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.subnav li a, .subnav span {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.nav li a:hover, .subnav li a:hover, .activeNav, .button:link:hover, .button:visited:hover, input[type=button]:hover,
|
||||
input[type=submit]:hover, input[type=reset]:hover, .linkForm input[type=submit]:hover {
|
||||
background: #04AA6D;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.nav li a:active, .subnav li a:active {
|
||||
background: #026b45;
|
||||
color: #cae0d0;
|
||||
}
|
||||
|
||||
.activeNav {
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.navTime {
|
||||
background: #292929;
|
||||
}
|
||||
|
||||
.button, .button:link, .button:visited, input[type=button], input[type=submit],input[type=reset],
|
||||
.linkForm input[type=submit] {
|
||||
background: #026b45;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
padding: 6px 12px;
|
||||
/*text-align: center;*/
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.button:link:active, .button:visited:active, input[type=button]:active, input[type=submit]:active,
|
||||
input[type=reset]:active, .linkForm input[type=submit]:active {
|
||||
background: #026b45;
|
||||
color: #cae0d0;
|
||||
}
|
||||
|
||||
input[type=text], input[type=date], input[type=number], input[type=password], select {
|
||||
background: #cae0d0;
|
||||
color: #1d211e;
|
||||
border: 1px solid #026b45;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.linkForm {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.linkForm input[type=submit] {
|
||||
background: #262B27;
|
||||
padding: 5px;
|
||||
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.visible {
|
||||
display: inline;
|
||||
/*justify-content: center!important;*/
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1820px) {
|
||||
/*.center, .nav {
|
||||
width: 60; min-width: 420px;
|
||||
}*/
|
||||
.subnav li {
|
||||
display: '';
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
/*@media only screen and (min-width: 640px) {
|
||||
|
||||
}*/)";
|
||||
|
||||
void WebFile_cangrow_CSS(AsyncWebServerRequest *request) {
|
||||
request->send_P(200, "text/css", File_cangrow_CSS);
|
||||
}
|
69
Arduino/CanGrow/include/Webserver/File_cangrow_JS.h
Normal file
69
Arduino/CanGrow/include/Webserver/File_cangrow_JS.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/File_cangrow_JS.h - /cangrow.js header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
const char* File_cangrow_JS PROGMEM = R"(function toggleDisplay(id) {
|
||||
let el = document.getElementById(id);
|
||||
let el_cs = getComputedStyle(el);
|
||||
|
||||
if (el_cs.getPropertyValue('display') === 'none') {
|
||||
el.style.display = 'inline';
|
||||
} else {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function hideAllClass(classname) {
|
||||
|
||||
const el = document.getElementsByClassName(classname);
|
||||
|
||||
for(let i = 0; i < el.length ; i++) {
|
||||
el[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function showSelect(selectId, prefix, hideClass = '') {
|
||||
if(hideClass != '') {
|
||||
hideAllClass(hideClass);
|
||||
}
|
||||
|
||||
let selVal = document.getElementById(selectId).value;
|
||||
toggleId = prefix + selVal;
|
||||
if(document.getElementById(toggleId) !== null ) {
|
||||
toggleDisplay(toggleId);
|
||||
}
|
||||
}
|
||||
|
||||
function confirmDelete(name) {
|
||||
return confirm('Delete ' + name + '?');
|
||||
})";
|
||||
|
||||
void WebFile_cangrow_JS(AsyncWebServerRequest *request) {
|
||||
request->send_P(200, "text/javascript", File_cangrow_JS);
|
||||
}
|
37
Arduino/CanGrow/include/Webserver/File_favicon_ico.h
Normal file
37
Arduino/CanGrow/include/Webserver/File_favicon_ico.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
unsigned char File_favicon_ico_gz[] = {
|
||||
0x1f, 0x8b, 0x08, 0x08, 0x11, 0x71, 0x19, 0x67, 0x00, 0x03, 0x43, 0x61,
|
||||
0x6e, 0x47, 0x72, 0x6f, 0x77, 0x5f, 0x66, 0x61, 0x76, 0x69, 0x63, 0x6f,
|
||||
0x2e, 0x69, 0x63, 0x6f, 0x00, 0xed, 0x94, 0x49, 0x4b, 0xc3, 0x40, 0x18,
|
||||
0x86, 0xdf, 0xd8, 0xc5, 0xaa, 0xe9, 0x12, 0xa7, 0xcd, 0xd2, 0x26, 0x99,
|
||||
0x7c, 0x89, 0x76, 0x45, 0xb4, 0x2a, 0xb6, 0xa2, 0x42, 0xb1, 0x52, 0x73,
|
||||
0x11, 0xd4, 0x83, 0xdb, 0xc1, 0x8b, 0x08, 0x75, 0xf9, 0xff, 0x67, 0xbf,
|
||||
0x49, 0x3c, 0x58, 0xa4, 0x17, 0xc1, 0x5b, 0x9e, 0xe4, 0x1d, 0xe6, 0xf9,
|
||||
0x86, 0x61, 0x32, 0x03, 0x19, 0x40, 0xe3, 0xa7, 0x56, 0x03, 0xb7, 0x25,
|
||||
0xcc, 0x0b, 0x80, 0x09, 0xa0, 0xcb, 0xe1, 0x12, 0x02, 0xa4, 0xf5, 0x65,
|
||||
0x44, 0xed, 0x08, 0x51, 0x27, 0xc2, 0x56, 0x2f, 0xc2, 0x76, 0x9f, 0x33,
|
||||
0x88, 0xd0, 0xde, 0x09, 0xd1, 0xd9, 0x0b, 0xd1, 0x1d, 0x86, 0xe8, 0x1d,
|
||||
0x86, 0xe8, 0x8f, 0x08, 0x83, 0x31, 0x61, 0x77, 0x12, 0x60, 0x38, 0x0d,
|
||||
0xb0, 0x3f, 0x93, 0x38, 0x88, 0x25, 0x8e, 0xae, 0x7c, 0x8c, 0xae, 0x7d,
|
||||
0x8c, 0x6f, 0x3c, 0x8e, 0x8f, 0xe3, 0x5b, 0x0f, 0x27, 0x77, 0x2e, 0x4e,
|
||||
0xef, 0x39, 0x0f, 0x2e, 0xce, 0x1e, 0x39, 0x4f, 0x2d, 0x4c, 0x9e, 0x9b,
|
||||
0x38, 0x7f, 0x71, 0x30, 0xe5, 0x5c, 0xbc, 0xda, 0x98, 0xcd, 0x2d, 0xcc,
|
||||
0xde, 0x2c, 0x5c, 0xbe, 0x5b, 0x88, 0x3f, 0x4c, 0xc4, 0x9f, 0xe6, 0xd2,
|
||||
0xef, 0xcb, 0xc8, 0xc8, 0xf8, 0x7f, 0x7e, 0xfc, 0x81, 0x45, 0x61, 0xe4,
|
||||
0x57, 0xac, 0xbc, 0x21, 0x8a, 0xa9, 0x6b, 0x82, 0xa4, 0x0c, 0x24, 0x09,
|
||||
0x2d, 0x55, 0x43, 0x78, 0xc4, 0x78, 0xc2, 0x50, 0x85, 0x9c, 0x4d, 0x92,
|
||||
0x12, 0x24, 0xd9, 0x1b, 0x7c, 0xe9, 0x08, 0x51, 0x72, 0x13, 0x5f, 0xaf,
|
||||
0x8a, 0x4d, 0x35, 0x61, 0xd5, 0x4b, 0xc7, 0xfd, 0xef, 0x5b, 0xa8, 0x4e,
|
||||
0x14, 0x24, 0xaf, 0x9f, 0xe8, 0x5a, 0x40, 0x75, 0x87, 0xc8, 0xe1, 0xb2,
|
||||
0xae, 0xbc, 0x2c, 0x85, 0xa6, 0x9c, 0x97, 0x2d, 0x2b, 0x6f, 0x15, 0x34,
|
||||
0x28, 0x87, 0x56, 0x6d, 0x2a, 0xcf, 0x71, 0x2a, 0xca, 0xd3, 0x6e, 0x0a,
|
||||
0x51, 0x65, 0x61, 0x3b, 0x44, 0x8b, 0xdb, 0x6b, 0x34, 0x16, 0x5d, 0xd7,
|
||||
0xff, 0x74, 0x4a, 0xbf, 0xf9, 0x02, 0x31, 0x98, 0x4b, 0x6b, 0x7e, 0x05,
|
||||
0x00, 0x00
|
||||
};
|
||||
unsigned int File_favicon_ico_gz_len = 326;
|
||||
|
||||
void WebFile_favicon_ico(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, "image/x-icon", File_favicon_ico_gz, File_favicon_ico_gz_len);
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
request->send(response);
|
||||
}
|
31
Arduino/CanGrow/include/Webserver/Footer.h
Normal file
31
Arduino/CanGrow/include/Webserver/Footer.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/footer_HTML.h - footer page HTML header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
const char* Footer_HTML PROGMEM = R"(<div class='footer'><span>Build: %CGBUILD%</span></div>
|
||||
</div></body></html>)";
|
49
Arduino/CanGrow/include/Webserver/Header.h
Normal file
49
Arduino/CanGrow/include/Webserver/Header.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/header_HTML.h - header page HTML header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
const char* Header_HTML PROGMEM = R"(<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||
<title>%GROWNAME% - CanGrow v%CGVER%</title>
|
||||
<link rel='stylesheet' href='/cangrow.css'>
|
||||
<script type='text/javascript' src='/cangrow.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<ul class='nav'><li><a href='/'>🌱 %GROWNAME%</a></li>
|
||||
<li><a class='%ACTIVE_NAV_GROW%' href='/grow/' >🔆 Grow settings</a></li>
|
||||
<li><a class='%ACTIVE_NAV_SYSTEM%' href='/system/' >⚙ System settings</a></li>
|
||||
<li><a class='%ACTIVE_NAV_WIFI%' href='/wifi/' >📡 WiFi settings</a></li>
|
||||
<li><a class='%ACTIVE_NAV_HELP%' href='/help' >❓ Help</a></li>
|
||||
<li><span class='navTime'>04:20:23</span></li>
|
||||
<li><a href='https://git.la10cy.net/DeltaLima/CanGrow' target='_blank'>CanGrow v%CGVER%</a></li>
|
||||
</ul>
|
||||
<div class='center'>
|
||||
%NEED_RESTART%)";
|
30
Arduino/CanGrow/include/Webserver/Page_grow.h
Normal file
30
Arduino/CanGrow/include/Webserver/Page_grow.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_grow.h - grow page header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
28
Arduino/CanGrow/include/Webserver/Page_grow_HTML.h
Normal file
28
Arduino/CanGrow/include/Webserver/Page_grow_HTML.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_grow_HTML.h - grow page HTML header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
49
Arduino/CanGrow/include/Webserver/Page_root.h
Normal file
49
Arduino/CanGrow/include/Webserver/Page_root.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_root.h - root page header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Page_root_HTML.h"
|
||||
|
||||
|
||||
|
||||
// https://techtutorialsx.com/2018/07/23/esp32-arduino-http-server-template-processing-with-multiple-placeholders/
|
||||
String Proc_WebPage_root(const String& var) {
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var);
|
||||
} else if(var == "LOL") {
|
||||
return String("Nice");
|
||||
} else if(var == "LOL") {
|
||||
return String("Jojoojo :)");
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
void WebPage_root(AsyncWebServerRequest *request) {
|
||||
request->send_P(200, "text/html", Page_root_HTML, Proc_WebPage_root);
|
||||
}
|
33
Arduino/CanGrow/include/Webserver/Page_root_HTML.h
Normal file
33
Arduino/CanGrow/include/Webserver/Page_root_HTML.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_root_HTML.h - root page HTML header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
const char* Page_root_HTML PROGMEM = R"(%HEADER%
|
||||
<h2>🌱 Hello world!</h2>
|
||||
%FOOTER%)";
|
736
Arduino/CanGrow/include/Webserver/Page_system.h
Normal file
736
Arduino/CanGrow/include/Webserver/Page_system.h
Normal file
|
@ -0,0 +1,736 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_system.h - system settings page header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "Page_system_HTML.h"
|
||||
|
||||
/* global runtime variables */
|
||||
|
||||
/* VERY VERY DIRTY WORKAROUND
|
||||
* I have the problem, that I cannot pass a parameter I receive from a http
|
||||
* request to it's template processor. In my case i want to edit an output,
|
||||
* the user should click an edit button on the system/output overview page.
|
||||
* I am lazy so i want to reuse the output_add page, because it is quite
|
||||
* kinda exactly the same. so i want to call GET /system/output/add?edit=ID
|
||||
* I have searched and came to the conclusion, that at this point i see no
|
||||
* other way then giving the parameter I need, the outputId, to an global
|
||||
* variable, so the template processor can read it.
|
||||
*/
|
||||
byte tmpParam_editOutputId = 255;
|
||||
|
||||
|
||||
|
||||
|
||||
/* subnav processor */
|
||||
|
||||
bool Test_WebPage_system_SUBNAV(const String& var) {
|
||||
if(
|
||||
(var == "SUBNAV") ||
|
||||
(var == "ACTIVE_SUBNAV_OUTPUT") ||
|
||||
(var == "ACTIVE_SUBNAV_UPDATE") ||
|
||||
(var == "ACTIVE_SUBNAV_RESTART") ||
|
||||
(var == "ACTIVE_SUBNAV_WIPE")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Proc_WebPage_system_SUBNAV - subnav processor for system
|
||||
* this function works as same as AddHeaderFooter from Common.h
|
||||
* byte activeSubnav:
|
||||
* 1 - Output
|
||||
* 2 - Update
|
||||
* 3 - Restart
|
||||
* 4 - Wipe
|
||||
*/
|
||||
String Proc_WebPage_system_SUBNAV(const String& var, byte activeSubnav = 0) {
|
||||
String activeSubnav_ClassName = "activeNav";
|
||||
if(var == "SUBNAV") {
|
||||
return String(Page_system_HTML_SUBNAV);
|
||||
} else if((var == "ACTIVE_SUBNAV_OUTPUT") && (activeSubnav == 1)) {
|
||||
return activeSubnav_ClassName;
|
||||
} else if((var == "ACTIVE_SUBNAV_UPDATE") && (activeSubnav == 2)) {
|
||||
return activeSubnav_ClassName;
|
||||
} else if((var == "ACTIVE_SUBNAV_RESTART") && (activeSubnav == 3)) {
|
||||
return activeSubnav_ClassName;
|
||||
} else if((var == "ACTIVE_SUBNAV_WIPE") && (activeSubnav == 4)) {
|
||||
return activeSubnav_ClassName;
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Main system page
|
||||
*/
|
||||
// https://techtutorialsx.com/2018/07/23/esp32-arduino-http-server-template-processing-with-multiple-placeholders/
|
||||
String Proc_WebPage_system(const String& var) {
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var);
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_POST(const String& var) {
|
||||
if(var == "SAVE_MSG") {
|
||||
return String(Common_HTML_SAVE_MSG);
|
||||
} else {
|
||||
return Proc_WebPage_system(var);
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_POST_ERR(const String& var) {
|
||||
if(var == "SAVE_MSG") {
|
||||
return String(Common_HTML_SAVE_MSG_ERR);
|
||||
} else {
|
||||
return Proc_WebPage_system(var);
|
||||
}
|
||||
}
|
||||
|
||||
void WebPage_system(AsyncWebServerRequest *request) {
|
||||
if(request->method() == HTTP_POST) {
|
||||
request->send_P(200, "text/html", Page_system_HTML, Proc_WebPage_system_POST);
|
||||
Serial.println(":: [Webserver:system:output] [POST] hello");
|
||||
|
||||
if(request->hasParam("config.system.ntpOffset", true)) {
|
||||
const AsyncWebParameter* p_ntpOffset = request->getParam("config.system.ntpOffset", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_ntpOffset->name().c_str(), p_ntpOffset->value().c_str());
|
||||
config.system.ntpOffset = p_ntpOffset->value().toInt();
|
||||
}
|
||||
|
||||
if(request->hasParam("config.system.httpLogSerial", true)) {
|
||||
const AsyncWebParameter* p_httpLogSerial = request->getParam("config.system.httpLogSerial", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_httpLogSerial->name().c_str(), p_httpLogSerial->value().c_str());
|
||||
config.system.httpLogSerial = p_httpLogSerial->value().toInt();
|
||||
}
|
||||
|
||||
if(SaveConfig()) {
|
||||
// we need a restart to apply the new settings
|
||||
needRestart = true;
|
||||
Serial.println(":: [Webserver:system] config saved");
|
||||
request->send_P(200, "text/html", Page_system_HTML, Proc_WebPage_system_POST);
|
||||
} else {
|
||||
Serial.println("!! [Webserver:system] ERROR saving config ");
|
||||
request->send_P(200, "text/html", Page_system_HTML, Proc_WebPage_system_POST_ERR);
|
||||
}
|
||||
} else {
|
||||
request->send_P(200, "text/html", Page_system_HTML, Proc_WebPage_system);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Subpage restart
|
||||
*/
|
||||
String Proc_WebPage_system_restart(const String& var) {
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var, 3);
|
||||
} else if(var == "RESTART_MSG") {
|
||||
return String(Page_system_restart_HTML_RESTART_MSG);
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_restart_POST(const String& var) {
|
||||
if(var == "RESTART_MSG") {
|
||||
return String(Page_system_restart_HTML_RESTART_MSG_POST);
|
||||
} else {
|
||||
return Proc_WebPage_system_restart(var);
|
||||
}
|
||||
}
|
||||
|
||||
void WebPage_system_restart(AsyncWebServerRequest *request) {
|
||||
if(request->method() == HTTP_POST) {
|
||||
if(request->hasParam("confirmed", true)) {
|
||||
doRestart = false;
|
||||
}
|
||||
request->send_P(200, "text/html", Page_system_restart_HTML, Proc_WebPage_system_restart_POST);
|
||||
|
||||
if(request->hasParam("confirmed", true)) {
|
||||
Serial.println(":: [Webserver:system:restart] POST[confirmed]: is set, triggering restart");
|
||||
|
||||
doRestart = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
request->send_P(200, "text/html", Page_system_restart_HTML, Proc_WebPage_system_restart);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Subpage update
|
||||
*/
|
||||
|
||||
// https://github.com/mathieucarbou/ESPAsyncWebServer/blob/main/docs/index.md#setting-up-the-server
|
||||
void WebPage_system_update_ApplyUpdate(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
|
||||
if(!index){
|
||||
Serial.printf(":: [Webserver:system:update:ApplyUpdate] Update Start: %s\n", filename.c_str());
|
||||
|
||||
// https://github.com/me-no-dev/ESPAsyncWebServer/issues/455#issuecomment-451728099
|
||||
// workaround for bug with ESP32
|
||||
#ifdef ESP8266
|
||||
Update.runAsync(true);
|
||||
#endif
|
||||
if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)){
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
if(!Update.hasError()){
|
||||
if(Update.write(data, len) != len){
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
if(final){
|
||||
if(Update.end(true)){
|
||||
Serial.printf(":: [Webserver:system:update:ApplyUpdate] Update Success: %uB\n", index+len);
|
||||
} else {
|
||||
Serial.println(":: [Webserver:system:update:ApplyUpdate] FAILED Update:");
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_update(const String& var) {
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var, 2);
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
/* After an update.bin file was uploaded*/
|
||||
String Proc_WebPage_system_update_POST(const String& var) {
|
||||
if(var == "CONFIGWIFI_IP") {
|
||||
if(config.wifi.dhcp == true) {
|
||||
return WiFi.localIP().toString();
|
||||
} else {
|
||||
return String(IP2Char(config.wifi.ip));
|
||||
}
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
void WebPage_system_update(AsyncWebServerRequest *request) {
|
||||
if(request->method() == HTTP_POST) {
|
||||
doRestart = !Update.hasError();
|
||||
// when doRestart is true, deliver Page_system_update_HTML_POST
|
||||
// otherwise Page_system_update_HTML_POST_FAILED
|
||||
AsyncWebServerResponse *response = request->beginResponse(200, "text/html", doRestart?Page_system_update_HTML_POST:Page_system_update_HTML_POST_FAILED, Proc_WebPage_system_update_POST);
|
||||
response->addHeader("Connection", "close");
|
||||
request->send(response);
|
||||
} else {
|
||||
request->send_P(200, "text/html", Page_system_update_HTML, Proc_WebPage_system_update);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Subpage wipe
|
||||
*/
|
||||
String Proc_WebPage_system_wipe(const String& var) {
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var, 4);
|
||||
} else if(var == "WIPE_MSG") {
|
||||
return String(Page_system_wipe_HTML_WIPE_MSG);
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_wipe_POST(const String& var) {
|
||||
if(var == "WIPE_MSG") {
|
||||
return String(Page_system_wipe_HTML_WIPE_MSG_POST);
|
||||
} else {
|
||||
return Proc_WebPage_system_wipe(var);
|
||||
}
|
||||
}
|
||||
|
||||
void WebPage_system_wipe(AsyncWebServerRequest *request) {
|
||||
if(request->method() == HTTP_POST) {
|
||||
request->send_P(200, "text/html", Page_system_wipe_HTML, Proc_WebPage_system_wipe_POST);
|
||||
|
||||
if(request->hasParam("confirmed", true)) {
|
||||
Serial.println(":: [Webserver:system:wipe] POST[confirmed]: is set, triggering wipe / factory reset");
|
||||
LFS_Format();
|
||||
Serial.println(":: [Webserver:system:wipe] triggering restart");
|
||||
doRestart = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
request->send_P(200, "text/html", Page_system_wipe_HTML, Proc_WebPage_system_wipe);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Subpage output
|
||||
*/
|
||||
String Proc_WebPage_system_output(const String& var) {
|
||||
#ifndef DEBUG
|
||||
Serial.print("DB [Webserver:system:output(Proc)] var: ");
|
||||
Serial.println(var);
|
||||
#endif
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var, 1);
|
||||
} else if(var == "OUTPUT_TR_TD") {
|
||||
// build table body
|
||||
// i dont know a better way at the moment. if you do, please tell me!
|
||||
String output_tr_td;
|
||||
for(byte i=0; i < Max_Outputs; i++) {
|
||||
if(config.system.output.type[i] > 0) {
|
||||
#ifndef DEBUG
|
||||
Serial.printf("DB [Webserver:system:output(Proc)] OutputID %d Type %d\n", i, config.system.output.type[i]);
|
||||
#endif
|
||||
output_tr_td += "<tr><td>";
|
||||
output_tr_td += i;
|
||||
output_tr_td += "</td><td>";
|
||||
output_tr_td += config.system.output.name[i];
|
||||
output_tr_td += "</td><td>";
|
||||
output_tr_td += Output_Type_descr[config.system.output.type[i]];
|
||||
output_tr_td += "</td><td>";
|
||||
output_tr_td += Output_Device_descr[config.system.output.device[i]];
|
||||
output_tr_td += "</td><td>";
|
||||
output_tr_td += config.system.output.enabled[i];
|
||||
output_tr_td += "</td><td>";
|
||||
|
||||
// edit button
|
||||
output_tr_td += "<form class='linkForm' action='/system/output/add' method='get'>";
|
||||
output_tr_td += "<input type='hidden' name='edit' value='";
|
||||
output_tr_td += i;
|
||||
output_tr_td += "'>";
|
||||
output_tr_td += "<input type='submit' value='✏️'></form> ";
|
||||
|
||||
|
||||
// delete button
|
||||
output_tr_td += "<form class='linkForm' action='/system/output/' method='post'>";
|
||||
output_tr_td += "<input type='hidden' name='delete_output' value='";
|
||||
output_tr_td += i;
|
||||
output_tr_td += "'>";
|
||||
output_tr_td += "<input type='submit' value='❌' onclick=\"return confirmDelete('";
|
||||
output_tr_td += config.system.output.name[i];;
|
||||
output_tr_td += "')\"></form>";
|
||||
|
||||
output_tr_td += "</td></tr>";
|
||||
}
|
||||
}
|
||||
|
||||
return output_tr_td;
|
||||
} else{
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_output_POST(const String& var) {
|
||||
if(var == "SAVE_MSG") {
|
||||
return String(Common_HTML_SAVE_MSG);
|
||||
} else {
|
||||
return Proc_WebPage_system_output(var);
|
||||
}
|
||||
}
|
||||
|
||||
void WebPage_system_output(AsyncWebServerRequest *request) {
|
||||
if(request->method() == HTTP_POST) {
|
||||
if(request->hasParam("delete_output", true)) {
|
||||
byte outputId;
|
||||
|
||||
const AsyncWebParameter* p_delete_output = request->getParam("delete_output", true);
|
||||
Serial.printf(":: [Webserver:system:output] POST[%s]: %s\n", p_delete_output->name().c_str(), p_delete_output->value().c_str());
|
||||
|
||||
outputId = p_delete_output->value().toInt();
|
||||
|
||||
Serial.printf(":: [Webserver:system:output] Deleting output: %d\n", outputId);
|
||||
|
||||
// we ensure that every field is empty
|
||||
config.system.output.type[outputId] = 0;
|
||||
config.system.output.device[outputId] = 0;
|
||||
// set every field of char array to 0x00 with memset
|
||||
memset(config.system.output.name[outputId], '\0', sizeof config.system.output.name[outputId]);
|
||||
config.system.output.enabled[outputId] = 0;
|
||||
config.system.output.gpio[outputId] = 0;
|
||||
config.system.output.gpio_pwm[outputId] = 0;
|
||||
config.system.output.gpio_invert[outputId] = 0;
|
||||
memset(config.system.output.i2c[outputId], '\0', sizeof config.system.output.i2c[outputId]);
|
||||
memset(config.system.output.webcall_host[outputId], '\0', sizeof config.system.output.webcall_host[outputId]);
|
||||
memset(config.system.output.webcall_path_on[outputId], '\0', sizeof config.system.output.webcall_path_on[outputId]);
|
||||
memset(config.system.output.webcall_path_off[outputId], '\0', sizeof config.system.output.webcall_path_off[outputId]);
|
||||
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
|
||||
request->send_P(200, "text/html", Page_system_output_HTML, Proc_WebPage_system_output_POST);
|
||||
Serial.println(":: [Webserver:system:output] [POST] hello");
|
||||
|
||||
|
||||
} else {
|
||||
if(request->hasParam("success")) {
|
||||
// when GET param success is present, we use the _POST processor for the save message
|
||||
request->send_P(200, "text/html", Page_system_output_HTML, Proc_WebPage_system_output_POST);
|
||||
} else {
|
||||
request->send_P(200, "text/html", Page_system_output_HTML, Proc_WebPage_system_output);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Subpage output add
|
||||
*/
|
||||
|
||||
/* returns select <option> list of available output types */
|
||||
String Html_SelOpt_type_WebPage_system_output_add(byte selectId = 255) {
|
||||
String outputType_html;
|
||||
// go through all available Output Devices, skip 0 because it means unconfigured
|
||||
for(byte i = 1; i < Output_Type_total; i++) {
|
||||
outputType_html += "<option value='";
|
||||
outputType_html += i;
|
||||
outputType_html += "'";
|
||||
if(i == selectId) {
|
||||
outputType_html += " selected";
|
||||
}
|
||||
outputType_html += ">";
|
||||
outputType_html += Output_Type_descr[i];
|
||||
outputType_html += "</option>";
|
||||
}
|
||||
return outputType_html;
|
||||
}
|
||||
|
||||
String Html_SelOpt_device_WebPage_system_output_add(byte selectId = 255) {
|
||||
String outputDevice_html;
|
||||
// go through all available Output Devices, skip 0 because it means unconfigured
|
||||
for(byte i = 1; i < Output_Device_total; i++) {
|
||||
outputDevice_html += "<option value='";
|
||||
outputDevice_html += i;
|
||||
outputDevice_html += "'";
|
||||
if(i == selectId) {
|
||||
outputDevice_html += " selected";
|
||||
}
|
||||
outputDevice_html += ">";
|
||||
outputDevice_html += Output_Device_descr[i];
|
||||
outputDevice_html += "</option>";
|
||||
}
|
||||
return outputDevice_html;
|
||||
}
|
||||
|
||||
|
||||
String Proc_WebPage_system_output_addCommon(const String& var) {
|
||||
#ifndef DEBUG
|
||||
Serial.print("DB [Webserver:system:output:add(Proc)] var: ");
|
||||
Serial.println(var);
|
||||
#endif
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var, 1);
|
||||
} else if(var == "OUTPUT_ID") {
|
||||
// we check which id is free. A free ID as type == 0
|
||||
return String(Give_Free_OutputId());
|
||||
|
||||
/* OUTPUT_TYPE */
|
||||
} else if(var == "OUTPUT_TYPE") {
|
||||
return Html_SelOpt_type_WebPage_system_output_add();
|
||||
|
||||
/* OUTPUT_DEVICE */
|
||||
} else if(var == "OUTPUT_DEVICE") {
|
||||
return Html_SelOpt_device_WebPage_system_output_add();
|
||||
|
||||
/* GPIO_INDEX */
|
||||
} else if(var == "GPIO_INDEX") {
|
||||
return Html_SelectOpt_GPIOindex();
|
||||
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String Proc_WebPage_system_output_add(const String& var) {
|
||||
#ifndef DEBUG
|
||||
Serial.print("DB [Webserver:system:output:add(Proc)] var: ");
|
||||
Serial.println(var);
|
||||
#endif
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var, 1);
|
||||
} else if(var == "ACTION") {
|
||||
return String("➕ Add");
|
||||
|
||||
} else if(var == "OUTPUT_ID") {
|
||||
// we check which id is free. A free ID as type == 0
|
||||
return String(Give_Free_OutputId());
|
||||
|
||||
|
||||
} else if(var == "OUTPUT_TYPE") {
|
||||
return Html_SelOpt_type_WebPage_system_output_add();
|
||||
|
||||
} else if(var == "OUTPUT_DEVICE") {
|
||||
return Html_SelOpt_device_WebPage_system_output_add();
|
||||
|
||||
} else if(var == "OUTPUT_ENABLED") {
|
||||
return Html_SelectOpt_bool();
|
||||
|
||||
} else if(var == "GPIO_INDEX") {
|
||||
return Html_SelectOpt_GPIOindex();
|
||||
|
||||
} else if(var == "GPIO_PWM") {
|
||||
return Html_SelectOpt_bool();
|
||||
|
||||
} else if(var == "GPIO_INVERT") {
|
||||
return Html_SelectOpt_bool();
|
||||
|
||||
//} else if(
|
||||
//(var == "CLASS_TYPE_1") ||
|
||||
//(var == "CLASS_TYPE_2") ||
|
||||
//(var == "CLASS_TYPE_3")) {
|
||||
/* all type div container are hidden when adding */
|
||||
//return String("hidden");
|
||||
|
||||
//}
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_output_addEdit(const String& var) {
|
||||
#ifndef DEBUG
|
||||
Serial.print("DB [Webserver:system:output:addEdit(Proc)] var: ");
|
||||
Serial.println(var);
|
||||
#endif
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 2);
|
||||
} else if(Test_WebPage_system_SUBNAV(var)) {
|
||||
return Proc_WebPage_system_SUBNAV(var, 1);
|
||||
} else if(var == "ACTION") {
|
||||
return String("✏️ Edit");
|
||||
|
||||
} else if(var == "OUTPUT_ID") {
|
||||
// return the outputId we got from GET .../add?edit=ID
|
||||
// dirty workaround to put this in a global variable
|
||||
return String(tmpParam_editOutputId);
|
||||
|
||||
} else if(var == "OUTPUT_TYPE") {
|
||||
return Html_SelOpt_type_WebPage_system_output_add(config.system.output.type[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "OUTPUT_DEVICE") {
|
||||
return Html_SelOpt_device_WebPage_system_output_add(config.system.output.device[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "OUTPUT_NAME") {
|
||||
// "escape" % character, because it would break the template processor.
|
||||
// tasmote webcall for example has percentage char in its path
|
||||
String outputName = config.system.output.name[tmpParam_editOutputId];;
|
||||
outputName.replace("%", "%");
|
||||
return outputName;
|
||||
|
||||
} else if(var == "OUTPUT_ENABLED") {
|
||||
return Html_SelectOpt_bool(config.system.output.enabled[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "GPIO_INDEX") {
|
||||
return Html_SelectOpt_GPIOindex(config.system.output.gpio[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "GPIO_PWM") {
|
||||
return Html_SelectOpt_bool(config.system.output.gpio_pwm[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "GPIO_INVERT") {
|
||||
return Html_SelectOpt_bool(config.system.output.gpio_invert[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "I2C") {
|
||||
return String(config.system.output.i2c[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "WEBCALL_HOST") {
|
||||
return String(config.system.output.webcall_host[tmpParam_editOutputId]);
|
||||
|
||||
} else if(var == "WEBCALL_PATH_ON") {
|
||||
String webcallPathOn = config.system.output.webcall_path_on[tmpParam_editOutputId];
|
||||
webcallPathOn.replace("%", "%");
|
||||
return webcallPathOn;
|
||||
|
||||
} else if(var == "WEBCALL_PATH_OFF") {
|
||||
String webcallPathOff = config.system.output.webcall_path_off[tmpParam_editOutputId];
|
||||
webcallPathOff.replace("%", "%");
|
||||
return webcallPathOff;
|
||||
|
||||
} else if(
|
||||
((var == "CLASS_TYPE_1") && (config.system.output.type[tmpParam_editOutputId] == 1)) ||
|
||||
((var == "CLASS_TYPE_2") && (config.system.output.type[tmpParam_editOutputId] == 2)) ||
|
||||
((var == "CLASS_TYPE_3") && (config.system.output.type[tmpParam_editOutputId] == 3))) {
|
||||
// add class 'visible' which overwrites display with flex!important and justify center
|
||||
return String("visible");
|
||||
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_system_output_add_POST(const String& var) {
|
||||
if(var == "SAVE_MSG") {
|
||||
return String(Common_HTML_SAVE_MSG);
|
||||
} else {
|
||||
return Proc_WebPage_system_output_add(var);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WebPage_system_output_add(AsyncWebServerRequest *request) {
|
||||
if(request->method() == HTTP_POST) {
|
||||
Serial.println(":: [Webserver:system:output:add] [POST] hello");
|
||||
byte outputId;
|
||||
byte outputType;
|
||||
if(request->hasParam("outputId", true)) {
|
||||
const AsyncWebParameter* p_outputId = request->getParam("outputId", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_outputId->name().c_str(), p_outputId->value().c_str());
|
||||
outputId = p_outputId->value().toInt();
|
||||
}
|
||||
|
||||
if(request->hasParam("type", true)) {
|
||||
const AsyncWebParameter* p_type = request->getParam("type", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_type->name().c_str(), p_type->value().c_str());
|
||||
// put info config struct
|
||||
config.system.output.type[outputId] = p_type->value().toInt();
|
||||
|
||||
// remember the value in own var to work later with here
|
||||
outputType = p_type->value().toInt();
|
||||
}
|
||||
|
||||
if(request->hasParam("device", true)) {
|
||||
const AsyncWebParameter* p_device = request->getParam("device", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_device->name().c_str(), p_device->value().c_str());
|
||||
config.system.output.device[outputId] = p_device->value().toInt();
|
||||
}
|
||||
|
||||
if(request->hasParam("name", true)) {
|
||||
const AsyncWebParameter* p_name = request->getParam("name", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_name->name().c_str(), p_name->value().c_str());
|
||||
strlcpy(config.system.output.name[outputId], p_name->value().c_str(), sizeof(config.system.output.name[outputId]));
|
||||
}
|
||||
|
||||
if(request->hasParam("enabled", true)) {
|
||||
const AsyncWebParameter* p_enabled = request->getParam("enabled", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_enabled->name().c_str(), p_enabled->value().c_str());
|
||||
config.system.output.enabled[outputId] = p_enabled->value().toInt();
|
||||
}
|
||||
|
||||
// only fill the type related config vars
|
||||
switch(outputType) {
|
||||
// GPIO
|
||||
case 1:
|
||||
if(request->hasParam("gpio", true)) {
|
||||
const AsyncWebParameter* p_gpio = request->getParam("gpio", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_gpio->name().c_str(), p_gpio->value().c_str());
|
||||
config.system.output.gpio[outputId] = p_gpio->value().toInt();
|
||||
}
|
||||
|
||||
if(request->hasParam("gpio_pwm", true)) {
|
||||
const AsyncWebParameter* p_gpio_pwm = request->getParam("gpio_pwm", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_gpio_pwm->name().c_str(), p_gpio_pwm->value().c_str());
|
||||
config.system.output.gpio_pwm[outputId] = p_gpio_pwm->value().toInt();
|
||||
}
|
||||
|
||||
if(request->hasParam("gpio_invert", true)) {
|
||||
const AsyncWebParameter* p_gpio_invert = request->getParam("gpio_invert", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_gpio_invert->name().c_str(), p_gpio_invert->value().c_str());
|
||||
config.system.output.gpio_invert[outputId] = p_gpio_invert->value().toInt();
|
||||
}
|
||||
break;
|
||||
|
||||
// I2C
|
||||
case 2:
|
||||
if(request->hasParam("i2c", true)) {
|
||||
const AsyncWebParameter* p_i2c = request->getParam("i2c", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_i2c->name().c_str(), p_i2c->value().c_str());
|
||||
strlcpy(config.system.output.i2c[outputId], p_i2c->value().c_str(), sizeof(config.system.output.i2c[outputId]));
|
||||
}
|
||||
break;
|
||||
// Webcall
|
||||
case 3:
|
||||
if(request->hasParam("webcall_host", true)) {
|
||||
const AsyncWebParameter* p_webcall_host = request->getParam("webcall_host", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_webcall_host->name().c_str(), p_webcall_host->value().c_str());
|
||||
strlcpy(config.system.output.webcall_host[outputId], p_webcall_host->value().c_str(), sizeof(config.system.output.webcall_host[outputId]));
|
||||
}
|
||||
|
||||
if(request->hasParam("webcall_path_on", true)) {
|
||||
const AsyncWebParameter* p_webcall_path_on = request->getParam("webcall_path_on", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_webcall_path_on->name().c_str(), p_webcall_path_on->value().c_str());
|
||||
strlcpy(config.system.output.webcall_path_on[outputId], p_webcall_path_on->value().c_str(), sizeof(config.system.output.webcall_path_on[outputId]));
|
||||
}
|
||||
|
||||
if(request->hasParam("webcall_path_off", true)) {
|
||||
const AsyncWebParameter* p_webcall_path_off = request->getParam("webcall_path_off", true);
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %s\n", p_webcall_path_off->name().c_str(), p_webcall_path_off->value().c_str());
|
||||
strlcpy(config.system.output.webcall_path_off[outputId], p_webcall_path_off->value().c_str(), sizeof(config.system.output.webcall_path_off[outputId]));
|
||||
}
|
||||
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
||||
SaveConfig();
|
||||
|
||||
// request->send_P(200, "text/html", Page_system_output_add_HTML, Proc_WebPage_system_output_add_POST);
|
||||
// I like it more when user gets redirected to the output overview after saving
|
||||
request->redirect("/system/output/?success");
|
||||
} else {
|
||||
|
||||
if(request->hasParam("edit")) {
|
||||
|
||||
|
||||
const AsyncWebParameter* p_edit = request->getParam("edit");
|
||||
Serial.println("DB [Webserver:system:output:add?edit] ");
|
||||
Serial.printf(":: [Webserver:system] POST[%s]: %d\n", p_edit->name().c_str(), p_edit->value().toInt());
|
||||
|
||||
tmpParam_editOutputId = p_edit->value().toInt();
|
||||
|
||||
request->send_P(200, "text/html", Page_system_output_add_HTML, Proc_WebPage_system_output_addEdit);
|
||||
|
||||
} else {
|
||||
request->send_P(200, "text/html", Page_system_output_add_HTML, Proc_WebPage_system_output_add);
|
||||
}
|
||||
}
|
||||
}
|
230
Arduino/CanGrow/include/Webserver/Page_system_HTML.h
Normal file
230
Arduino/CanGrow/include/Webserver/Page_system_HTML.h
Normal file
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_system_HTML.h - system settings page HTML header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
const char* Page_system_HTML PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
%SAVE_MSG%
|
||||
<p>here you can set which features and sensors you use<br></p><form method='post' action='/system/'>
|
||||
|
||||
<u>NTP offset</u>:<br>
|
||||
<input class='inputShort' type='number' name='ntpOffset' min='-12' max='14' value='%CONFIG_SYSTEM_NTPOFFSET%' required> Hours<br>
|
||||
|
||||
<u>Maintenance duration</u>:<br> <input class='inputShort' type='number' name='maintenanceDuration' min='0' max='900' value='%CONFIG_SYSTEM_MAINTDUR%' required> Seconds<br>
|
||||
|
||||
<u>ESP32-Cam IP (optional)</u>:<br>
|
||||
<input type='text' name='esp32camIp' maxlength='16' value='%CONFIG_SYSTEM_ESP32CAMIP%' ><br>
|
||||
|
||||
<u>HTTP log to serial</u>:<br>
|
||||
<select name='config.system.httpLogSerial' required>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
<option value='1'>On</option>
|
||||
<option value='0'>Off</option>
|
||||
</select><br>
|
||||
|
||||
<input type='submit' value='💾 Save settings'>
|
||||
</form>
|
||||
%FOOTER%)";
|
||||
|
||||
const char* Page_system_HTML_SUBNAV PROGMEM = R"(<ul class='subnav'>
|
||||
<li><a class='' href='/system/sensor/'>🌡️ Sensor configuration</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_OUTPUT%' href='/system/output/'>⚡ Output configuration</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_UPDATE%' href='/system/update'>🔄 Firmware update</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_RESTART%' href='/system/restart' >🔁 CanGrow restart</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_WIPE%' href='/system/wipe' >💣 Factory reset</a></li>
|
||||
</ul>)";
|
||||
|
||||
/*
|
||||
* Subpage update
|
||||
*/
|
||||
const char* Page_system_update_HTML PROGMEM = R"(%HEADER%
|
||||
|
||||
%SUBNAV%
|
||||
Version: %CGVER% <br>
|
||||
Build : %CGBUILD% <br>
|
||||
|
||||
<p>You find the latest CanGrow firmware version on the <a href='https://git.la10cy.net/DeltaLima/CanGrow/releases' target='_blank'>release page</a> of the git repository.</p>
|
||||
<form method='POST' action='/system/update' enctype='multipart/form-data' onsubmit="document.getElementById('divUploading').style.display = '';">
|
||||
<b>Select .bin file:</b><br>
|
||||
<input type='file' accept='.bin,.bin.gz' name='firmware' required>
|
||||
<input type='submit' value='Update Firmware'>
|
||||
</form>
|
||||
<div id='divUploading' style='display: none;' class='warnmsg'>🛜 Uploading, please wait...</div>
|
||||
|
||||
%FOOTER%)";
|
||||
|
||||
const char* Page_system_update_HTML_POST PROGMEM = R"(<html>
|
||||
<head><meta http-equiv='refresh' content='15;url=/' /></head>
|
||||
<body><h1>Successfully updated!
|
||||
<br>Restarting...</h1><br>
|
||||
Redirecting to "/" in 15 seconds
|
||||
</body></html>)";
|
||||
|
||||
const char* Page_system_update_HTML_POST_FAILED PROGMEM = R"(<html>
|
||||
<head></head><body><h1>UPDATE FAILED</h1><br>
|
||||
Please see messages on serial monitor for more information and go back to <a href='/system/update'>\"System settings > Firmware update\"</a> and try another file.
|
||||
</body></html>)";
|
||||
|
||||
/*
|
||||
* Subpage restart
|
||||
*/
|
||||
const char* Page_system_restart_HTML PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
<div class='warnmsg'>
|
||||
%RESTART_MSG%
|
||||
</div>
|
||||
%FOOTER%)";
|
||||
|
||||
const char* Page_system_restart_HTML_RESTART_MSG PROGMEM = R"(Do you want to restart CanGrow?<br>Please confirm.
|
||||
<form action='/system/restart' method='post'><input type='hidden' name='confirmed' value='true' />
|
||||
<input type='submit' value='Confirm restart' />
|
||||
</form>)";
|
||||
|
||||
const char* Page_system_restart_HTML_RESTART_MSG_POST PROGMEM = R"(Restarting...)";
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Subpage wipe
|
||||
*/
|
||||
const char* Page_system_wipe_HTML PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
<div class='warnmsg'>
|
||||
%WIPE_MSG%
|
||||
</div>
|
||||
|
||||
%FOOTER%)";
|
||||
|
||||
const char* Page_system_wipe_HTML_WIPE_MSG PROGMEM = R"(All settings will be removed!!<br><br>
|
||||
Please confirm wiping LittleFS
|
||||
<form action='/system/wipe' method='post'><br>
|
||||
Please confirm: <input type='checkbox' id='confirmed' name='confirmed' required /><br>
|
||||
<input type='submit' value='Confirm wiping' />
|
||||
</form>)";
|
||||
|
||||
|
||||
const char* Page_system_wipe_HTML_WIPE_MSG_POST PROGMEM = R"(Restarting...)";
|
||||
|
||||
/*
|
||||
* Subpage output
|
||||
*/
|
||||
const char* Page_system_output_HTML PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
%SAVE_MSG%
|
||||
<a class='button' href='/system/output/add'>➕ Add output</a>
|
||||
<table class='centered'>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Device</th>
|
||||
<th>Enabled</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
%OUTPUT_TR_TD%
|
||||
</table>
|
||||
%FOOTER%)";
|
||||
|
||||
/*
|
||||
* Subpage output add
|
||||
*/
|
||||
const char* Page_system_output_add_HTML PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
<h3>%ACTION% output ID %OUTPUT_ID%</h3>
|
||||
%SAVE_MSG%
|
||||
|
||||
<p>Add/Edit CanGrow output.</p>
|
||||
<form method='post' action='/system/output/add'>
|
||||
<input type='hidden' name='outputId' value='%OUTPUT_ID%' />
|
||||
<u>Type</u>:<br>
|
||||
<select id='type_sel' name='type' onchange="showSelect('type_sel', 'type_', 'hidden');" required>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%OUTPUT_TYPE%
|
||||
</select><br>
|
||||
|
||||
<u>Device</u>:<br>
|
||||
<select name='device' required>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%OUTPUT_DEVICE%
|
||||
</select><br>
|
||||
|
||||
<u>Name</u>:<br>
|
||||
<input type='text' name='name' maxlength='16' value='%OUTPUT_NAME%' required><br>
|
||||
|
||||
<u>Enable</u>:<br>
|
||||
<select name='enabled' required>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%OUTPUT_ENABLED%
|
||||
</select><br>
|
||||
|
||||
<div class='hidden %CLASS_TYPE_1%' id='type_1'>
|
||||
<u>GPIO</u>:<br>
|
||||
<select name='gpio'>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%GPIO_INDEX%
|
||||
</select><br>
|
||||
|
||||
<u>GPIO PWM</u>:<br>
|
||||
<select name='gpio_pwm' >
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%GPIO_PWM%
|
||||
</select><br>
|
||||
|
||||
<u>GPIO invert</u>:<br>
|
||||
<select name='gpio_invert' >
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%GPIO_INVERT%
|
||||
</select><br>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='hidden %CLASS_TYPE_2%' id='type_2'>
|
||||
<u>I2C</u>:<br>
|
||||
<input type='text' name='i2c' maxlength='16' value='%I2C%' ><br>
|
||||
</div>
|
||||
|
||||
<div class='hidden %CLASS_TYPE_3%' id='type_3'>
|
||||
<u>Webcall host</u>:<br>
|
||||
<input type='text' name='webcall_host' maxlength='32' value='%WEBCALL_HOST%' ><br>
|
||||
|
||||
<u>Webcall path 'on'</u>:<br>
|
||||
<input type='text' name='webcall_path_on' maxlength='32' value='%WEBCALL_PATH_ON%' ><br>
|
||||
|
||||
<u>Webcall path 'off'</u>:<br>
|
||||
<input type='text' name='webcall_path_off' maxlength='32' value='%WEBCALL_PATH_OFF%' ><br>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<input type='submit' value='💾 Save settings'>
|
||||
</form>
|
||||
|
||||
%FOOTER%)";
|
212
Arduino/CanGrow/include/Webserver/Page_wifi.h
Normal file
212
Arduino/CanGrow/include/Webserver/Page_wifi.h
Normal file
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_wifi.h - wifi page header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "Page_wifi_HTML.h"
|
||||
|
||||
String WebPage_wifi_ScanNetworks() {
|
||||
String html;
|
||||
|
||||
Serial.println(":: [Webserver:wifi:ScanNetworks] scanning for available networks:");
|
||||
// https://github.com/mathieucarbou/ESPAsyncWebServer/blob/main/docs/index.md#scanning-for-available-wifi-networks
|
||||
int n = WiFi.scanComplete();
|
||||
if(n == -2){
|
||||
WiFi.scanNetworks(true);
|
||||
} else if(n){
|
||||
for (int i = 0; i < n; ++i){
|
||||
html += "<option value='" + WiFi.SSID(i) + "'>" + WiFi.SSID(i) + "</option>";
|
||||
Serial.print(":: [Webserver:wifi:ScanNetworks] - ");
|
||||
Serial.println(WiFi.SSID(i));
|
||||
}
|
||||
WiFi.scanDelete();
|
||||
if(WiFi.scanComplete() == -2){
|
||||
WiFi.scanNetworks(true);
|
||||
}
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
// https://techtutorialsx.com/2018/07/23/esp32-arduino-http-server-template-processing-with-multiple-placeholders/
|
||||
String Proc_WebPage_wifi(const String& var) {
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var, 3);
|
||||
//CURRENT_SETTINGS
|
||||
} else if(var == "CURRENT_SETTINGS") {
|
||||
if(strlen(config.wifi.ssid) > 0) {
|
||||
return String(Page_wifi_HTML_CURRENT_SETTINGS);
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
} else if(var == "CONFIGWIFI_SSID") {
|
||||
return String(config.wifi.ssid);
|
||||
} else if(var == "CONFIGWIFI_DHCP") {
|
||||
return String(config.wifi.dhcp);
|
||||
} else if(var == "CONFIGWIFI_IP") {
|
||||
return String(WiFi.localIP().toString());
|
||||
} else if(var == "CONFIGWIFI_NETMASK") {
|
||||
return String(WiFi.subnetMask().toString());
|
||||
} else if(var == "CONFIGWIFI_GATEWAY") {
|
||||
return String(WiFi.gatewayIP().toString());
|
||||
} else if(var == "CONFIGWIFI_DNS") {
|
||||
return String(WiFi.dnsIP().toString());
|
||||
} else if(var == "WIFI_LIST") {
|
||||
return String(WebPage_wifi_ScanNetworks());
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String Proc_WebPage_wifi_POST(const String& var) {
|
||||
if(var == "SAVE_MSG") {
|
||||
return String(Common_HTML_SAVE_MSG);
|
||||
} else {
|
||||
return Proc_WebPage_wifi(var);
|
||||
}
|
||||
}
|
||||
|
||||
String Proc_WebPage_wifi_POST_ERR(const String& var) {
|
||||
if(var == "SAVE_MSG") {
|
||||
return String(Common_HTML_SAVE_MSG_ERR);
|
||||
} else {
|
||||
return Proc_WebPage_wifi(var);
|
||||
}
|
||||
}
|
||||
|
||||
void WebPage_wifi(AsyncWebServerRequest *request) {
|
||||
|
||||
if(request->method() == HTTP_POST) {
|
||||
if(request->hasParam("config.wifi.ssid", true)) {
|
||||
const AsyncWebParameter* p_ssid = request->getParam("config.wifi.ssid", true);
|
||||
Serial.printf(":: [Webserver:wifi] POST[%s]: %s\n", p_ssid->name().c_str(), p_ssid->value().c_str());
|
||||
strlcpy(config.wifi.ssid, p_ssid->value().c_str(), sizeof(config.wifi.ssid));
|
||||
|
||||
}
|
||||
|
||||
if(request->hasParam("config.wifi.password", true)) {
|
||||
const AsyncWebParameter* p_password = request->getParam("config.wifi.password", true);
|
||||
Serial.printf(":: [Webserver:wifi] POST[%s]: %s\n", p_password->name().c_str(), p_password->value().c_str());
|
||||
strlcpy(config.wifi.password, p_password->value().c_str(), sizeof(config.wifi.password));
|
||||
}
|
||||
|
||||
|
||||
if(
|
||||
(request->hasParam("config.wifi.ip0", true)) &&
|
||||
(request->hasParam("config.wifi.ip1", true)) &&
|
||||
(request->hasParam("config.wifi.ip2", true)) &&
|
||||
(request->hasParam("config.wifi.ip3", true))) {
|
||||
|
||||
const AsyncWebParameter* p_ip0 = request->getParam("config.wifi.ip0", true);
|
||||
const AsyncWebParameter* p_ip1 = request->getParam("config.wifi.ip1", true);
|
||||
const AsyncWebParameter* p_ip2 = request->getParam("config.wifi.ip2", true);
|
||||
const AsyncWebParameter* p_ip3 = request->getParam("config.wifi.ip3", true);
|
||||
Serial.printf(":: [Webserver:wifi] POST[config.wifi.ip0-3]: %s . %s . %s . %s\n", p_ip0->value().c_str(), p_ip1->value().c_str(), p_ip2->value().c_str(), p_ip3->value().c_str());
|
||||
config.wifi.ip[0] = p_ip0->value().toInt();
|
||||
config.wifi.ip[1] = p_ip1->value().toInt();
|
||||
config.wifi.ip[2] = p_ip2->value().toInt();
|
||||
config.wifi.ip[3] = p_ip3->value().toInt();
|
||||
}
|
||||
|
||||
|
||||
if(
|
||||
(request->hasParam("config.wifi.netmask0", true)) &&
|
||||
(request->hasParam("config.wifi.netmask1", true)) &&
|
||||
(request->hasParam("config.wifi.netmask2", true)) &&
|
||||
(request->hasParam("config.wifi.netmask3", true))) {
|
||||
|
||||
const AsyncWebParameter* p_netmask0 = request->getParam("config.wifi.netmask0", true);
|
||||
const AsyncWebParameter* p_netmask1 = request->getParam("config.wifi.netmask1", true);
|
||||
const AsyncWebParameter* p_netmask2 = request->getParam("config.wifi.netmask2", true);
|
||||
const AsyncWebParameter* p_netmask3 = request->getParam("config.wifi.netmask3", true);
|
||||
Serial.printf(":: [Webserver:wifi] POST[config.wifi.netmask0-3]: %s . %s . %s . %s\n", p_netmask0->value().c_str(), p_netmask1->value().c_str(), p_netmask2->value().c_str(), p_netmask3->value().c_str());
|
||||
config.wifi.netmask[0] = p_netmask0->value().toInt();
|
||||
config.wifi.netmask[1] = p_netmask1->value().toInt();
|
||||
config.wifi.netmask[2] = p_netmask2->value().toInt();
|
||||
config.wifi.netmask[3] = p_netmask3->value().toInt();
|
||||
}
|
||||
|
||||
if(
|
||||
(request->hasParam("config.wifi.gateway0", true)) &&
|
||||
(request->hasParam("config.wifi.gateway1", true)) &&
|
||||
(request->hasParam("config.wifi.gateway2", true)) &&
|
||||
(request->hasParam("config.wifi.gateway3", true))) {
|
||||
|
||||
const AsyncWebParameter* p_gateway0 = request->getParam("config.wifi.gateway0", true);
|
||||
const AsyncWebParameter* p_gateway1 = request->getParam("config.wifi.gateway1", true);
|
||||
const AsyncWebParameter* p_gateway2 = request->getParam("config.wifi.gateway2", true);
|
||||
const AsyncWebParameter* p_gateway3 = request->getParam("config.wifi.gateway3", true);
|
||||
Serial.printf(":: [Webserver:wifi] POST[config.wifi.gateway0-3]: %s . %s . %s . %s\n", p_gateway0->value().c_str(), p_gateway1->value().c_str(), p_gateway2->value().c_str(), p_gateway3->value().c_str());
|
||||
config.wifi.gateway[0] = p_gateway0->value().toInt();
|
||||
config.wifi.gateway[1] = p_gateway1->value().toInt();
|
||||
config.wifi.gateway[2] = p_gateway2->value().toInt();
|
||||
config.wifi.gateway[3] = p_gateway3->value().toInt();
|
||||
}
|
||||
|
||||
if(
|
||||
(request->hasParam("config.wifi.dns0", true)) &&
|
||||
(request->hasParam("config.wifi.dns1", true)) &&
|
||||
(request->hasParam("config.wifi.dns2", true)) &&
|
||||
(request->hasParam("config.wifi.dns3", true))) {
|
||||
|
||||
const AsyncWebParameter* p_dns0 = request->getParam("config.wifi.dns0", true);
|
||||
const AsyncWebParameter* p_dns1 = request->getParam("config.wifi.dns1", true);
|
||||
const AsyncWebParameter* p_dns2 = request->getParam("config.wifi.dns2", true);
|
||||
const AsyncWebParameter* p_dns3 = request->getParam("config.wifi.dns3", true);
|
||||
Serial.printf(":: [Webserver:wifi] POST[config.wifi.dns0-3]: %s . %s . %s . %s\n", p_dns0->value().c_str(), p_dns1->value().c_str(), p_dns2->value().c_str(), p_dns3->value().c_str());
|
||||
config.wifi.dns[0] = p_dns0->value().toInt();
|
||||
config.wifi.dns[1] = p_dns1->value().toInt();
|
||||
config.wifi.dns[2] = p_dns2->value().toInt();
|
||||
config.wifi.dns[3] = p_dns3->value().toInt();
|
||||
}
|
||||
|
||||
if(request->hasParam("config.wifi.dhcp", true)) {
|
||||
const AsyncWebParameter* p_dhcp = request->getParam("config.wifi.dhcp", true);
|
||||
Serial.printf(":: [Webserver:wifi] POST[%s]: %s\n", p_dhcp->name().c_str(), p_dhcp->value().c_str());
|
||||
config.wifi.dhcp = p_dhcp->value().toInt();
|
||||
}
|
||||
|
||||
if(SaveConfig()) {
|
||||
// we need a restart to apply the new settings
|
||||
needRestart = true;
|
||||
Serial.println(":: [Webserver:wifi] config saved");
|
||||
request->send_P(200, "text/html", Page_wifi_HTML, Proc_WebPage_wifi_POST);
|
||||
} else {
|
||||
Serial.println("!! [Webserver:wifi] ERROR saving config ");
|
||||
request->send_P(200, "text/html", Page_wifi_HTML, Proc_WebPage_wifi_POST_ERR);
|
||||
}
|
||||
|
||||
} else {
|
||||
request->send_P(200, "text/html", Page_wifi_HTML, Proc_WebPage_wifi);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
93
Arduino/CanGrow/include/Webserver/Page_wifi_HTML.h
Normal file
93
Arduino/CanGrow/include/Webserver/Page_wifi_HTML.h
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_wifi_HTML.h - wifi page HTML header file
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 DeltaLima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
const char* Page_wifi_HTML PROGMEM = R"(%HEADER%
|
||||
%SAVE_MSG%
|
||||
%CURRENT_SETTINGS%
|
||||
|
||||
<p>Select your wifi network from the SSID list.
|
||||
<br>Reload the page, if your network is not listed.</p>
|
||||
<form method='post' action='/wifi/'>
|
||||
|
||||
<u>SSID</u>:<br>
|
||||
<select id='config.wifi.ssid' name='config.wifi.ssid' required>
|
||||
<option disabled value='' selected hidden>-Select your network-</option>
|
||||
|
||||
%WIFI_LIST%
|
||||
|
||||
</select><br>
|
||||
|
||||
<u>Password</u>:<br>
|
||||
<input type='password' name='config.wifi.password'><br>
|
||||
|
||||
<u>DHCP</u>:<br>
|
||||
<select id='dhcp_sel' name='config.wifi.dhcp' onchange="showSelect('dhcp_sel', 'dhcp_', 'hidden');" required>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
<option value='1'>On</option>
|
||||
<option value='0'>Off</option>
|
||||
</select><br>
|
||||
|
||||
<div class='hidden' id='dhcp_0'>
|
||||
<u>IP</u>:<br>
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.ip0'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.ip1'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.ip2'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.ip3'><br>
|
||||
|
||||
<u>Netmask</u>:<br>
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.netmask0'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.netmask1'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.netmask2'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.netmask3'><br>
|
||||
|
||||
<u>Gateway</u>:<br>
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.gateway0'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.gateway1'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.gateway2'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.gateway3'><br>
|
||||
|
||||
<u>DNS</u>:<br>
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.dns0'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.dns1'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.dns2'> .
|
||||
<input class='inputShort' type='number' min='0' max='255' name='config.wifi.dns3'><br>
|
||||
</div>
|
||||
<br>
|
||||
<input type='submit' value='💾 Save settings'>
|
||||
</form>
|
||||
%FOOTER%)";
|
||||
|
||||
const char* Page_wifi_HTML_CURRENT_SETTINGS PROGMEM = R"(<b><u>Current Settings:</u></b><br>WiFi SSID: <b>%CONFIGWIFI_SSID%</b><br>
|
||||
Use DHCP: <b>%CONFIGWIFI_DHCP%</b><br>
|
||||
IP address: <b>%CONFIGWIFI_IP%</b><br>
|
||||
Subnet mask: <b>%CONFIGWIFI_NETMASK%</b><br>
|
||||
Gateway: <b>%CONFIGWIFI_GATEWAY%</b><br>
|
||||
DNS: <b>%CONFIGWIFI_DNS%</b><br><br>)";
|
||||
|
||||
|
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>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||
<title>CanGrow - Amnesia Haze</title>
|
||||
|
||||
<link rel='icon' href='data:;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAABcElEQVQ4y42TzU/bQBDFf7Nx1qGuAYVgQSuo2khBggPhyIH//9AiJAQ9tEeLqCKiUD6sxF52OMSEBCdW57aa9968fTsr3V5XWVLPO6sANNL7ZRAMNeU6Ea4T1UEI6pr55kcAwhpMrYOpk2/r/yEQmKWkIonf+TZVgex4Fw0bIEtIAALF3gbZ8U5VwKa3PJ18JT9IpiLvyflBwuhLG5veVUM0/0aoCONPa2hQjWZ8uEVeupJnXSBwO8YOH8iTeAKc2Q4Xt2C1VZL93F7MjbK/bxDnp5Zn7b+So+9pdQ+K/Q5qJlrRj5Ts6DM+rK7Ih7Mr3HaM7jYQVZqXQ6Tb6yqBYdTfomhHiFfUyMI3f+01/z7RHNzTGDyWGThP63SA2d8EEfIkrgQpzmOvH0AV+3M4zegNpUwagAYG8Yp4BS0nl4Kz5Mpf0JXJMby6w/66Aa+M+9uE53/Iexsggq4ESOYWC0jmsBfX8xdXhcJjL4cLc3kBl8uJGQ/CrpAAAAAASUVORK5CYII='>
|
||||
<title>CanGrow - CanGrow v0.2-dev</title>
|
||||
<link rel='stylesheet' href='cangrow.css'>
|
||||
<script type='text/javascript' src='cangrow.js'></script>
|
||||
<style>
|
||||
body {
|
||||
color: #cae0d0;
|
||||
background-color: #1d211e;
|
||||
font-family: helvetica;
|
||||
}
|
||||
|
||||
.center {
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.centered {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
text-align: center;
|
||||
}
|
||||
a:link, a:visited {
|
||||
color: #04AA6D;
|
||||
}
|
||||
a:hover {
|
||||
color: #64AA6D;
|
||||
}
|
||||
a:active {
|
||||
color: #04AA6D;
|
||||
}
|
||||
.infomsg , .warnmsg {
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
padding: 4px;
|
||||
width: fit-content; min-width: 200px; max-width: 420px;
|
||||
margin: auto;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.infomsg {
|
||||
background: #04AA6D;
|
||||
}
|
||||
.warnmsg {
|
||||
background: #aa4204;
|
||||
}
|
||||
.inputShort {
|
||||
width: 42px;
|
||||
}
|
||||
|
||||
.nav {
|
||||
background: #333;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.nav li {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.nav li:first-of-type {
|
||||
background: #026b45;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
.nav li a , .nav span, .button, .button:link, input[type=button], input[type=submit], input[type=reset] {
|
||||
color: #ddd;
|
||||
display: block;
|
||||
font-family: 'Lucida Sans Unicode', 'Lucida Grande', sans-serif;
|
||||
font-size:0.8em;
|
||||
padding: 10px 20px;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.nav li a:hover , .activeMenu, .button:link:hover, .button:visited:hover, input[type=button]:hover, input[type=submit]:hover, input[type=reset]:hover {
|
||||
background: #04AA6D;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.nav li a:active {
|
||||
background: #026b45;
|
||||
color: #cae0d0;
|
||||
}
|
||||
|
||||
.activeMenu {
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.MenuTime {
|
||||
background: #292929;
|
||||
}
|
||||
|
||||
.button, .button:link, .button:visited, input[type=button], input[type=submit], input[type=reset] {
|
||||
background: #026b45;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
padding: 6px 12px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.button:link:active, .button:visited:active, input[type=button]:active, input[type=submit]:active, input[type=reset]:active {
|
||||
background: #026b45;
|
||||
color: #cae0d0;
|
||||
}
|
||||
|
||||
input[type=text], input[type=date], input[type=number], input[type=password], select {
|
||||
background: #cae0d0;
|
||||
color: #1d211e;
|
||||
border: 1px solid #026b45;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1280px) {
|
||||
.center, .nav {
|
||||
width: 60%; min-width: 420px;
|
||||
.linkForm {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.linkForm input[type=submit] {
|
||||
background: #262B27;
|
||||
padding: 5px;
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ul class='nav'><li><a href='/'>🌱 Amnesia Haze</a></li>
|
||||
<li><a href='/growSettings' >🔆 Grow settings</a></li>
|
||||
<li><a href='/systemSettings' >⚙ System settings</a></li>
|
||||
<li><a href='/wifiSettings' >📡 WiFi settings</a></li>
|
||||
<li><a href='/help' >❓ Help</a></li>
|
||||
<li><span class='MenuTime'>09:48:27</span></li>
|
||||
<li><a href='https://git.la10cy.net/DeltaLima/CanGrow' target='_blank'>CanGrow v0.1</a></li>
|
||||
</ul><div class='center'><h2>🔄 Firmware update</h2>Version: 0.1<br>Build: 4ad16c9
|
||||
<p>You find the latest CanGrow Firmware Version on the projects <a href='https://git.la10cy.net/DeltaLima/CanGrow/releases' target='_blank'>release page</a></p>
|
||||
<form method='GET' action='' enctype='multipart/form-data'>
|
||||
Firmware .bin file:<br>
|
||||
<input type='file' accept='.bin,.bin.gz' name='firmware'>
|
||||
<ul class='nav'><li><a href='/'>🌱 CanGrow</a></li>
|
||||
<li><a class='' href='/grow/' >🔆 Grow settings</a></li>
|
||||
<li><a class='activeNav' href='/system/' >⚙ System settings</a></li>
|
||||
<li><a class='' href='/wifi/' >📡 WiFi settings</a></li>
|
||||
<li><a class='' href='/help' >❓ Help</a></li>
|
||||
<li><span class='navTime'>04:20:23</span></li>
|
||||
<li><a href='https://git.la10cy.net/DeltaLima/CanGrow' target='_blank'>CanGrow v0.2-dev</a></li>
|
||||
</ul>
|
||||
<div class='center'>
|
||||
|
||||
</form>
|
||||
<button onclick="document.getElementById('divUploading').style.display = ''; window.alert('click');">asd</button>
|
||||
<div id='divUploading' style='display: none;' class='warnmsg'>Uploading, please wait...<div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<ul class='subnav'>
|
||||
<li><a class='activeNav' href='/system/output/'>⚡ Output configuration</a></li>
|
||||
<li><a class='' href='/system/update'>🔄 Firmware update</a></li>
|
||||
<li><a class='' href='/system/restart' >🔁 CanGrow restart</a></li>
|
||||
<li><a class='' href='/system/wipe' >💣 Factory reset</a></li>
|
||||
</ul>
|
||||
<a class='button' href='/system/output/add'>➕ Add output</a>
|
||||
<table class='centered hidden visible'>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Device</th>
|
||||
<th>Enabled</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
<tr><td>0</td>
|
||||
<td>Tasmota LED1</td>
|
||||
<td>Webcall</td>
|
||||
<td>Light</td>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<form class='linkForm' action='add?edit=0'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='✏️'>
|
||||
</form>
|
||||
<form class='linkForm' method='post' action='index.html'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='❌' onclick="return confirmDelete('Tasmota LED1')">
|
||||
</form>
|
||||
</td></tr><tr><td>1</td>
|
||||
<td>LED2 (red)</td>
|
||||
<td>GPIO</td>
|
||||
<td>Light</td>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<form class='linkForm' action='add?edit=0'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='✏️'>
|
||||
</form>
|
||||
<form class='linkForm' method='post' action='index.html'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='❌' onclick="return confirmDelete('Tasmota LED1')">
|
||||
</form>
|
||||
</td></tr><tr><td>2</td>
|
||||
<td>LED1 Dimmer</td>
|
||||
<td>I2C</td>
|
||||
<td>Light</td>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<form class='linkForm' action='add?edit=0'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='✏️'>
|
||||
</form>
|
||||
<form class='linkForm' method='post' action='index.html'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='❌' onclick="return confirmDelete('Tasmota LED1')">
|
||||
</form>
|
||||
</td></tr><tr><td>3</td>
|
||||
<td>Fan exhaust</td>
|
||||
<td>GPIO</td>
|
||||
<td>Fan</td>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<form class='linkForm' action='add?edit=0'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='✏️'>
|
||||
</form>
|
||||
<form class='linkForm' method='post' action='index.html'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='❌' onclick="return confirmDelete('Tasmota LED1')">
|
||||
</form>
|
||||
</td></tr><tr><td>4</td>
|
||||
<td>Fan inside</td>
|
||||
<td>GPIO</td>
|
||||
<td>Fan</td>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<form class='linkForm' action='add?edit=0'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='✏️'>
|
||||
</form>
|
||||
<form class='linkForm' method='post' action='index.html'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='❌' onclick="return confirmDelete('Tasmota LED1')">
|
||||
</form>
|
||||
</td></tr><tr><td>5</td>
|
||||
<td>Tasmota Dehum</td>
|
||||
<td>Webcall</td>
|
||||
<td>Dehumidifier</td>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<form class='linkForm' action='add?edit=0'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='✏️'>
|
||||
</form>
|
||||
<form class='linkForm' method='post' action='index.html'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input type='submit' value='❌' onclick="return confirmDelete('Tasmota LED1')">
|
||||
</form>
|
||||
</td></tr><tr><td>6</td>
|
||||
<td>Test</td>
|
||||
<td>GPIO</td>
|
||||
<td>Humidifier</td>
|
||||
<td>0</td>
|
||||
<td>
|
||||
<form class='linkForm' action='add?edit=0'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='✏️'>
|
||||
</form>
|
||||
<form class='linkForm' method='post' action='index.html'>
|
||||
<input type='hidden' name='del' value='0'>
|
||||
<input class='linkForm' type='submit' value='❌' onclick="return confirmDelete('Tasmota LED1')">
|
||||
</form>
|
||||
</td></tr>
|
||||
</table>
|
||||
<div class='footer'><span>Build: 40d0175-esp8266-20241026222209</span></div>
|
||||
</div></body></html>
|
||||
|
|
Loading…
Reference in a new issue