removed KiCad files, moved Arduino source code files
PCB lives now in its own git repo https://git.la10cy.net/DeltaLima/CanGrow-12V-PCB
This commit is contained in:
parent
6ca2cb5545
commit
28df687bf9
54 changed files with 10239 additions and 81 deletions
CanGrow.geanyCanGrow.inoREADME.mdScreenshot_montage.pngallbuild.sharduino-cli.ymlcangrow.sh
include
Architecture
CanGrow.hCanGrow_ConfigHelper.hCanGrow_Control.hCanGrow_Core.hCanGrow_LittleFS.hCanGrow_Logo.hCanGrow_Output.hCanGrow_Sensor.hCanGrow_Timer.hCanGrow_Webserver.hCanGrow_Wifi.hOutput
Sensor
00_Example.h01_ADC_builtin.h02_BME280.h03_BME680.h04_SHT3x.h05_MLX90614.h06_TCS34725.h07_ADS1115.h08_ADS1015.h09_Chirp.h10_CCS811.hSensor_Common.h
Webserver
61
CanGrow.geany
Normal file
61
CanGrow.geany
Normal file
|
@ -0,0 +1,61 @@
|
|||
[editor]
|
||||
line_wrapping=false
|
||||
line_break_column=72
|
||||
auto_continue_multiline=true
|
||||
|
||||
[file_prefs]
|
||||
final_new_line=true
|
||||
ensure_convert_new_lines=false
|
||||
strip_trailing_spaces=false
|
||||
replace_tabs=false
|
||||
|
||||
[indentation]
|
||||
indent_width=2
|
||||
indent_type=0
|
||||
indent_hard_tab_width=8
|
||||
detect_indent=false
|
||||
detect_indent_width=false
|
||||
indent_mode=2
|
||||
|
||||
[project]
|
||||
name=CanGrow
|
||||
base_path=./
|
||||
description=
|
||||
file_patterns=.ino,;.h;
|
||||
|
||||
[long line marker]
|
||||
long_line_behaviour=1
|
||||
long_line_column=72
|
||||
|
||||
[files]
|
||||
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=cd .. ; ./cangrow.sh build
|
||||
C++FT_00_WD=
|
||||
filetypes=C++;Arduino;Sh;
|
||||
ArduinoFT_00_LB=_Build
|
||||
ArduinoFT_00_CM=./cangrow.sh build
|
||||
ArduinoFT_00_WD=
|
||||
ArduinoFT_01_LB=Build & Upload
|
||||
ArduinoFT_01_CM=./cangrow.sh upload
|
||||
ArduinoFT_01_WD=
|
||||
C++FT_01_LB=_Build & Upload
|
||||
C++FT_01_CM=cd .. ; ./cangrow.sh upload
|
||||
C++FT_01_WD=
|
||||
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=
|
216
CanGrow.ino
Normal file
216
CanGrow.ino
Normal file
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Libraries include
|
||||
*/
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
// * ESP8266 *
|
||||
#ifdef ESP8266
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <WiFiClient.h>
|
||||
#endif
|
||||
|
||||
// * ESP32 *
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include <Update.h>
|
||||
#include <HTTPClient.h>
|
||||
#endif
|
||||
|
||||
#include <WiFiUdp.h>
|
||||
|
||||
// https://github.com/thijse/Arduino-Log/
|
||||
#include <ArduinoLog.h>
|
||||
|
||||
// https://github.com/mathieucarbou/ESPAsyncWebServer
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
// LittleFS filesystem
|
||||
#include "FS.h"
|
||||
// arduino-core for esp8266 and esp32
|
||||
#include "LittleFS.h"
|
||||
|
||||
//#include <SPI.h>
|
||||
#include <Wire.h>
|
||||
|
||||
// https://github.com/bblanchon/ArduinoJson
|
||||
#include <ArduinoJson.h>
|
||||
#include "AsyncJson.h"
|
||||
|
||||
// https://github.com/PaulStoffregen/Time
|
||||
#include <TimeLib.h>
|
||||
// https://github.com/arduino-libraries/NTPClient/
|
||||
#include <NTPClient.h>
|
||||
|
||||
// https://github.com/nusabot-iot/NusabotSimpleTimer/
|
||||
#include <NusabotSimpleTimer.h>
|
||||
|
||||
// https://github.com/adafruit/RTClib/
|
||||
#include "RTClib.h"
|
||||
|
||||
/*
|
||||
* CanGrow includes
|
||||
*/
|
||||
|
||||
/* main header file, where all variables, consts and structs get defined */
|
||||
#include "include/CanGrow.h"
|
||||
/* CanGrow platform specific includes */
|
||||
#include "include/Architecture/ESP8266.h"
|
||||
#include "include/Architecture/ESP32.h"
|
||||
#include "include/Architecture/ESP32_LOLIN_S2_MINI.h"
|
||||
#include "include/Architecture/ESP32_MAKERGO_C3_SUPERMINI.h"
|
||||
|
||||
/* CanGrow header with all functions
|
||||
* order is important - I need to learn how to do it right, so order is not important */
|
||||
#include "include/CanGrow_ConfigHelper.h"
|
||||
#include "include/CanGrow_Sensor.h"
|
||||
#include "include/CanGrow_Output.h"
|
||||
#include "include/CanGrow_Core.h"
|
||||
#include "include/CanGrow_Wifi.h"
|
||||
#include "include/CanGrow_LittleFS.h"
|
||||
|
||||
#include "include/CanGrow_Control.h"
|
||||
#include "include/CanGrow_Timer.h"
|
||||
#include "include/CanGrow_Webserver.h"
|
||||
|
||||
|
||||
|
||||
void setup() {
|
||||
/* Measure start up time */
|
||||
unsigned long millisFinish;
|
||||
// define output for onboard LED/WIPE pin
|
||||
pinMode(PinWIPE, OUTPUT);
|
||||
|
||||
|
||||
// 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");
|
||||
|
||||
// initiate ArduinoLog
|
||||
|
||||
Log.setPrefix(LogPrefix);
|
||||
Log.begin(LOG_LEVEL_VERBOSE, &Serial);
|
||||
// disable show loglevel, we do it in Prefix
|
||||
Log.setShowLevel(false);
|
||||
// set Log Location, to tell user at which part of the code we are
|
||||
const char LogLoc[] = "[SETUP]";
|
||||
|
||||
//Serial.printf(".:: CanGrow firmware v%s build %s starting ::.\n", CANGROW_VER, CANGROW_BUILD);
|
||||
Log.notice(F("CanGrow firmware v%s build %s starting ::" CR), CANGROW_VER, CANGROW_BUILD);
|
||||
|
||||
Log.warning(F("%s To format / factory reset LittleFS, pull GPIO %d (PinWIPE) to %d - NOW! (2 seconds left)" CR), LogLoc, PinWIPE, 1 - PinWIPE_default );
|
||||
|
||||
// blink with the onboard LED on D4/GPIO2 (PinWIPE)
|
||||
for(byte i = 0; i <= 6 ; i++) {
|
||||
if(i % 2) {
|
||||
digitalWrite(PinWIPE, 1 - PinWIPE_default);
|
||||
} else {
|
||||
digitalWrite(PinWIPE, PinWIPE_default);
|
||||
}
|
||||
delay(333);
|
||||
}
|
||||
|
||||
// set PinWIPE back to its default
|
||||
digitalWrite(PinWIPE, PinWIPE_default);
|
||||
|
||||
// read status from PinWIPE to WIPE
|
||||
// when PinWIPE is set to LOW, format LittleFS
|
||||
if(digitalRead(PinWIPE) != PinWIPE_default) {
|
||||
LFS_Format();
|
||||
Restart();
|
||||
}
|
||||
/* for ESP32-C3 supermini board compatibility, we initiate I2C here and not at the beginning
|
||||
* ESP32-C3 supermini board shares GPIO 8 Internal LED with I2C SDA */
|
||||
/* I2C init*/
|
||||
Wire.begin(Pin_I2C_SDA, Pin_I2C_SCL);
|
||||
|
||||
LFS_Init();
|
||||
LoadConfig();
|
||||
Wifi_Init();
|
||||
Webserver_Init();
|
||||
|
||||
Log.notice(F("%s Usable Pins: %d" CR), LogLoc, GPIOindex_length);
|
||||
// List all available pins
|
||||
for(byte i = 1; i <= GPIOindex_length; i++) {
|
||||
Log.notice(F("%s Pin Index: %d, GPIO: %d, Notes: %s" CR), LogLoc, i , GPIOindex[i].gpio, GPIO_Index_note_descr[GPIOindex[i].note]);
|
||||
}
|
||||
|
||||
// time init
|
||||
Time_Init();
|
||||
TimeR_Init();
|
||||
|
||||
|
||||
#ifdef ESP8266
|
||||
/* set pwm frequency global for ESP8266.
|
||||
* ESP32 pwm frequency setting is done withing CanGrow_Output / Init */
|
||||
analogWriteFreq(config.system.pwmFreq);
|
||||
#endif
|
||||
|
||||
Output_Init();
|
||||
|
||||
Sensor_Init();
|
||||
|
||||
Log.notice(F("%s Done. Startup took : %u ms" CR), LogLoc, millis());
|
||||
}
|
||||
|
||||
bool alrdySaved = false;
|
||||
|
||||
void loop() {
|
||||
const char LogLoc[] = "[LOOP]";
|
||||
|
||||
/* Execute main timer, runs Timer_1s, Timer_3s, Timer_5s by default */
|
||||
timer.run();
|
||||
|
||||
// if global var doRestart is true, perform a restart
|
||||
if(doRestart == true) {
|
||||
/* wait 100ms after Restart got triggered. This should workaround some crash problems with AsyncWebserver stuff
|
||||
* for example when updating the firmware by web upload */
|
||||
Log.verbose(F("%s Restart got triggered. Waiting 100ms before doing it" CR), LogLoc);
|
||||
timer.setTimeout(100, Restart);
|
||||
//Restart();
|
||||
}
|
||||
|
||||
// does ntp offset need an update?
|
||||
if(updateNtpOffset) {
|
||||
/* doing ntp offset update here, because when doing it in the webserver:system function
|
||||
* where the new value gets entered, it sometimes crashed */
|
||||
NTP_OffsetUpdate();
|
||||
updateNtpOffset = false;
|
||||
}
|
||||
}
|
121
README.md
121
README.md
|
@ -1,94 +1,53 @@
|
|||
# CanGrow
|
||||
# CanGrow - An OpenSource grow controller firmware for ESP8266 / ESP32
|
||||

|
||||
|
||||
|
||||

|
||||
|
||||
## Build environment
|
||||
The helper script `cangrow.sh` is written for a Debian 12 system.
|
||||
|
||||
An easy to use DIY grow controller firmware (for cannabis).
|
||||
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
|
||||
|
||||
# WORK IN PROGRESS
|
||||
# Install all dependencies for build environment
|
||||
$ ./cangrow.sh setup
|
||||
```
|
||||
|
||||
## Motivation
|
||||
I havn't found an already existing grow controller project within the ESP / Arduino Core eco system which
|
||||
met my personal requirements.
|
||||
Those are an easy DIY, using low cost parts, Arduino Core sourcecode to hack own things together, having a WebUI, grab some Metrics for monitoring, standalone and my very special need that the Hardware should run completely with 12V.
|
||||
The script installs [arduino-cli](https://github.com/arduino/arduino-cli) to `~/.local/bin/arduino-cli`.
|
||||
|
||||
### Update 14.09.2024 - Code Rewrite v0.2
|
||||
## Compile
|
||||
|
||||
I took some "summer break" from the project, and had the opportunity to talk to different people about it.
|
||||
My conclusion at this point is, that the focus of this project is not the Hardware, it came out that it should be the software.
|
||||
So I decided to completely rewrite the code from 0 - with recycling some parts of it.
|
||||
Goal of the Rewrite is that the Firmware becomes more independent of the hardware used. It has to support both ESP8266 and ESP32
|
||||
and let the user decide at which pin which output, sensor or whatever will be connected to. Like done in the [Tasmota](https://github.com/arendst/Tasmota) Firmware, I also want to support "Hardware Templates" which come with presets for PCBs like the one I created.
|
||||
```sh
|
||||
# compile and output to build/CanGrow_v0.2...bin
|
||||
# Default Target is ESP8266 D1 Mini
|
||||
$ ./cangrow.sh build
|
||||
|
||||
**Checklist for v0.2 Firmware**
|
||||
- Support ESP8266 and ESP32
|
||||
- AsyncWebserver instead ESP8266Webserver
|
||||
- LittleFS instead of EEPROM()
|
||||
- deliver static HTML, dynamic Stuff with Javascript
|
||||
- (or is there a better way? please tell me!)
|
||||
- Free configurable outputs
|
||||
- Main outputs for Light, Air, Water
|
||||
- Support for Tasmota Wifi Plugs (and others?)
|
||||
- No Limitation for Amount of outputs
|
||||
- Light
|
||||
- support for I2C 0-10V Dimm control
|
||||
- PWM dimm control
|
||||
- Air
|
||||
- support for I2C 0-10V Dimm control
|
||||
- PWM dimm control
|
||||
- Support for humidifier, heater (, CO2?)
|
||||
- Read Fan RPM
|
||||
- Water
|
||||
- Usual watering
|
||||
- Pump for fertilizer
|
||||
- Free configurable Inputs
|
||||
- Support for various I2C devices
|
||||
- All kind of sensors for Temp, Humidity, Moisture, and so on
|
||||
- Support for ADCs to connect multiple analoge sensors
|
||||
- Support for Analog inputs
|
||||
- onboard ones or I2C (ADC)
|
||||
- Analog Multiplexer support (like CD4051)
|
||||
- Calibrate sensors
|
||||
- define 0% and 100% values
|
||||
- Offsets
|
||||
- MQTT support
|
||||
- API
|
||||
# Compile for ESP32 D1 Mini
|
||||
$ export BOARD="esp32:esp32:d1_mini32"
|
||||
$ ./cangrow.sh build
|
||||
|
||||
|
||||
|
||||
## Old v0.1 Features / ToDo List
|
||||
# Build and webupload to IP
|
||||
$ export IP="192.168.4.69"
|
||||
$ ./cangrow.sh build # need to make .bin first
|
||||
$ ./cangrow.sh webupload # upload
|
||||
|
||||
- Measure values :white_check_mark:
|
||||
- Humidity :white_check_mark:
|
||||
- soil moisture :white_check_mark:
|
||||
- temperature :white_check_mark:
|
||||
- water level for water tank :white_check_mark:
|
||||
- LED grow light control (on/off, dimming, max. 12V 50W load ) :white_check_mark:
|
||||
- You can of course use a relais as well, if you want to drive 220V lights :white_check_mark:
|
||||
- fan control (on/off, (PWM?) max 1A) :white_check_mark:
|
||||
- pump control for automatic watering (max 1A) :large_blue_circle:
|
||||
- Web UI and REST API for data and controlling :large_blue_circle:
|
||||
- simple web ui :white_check_mark:
|
||||
- REST API :large_blue_circle:
|
||||
- Send notifications with web call (e.g. for mastodon) :red_circle:
|
||||
- predefined grow profiles :large_blue_circle:
|
||||
- persistent data :white_check_mark:
|
||||
- Start of Grow :white_check_mark:
|
||||
- day of grow :large_blue_circle:
|
||||
- grow profile
|
||||
- watering amount per week :large_blue_circle:
|
||||
- light cycle :white_check_mark:
|
||||
- wifi settings :white_check_mark:
|
||||
- settings in general :white_check_mark:
|
||||
- Easy to build and use for beginners (i hope so!) :white_check_mark:
|
||||
- PCB layout to order from manufacture (jlcpcb or pcbway) :white_check_mark:
|
||||
- easy to build up on a perfboard :white_check_mark:
|
||||
- easy to etch pcb :white_check_mark:
|
||||
- easy to access and modify :white_check_mark:
|
||||
- low cost as possible! :white_check_mark:
|
||||
# listen to serial monitor on /dev/ttyUSB2
|
||||
$ export TTY="/dev/ttyUSB2"
|
||||
./cangrow.sh monitor
|
||||
```
|
||||
|
||||
:white_check_mark: Done - :large_blue_circle: In Progress - :red_circle: ToDo
|
||||
I wrote this project using [Geany IDE. ](https://www.geany.org/). The Geany Projectfile is also included, just run
|
||||
```sh
|
||||
$ geany CanGrow.geany
|
||||
```
|
||||
|
||||
**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.
|
||||
|
|
BIN
Screenshot_montage.png
Normal file
BIN
Screenshot_montage.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 940 KiB |
10
allbuild.sh
Executable file
10
allbuild.sh
Executable file
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
|
||||
rm -Rf build/*
|
||||
for board in esp8266:esp8266:d1_mini_clone esp32:esp32:d1_mini32 esp32:esp32:makergo_c3_supermini esp32:esp32:lolin_s2_mini
|
||||
do
|
||||
echo "Build firmware binary for $board"
|
||||
echo "==================================================================="
|
||||
BOARD="$board" ./cangrow.sh build
|
||||
done
|
4
arduino-cli.yml
Normal file
4
arduino-cli.yml
Normal file
|
@ -0,0 +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
|
167
cangrow.sh
Executable file
167
cangrow.sh
Executable file
|
@ -0,0 +1,167 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
|
||||
test -z $TTY && TTY="/dev/ttyUSB0"
|
||||
test -z $IP && IP="192.168.4.20"
|
||||
test -z $VER && VER="$(grep "define CANGROW_VER" include/CanGrow.h | cut -d \" -f2 |sed -e 's/\"//g')" #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)_$(echo $BOARD | cut -d : -f3)-$(date '+%Y%m%d%H%M%S')"
|
||||
|
||||
# arduino-cli path and version
|
||||
ACLI="$HOME/.local/bin/arduino-cli"
|
||||
ACLI_VER="1.2.0"
|
||||
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)"
|
||||
ALIB_DIR="${HOME}/Arduino/libraries/"
|
||||
declare -a CORES=(
|
||||
"esp8266:esp8266@3.1.2"
|
||||
"esp32:esp32@3.0.7"
|
||||
)
|
||||
declare -a LIBS=(
|
||||
"Adafruit SSD1306@2.5.12"
|
||||
"Adafruit BME280 Library@2.2.4"
|
||||
"ArduinoJson@7.3.0"
|
||||
"NTPClient@3.2.1"
|
||||
"Time@1.6.1"
|
||||
"ESP Async WebServer@3.6.0"
|
||||
"Async TCP@3.3.2"
|
||||
"Nusabot Simple Timer@1.0.0"
|
||||
"ArduinoLog@1.1.1"
|
||||
"RTClib@2.1.4"
|
||||
"Adafruit BME680 Library@2.0.5"
|
||||
"Adafruit ADS1X15@2.5.0"
|
||||
"Adafruit SHT31 Library@2.2.2"
|
||||
"Adafruit MCP4725@2.0.2"
|
||||
"Adafruit TCS34725@1.4.4"
|
||||
"Adafruit MLX90614 Library@2.1.5"
|
||||
"I2CSoilMoistureSensor@1.1.4"
|
||||
"DFRobot_GP8XXX@1.0.1"
|
||||
"Adafruit CCS811 Library@1.1.3"
|
||||
)
|
||||
|
||||
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 ${ACLI_VER} into ${ACLI_DIR}/"
|
||||
wget -O - "https://github.com/arduino/arduino-cli/releases/download/v${ACLI_VER}/arduino-cli_${ACLI_VER}_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 ":: fetching ESPAsyncTCP-esphome from GIT"
|
||||
wget -q https://github.com/mathieucarbou/esphome-ESPAsyncTCP/archive/refs/tags/v2.0.0.tar.gz -O - | tar -xzf - -C $ALIB_DIR
|
||||
mv $ALIB_DIR/esphome-ESPAsyncTCP-2.0.0 $ALIB_DIR/ESPAsyncTCP-esphome
|
||||
echo ":: Patching ArduinoLog (https://github.com/thijse/Arduino-Log/pull/28/commits/57d350a25428376935b793a2138210320cf3801c)"
|
||||
sed -i -e 's/register//g' $ALIB_DIR/ArduinoLog/ArduinoLog.cpp
|
||||
|
||||
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
|
69
include/Architecture/ESP32.h
Normal file
69
include/Architecture/ESP32.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_ESP32.h - ESP32 specific header file for generic ESP32_DEV board
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
#if defined(ARDUINO_ESP32_DEV) || defined(ARDUINO_D1_MINI32)
|
||||
|
||||
#define PinWIPE 2
|
||||
#define PinWIPE_default LOW
|
||||
#define Pin_I2C_SCL 22
|
||||
#define Pin_I2C_SDA 21
|
||||
|
||||
/* https://randomnerdtutorials.com/esp32-pinout-reference-gpios/
|
||||
*
|
||||
* free usable pins
|
||||
* - GPIO 0 PU OK outputs PWM signal at boot, must be LOW to enter flashing mode
|
||||
* - GPIO 4 OK OK
|
||||
* - GPIO 5 OK OK outputs PWM signal at boot, strapping pin
|
||||
* - GPIO 12 OK OK boot fails if pulled high, strapping pin
|
||||
* - GPIO 13 OK OK
|
||||
* - GPIO 14 OK OK outputs PWM signal at boot
|
||||
* - GPIO 15 OK OK outputs PWM signal at boot, strapping pin
|
||||
* - GPIO 16 OK OK
|
||||
* - GPIO 17 OK OK
|
||||
* - GPIO 18 OK OK
|
||||
* - GPIO 19 OK OK
|
||||
* - GPIO 23 OK OK
|
||||
* - GPIO 25 OK OK
|
||||
* - GPIO 26 OK OK
|
||||
* - GPIO 27 OK OK
|
||||
* - GPIO 32 OK OK
|
||||
* - GPIO 33 OK OK
|
||||
* - GPIO 34 OK input only
|
||||
* - GPIO 35 OK input only
|
||||
* - GPIO 36 OK input only
|
||||
* - GPIO 39 OK input only
|
||||
*/
|
||||
|
||||
|
||||
//
|
||||
const byte GPIOindex_length = 21;
|
||||
// initialize pinIndex with all usable GPIOs
|
||||
GPIO_Index GPIOindex[] = {{ 255, 255 },
|
||||
{ 0, FLASHMODE_LOW },
|
||||
{ 4 },
|
||||
{ 5 },
|
||||
{ 12, BOOTFAILS_HIGH },
|
||||
{ 13 },
|
||||
{ 14 },
|
||||
{ 15 },
|
||||
{ 16 },
|
||||
{ 17 },
|
||||
{ 18 },
|
||||
{ 19 },
|
||||
{ 23 },
|
||||
{ 25, INT_DAC },
|
||||
{ 26, INT_DAC },
|
||||
{ 27 },
|
||||
{ 32, INT_ADC },
|
||||
{ 33, INT_ADC },
|
||||
{ 34, INPUT_ONLY },
|
||||
{ 35, INPUT_ONLY },
|
||||
{ 36, INPUT_ONLY },
|
||||
{ 39, INPUT_ONLY }
|
||||
};
|
||||
#endif
|
74
include/Architecture/ESP32_LOLIN_S2_MINI.h
Normal file
74
include/Architecture/ESP32_LOLIN_S2_MINI.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_ESP32.h - ESP32 specific header file for Lolin S2 Mini
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
#ifdef ARDUINO_LOLIN_S2_MINI
|
||||
|
||||
#define PinWIPE 15
|
||||
#define PinWIPE_default LOW
|
||||
#define Pin_I2C_SCL 33
|
||||
#define Pin_I2C_SDA 35
|
||||
|
||||
|
||||
/* https://done.land/components/microcontroller/families/esp/esp32/developmentboards/esp32-s2/s2mini/
|
||||
*
|
||||
* free usable pins
|
||||
Pin Remark Description
|
||||
EN Reset button
|
||||
3V3 direct power supply to CPU
|
||||
VBUS connected to ME6211C33 voltage regulator
|
||||
0 not exposed Boot button pulls it low
|
||||
1-6 general purpose: analog input (ADC1) and digital in/output
|
||||
7 SPI SCK general purpose: analog input (ADC1) and digital in/output
|
||||
8 general purpose: analog input (ADC1) and digital in/output
|
||||
9 SPI MISO general purpose: analog input (ADC1) and digital in/output
|
||||
10 general purpose: analog input (ADC1) and digital in/output
|
||||
11 SPI MOSI general purpose: analog input (ADC2) and digital in/output
|
||||
12 SPI SS general purpose: analog input (ADC2) and digital in/output
|
||||
13-14 general purpose: analog input (ADC2) and digital in/output
|
||||
15 internal LED general purpose: analog input (ADC2) and digital in/output
|
||||
16 general purpose: analog input (ADC2) and digital in/output
|
||||
17 DAC1 general purpose: analog input (ADC2) and digital in/output
|
||||
18 DAC2 general purpose: analog input (ADC2) and digital in/output
|
||||
19, 20 not exposed USB D1/D2, connected to the USB C connector
|
||||
21 general purpose digital in/output
|
||||
33 I2C SDA general purpose digital in/output
|
||||
34 general purpose digital in/output
|
||||
35 I2C SCL general purpose digital in/output
|
||||
36-40 general purpose digital in/output
|
||||
*/
|
||||
|
||||
|
||||
//
|
||||
const byte GPIOindex_length = 24;
|
||||
// initialize pinIndex with all usable GPIOs
|
||||
GPIO_Index GPIOindex[] = {{ 255, 255 },
|
||||
{ 1, INT_ADC },
|
||||
{ 2, INT_ADC },
|
||||
{ 3, INT_ADC },
|
||||
{ 4, INT_ADC },
|
||||
{ 5, INT_ADC },
|
||||
{ 6, INT_ADC },
|
||||
{ 7, INT_ADC },
|
||||
{ 8, INT_ADC },
|
||||
{ 9, INT_ADC },
|
||||
{ 10, INT_ADC },
|
||||
{ 11 },
|
||||
{ 12 },
|
||||
{ 13 },
|
||||
{ 14 },
|
||||
{ 16 },
|
||||
{ 17, INT_DAC },
|
||||
{ 18, INT_DAC },
|
||||
{ 21 },
|
||||
{ 34 },
|
||||
{ 36 },
|
||||
{ 37 },
|
||||
{ 38 },
|
||||
{ 39 },
|
||||
{ 40 }
|
||||
};
|
||||
#endif
|
48
include/Architecture/ESP32_MAKERGO_C3_SUPERMINI.h
Normal file
48
include/Architecture/ESP32_MAKERGO_C3_SUPERMINI.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
*
|
||||
* include/Platform/ESP32_MAKERGO_C3_SUPERMINI.h - ESP32 specific header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
#ifdef ARDUINO_MAKERGO_C3_SUPERMINI
|
||||
|
||||
#define PinWIPE 8
|
||||
#define PinWIPE_default HIGH
|
||||
#define Pin_I2C_SCL 9
|
||||
#define Pin_I2C_SDA 8
|
||||
|
||||
/* https://www.sudo.is/docs/esphome/boards/esp32c3supermini/
|
||||
*
|
||||
* free usable pins
|
||||
0 GPIO0 ADC1
|
||||
1 GPIO1 ADC1
|
||||
2 GPIO2 ADC1, boot mode / strapping pin
|
||||
3 GPIO3 ADC1
|
||||
4 GPIO4 ADC1, JTAG
|
||||
5 GPIO5 JTAG
|
||||
6 GPIO6 JTAG
|
||||
7 GPIO7 JTAG
|
||||
8 GPIO8 Blue status_led (inverted), boot mode / strapping pin
|
||||
9 GPIO9 Boot mode / strapping pin, boot button
|
||||
10 GPIO10
|
||||
20 GPIO20 RX
|
||||
21 GPIO21 TX
|
||||
*/
|
||||
|
||||
|
||||
//
|
||||
const byte GPIOindex_length = 9;
|
||||
// initialize pinIndex with all usable GPIOs
|
||||
GPIO_Index GPIOindex[] = {{ 255, 255 },
|
||||
{ 0, INT_ADC },
|
||||
{ 1, INT_ADC },
|
||||
{ 2, INT_ADC },
|
||||
{ 3, INT_ADC },
|
||||
{ 4, INT_ADC },
|
||||
{ 5 },
|
||||
{ 6 },
|
||||
{ 7 },
|
||||
{ 10 }
|
||||
};
|
||||
#endif
|
51
include/Architecture/ESP8266.h
Normal file
51
include/Architecture/ESP8266.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_ESP8266.h - ESP8266 specific header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
#ifdef ESP8266
|
||||
|
||||
// GPIO 2 Boot fails if pulled to LOW
|
||||
#define PinWIPE 2
|
||||
#define PinWIPE_default HIGH
|
||||
#define Pin_I2C_SCL 5
|
||||
#define Pin_I2C_SDA 4
|
||||
|
||||
/* https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/
|
||||
*
|
||||
* free usable pins
|
||||
* - GPIO 0 / D3 boot fails if pulled LOW
|
||||
* - GPIO 12 / D6
|
||||
* - GPIO 13 / D7
|
||||
* - GPIO 14 / D5
|
||||
* - GPIO 15 / D8 Boot fails if pulled HIGH
|
||||
* - GPIO 16 / D0
|
||||
*/
|
||||
|
||||
const byte GPIOindex_length = 6;
|
||||
// initialize pinIndex with all usable GPIOs
|
||||
GPIO_Index GPIOindex[] = {{ 255, 255 },
|
||||
{ 0, BOOTFAILS_LOW },
|
||||
{ 12 },
|
||||
{ 13 },
|
||||
{ 14 },
|
||||
{ 15, BOOTFAILS_HIGH },
|
||||
{ 16, NO_PWM } };
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/* CanGrow 12V PCB v0.6 Pin assignment
|
||||
*
|
||||
*
|
||||
* LED - D6 (GPIO 12)
|
||||
* FAN1 - D5 (GPIO 14)
|
||||
* FAN2 - D3 (GPIO 0)
|
||||
* PUMP - D0 (GPIO 16)
|
||||
*
|
||||
* WaterlevelVCC - D7 (GPIO 13)
|
||||
* SoilmoistureVCC - D8 (GPIO 15)
|
||||
*
|
||||
*/
|
411
include/CanGrow.h
Normal file
411
include/CanGrow.h
Normal file
|
@ -0,0 +1,411 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow.h - main header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/* If you need detailed debug output, uncomment the following lines.
|
||||
* DEBUG is less noisy messages
|
||||
* DEBUG2 are noisy messages
|
||||
* DEBUG3 are super noisy messages */
|
||||
//#define DEBUG
|
||||
//#define DEBUG2
|
||||
//#define DEBUG3
|
||||
|
||||
/* 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.2-dev2"
|
||||
#endif
|
||||
#ifndef CANGROW_BUILD
|
||||
#define CANGROW_BUILD "0420"
|
||||
#endif
|
||||
#ifndef CANGROW_BUILDTIME
|
||||
#define CANGROW_BUILDTIME "1711922400" // 1.4.2024
|
||||
#endif
|
||||
|
||||
#define CANGROW_DEFAULT_WIFI_SSID "CanGrow-unconfigured"
|
||||
#define CANGROW_DEFAULT_WIFI_PASSWORD "letitgrow!"
|
||||
|
||||
#define CANGROW_CFG "/config.json"
|
||||
#define TIME2FS "/time"
|
||||
|
||||
/* define Max limits for outputs and sensors */
|
||||
const byte Max_Outputs = 16;
|
||||
const byte Max_Sensors = 16;
|
||||
/* How much values can a sensor contain at max */
|
||||
const byte Max_Sensors_Read = 6;
|
||||
/* how much GPIOs a Sensor can use */
|
||||
const byte Max_Sensors_GPIO = 2;
|
||||
|
||||
|
||||
/* 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
|
||||
* 7 - INT_ADC Pin for internal ADC (only ESP32, ESP8266 only has one Pin, A0)
|
||||
*/
|
||||
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;
|
||||
const byte INT_ADC = 7;
|
||||
const byte INT_DAC = 8;
|
||||
|
||||
|
||||
//const char signMessage[] PROGMEM = {"I AM PREDATOR, UNSEEN COMBATANT. CREATED BY THE UNITED STATES DEPART"};
|
||||
|
||||
|
||||
const char BOOTFAILS_LOW_descr[] PROGMEM = {"BF_LOW"};
|
||||
const char BOOTFAILS_HIGH_descr[] PROGMEM = {"BF_HIGH"};
|
||||
const char FLASMODE_LOW_descr[] PROGMEM = {"FM_LOW"};
|
||||
const char INPUT_ONLY_descr[] PROGMEM = {"IN_ONLY"};
|
||||
const char NO_PWM_descr[] PROGMEM = {"NO_PWM"};
|
||||
const char HIGH_BOOT_descr[] PROGMEM = {"B_HIGH"};
|
||||
const char INT_ADC_descr[] PROGMEM = {"INT_ADC"};
|
||||
const char INT_DAC_descr[] PROGMEM = {"INT_DAC"};
|
||||
|
||||
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
|
||||
INT_ADC_descr, // 7
|
||||
INT_DAC_descr, // 8
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* RTCs available
|
||||
*/
|
||||
|
||||
// 0 is unconfigured
|
||||
const byte RTCs_total = 4;
|
||||
|
||||
const byte RTCs_DS1307 = 1;
|
||||
const byte RTCs_DS3231 = 2;
|
||||
const byte RTCs_PCF8523 = 3;
|
||||
const byte RTCs_PCF8563 = 4;
|
||||
|
||||
const char RTCs_DS1307_descr[] PROGMEM = {"DS1307"};
|
||||
const char RTCs_DS3231_descr[] PROGMEM = {"DS3231"};
|
||||
const char RTCs_PCF8523_descr[] PROGMEM = {"PCF8523"};
|
||||
const char RTCs_PCF8563_descr[] PROGMEM = {"PCF8563"};
|
||||
|
||||
const char * RTCs_descr[] = {
|
||||
NULL, // unconfigured
|
||||
RTCs_DS1307_descr,
|
||||
RTCs_DS3231_descr,
|
||||
RTCs_PCF8523_descr,
|
||||
RTCs_PCF8563_descr,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Time scales
|
||||
*/
|
||||
|
||||
// 0 is unconfigured
|
||||
const byte TIMESCALE_total = 7;
|
||||
|
||||
const byte TIMESCALE_SECOND = 0;
|
||||
const byte TIMESCALE_MINUTE = 1;
|
||||
const byte TIMESCALE_HOUR = 2;
|
||||
const byte TIMESCALE_DAY = 3;
|
||||
const byte TIMESCALE_WEEK = 4;
|
||||
const byte TIMESCALE_MONTH = 5;
|
||||
const byte TIMESCALE_YEAR = 6;
|
||||
|
||||
|
||||
const char TIMESCALE_SECOND_descr[] PROGMEM = {"Second"};
|
||||
const char TIMESCALE_MINUTE_descr[] PROGMEM = {"Minute"};
|
||||
const char TIMESCALE_HOUR_descr[] PROGMEM = {"Hour"};
|
||||
const char TIMESCALE_DAY_descr[] PROGMEM = {"Day"};
|
||||
const char TIMESCALE_WEEK_descr[] PROGMEM = {"Week"};
|
||||
const char TIMESCALE_MONTH_descr[] PROGMEM = {"Month"};
|
||||
const char TIMESCALE_YEAR_descr[] PROGMEM = {"Year"};
|
||||
|
||||
const char * Timescale_descr[] = {
|
||||
TIMESCALE_SECOND_descr,
|
||||
TIMESCALE_MINUTE_descr,
|
||||
TIMESCALE_HOUR_descr,
|
||||
TIMESCALE_DAY_descr,
|
||||
TIMESCALE_WEEK_descr,
|
||||
TIMESCALE_MONTH_descr,
|
||||
TIMESCALE_YEAR_descr,
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* GPIO Index struct
|
||||
* filled with CanGrow_ESP8266.h and CanGrow_ESP32.h
|
||||
*/
|
||||
|
||||
struct GPIO_Index {
|
||||
const byte gpio;
|
||||
const byte note;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* Config
|
||||
*
|
||||
* Note: when adding/removing/changing a saved Config variable
|
||||
* you have to touch the config struct, LoadConfig() and SaveConfig() at least too!
|
||||
*/
|
||||
|
||||
/*
|
||||
* Config WiFi
|
||||
*/
|
||||
struct Config_WiFi {
|
||||
char ssid[32];
|
||||
char password[64];
|
||||
bool dhcp;
|
||||
byte ip[4] = {192,168,4,20};
|
||||
byte netmask[4] = {255,255,255,0};
|
||||
byte gateway[4] = {0,0,0,0};
|
||||
byte dns[4] = {0,0,0,0};
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Config System
|
||||
*/
|
||||
|
||||
struct Config_System_Output {
|
||||
|
||||
/*
|
||||
* Config System Output
|
||||
*
|
||||
* - type: output type like GPIO, I2C, URL
|
||||
* 1 - GPIO
|
||||
* 2 - I2C
|
||||
* 3 - Web
|
||||
* - device: what this output is connected to
|
||||
* 1 - Light
|
||||
* 2 - Fan
|
||||
* 3 - Pump
|
||||
* 4 - Humudifier
|
||||
* 5 - Dehumidifier
|
||||
* 6 - Heating
|
||||
* - name: name of output
|
||||
* - enabled: enable output
|
||||
* - gpio: which gpio is used
|
||||
* - invert: invert 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 invert[Max_Outputs];
|
||||
byte i2c_type[Max_Outputs];
|
||||
byte i2c_addr[Max_Outputs];
|
||||
byte i2c_port[Max_Outputs];
|
||||
char webcall_host[Max_Outputs][32];
|
||||
char webcall_path_on[Max_Outputs][32];
|
||||
char webcall_path_off[Max_Outputs][32];
|
||||
char webcall_user[Max_Outputs][32];
|
||||
char webcall_password[Max_Outputs][32];
|
||||
};
|
||||
|
||||
struct Config_System_Sensor {
|
||||
/*
|
||||
* Config System Sensor
|
||||
* - type: Index ID of SensorIndex, which Sensor to use (ADC, BME280, Chirp, ...)
|
||||
* - name: nice name
|
||||
* - gpio[]: gpio to use for RPM reading, builtin ADC, OneWire, TwoWire
|
||||
*/
|
||||
|
||||
byte type[Max_Sensors];
|
||||
char name[Max_Sensors][32];
|
||||
byte i2c_addr[Max_Sensors];
|
||||
byte gpio[Max_Sensors][Max_Sensors_GPIO];
|
||||
float offset[Max_Sensors][Max_Sensors_Read];
|
||||
unsigned int low[Max_Sensors][Max_Sensors_Read];
|
||||
unsigned int high[Max_Sensors][Max_Sensors_Read];
|
||||
byte rawConvert[Max_Sensors][Max_Sensors_Read];
|
||||
};
|
||||
|
||||
/* main System struct */
|
||||
struct Config_System {
|
||||
bool ntp = true;
|
||||
byte rtc;
|
||||
bool time2fs;
|
||||
short ntpOffset;
|
||||
unsigned short maintenanceDuration;
|
||||
char esp32cam[16];
|
||||
char httpUser[32];
|
||||
char httpPass[32];
|
||||
bool httpLogSerial;
|
||||
unsigned short schedulerInterval = 1000;
|
||||
unsigned short pwmFreq = 13370;
|
||||
Config_System_Output output;
|
||||
Config_System_Sensor sensor;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Config Grow
|
||||
*/
|
||||
|
||||
struct Config_Grow_Light {
|
||||
bool configured[Max_Outputs];
|
||||
byte output[Max_Outputs];
|
||||
byte sunriseHourVeg[Max_Outputs];
|
||||
byte sunriseMinuteVeg[Max_Outputs];
|
||||
byte sunsetHourVeg[Max_Outputs];
|
||||
byte sunsetMinuteVeg[Max_Outputs];
|
||||
|
||||
byte sunriseHourBloom[Max_Outputs];
|
||||
byte sunriseMinuteBloom[Max_Outputs];
|
||||
byte sunsetHourBloom[Max_Outputs];
|
||||
byte sunsetMinuteBloom[Max_Outputs];
|
||||
|
||||
byte power[Max_Outputs];
|
||||
bool fade[Max_Outputs];
|
||||
byte fadeDuration[Max_Outputs];
|
||||
};
|
||||
|
||||
struct Config_Grow_Air {
|
||||
bool configured[Max_Outputs];
|
||||
byte output[Max_Outputs];
|
||||
byte power[Max_Sensors];
|
||||
byte controlSensor[Max_Outputs];
|
||||
byte controlRead[Max_Outputs];
|
||||
byte controlMode[Max_Outputs];
|
||||
float min[Max_Outputs];
|
||||
float max[Max_Outputs];
|
||||
};
|
||||
|
||||
struct Config_Grow_Water {
|
||||
bool configured[Max_Outputs];
|
||||
byte output[Max_Outputs];
|
||||
byte controlSensor[Max_Outputs];
|
||||
byte controlRead[Max_Outputs];
|
||||
byte controlMode[Max_Outputs];
|
||||
byte onTime[Max_Sensors];
|
||||
byte min[Max_Sensors];
|
||||
byte max[Max_Sensors];
|
||||
byte interval[Max_Sensors];
|
||||
byte intervalUnit[Max_Sensors];
|
||||
};
|
||||
|
||||
struct Config_Grow_Dashboard {
|
||||
bool configured[Max_Sensors][Max_Sensors_Read];
|
||||
byte sensor[Max_Sensors][Max_Sensors_Read];
|
||||
};
|
||||
|
||||
struct Config_Grow {
|
||||
char name[64] = "CanGrow";
|
||||
unsigned long start;
|
||||
byte daysVeg = 42;
|
||||
byte daysBloom = 69;
|
||||
Config_Grow_Light light;
|
||||
Config_Grow_Air air;
|
||||
Config_Grow_Water water;
|
||||
Config_Grow_Dashboard dashboard;
|
||||
//unsigned short dayOfGrow;
|
||||
//byte daysSeed;
|
||||
|
||||
//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;
|
||||
/* in which time status is the system
|
||||
* 0 - OK
|
||||
* 1 - RTC fallback is used
|
||||
* 2 - Time2FS fallback is used
|
||||
*/
|
||||
byte timeSrcStatus;
|
||||
|
||||
/* rtcError - false no Error, true had error while init */
|
||||
bool rtcError = false;
|
||||
// did ntp offset got changed?
|
||||
bool updateNtpOffset = false;
|
||||
/* sensorStatus[] to keep track if sensor init succeeded or not, true is OK */
|
||||
bool sensorStatus[Max_Sensors];
|
||||
/* outputStatus[] to keep track if output init succeeded or not, true is OK */
|
||||
bool outputStatus[Max_Outputs];
|
||||
/* outputState[] gets read by Output_Update() */
|
||||
byte outputState[Max_Outputs];
|
||||
/* keep track how often a http call failed */
|
||||
byte outputWebcallFailed[Max_Outputs];
|
||||
|
||||
/* remember timestamp when pump was turned on to turn it off after config.grow.water.onTime */
|
||||
unsigned long controlWaterLastStarted[Max_Outputs];
|
||||
/* remember timestamp when last water cycle was done.*/
|
||||
unsigned long controlWaterLast[Max_Outputs];
|
22
include/CanGrow_ConfigHelper.h
Normal file
22
include/CanGrow_ConfigHelper.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_Core.h - core stuff header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/* Give free grow.light id */
|
||||
byte Give_Free_Grow_LightId() {
|
||||
byte freeId;
|
||||
for(byte i = 0; i < Max_Outputs; i++) {
|
||||
if(config.grow.light.configured[i] == true) {
|
||||
// here i define that 255 stands for "no more free outputs"
|
||||
freeId = 255;
|
||||
} else {
|
||||
freeId = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return freeId;
|
||||
}
|
407
include/CanGrow_Control.h
Normal file
407
include/CanGrow_Control.h
Normal file
|
@ -0,0 +1,407 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_Control.h - control stuff for light,air,water header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* Light stuff
|
||||
*
|
||||
*/
|
||||
|
||||
/* Light fade */
|
||||
byte Light_Power(byte id, unsigned int sunriseSec, unsigned int sunsetSec, unsigned int nowSec, bool shifted) {
|
||||
const static char LogLoc[] PROGMEM = "[Control:Light_Power]";
|
||||
if(config.grow.light.fade[id] == true) {
|
||||
unsigned int fadeDurationSec = config.grow.light.fadeDuration[id] * 60;
|
||||
byte power_tmp;
|
||||
//byte power_tmp; // = (durationSec - ((sunriseSec + durationSec) - nowSec) * config.grow.light.power[id] / durationSec);
|
||||
|
||||
/* rising sun */
|
||||
if(nowSec <= sunriseSec + fadeDurationSec) {
|
||||
/* calculate fade power value */
|
||||
//power_tmp = ( ( (nowSec - sunriseSec) / (fadeDurationSec / 255) ) * config.grow.light.power[id] ) / 255;
|
||||
power_tmp = (fadeDurationSec - ((sunriseSec + fadeDurationSec) - nowSec)) * config.grow.light.power[id] / fadeDurationSec;
|
||||
/* setting sun */
|
||||
} else if((nowSec >= sunsetSec - fadeDurationSec) && (nowSec <= sunsetSec)) {
|
||||
/* calculate fade power value */
|
||||
//power_tmp = ( ( (sunsetSec - nowSec) / (fadeDurationSec / 255) ) * config.grow.light.power[id] ) / 255;
|
||||
power_tmp = (sunsetSec - nowSec) * config.grow.light.power[id] / fadeDurationSec;
|
||||
} else {
|
||||
/* otherwise just turn the light on with configured value */
|
||||
power_tmp = config.grow.light.power[id];
|
||||
}
|
||||
|
||||
//if(shifted == false) {
|
||||
|
||||
//} else {
|
||||
|
||||
//}
|
||||
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s Light %d - power_tmp %d" CR), LogLoc, id, power_tmp);
|
||||
#endif
|
||||
return power_tmp;
|
||||
} else {
|
||||
return config.grow.light.power[id];
|
||||
}
|
||||
|
||||
|
||||
//return 0;
|
||||
}
|
||||
/* Function to set light based on time */
|
||||
void Control_Light() {
|
||||
const static char LogLoc[] PROGMEM = "[Control:Light]";
|
||||
//Log.verbose(F("%s start %s %s" CR), LogLoc, Str_DateNow(), Str_TimeNow());
|
||||
/* iterate through all configured lights */
|
||||
for(byte i = 0; i < Max_Outputs; i++) {
|
||||
if(config.grow.light.configured[i] == true) {
|
||||
unsigned int nowSec = (hour() * 60 * 60) + (minute() * 60) + second();
|
||||
unsigned int sunriseSec;
|
||||
unsigned int sunsetSec;
|
||||
|
||||
/* check if veg or bloom */
|
||||
if((config.grow.start < 1) || (now() - config.grow.start <= config.grow.daysVeg * 24 * 60 * 60)) {
|
||||
sunriseSec = (config.grow.light.sunriseHourVeg[i] * 60 * 60) + (config.grow.light.sunriseMinuteVeg[i] * 60);
|
||||
sunsetSec = (config.grow.light.sunsetHourVeg[i] * 60 * 60) + (config.grow.light.sunsetMinuteVeg[i] * 60);
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s Veg" CR), LogLoc);
|
||||
#endif
|
||||
/* now > than veg = bloom */
|
||||
} else if(now() - config.grow.start > config.grow.daysVeg * 24 * 60 * 60) {
|
||||
sunriseSec = (config.grow.light.sunriseHourBloom[i] * 60 * 60) + (config.grow.light.sunriseMinuteBloom[i] * 60);
|
||||
sunsetSec = (config.grow.light.sunsetHourBloom[i] * 60 * 60) + (config.grow.light.sunsetMinuteBloom[i] * 60);
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s Bloom" CR), LogLoc);
|
||||
#endif
|
||||
/* now > than veg+bloom = harvest*/
|
||||
} //else if(now() - config.grow.start > (config.grow.daysVeg + config.grow.daysBloom) * 24 * 60 * 60)) {
|
||||
//}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Sunrise / Day
|
||||
*/
|
||||
|
||||
/* when now is greater than sunrise AND sunsetTime is greater than sunrise */
|
||||
if((nowSec >= sunriseSec) && (nowSec < sunsetSec) && (sunsetSec > sunriseSec)) {
|
||||
//outputState[i] = config.grow.light.power[i];
|
||||
outputState[i] = Light_Power(i, sunriseSec, sunsetSec, nowSec, false);
|
||||
//Log.verbose(F("%s Light %d - nowSec %d - sunriseSec %d - sunsetSec %d - %s %s Day" CR), LogLoc, i, nowSec, sunriseSec, sunsetSec, Str_DateNow(), Str_TimeNow());
|
||||
|
||||
|
||||
/* when now is greater than sunrise OR */
|
||||
} else if(((nowSec >= sunriseSec) && (sunsetSec < sunriseSec)) ||
|
||||
/* when now is smaller than sunset AND sunset is
|
||||
* smaller than sunrise - this is a shifted daytime */
|
||||
((nowSec <= sunsetSec) && (sunsetSec < sunriseSec))) {
|
||||
|
||||
//outputState[i] = config.grow.light.power[i];
|
||||
outputState[i] = Light_Power(i, sunriseSec, sunsetSec, nowSec, true);
|
||||
//Log.verbose(F("%s Light %d - nowSec %d - sunriseSec %d - sunsetSec %d - %s %s Day (shifted)" CR), LogLoc, i, nowSec, sunriseSec, sunsetSec, Str_DateNow(), Str_TimeNow());
|
||||
|
||||
|
||||
} else {
|
||||
/* otherwise its night, turn off the light */
|
||||
outputState[i] = 0;
|
||||
//Log.verbose(F("%s Light %d - nowSec %d - sunriseSec %d - sunsetSec %d - %s %s Night" CR), LogLoc, i, nowSec, sunriseSec, sunsetSec, Str_DateNow(), Str_TimeNow());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* Air stuff
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Output Device
|
||||
*/
|
||||
|
||||
/* Air Mode definitions */
|
||||
// 0 is unconfigured
|
||||
const byte CONTROL_AIR_MODE__TOTAL = 3;
|
||||
|
||||
const byte CONTROL_AIR_MODE_ONOFF = 1;
|
||||
const byte CONTROL_AIR_MODE_LINEAR = 2;
|
||||
const byte CONTROL_AIR_MODE_STEPS = 3;
|
||||
|
||||
const char CONTROL_AIR_MODE_ONOFF_descr[] PROGMEM = {"On/Off"};
|
||||
const char CONTROL_AIR_MODE_LINEAR_descr[] PROGMEM = {"Linear"};
|
||||
const char CONTROL_AIR_MODE_STEPS_descr[] PROGMEM = {"Steps"};
|
||||
|
||||
const char * Control_Air_Mode_descr[] = {
|
||||
NULL, // 0 - no description because 0 means unconfigured
|
||||
CONTROL_AIR_MODE_ONOFF_descr,
|
||||
CONTROL_AIR_MODE_LINEAR_descr,
|
||||
CONTROL_AIR_MODE_STEPS_descr,
|
||||
};
|
||||
|
||||
|
||||
/* Air control modes themselfs */
|
||||
|
||||
byte Control_Air_Mode_OnOff(byte id) {
|
||||
/* turns the output on or off, depending if the is within min and max */
|
||||
|
||||
/* if only min is set (max = 0), turn on when above it */
|
||||
if((config.grow.air.min[id] > 0) && (config.grow.air.max[id] == 0)) {
|
||||
/* check if Sensor reading is above min value, then turn on */
|
||||
if(Sensor_getCalibratedValue(config.grow.air.controlSensor[id], config.grow.air.controlRead[id]) >= config.grow.air.min[id]) {
|
||||
return config.grow.air.power[id];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* if only max is set (min = 0), turn off when above */
|
||||
} else if((config.grow.air.min[id] == 0) && (config.grow.air.max[id] > 0)) {
|
||||
/* check if Sensor reading is under max value, then turn on */
|
||||
if(Sensor_getCalibratedValue(config.grow.air.controlSensor[id], config.grow.air.controlRead[id]) <= config.grow.air.max[id]) {
|
||||
return config.grow.air.power[id];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
/* when min and max are set (> 0) turn output on when within the given values */
|
||||
} else if((config.grow.air.min[id] > 0) && (config.grow.air.max[id] > 0)) {
|
||||
if((Sensor_getCalibratedValue(config.grow.air.controlSensor[id], config.grow.air.controlRead[id]) >= config.grow.air.min[id]) && (Sensor_getCalibratedValue(config.grow.air.controlSensor[id], config.grow.air.controlRead[id]) <= config.grow.air.max[id])) {
|
||||
return config.grow.air.power[id];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
byte Control_Air_Mode_Linear(byte id) {
|
||||
/* if min and max are set */
|
||||
if((config.grow.air.min[id] > 0) && (config.grow.air.max[id] > 0)) {
|
||||
/* return power value calculated with map() and contrain()
|
||||
* multiply by 100 to "convert" the float to int. With constrain we prevent returning negative or out of range values */
|
||||
return map(constrain(Sensor_getCalibratedValue(config.grow.air.controlSensor[id], config.grow.air.controlRead[id]) * 100, config.grow.air.min[id] * 100, config.grow.air.max[id] * 100),
|
||||
config.grow.air.min[id] * 100,
|
||||
config.grow.air.max[id] * 100,
|
||||
0,
|
||||
config.grow.air.power[id]);
|
||||
} else {
|
||||
return Control_Air_Mode_OnOff(id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
byte Control_Air_Mode_Steps(byte id) {
|
||||
/* if min and max are set */
|
||||
if((config.grow.air.min[id] > 0) && (config.grow.air.max[id] > 0)) {
|
||||
/* return power value calculated with map() and contrain()
|
||||
* multiply by 100 to "convert" the float to int. With constrain we prevent returning negative or out of range values */
|
||||
byte power_tmp = map(constrain(Sensor_getCalibratedValue(config.grow.air.controlSensor[id], config.grow.air.controlRead[id]) * 100, config.grow.air.min[id] * 100, config.grow.air.max[id] * 100),
|
||||
config.grow.air.min[id] * 100,
|
||||
config.grow.air.max[id] * 100,
|
||||
0,
|
||||
config.grow.air.power[id]);
|
||||
if(power_tmp == 0) {
|
||||
return 0;
|
||||
} else if(power_tmp < 64) {
|
||||
return 64;
|
||||
} else if(power_tmp < 128) {
|
||||
return 128;
|
||||
} else if(power_tmp < 192) {
|
||||
return 192;
|
||||
} else if(power_tmp < 255) {
|
||||
return 192;
|
||||
} else {
|
||||
return 255;
|
||||
}
|
||||
} else {
|
||||
return Control_Air_Mode_OnOff(id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Function to set air devices */
|
||||
void Control_Air() {
|
||||
const static char LogLoc[] PROGMEM = "[Control:Air]";
|
||||
|
||||
/* iterate through all configured air devices */
|
||||
for(byte i = 0; i < Max_Outputs; i++) {
|
||||
if(config.grow.air.configured[i] == true) {
|
||||
/* check if a control Sensor reading is set. As SensorIndex starts by 0, 255 is "unset" */
|
||||
if((config.grow.air.controlSensor[i] < 255) && (config.grow.air.controlRead[i] < 255)) {
|
||||
/* switch for control modes */
|
||||
switch(config.grow.air.controlMode[i]) {
|
||||
|
||||
case CONTROL_AIR_MODE_ONOFF:
|
||||
outputState[i] = Control_Air_Mode_OnOff(i);
|
||||
break;
|
||||
|
||||
case CONTROL_AIR_MODE_LINEAR:
|
||||
outputState[i] = Control_Air_Mode_Linear(i);
|
||||
break;
|
||||
|
||||
case CONTROL_AIR_MODE_STEPS:
|
||||
outputState[i] = Control_Air_Mode_Steps(i);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
/* if there is no control sensor reading selected, just set power */
|
||||
} else {
|
||||
outputState[i] = config.grow.air.power[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* Water stuff
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Output Device
|
||||
*/
|
||||
|
||||
/* Water Mode definitions */
|
||||
// 0 is unconfigured
|
||||
const byte CONTROL_WATER_MODE__TOTAL = 3;
|
||||
|
||||
const byte CONTROL_WATER_MODE_TIMEINTERVAL = 1;
|
||||
const byte CONTROL_WATER_MODE_SENSOR_MIN_THRESHOLD = 2;
|
||||
const byte CONTROL_WATER_MODE_SENSMIN_TIMEINT_COMBINED = 3;
|
||||
|
||||
const char CONTROL_WATER_MODE_TIMEINTERVAL_descr[] PROGMEM = {"Timeinterval"};
|
||||
const char CONTROL_WATER_MODE_SENSOR_MIN_THRESHOLD_descr[] PROGMEM = {"Sensor min threshold"};
|
||||
const char CONTROL_WATER_MODE_SENSMIN_TIMEINT_COMBINED_descr[] PROGMEM = {"Sensor min + Timeinterval"};
|
||||
|
||||
const char * Control_Water_Mode_descr[] = {
|
||||
NULL, // 0 - no description because 0 means unconfigured
|
||||
CONTROL_WATER_MODE_TIMEINTERVAL_descr,
|
||||
CONTROL_WATER_MODE_SENSOR_MIN_THRESHOLD_descr,
|
||||
CONTROL_WATER_MODE_SENSMIN_TIMEINT_COMBINED_descr,
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Function to set water devices */
|
||||
void Control_Water() {
|
||||
const static char LogLoc[] PROGMEM = "[Control:Water]";
|
||||
|
||||
/* iterate through all configured water devices which have a control mode set */
|
||||
for(byte i = 0; i < Max_Outputs; i++) {
|
||||
if((config.grow.water.configured[i] == true) && (config.grow.water.controlMode[i] > 0)) {
|
||||
|
||||
/* which mode was set in config.grow.water.controlMode */
|
||||
switch(config.grow.water.controlMode[i]) {
|
||||
|
||||
case CONTROL_WATER_MODE_TIMEINTERVAL:
|
||||
// when diff of time now and time pumpLastOn is greater then water.interval, do some watering (Or manual watering)
|
||||
if( (now() - controlWaterLast[i]) >= (config.grow.water.interval[i] * Timescale(config.grow.water.intervalUnit[i])) ) {
|
||||
/* check if output is already on. If not so, we begin a watering cycle and remember the timestamp of it. */
|
||||
if(outputState[i] == 0) {
|
||||
controlWaterLastStarted[i] = now();
|
||||
}
|
||||
|
||||
/* when diff of now and controlWaterLastStarted is smaller than onTime, turn output on */
|
||||
if((now() - controlWaterLastStarted[i]) < config.grow.water.onTime[i]) {
|
||||
/* at the moment i think PWM is not necessary here, so we set the output to 255 */
|
||||
outputState[i] = 255;
|
||||
/* when onTime is exceeded, turn output off */
|
||||
} else {
|
||||
outputState[i] = 0;
|
||||
|
||||
/* remember when we finished watering */
|
||||
controlWaterLast[i] = now();
|
||||
|
||||
/* Todo, write controlWaterLast to LittleFS. */
|
||||
}
|
||||
} else {
|
||||
/* turn output off when interval is not exceeded */
|
||||
outputState[i] = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONTROL_WATER_MODE_SENSOR_MIN_THRESHOLD:
|
||||
/* when sensor reading config.grow.water.controlSensor is lower then config.grow.water.min , do some watering */
|
||||
if( (Sensor_getCalibratedValue(config.grow.water.controlSensor[i], config.grow.water.controlRead[i]) < config.grow.water.min[i]) ||
|
||||
/* or when the sensor value is larger than min but onTime has not exceeded yet */
|
||||
((Sensor_getCalibratedValue(config.grow.water.controlSensor[i], config.grow.water.controlRead[i]) >= config.grow.water.min[i]) && ( (now() - controlWaterLastStarted[i]) < config.grow.water.onTime[i]))
|
||||
) {
|
||||
/* check if output is already on. If not so, we begin a watering cycle and remember the timestamp of it. */
|
||||
if(outputState[i] == 0) {
|
||||
controlWaterLastStarted[i] = now();
|
||||
}
|
||||
|
||||
/* when diff of now and controlWaterLastStarted is smaller than onTime, turn output on */
|
||||
if((now() - controlWaterLastStarted[i]) < config.grow.water.onTime[i]) {
|
||||
/* at the moment i think PWM is not necessary here, so we set the output to 255 */
|
||||
outputState[i] = 255;
|
||||
/* when onTime is exceeded, turn output off */
|
||||
} else {
|
||||
outputState[i] = 0;
|
||||
}
|
||||
/* turn output off when water conditions are not met */
|
||||
} else {
|
||||
outputState[i] = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONTROL_WATER_MODE_SENSMIN_TIMEINT_COMBINED:
|
||||
// when diff of time now and time pumpLastOn is greater then water.interval AND sensor read value is below min
|
||||
if( ( (now() - controlWaterLast[i]) >= (config.grow.water.interval[i] * Timescale(config.grow.water.intervalUnit[i])) ) &&
|
||||
( (Sensor_getCalibratedValue(config.grow.water.controlSensor[i], config.grow.water.controlRead[i]) < config.grow.water.min[i]) ||
|
||||
/* or when the sensor value is larger than min but onTime has not exceeded yet */
|
||||
((Sensor_getCalibratedValue(config.grow.water.controlSensor[i], config.grow.water.controlRead[i]) >= config.grow.water.min[i]) && ( (now() - controlWaterLastStarted[i]) < config.grow.water.onTime[i])) )
|
||||
) {
|
||||
/* check if output is already on. If not so, we begin a watering cycle and remember the timestamp of it. */
|
||||
if(outputState[i] == 0) {
|
||||
controlWaterLastStarted[i] = now();
|
||||
}
|
||||
|
||||
/* when diff of now and controlWaterLastStarted is smaller than onTime, turn output on */
|
||||
if((now() - controlWaterLastStarted[i]) < config.grow.water.onTime[i]) {
|
||||
/* at the moment i think PWM is not necessary here, so we set the output to 255 */
|
||||
outputState[i] = 255;
|
||||
/* when onTime is exceeded, turn output off */
|
||||
} else {
|
||||
outputState[i] = 0;
|
||||
|
||||
/* remember when we finished watering */
|
||||
controlWaterLast[i] = now();
|
||||
|
||||
/* Todo, write controlWaterLast to LittleFS. */
|
||||
}
|
||||
} else {
|
||||
/* turn output off when interval is not exceeded */
|
||||
outputState[i] = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/* when no mode is selected, turn output off */
|
||||
outputState[i] = 0;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
/* if neither configured or mode set, force output being off */
|
||||
}
|
||||
}
|
642
include/CanGrow_Core.h
Normal file
642
include/CanGrow_Core.h
Normal file
|
@ -0,0 +1,642 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_Core.h - core stuff header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* NTP Stuff
|
||||
*/
|
||||
|
||||
WiFiUDP ntpUDP;
|
||||
NTPClient timeClient(ntpUDP);
|
||||
|
||||
/*
|
||||
* RTC Stuff
|
||||
*/
|
||||
|
||||
/* I would more like not to define four individual globals for each RTC type
|
||||
* but Adafruit lib seems to work only this way - and i am too lazyscared to use
|
||||
* some other lib or do it myself - so i hope this will not eat up my ram */
|
||||
RTC_DS1307 rtc_ds1307;
|
||||
RTC_DS3231 rtc_ds3231;
|
||||
RTC_PCF8523 rtc_pcf8523;
|
||||
RTC_PCF8563 rtc_pcf8563;
|
||||
|
||||
|
||||
/*
|
||||
* Timer stuff
|
||||
*/
|
||||
NusabotSimpleTimer timer;
|
||||
|
||||
|
||||
/*
|
||||
* Logging stuff
|
||||
*
|
||||
* Example Log call
|
||||
* const static char LogLoc[] PROGMEM= "[Some:Stuff:Happening]"
|
||||
* Log.notice(F("%s This is %d" CR), LogLoc, i);
|
||||
*
|
||||
* LogLoc stands for "LogLocation"
|
||||
*/
|
||||
|
||||
/* Logging prefix */
|
||||
void LogPrefix(Print* _logOutput, int logLevel) {
|
||||
//_logOutput->print(":: TEST");
|
||||
switch (logLevel)
|
||||
{
|
||||
default:
|
||||
// silent
|
||||
case 0:_logOutput->print("--" ); break;
|
||||
// fatal
|
||||
case 1:_logOutput->print("!!!! " ); break;
|
||||
// error
|
||||
case 2:_logOutput->print("!! " ); break;
|
||||
// warning
|
||||
case 3:_logOutput->print("!: "); break;
|
||||
// info / notice
|
||||
case 4:_logOutput->print(":: " ); break;
|
||||
// trace
|
||||
case 5:_logOutput->print("T: " ); break;
|
||||
// verbose / debug
|
||||
case 6:_logOutput->print("DB "); break;
|
||||
}
|
||||
}
|
||||
|
||||
/* System core stuff , like restart , give free Id of xy, .. */
|
||||
void Restart() {
|
||||
const static char LogLoc[] PROGMEM = "[Core:Restart]";
|
||||
Log.notice(F("%s got triggered, restarting in 2 seconds" CR), LogLoc);
|
||||
|
||||
// blink fast with the built in LED in an infinite loop
|
||||
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() {
|
||||
const static char LogLoc[] PROGMEM = "[Core: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;
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s next free output id: %d" CR), LogLoc, outputId_free);
|
||||
#endif
|
||||
return outputId_free;
|
||||
}
|
||||
|
||||
byte Give_Free_SensorId() {
|
||||
const static char LogLoc[] PROGMEM = "[Core:Give_Free_SensorId]";
|
||||
|
||||
byte sensorId_free;
|
||||
for(byte i=0; i < Max_Sensors; i++) {
|
||||
if(config.system.sensor.type[i] > 0) {
|
||||
// here i define that 255 stands for "no more free outputs"
|
||||
sensorId_free = 255;
|
||||
} else {
|
||||
sensorId_free = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s next free sensor id: %d" CR), LogLoc, sensorId_free);
|
||||
#endif
|
||||
return sensorId_free;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// checks if GPIO is already in use by output or sensor
|
||||
bool Check_GPIOindex_Used(byte gpio) {
|
||||
const static char LogLoc[] PROGMEM = "[Core:Check_GPIOindex_Used]";
|
||||
|
||||
bool used;
|
||||
|
||||
//Log.verbose(F("%s check GPIO: %d" CR), LogLoc, gpio);
|
||||
|
||||
// go through each outputid
|
||||
for(byte i=0; i < Max_Outputs; i++) {
|
||||
|
||||
// check if output type is gpio
|
||||
if(config.system.output.type[i] == OUTPUT_TYPE_GPIO) {
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s OutputId: %d is GPIO (type %d)" CR), LogLoc, i, config.system.output.type[i]);
|
||||
#endif
|
||||
// check if gpio id is already in use
|
||||
if(config.system.output.gpio[i] == gpio) {
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s output.gpio[%d](%d) == GPIO %d" CR), LogLoc, i, config.system.output.gpio[i], gpio);
|
||||
#endif
|
||||
used = true;
|
||||
break;
|
||||
} else {
|
||||
used = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(used == false) {
|
||||
for(byte i=0; i < Max_Sensors; i++) {
|
||||
|
||||
// check if sensor type uses gpio
|
||||
if((SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_INTADC) || (SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_ONEWIRE) || (SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_TWOWIRE)) {
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s SensorId: %d is using GPIO (type %d)" CR), LogLoc, i, config.system.sensor.type[i]);
|
||||
#endif
|
||||
// check if gpio id is already in use
|
||||
for(byte j = 0; j < Max_Sensors_GPIO; j++) {
|
||||
if(config.system.sensor.gpio[i][j] == gpio) {
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s sensor.gpio[%d][%d](%d) == GPIO %d" CR), LogLoc, i, j, config.system.sensor.gpio[i][j], gpio);
|
||||
#endif
|
||||
used = true;
|
||||
break;
|
||||
} else {
|
||||
used = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s GPIO: %d, used: %d" CR), LogLoc, gpio, used);
|
||||
#endif
|
||||
return used;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* Time related stuff
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* NTP stuff
|
||||
*/
|
||||
|
||||
void NTP_OffsetUpdate() {
|
||||
const static char LogLoc[] PROGMEM = "[Core:NTP_OffsetUpdate]";
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s updating time with offset %dh" CR), LogLoc, config.system.ntpOffset);
|
||||
#endif
|
||||
timeClient.setTimeOffset(config.system.ntpOffset * 60 * 60);
|
||||
if( (config.system.ntp == true) && (timeSrcStatus < 1) ) {
|
||||
timeClient.update();
|
||||
setTime(timeClient.getEpochTime());
|
||||
}
|
||||
#ifdef DEBUG
|
||||
else {
|
||||
Log.verbose(F("%s update requirements not met, timeSrcStatus %d > 0" CR), LogLoc, timeSrcStatus);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool NTP_Init() {
|
||||
const static char LogLoc[] PROGMEM = "[Core:NTP_Init]";
|
||||
bool result;
|
||||
timeClient.begin();
|
||||
NTP_OffsetUpdate();
|
||||
// when NTP update failes (e.g. no connection to internet)
|
||||
Log.notice(F("%s updating " ), LogLoc);
|
||||
|
||||
byte i = 0;
|
||||
while( (! timeClient.isTimeSet()) && ( i < 5 )) {
|
||||
timeClient.update();
|
||||
delay(100);
|
||||
Serial.print(".");
|
||||
i++;
|
||||
}
|
||||
Serial.println();
|
||||
|
||||
if( ! timeClient.isTimeSet()) {
|
||||
Log.error(F("%s FAILED" CR), LogLoc);
|
||||
//Serial.println("!! [Core:NTP_Init] update failed");
|
||||
result = false;
|
||||
} else {
|
||||
|
||||
Log.notice(F("%s Success! Time: %s (%u), Offset: %d h" CR), LogLoc, timeClient.getFormattedTime(), timeClient.getEpochTime(), config.system.ntpOffset);
|
||||
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
time_t NTP_getEpochTime() {
|
||||
/* convert epoch from ntp (UL) to time_t */
|
||||
const static char LogLoc[] PROGMEM = "[Core:NTP_getEpochTime]";
|
||||
unsigned long epochTime = timeClient.getEpochTime();
|
||||
Log.verbose(F("%s epochTime: %u" CR), LogLoc, epochTime);
|
||||
return epochTime;
|
||||
}
|
||||
|
||||
/*
|
||||
* RTC stuff
|
||||
*/
|
||||
|
||||
void RTC_Init() {
|
||||
const static char LogLoc[] PROGMEM = "[Core:RTC_Init]";
|
||||
|
||||
switch(config.system.rtc) {
|
||||
case RTCs_DS1307:
|
||||
if (! rtc_ds1307.begin()) {
|
||||
Log.warning(F("%s Couldn't find RTC DS1307" CR), LogLoc);
|
||||
rtcError = true;
|
||||
} else {
|
||||
Log.notice(F("%s RTC DS1307 found" CR), LogLoc);
|
||||
if (rtc_ds1307.isrunning()) {
|
||||
Log.warning(F("%s RTC DS1307 is not running, let's set the time!" CR), LogLoc);
|
||||
rtcError = true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case RTCs_DS3231:
|
||||
if (! rtc_ds3231.begin()) {
|
||||
Log.warning(F("%s Couldn't find RTC DS3231" CR), LogLoc);
|
||||
rtcError = true;
|
||||
} else {
|
||||
Log.notice(F("%s RTC DS3231 found" CR), LogLoc);
|
||||
if (rtc_ds3231.lostPower()) {
|
||||
Log.warning(F("%s RTC DS3231 lost power, let's set the time!" CR), LogLoc);
|
||||
rtcError = true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case RTCs_PCF8563:
|
||||
if (! rtc_pcf8563.begin()) {
|
||||
Log.warning(F("%s Couldn't find RTC PCF8563" CR), LogLoc);
|
||||
rtcError = true;
|
||||
|
||||
} else {
|
||||
Log.notice(F("%s RTC PCF8563 found" CR), LogLoc);
|
||||
if (rtc_pcf8563.lostPower()) {
|
||||
Log.warning(F("%s RTC PCF8563 lost power, let's set the time!" CR), LogLoc);
|
||||
rtcError = true;
|
||||
}
|
||||
}
|
||||
rtc_pcf8563.start();
|
||||
break;
|
||||
|
||||
case RTCs_PCF8523:
|
||||
if (! rtc_pcf8523.begin()) {
|
||||
Log.warning(F("%s Couldn't find RTC PCF8523" CR), LogLoc);
|
||||
rtcError = true;
|
||||
|
||||
} else {
|
||||
Log.notice(F("%s RTC PCF8523 found" CR), LogLoc);
|
||||
if ( ! rtc_pcf8523.initialized() || rtc_pcf8523.lostPower()) {
|
||||
Log.warning(F("%s RTC PCF8523 lost power, let's set the time!" CR), LogLoc);
|
||||
rtcError = true;
|
||||
}
|
||||
}
|
||||
rtc_pcf8523.start();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
time_t RTC_getEpochTime() {
|
||||
/* convert epoch from RTC (UL) to time_t */
|
||||
const static char LogLoc[] PROGMEM = "[Core:RTC_getEpochTime]";
|
||||
unsigned long epochTime; // = timeClient.getEpochTime();
|
||||
DateTime TimeNow;
|
||||
switch(config.system.rtc) {
|
||||
case RTCs_DS1307:
|
||||
TimeNow = rtc_ds1307.now();
|
||||
break;
|
||||
|
||||
case RTCs_DS3231:
|
||||
TimeNow = rtc_ds3231.now();
|
||||
break;
|
||||
|
||||
case RTCs_PCF8523:
|
||||
TimeNow = rtc_pcf8523.now();
|
||||
break;
|
||||
|
||||
case RTCs_PCF8563:
|
||||
TimeNow = rtc_pcf8563.now();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
epochTime = TimeNow.unixtime();
|
||||
Log.verbose(F("%s epochTime: %u" CR), LogLoc, epochTime);
|
||||
return epochTime;
|
||||
}
|
||||
|
||||
void RTC_SaveTime() {
|
||||
const static char LogLoc[] PROGMEM = "[Core:RTC_SaveTime]";
|
||||
unsigned int TimeNow = now();
|
||||
bool saved = true;
|
||||
|
||||
switch(config.system.rtc) {
|
||||
case RTCs_DS1307:
|
||||
rtc_ds1307.adjust(DateTime(TimeNow));
|
||||
break;
|
||||
|
||||
case RTCs_DS3231:
|
||||
rtc_ds3231.adjust(DateTime(TimeNow));
|
||||
break;
|
||||
|
||||
case RTCs_PCF8523:
|
||||
rtc_pcf8523.adjust(DateTime(TimeNow));
|
||||
break;
|
||||
|
||||
case RTCs_PCF8563:
|
||||
rtc_pcf8563.adjust(DateTime(TimeNow));
|
||||
break;
|
||||
|
||||
default:
|
||||
/* only when not in case, we consider not saved */
|
||||
saved = false;
|
||||
break;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
if(saved == true)
|
||||
Log.verbose(F("%s Time (%u) saved to %S" CR), LogLoc, TimeNow, RTCs_descr[config.system.rtc]);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Main Time stuff
|
||||
*
|
||||
*/
|
||||
|
||||
String Str_TimeNow() {
|
||||
/* simple helper function to return a String with HH:MM:SS */
|
||||
String str_time;
|
||||
if(hour() < 10)
|
||||
str_time += F("0");
|
||||
str_time += hour();
|
||||
str_time += F(":");
|
||||
if(minute() < 10)
|
||||
str_time += F("0");
|
||||
str_time += minute();
|
||||
str_time += F(":");
|
||||
if(second() < 10)
|
||||
str_time += F("0");
|
||||
str_time += second();
|
||||
return str_time;
|
||||
}
|
||||
|
||||
String Str_DateNow() {
|
||||
/* simple helper function to return a String with HH:MM:SS */
|
||||
String str_date;
|
||||
if(day() < 10)
|
||||
str_date += F("0");
|
||||
str_date += day();
|
||||
str_date += F(".");
|
||||
if(month() < 10)
|
||||
str_date += F("0");
|
||||
str_date += month();
|
||||
str_date += F(".");
|
||||
str_date += year();
|
||||
return str_date;
|
||||
}
|
||||
|
||||
String Str_Epoch2Date(unsigned long epochTime) {
|
||||
String dateStr;
|
||||
byte Day = day(epochTime);
|
||||
byte Month = month(epochTime);
|
||||
unsigned int Year = year(epochTime);
|
||||
|
||||
dateStr = Year;
|
||||
dateStr += "-";
|
||||
|
||||
if(Month < 10) {
|
||||
dateStr += "0";
|
||||
dateStr += Month;
|
||||
} else {
|
||||
dateStr += Month;
|
||||
}
|
||||
|
||||
dateStr += "-";
|
||||
|
||||
if(Day < 10) {
|
||||
dateStr += "0";
|
||||
dateStr += Day;
|
||||
} else {
|
||||
dateStr += Day;
|
||||
}
|
||||
|
||||
return dateStr;
|
||||
}
|
||||
|
||||
|
||||
/* Those two functions should be in LittleFS file, but because dependency and lazyness */
|
||||
void Time2FS_Save() {
|
||||
const static char LogLoc[] PROGMEM = "[Core:Time2FS_Save]";
|
||||
unsigned long TimeNow;
|
||||
#ifdef ESP8266
|
||||
File file = LittleFS.open(TIME2FS, "w");
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(TIME2FS, FILE_WRITE);
|
||||
#endif
|
||||
|
||||
if (!file) {
|
||||
Log.error(F("%s FAILED to open file for writing: %s" CR), LogLoc, TIME2FS);
|
||||
return;
|
||||
}
|
||||
TimeNow = now();
|
||||
if (!file.print(TimeNow)) {
|
||||
Log.error(F("%s writing time FAILED" CR), LogLoc);
|
||||
}
|
||||
#ifdef DEBUG
|
||||
else {
|
||||
Log.verbose(F("%s time (%u) written: %s" CR), LogLoc, TimeNow, TIME2FS);
|
||||
}
|
||||
#endif
|
||||
//delay(2000); // Make sure the CREATE and LASTWRITE times are different
|
||||
file.close();
|
||||
|
||||
}
|
||||
|
||||
void Time2FS_Read() {
|
||||
const static char LogLoc[] PROGMEM = "[Core:Time2FS_Read]";
|
||||
String TimeRead;
|
||||
#ifdef ESP8266
|
||||
File file = LittleFS.open(TIME2FS, "r");
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(TIME2FS);
|
||||
#endif
|
||||
|
||||
if (!file) {
|
||||
Log.error(F("%s FAILED to open time file: %s" CR), LogLoc, TIME2FS);
|
||||
return;
|
||||
}
|
||||
|
||||
//Log.notice(F("%s file content: %s" CR), LogLoc, TIME2FS);
|
||||
//Log.notice(F("%s ----------" CR), LogLoc);
|
||||
//while (file.available()) { Serial.write(file.read()); }
|
||||
//Log.notice(F("%s ----------" CR), LogLoc);
|
||||
|
||||
while (file.available()) { TimeRead = file.readString(); }
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s applying time (%u) to system" CR), LogLoc, TimeRead.toInt());
|
||||
#endif
|
||||
setTime(TimeRead.toInt());
|
||||
file.close();
|
||||
}
|
||||
|
||||
|
||||
/* Time_Init - Main function for time initialization */
|
||||
void Time_Init() {
|
||||
const static char LogLoc[] PROGMEM = "[Core:Time_Init]";
|
||||
|
||||
/* first check if RTC is configured and init if */
|
||||
if(config.system.rtc > 0)
|
||||
RTC_Init();
|
||||
|
||||
/* check if ntp is enabled */
|
||||
if(config.system.ntp == true) {
|
||||
Log.notice(F("%s Using NTP" CR), LogLoc);
|
||||
/* initialize NTP and check */
|
||||
if(NTP_Init()) {
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s set NTP as TimeLib SyncProvider" CR), LogLoc);
|
||||
#endif
|
||||
setSyncProvider(NTP_getEpochTime);
|
||||
|
||||
/* when having a RTC, update it now with new not time */
|
||||
if(config.system.rtc > 0) {
|
||||
RTC_SaveTime();
|
||||
}
|
||||
|
||||
if(config.system.time2fs == true) {
|
||||
true;
|
||||
//writeFile(TIME2FS, now());
|
||||
Time2FS_Save();
|
||||
}
|
||||
|
||||
} else if((config.system.rtc > 0) && (rtcError == false)) {
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s set RTC as TimeLib SyncProvider" CR), LogLoc);
|
||||
#endif
|
||||
setSyncProvider(RTC_getEpochTime);
|
||||
//setTime(RTC_getEpochTime());
|
||||
timeSrcStatus = 1;
|
||||
} else {
|
||||
Log.warning(F("%s no TimeLib SyncProvider available. Reading last Timestamp from flash memory" CR), LogLoc);
|
||||
Time2FS_Read();
|
||||
timeSrcStatus = 2;
|
||||
}
|
||||
}
|
||||
/* how often TimeLib should sync with source
|
||||
* 10 minutes is ok i guess
|
||||
*/
|
||||
setSyncInterval(600);
|
||||
|
||||
Log.notice(F("%s Time initialization done. Fallback status %d, %s %s (%u)" CR), LogLoc, timeSrcStatus, Str_DateNow(), Str_TimeNow(), now());
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* semi random string generator
|
||||
* https://arduino.stackexchange.com/a/86659
|
||||
*/
|
||||
const byte RANDOMSTRING_MAX = 16;
|
||||
const char * RandomString(){
|
||||
/* Change to allowable characters */
|
||||
const char possible[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?_-=()%&.,;:";
|
||||
static char str[RANDOMSTRING_MAX + 1];
|
||||
for(byte p = 0, i = 0; i < RANDOMSTRING_MAX; i++){
|
||||
byte r = random(0, strlen(possible));
|
||||
str[p++] = possible[r];
|
||||
}
|
||||
str[RANDOMSTRING_MAX] = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Timescale()
|
||||
* returns timescale unit (seconds, minutes, hours,...) in seconds
|
||||
*/
|
||||
|
||||
unsigned long Timescale(byte unit) {
|
||||
switch(unit) {
|
||||
case TIMESCALE_SECOND:
|
||||
return 1;
|
||||
break;
|
||||
|
||||
case TIMESCALE_MINUTE:
|
||||
return 60;
|
||||
break;
|
||||
|
||||
case TIMESCALE_HOUR:
|
||||
//return 60*60;
|
||||
return 3600;
|
||||
break;
|
||||
|
||||
case TIMESCALE_DAY:
|
||||
//return 60*60*24;
|
||||
return 86400;
|
||||
break;
|
||||
|
||||
case TIMESCALE_WEEK:
|
||||
//return 60*60*24*7;
|
||||
return 604800;
|
||||
break;
|
||||
|
||||
case TIMESCALE_MONTH:
|
||||
//return 60*60*24*7*4;
|
||||
return 2419200;
|
||||
break;
|
||||
|
||||
case TIMESCALE_YEAR:
|
||||
//return 60*60*24*7*4*52;
|
||||
return 125798400;
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
}
|
855
include/CanGrow_LittleFS.h
Normal file
855
include/CanGrow_LittleFS.h
Normal file
|
@ -0,0 +1,855 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_LittleFS.h - LittleFS handling header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// LittleFS auto format
|
||||
#define FORMAT_LITTLEFS_IF_FAILED true
|
||||
|
||||
void LFS_Init() {
|
||||
const static char LogLoc[] PROGMEM = "[LittleFS:Init]";
|
||||
Log.notice(F("%s" CR), LogLoc);
|
||||
// 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
|
||||
|
||||
Log.notice(F("%s FAILED initializing. You have to format LittleFS manually. Will now restart." CR), LogLoc);
|
||||
Restart();
|
||||
}
|
||||
}
|
||||
|
||||
void LFS_Format() {
|
||||
const static char LogLoc[] PROGMEM = "[LittleFS:Format]";
|
||||
Log.notice(F("%s formatting ..." CR), LogLoc);
|
||||
// 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()) {
|
||||
Log.notice(F("%s done!" CR), LogLoc);
|
||||
} else {
|
||||
Log.error(F("%s FAILED" CR), LogLoc);
|
||||
}
|
||||
}
|
||||
|
||||
bool existFile(const char *path) {
|
||||
const static char LogLoc[] PROGMEM = "[LittleFS]";
|
||||
#ifdef ESP8266
|
||||
File file = LittleFS.open(path, "r");
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(path);
|
||||
#endif
|
||||
|
||||
if (!file) {
|
||||
Log.notice(F("%s file exists: %s" CR), LogLoc, path);
|
||||
file.close();
|
||||
return false;
|
||||
} else {
|
||||
Log.warning(F("%s file does not exist: %s" CR), LogLoc, path);
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
String readFile(const char *path) {
|
||||
const static char LogLoc[] PROGMEM = "[LittleFS]";
|
||||
String fileContent;
|
||||
|
||||
#ifdef ESP8266
|
||||
File file = LittleFS.open(path, "r");
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(path);
|
||||
#endif
|
||||
|
||||
if (!file) {
|
||||
Log.error(F("%s FAILED to open file for reading: %s" CR), LogLoc, path);
|
||||
return String(F("ERROR CANNOT OPEN"));
|
||||
}
|
||||
|
||||
Log.notice(F("%s file content: %s" CR), LogLoc, path);
|
||||
Log.notice(F("%s ----------" CR), LogLoc);
|
||||
while (file.available()) { Serial.write(file.read()); }
|
||||
Log.notice(F("%s ----------" CR), LogLoc);
|
||||
fileContent = file.readString();
|
||||
file.close();
|
||||
return fileContent;
|
||||
}
|
||||
|
||||
void writeFile(const char *path, const char *message) {
|
||||
const static char LogLoc[] PROGMEM = "[LittleFS]";
|
||||
|
||||
#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) {
|
||||
Log.error(F("%s FAILED to open file for reading: %s" CR), LogLoc, path);
|
||||
return;
|
||||
}
|
||||
if (file.print(message)) {
|
||||
Log.notice(F("%s file written: %s" CR), LogLoc, path);
|
||||
} else {
|
||||
Log.error(F("%s writing file FAILED: %s" CR), LogLoc, path);
|
||||
}
|
||||
//delay(2000); // Make sure the CREATE and LASTWRITE times are different
|
||||
file.close();
|
||||
}
|
||||
|
||||
void deleteFile(const char *path) {
|
||||
const static char LogLoc[] PROGMEM = "[LittleFS]";
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(path, FILE_WRITE);
|
||||
#endif
|
||||
|
||||
Log.notice(F("%s deleting file: %s" CR), LogLoc, path);
|
||||
#ifdef ESP8266
|
||||
if (LittleFS.remove(path)) {
|
||||
#endif
|
||||
#ifdef ESP32
|
||||
if (fs.remove(path)) {
|
||||
#endif
|
||||
Log.notice(F("%s deleted file: %s" CR), LogLoc, path);
|
||||
} else {
|
||||
Log.error(F("%s deleting file FAILED: %s" CR), LogLoc, path);
|
||||
}
|
||||
}
|
||||
|
||||
// https://arduinojson.org/v7/example/config/
|
||||
// https://arduinojson.org/v7/assistant/
|
||||
bool LoadConfig() {
|
||||
const static char LogLoc[] PROGMEM = "[LittleFS:LoadConfig]";
|
||||
#ifdef ESP8266
|
||||
File file = LittleFS.open(CANGROW_CFG, "r");
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
fs::FS &fs = LittleFS;
|
||||
File file = fs.open(CANGROW_CFG);
|
||||
#endif
|
||||
|
||||
Log.notice(F("%s loading config from: %s" CR), LogLoc, CANGROW_CFG);
|
||||
|
||||
JsonDocument doc;
|
||||
// Deserialize the JSON document
|
||||
DeserializationError error = deserializeJson(doc, file);
|
||||
if(error) {
|
||||
Log.error(F("%s FAILED to load config: %s" CR), LogLoc, 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];
|
||||
if(objWifi.containsKey("ssid"))
|
||||
strlcpy(config.wifi.ssid, objWifi["ssid"], sizeof(config.wifi.ssid));
|
||||
if(objWifi.containsKey("password"))
|
||||
strlcpy(config.wifi.password, objWifi["password"], sizeof(config.wifi.password));
|
||||
// Copy bool / int directly into struct
|
||||
if(objWifi.containsKey("dhcp"))
|
||||
config.wifi.dhcp = objWifi["dhcp"];
|
||||
// load the ip addresses as array
|
||||
if(objWifi.containsKey("ip")) {
|
||||
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];
|
||||
if(objSystem.containsKey("ntpOffset"))
|
||||
config.system.ntpOffset = objSystem["ntpOffset"];
|
||||
if(objSystem.containsKey("maintenanceDuration"))
|
||||
config.system.maintenanceDuration = objSystem["maintenanceDuration"];
|
||||
if(objSystem.containsKey("esp32cam"))
|
||||
strlcpy(config.system.esp32cam, objSystem["esp32cam"], sizeof(config.system.esp32cam));
|
||||
if(objSystem.containsKey("httpUser"))
|
||||
strlcpy(config.system.httpUser, objSystem["httpUser"], sizeof(config.system.httpUser));
|
||||
if(objSystem.containsKey("httpPass"))
|
||||
strlcpy(config.system.httpPass, objSystem["httpPass"], sizeof(config.system.httpPass));
|
||||
if(objSystem.containsKey("httpLogSerial"))
|
||||
config.system.httpLogSerial = objSystem["httpLogSerial"];
|
||||
|
||||
if(objSystem.containsKey("schedulerInterval"))
|
||||
config.system.schedulerInterval = objSystem["schedulerInterval"];
|
||||
|
||||
if(objSystem.containsKey("ntp"))
|
||||
config.system.ntp = objSystem["ntp"];
|
||||
if(objSystem.containsKey("rtc"))
|
||||
config.system.rtc = objSystem["rtc"];
|
||||
if(objSystem.containsKey("time2fs"))
|
||||
config.system.time2fs = objSystem["time2fs"];
|
||||
if(objSystem.containsKey("pwmFreq"))
|
||||
config.system.pwmFreq = objSystem["pwmFreq"];
|
||||
|
||||
/* System Outputs */
|
||||
JsonObject objSystemOutput = objSystem["output"][0];
|
||||
for(byte i=0; i < Max_Outputs; i++) {
|
||||
if(objSystemOutput["type"][i] > 0) {
|
||||
|
||||
if(objSystemOutput.containsKey("type"))
|
||||
config.system.output.type[i] = objSystemOutput["type"][i];
|
||||
if(objSystemOutput.containsKey("device"))
|
||||
config.system.output.device[i] = objSystemOutput["device"][i];
|
||||
if(objSystemOutput.containsKey("name"))
|
||||
strlcpy(config.system.output.name[i], objSystemOutput["name"][i], sizeof(config.system.output.name[i]));
|
||||
if(objSystemOutput.containsKey("enabled"))
|
||||
config.system.output.enabled[i] = objSystemOutput["enabled"][i];
|
||||
// gpio
|
||||
if(objSystemOutput.containsKey("gpio"))
|
||||
config.system.output.gpio[i] = objSystemOutput["gpio"][i];
|
||||
if(objSystemOutput.containsKey("invert"))
|
||||
config.system.output.invert[i] = objSystemOutput["invert"][i];
|
||||
if(objSystemOutput.containsKey("gpio_pwm"))
|
||||
config.system.output.gpio_pwm[i] = objSystemOutput["gpio_pwm"][i];
|
||||
// i2c type
|
||||
if(objSystemOutput.containsKey("i2c_type"))
|
||||
config.system.output.i2c_type[i] = objSystemOutput["i2c_type"][i];
|
||||
// i2c addr
|
||||
if(objSystemOutput.containsKey("i2c_addr"))
|
||||
config.system.output.i2c_addr[i] = objSystemOutput["i2c_addr"][i];
|
||||
// i2c port
|
||||
if(objSystemOutput.containsKey("i2c_port"))
|
||||
config.system.output.i2c_port[i] = objSystemOutput["i2c_port"][i];
|
||||
// web
|
||||
if(objSystemOutput.containsKey("webcall_host"))
|
||||
strlcpy(config.system.output.webcall_host[i], objSystemOutput["webcall_host"][i], sizeof(config.system.output.webcall_host[i]));
|
||||
if(objSystemOutput.containsKey("webcall_path_on"))
|
||||
strlcpy(config.system.output.webcall_path_on[i], objSystemOutput["webcall_path_on"][i], sizeof(config.system.output.webcall_path_on[i]));
|
||||
if(objSystemOutput.containsKey("webcall_path_off"))
|
||||
strlcpy(config.system.output.webcall_path_off[i], objSystemOutput["webcall_path_off"][i], sizeof(config.system.output.webcall_path_off[i]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* System Sensors */
|
||||
JsonObject objSystemSensor = objSystem["sensor"][0];
|
||||
for(byte i=0; i < Max_Sensors; i++) {
|
||||
if(objSystemSensor["type"][i] > 0) {
|
||||
if(objSystemSensor.containsKey("type"))
|
||||
config.system.sensor.type[i] = objSystemSensor["type"][i];
|
||||
if(objSystemSensor.containsKey("name"))
|
||||
strlcpy(config.system.sensor.name[i], objSystemSensor["name"][i], sizeof(config.system.sensor.name[i]));
|
||||
if(objSystemSensor.containsKey("i2c_addr"))
|
||||
//strlcpy(config.system.sensor.i2c_addr[i], objSystemSensor["i2c_addr"][i], sizeof(config.system.sensor.i2c_addr[i]));
|
||||
config.system.sensor.i2c_addr[i] = objSystemSensor["i2c_addr"][i];
|
||||
// gpio
|
||||
if(objSystemSensor.containsKey("gpio")) {
|
||||
for(byte j = 0; j < Max_Sensors_GPIO; j++) {
|
||||
config.system.sensor.gpio[i][j] = objSystemSensor["gpio"][i][j];
|
||||
}
|
||||
}
|
||||
|
||||
// offset
|
||||
if(objSystemSensor.containsKey("offset")) {
|
||||
for(byte j = 0; j < Max_Sensors_Read; j++) {
|
||||
config.system.sensor.offset[i][j] = objSystemSensor["offset"][i][j];
|
||||
}
|
||||
}
|
||||
|
||||
// low
|
||||
if(objSystemSensor.containsKey("low")) {
|
||||
for(byte j = 0; j < Max_Sensors_Read; j++) {
|
||||
config.system.sensor.low[i][j] = objSystemSensor["low"][i][j];
|
||||
}
|
||||
}
|
||||
|
||||
// high
|
||||
if(objSystemSensor.containsKey("high")) {
|
||||
for(byte j = 0; j < Max_Sensors_Read; j++) {
|
||||
config.system.sensor.high[i][j] = objSystemSensor["high"][i][j];
|
||||
}
|
||||
}
|
||||
|
||||
// rawConvert
|
||||
if(objSystemSensor.containsKey("rawConvert")) {
|
||||
for(byte j = 0; j < Max_Sensors_Read; j++) {
|
||||
config.system.sensor.rawConvert[i][j] = objSystemSensor["rawConvert"][i][j];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Grow */
|
||||
JsonObject objGrow = doc["grow"][0];
|
||||
if(objGrow.containsKey("name"))
|
||||
strlcpy(config.grow.name, objGrow["name"], sizeof(config.grow.name));
|
||||
if(objGrow.containsKey("start"))
|
||||
config.grow.start = objGrow["start"];
|
||||
if(objGrow.containsKey("daysVeg"))
|
||||
config.grow.daysVeg = objGrow["daysVeg"];
|
||||
if(objGrow.containsKey("daysBloom"))
|
||||
config.grow.daysBloom = objGrow["daysBloom"];
|
||||
|
||||
/* Grow Light */
|
||||
JsonObject objLight = objGrow["light"][0];
|
||||
for(byte i = 0; i < Max_Outputs; i++) {
|
||||
/* get light.configured */
|
||||
if(objLight.containsKey("configured"))
|
||||
config.grow.light.configured[i] = objLight["configured"][i];
|
||||
/* check if light is configured */
|
||||
if(config.grow.light.configured[i] == true) {
|
||||
/* get the rest of the config */
|
||||
if(objLight.containsKey("output"))
|
||||
config.grow.light.output[i] = objLight["output"][i];
|
||||
if(objLight.containsKey("sunriseHourVeg"))
|
||||
config.grow.light.sunriseHourVeg[i] = objLight["sunriseHourVeg"][i];
|
||||
if(objLight.containsKey("sunriseMinuteVeg"))
|
||||
config.grow.light.sunriseMinuteVeg[i] = objLight["sunriseMinuteVeg"][i];
|
||||
if(objLight.containsKey("sunsetHourVeg"))
|
||||
config.grow.light.sunsetHourVeg[i] = objLight["sunsetHourVeg"][i];
|
||||
if(objLight.containsKey("sunsetMinuteVeg"))
|
||||
config.grow.light.sunsetMinuteVeg[i] = objLight["sunsetMinuteVeg"][i];
|
||||
if(objLight.containsKey("sunriseHourBloom"))
|
||||
config.grow.light.sunriseHourBloom[i] = objLight["sunriseHourBloom"][i];
|
||||
if(objLight.containsKey("sunriseMinuteBloom"))
|
||||
config.grow.light.sunriseMinuteBloom[i] = objLight["sunriseMinuteBloom"][i];
|
||||
if(objLight.containsKey("sunsetHourBloom"))
|
||||
config.grow.light.sunsetHourBloom[i] = objLight["sunsetHourBloom"][i];
|
||||
if(objLight.containsKey("sunsetMinuteBloom"))
|
||||
config.grow.light.sunsetMinuteBloom[i] = objLight["sunsetMinuteBloom"][i];
|
||||
|
||||
if(objLight.containsKey("power"))
|
||||
config.grow.light.power[i] = objLight["power"][i];
|
||||
if(objLight.containsKey("fade"))
|
||||
config.grow.light.fade[i] = objLight["fade"][i];
|
||||
if(objLight.containsKey("fadeDuration"))
|
||||
config.grow.light.fadeDuration[i] = objLight["fadeDuration"][i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Grow Air */
|
||||
JsonObject objAir = objGrow["air"][0];
|
||||
for(byte i = 0; i < Max_Outputs; i++) {
|
||||
/* get air.configured */
|
||||
if(objAir.containsKey("configured"))
|
||||
config.grow.air.configured[i] = objAir["configured"][i];
|
||||
/* check if air is configured */
|
||||
if(config.grow.air.configured[i] == true) {
|
||||
/* get the rest of the config */
|
||||
if(objAir.containsKey("output"))
|
||||
config.grow.air.output[i] = objAir["output"][i];
|
||||
if(objAir.containsKey("power"))
|
||||
config.grow.air.power[i] = objAir["power"][i];
|
||||
if(objAir.containsKey("controlSensor"))
|
||||
config.grow.air.controlSensor[i] = objAir["controlSensor"][i];
|
||||
if(objAir.containsKey("controlRead"))
|
||||
config.grow.air.controlRead[i] = objAir["controlRead"][i];
|
||||
if(objAir.containsKey("controlMode"))
|
||||
config.grow.air.controlMode[i] = objAir["controlMode"][i];
|
||||
if(objAir.containsKey("min"))
|
||||
config.grow.air.min[i] = objAir["min"][i];
|
||||
if(objAir.containsKey("max"))
|
||||
config.grow.air.max[i] = objAir["max"][i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Grow Water */
|
||||
JsonObject objWater = objGrow["water"][0];
|
||||
for(byte i = 0; i < Max_Outputs; i++) {
|
||||
/* get air.configured */
|
||||
if(objWater.containsKey("configured"))
|
||||
config.grow.water.configured[i] = objWater["configured"][i];
|
||||
/* check if air is configured */
|
||||
if(config.grow.water.configured[i] == true) {
|
||||
/* get the rest of the config */
|
||||
if(objWater.containsKey("output"))
|
||||
config.grow.water.output[i] = objWater["output"][i];
|
||||
if(objWater.containsKey("onTime"))
|
||||
config.grow.water.onTime[i] = objWater["onTime"][i];
|
||||
if(objWater.containsKey("controlSensor"))
|
||||
config.grow.water.controlSensor[i] = objWater["controlSensor"][i];
|
||||
if(objWater.containsKey("controlRead"))
|
||||
config.grow.water.controlRead[i] = objWater["controlRead"][i];
|
||||
if(objWater.containsKey("controlMode"))
|
||||
config.grow.water.controlMode[i] = objWater["controlMode"][i];
|
||||
if(objWater.containsKey("min"))
|
||||
config.grow.water.min[i] = objWater["min"][i];
|
||||
if(objWater.containsKey("max"))
|
||||
config.grow.water.max[i] = objWater["max"][i];
|
||||
if(objWater.containsKey("interval"))
|
||||
config.grow.water.interval[i] = objWater["interval"][i];
|
||||
if(objWater.containsKey("intervalUnit"))
|
||||
config.grow.water.intervalUnit[i] = objWater["intervalUnit"][i];
|
||||
}
|
||||
}
|
||||
|
||||
// Close the file (Curiously, File's destructor doesn't close the file)
|
||||
file.close();
|
||||
Log.notice(F("%s config successfully loaded" CR), LogLoc);
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s --- runtime config ---" CR), LogLoc);
|
||||
serializeJsonPretty(doc, Serial);
|
||||
// Json output does not end with NewLine
|
||||
Serial.println("");
|
||||
Log.verbose(F("%s ----------------------" CR), LogLoc);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SaveConfig(bool writeToSerial = false) {
|
||||
const static char LogLoc[] PROGMEM = "[LittleFS:SaveConfig]";
|
||||
/*
|
||||
* 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["esp32cam"] = config.system.esp32cam;
|
||||
objSystem["httpUser"] = config.system.httpUser;
|
||||
objSystem["httpPass"] = config.system.httpPass;
|
||||
objSystem["httpLogSerial"] = config.system.httpLogSerial;
|
||||
objSystem["schedulerInterval"] = config.system.schedulerInterval;
|
||||
objSystem["ntp"] = config.system.ntp;
|
||||
objSystem["rtc"] = config.system.rtc;
|
||||
objSystem["time2fs"] = config.system.time2fs;
|
||||
objSystem["pwmFreq"] = config.system.pwmFreq;
|
||||
|
||||
/* 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["invert"][i] = config.system.output.invert[i];
|
||||
objSystemOutput["gpio_pwm"][i] = config.system.output.gpio_pwm[i];
|
||||
// i2c type
|
||||
objSystemOutput["i2c_type"][i] = config.system.output.i2c_type[i];
|
||||
objSystemOutput["i2c_addr"][i] = config.system.output.i2c_addr[i];
|
||||
objSystemOutput["i2c_port"][i] = config.system.output.i2c_port[i];
|
||||
// web
|
||||
objSystemOutput["webcall_host"][i] = config.system.output.webcall_host[i];
|
||||
objSystemOutput["webcall_path_on"][i] = config.system.output.webcall_path_on[i];
|
||||
objSystemOutput["webcall_path_off"][i] = config.system.output.webcall_path_off[i];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* System Sensors */
|
||||
JsonObject objSystemSensor = objSystem["sensor"].add<JsonObject>();
|
||||
for(byte i=0; i < Max_Sensors; i++) {
|
||||
if(config.system.sensor.type[i] > 0) {
|
||||
objSystemSensor["type"][i] = config.system.sensor.type[i];
|
||||
objSystemSensor["name"][i] = config.system.sensor.name[i];
|
||||
objSystemSensor["i2c_addr"][i] = config.system.sensor.i2c_addr[i];
|
||||
for(byte j = 0; j < Max_Sensors_GPIO; j++) {
|
||||
objSystemSensor["gpio"][i][j] = config.system.sensor.gpio[i][j];
|
||||
}
|
||||
|
||||
/* offset reading */
|
||||
for(byte j = 0; j < Max_Sensors_Read; j++) {
|
||||
objSystemSensor["offset"][i][j] = config.system.sensor.offset[i][j];
|
||||
}
|
||||
|
||||
/* low reading */
|
||||
for(byte j = 0; j < Max_Sensors_Read; j++) {
|
||||
objSystemSensor["low"][i][j] = config.system.sensor.low[i][j];
|
||||
}
|
||||
|
||||
/* high reading */
|
||||
for(byte j = 0; j < Max_Sensors_Read; j++) {
|
||||
objSystemSensor["high"][i][j] = config.system.sensor.high[i][j];
|
||||
}
|
||||
|
||||
/* rawConvert reading */
|
||||
for(byte j = 0; j < Max_Sensors_Read; j++) {
|
||||
objSystemSensor["rawConvert"][i][j] = config.system.sensor.rawConvert[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Grow */
|
||||
JsonObject objGrow = doc["grow"].add<JsonObject>();
|
||||
objGrow["name"] = config.grow.name;
|
||||
objGrow["start"] = config.grow.start;
|
||||
objGrow["daysVeg"] = config.grow.daysVeg;
|
||||
objGrow["daysBloom"] = config.grow.daysBloom;
|
||||
|
||||
/* Grow Light */
|
||||
JsonObject objLight = objGrow["light"].add<JsonObject>();
|
||||
for(byte i = 0; i < Max_Outputs; i++) {
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s LightId %d, Max_Outputs %d, light.configured %T" CR), LogLoc, i, Max_Outputs, config.grow.light.configured[i]);
|
||||
#endif
|
||||
if(config.grow.light.configured[i] == true) {
|
||||
objLight["configured"][i] = config.grow.light.configured[i];
|
||||
objLight["output"][i] = config.grow.light.output[i];
|
||||
objLight["sunriseHourVeg"][i] = config.grow.light.sunriseHourVeg[i];
|
||||
objLight["sunriseMinuteVeg"][i] = config.grow.light.sunriseMinuteVeg[i];
|
||||
objLight["sunsetHourVeg"][i] = config.grow.light.sunsetHourVeg[i];
|
||||
objLight["sunsetMinuteVeg"][i] = config.grow.light.sunsetMinuteVeg[i];
|
||||
|
||||
objLight["sunriseHourBloom"][i] = config.grow.light.sunriseHourBloom[i];
|
||||
objLight["sunriseMinuteBloom"][i] = config.grow.light.sunriseMinuteBloom[i];
|
||||
objLight["sunsetHourBloom"][i] = config.grow.light.sunsetHourBloom[i];
|
||||
objLight["sunsetMinuteBloom"][i] = config.grow.light.sunsetMinuteBloom[i];
|
||||
|
||||
objLight["power"][i] = config.grow.light.power[i];
|
||||
objLight["fade"][i] = config.grow.light.fade[i];
|
||||
objLight["fadeDuration"][i] = config.grow.light.fadeDuration[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Grow Air */
|
||||
JsonObject objAir = objGrow["air"].add<JsonObject>();
|
||||
for(byte i = 0; i < Max_Outputs; i++) {
|
||||
//Log.verbose(F("%s LightId %d, Max_Outputs %d, light.configured %T" CR), LogLoc, i, Max_Outputs, config.grow.light.configured[i]);
|
||||
if(config.grow.air.configured[i] == true) {
|
||||
objAir["configured"][i] = config.grow.air.configured[i];
|
||||
objAir["output"][i] = config.grow.air.output[i];
|
||||
objAir["power"][i] = config.grow.air.power[i];
|
||||
objAir["controlSensor"][i] = config.grow.air.controlSensor[i];
|
||||
objAir["controlRead"][i] = config.grow.air.controlRead[i];
|
||||
objAir["controlMode"][i] = config.grow.air.controlMode[i];
|
||||
objAir["min"][i] = config.grow.air.min[i];
|
||||
objAir["max"][i] = config.grow.air.max[i];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* Grow Water */
|
||||
JsonObject objWater = objGrow["water"].add<JsonObject>();
|
||||
for(byte i = 0; i < Max_Outputs; i++) {
|
||||
//Log.verbose(F("%s LightId %d, Max_Outputs %d, light.configured %T" CR), LogLoc, i, Max_Outputs, config.grow.light.configured[i]);
|
||||
if(config.grow.water.configured[i] == true) {
|
||||
objWater["configured"][i] = config.grow.water.configured[i];
|
||||
objWater["output"][i] = config.grow.water.output[i];
|
||||
objWater["onTime"][i] = config.grow.water.onTime[i];
|
||||
objWater["controlSensor"][i] = config.grow.water.controlSensor[i];
|
||||
objWater["controlRead"][i] = config.grow.water.controlRead[i];
|
||||
objWater["controlMode"][i] = config.grow.water.controlMode[i];
|
||||
objWater["min"][i] = config.grow.water.min[i];
|
||||
objWater["max"][i] = config.grow.water.max[i];
|
||||
objWater["interval"][i] = config.grow.water.interval[i];
|
||||
objWater["intervalUnit"][i] = config.grow.water.intervalUnit[i];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
//Log.notice(F("%s loading config from: %s" CR), LogLoc, CANGROW_CFG);
|
||||
Log.error(F("%s FAILED to open configfile for writing: %s" CR), LogLoc, CANGROW_CFG);
|
||||
return false;
|
||||
} else {
|
||||
Log.notice(F("%s opened for writing %s" CR), LogLoc, CANGROW_CFG);
|
||||
}
|
||||
// Serialize JSON to file
|
||||
if (serializeJson(doc, file) == 0) {
|
||||
Log.error(F("%s FAILED to write configfile: %s" CR), LogLoc, CANGROW_CFG);
|
||||
} else {
|
||||
Log.notice(F("%s successfully written %s" CR), LogLoc, CANGROW_CFG);
|
||||
}
|
||||
file.close();
|
||||
} else {
|
||||
Log.notice(F("%s --- %s ---" CR), LogLoc, CANGROW_CFG);
|
||||
serializeJsonPretty(doc, Serial);
|
||||
Serial.println("");
|
||||
Log.notice(F("%s ----------------------" CR), LogLoc, CANGROW_CFG);
|
||||
}
|
||||
|
||||
/* every time config get saved, we save the actual time too
|
||||
* so when ntp is not available, we hopefully do not lack behind too much
|
||||
* (better then nothing) */
|
||||
Time2FS_Save();
|
||||
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
|
41
include/CanGrow_Logo.h
Normal file
41
include/CanGrow_Logo.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
// 'CanGrow_Logo', 128x32px
|
||||
const unsigned char bmpCanGrow_Logo [] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x03, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x07, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0e, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x38, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x1c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x70, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x18, 0x03, 0x00, 0x00, 0x00, 0x04, 0x07, 0xe0, 0x20, 0x60, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x18, 0x03, 0x00, 0x00, 0x00, 0x06, 0x07, 0xe0, 0xe0, 0x60, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x18, 0x00, 0x00, 0x00, 0x00, 0x03, 0x87, 0xe1, 0xc0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x30, 0x00, 0x3f, 0xc3, 0xff, 0x03, 0xc7, 0xe3, 0xc0, 0xcf, 0xf9, 0xff, 0xe3, 0xfc, 0xc1, 0x83,
|
||||
0x30, 0x00, 0x7f, 0xe3, 0xff, 0x83, 0xe7, 0xe7, 0xc0, 0xcf, 0xfb, 0xff, 0xe7, 0xfe, 0xc3, 0x87,
|
||||
0x30, 0x00, 0xe0, 0x73, 0x80, 0xc1, 0xf7, 0xef, 0xc0, 0xc0, 0x1b, 0x80, 0x0e, 0x03, 0xc3, 0x86,
|
||||
0x30, 0x00, 0xc0, 0x33, 0x00, 0xc1, 0xff, 0xff, 0x80, 0xc0, 0x1b, 0x00, 0x0c, 0x03, 0xc7, 0x8e,
|
||||
0x30, 0x01, 0xc0, 0x37, 0x00, 0xc0, 0xff, 0xff, 0x80, 0xc0, 0x3b, 0x00, 0x1c, 0x03, 0xc7, 0x8c,
|
||||
0x60, 0x01, 0xc0, 0x37, 0x00, 0xc0, 0xff, 0xff, 0x01, 0x80, 0x3f, 0x00, 0x18, 0x03, 0xcf, 0x9c,
|
||||
0x60, 0x00, 0x00, 0x37, 0x00, 0xc0, 0x7f, 0xfe, 0x01, 0x80, 0x37, 0x00, 0x18, 0x03, 0xcf, 0x9c,
|
||||
0x60, 0x00, 0x00, 0x76, 0x01, 0xc0, 0x1f, 0xfc, 0x01, 0x80, 0x36, 0x00, 0x18, 0x06, 0xdf, 0xb8,
|
||||
0x60, 0x00, 0x7f, 0xe6, 0x01, 0x9f, 0x9f, 0xfc, 0xf9, 0x80, 0x36, 0x00, 0x18, 0x06, 0xdd, 0xb8,
|
||||
0x60, 0x00, 0xff, 0xe6, 0x01, 0x87, 0xff, 0xff, 0xf1, 0x80, 0x76, 0x00, 0x18, 0x06, 0xdd, 0xb0,
|
||||
0xc0, 0x01, 0xc0, 0xee, 0x01, 0x83, 0xff, 0xff, 0xc3, 0x00, 0x7e, 0x00, 0x30, 0x06, 0xf9, 0xf0,
|
||||
0xc0, 0x0b, 0x80, 0x6e, 0x01, 0x81, 0xff, 0xff, 0x83, 0x00, 0x6e, 0x00, 0x30, 0x06, 0xf9, 0xe0,
|
||||
0xc0, 0x1b, 0x00, 0xec, 0x01, 0x80, 0x1f, 0xf8, 0x03, 0x00, 0x6c, 0x00, 0x30, 0x0e, 0xf1, 0xe0,
|
||||
0xc0, 0x3b, 0x00, 0xcc, 0x03, 0x80, 0x3f, 0xfc, 0x03, 0x00, 0xec, 0x00, 0x30, 0x0c, 0xf1, 0xc0,
|
||||
0xc0, 0x7b, 0x01, 0xcc, 0x03, 0x00, 0x7f, 0xfe, 0x03, 0x01, 0xec, 0x00, 0x30, 0x1c, 0xe1, 0xc0,
|
||||
0x7f, 0xf1, 0xff, 0xdc, 0x03, 0x00, 0xf0, 0x8f, 0x01, 0xff, 0xfc, 0x00, 0x1f, 0xf8, 0xe1, 0xc0,
|
||||
0x3f, 0xe0, 0xff, 0xcc, 0x03, 0x00, 0x00, 0x80, 0x00, 0xff, 0xcc, 0x00, 0x0f, 0xf0, 0xc1, 0x80,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 528)
|
||||
const int bmpallArray_LEN = 1;
|
||||
const unsigned char* bmpallArray[1] = {
|
||||
bmpCanGrow_Logo
|
||||
};
|
540
include/CanGrow_Output.h
Normal file
540
include/CanGrow_Output.h
Normal file
|
@ -0,0 +1,540 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_Output.h - Output header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Output/Output_Common.h"
|
||||
#include "Output/Output_I2C_01_MCP4725.h"
|
||||
#include "Output/Output_I2C_02_GP8403.h"
|
||||
|
||||
/* OutputI2C index struct */
|
||||
struct OutputI2C_Index {
|
||||
const char name[16];
|
||||
const byte port[OUTPUT_TYPE_I2C_MAX_PORTS];
|
||||
const byte max;
|
||||
};
|
||||
|
||||
OutputI2C_Index OutputI2Cindex[] {
|
||||
/* OutputI2C 00 is unset */
|
||||
{ "unset", {
|
||||
{},
|
||||
}},
|
||||
/* OutputI2C 01 */
|
||||
{
|
||||
OUTPUT_I2C_01_NAME,
|
||||
{
|
||||
OUTPUT_TYPE_I2C_PORT_BYTE
|
||||
},
|
||||
sizeof(Output_I2C_01_MCP4725_Addr),
|
||||
},
|
||||
|
||||
/* 02 - DFRobot Gravity (GP8403) */
|
||||
{
|
||||
OUTPUT_I2C_02_NAME,
|
||||
{
|
||||
OUTPUT_TYPE_I2C_PORT_BYTE,
|
||||
OUTPUT_TYPE_I2C_PORT_BYTE
|
||||
},
|
||||
sizeof(Output_I2C_02_GP8403_Addr),
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* Dont forget to increase the index counter after you added a new output module */
|
||||
const byte OutputI2Cindex_length = 2;
|
||||
|
||||
|
||||
byte Output_I2C_Addr_Init_Update(const byte OutputI2CindexId, const byte AddrId, const byte PortId, const byte Mode, const bool Invert = false, const byte Value = 0) {
|
||||
const static char LogLoc[] PROGMEM = "[Output:I2C:Addr_Init_Update]";
|
||||
/* Multi purpose function.
|
||||
*
|
||||
* Modes:
|
||||
* 0 - return the i2c address as byte
|
||||
* 1 - init the output i2c module, returns true (1) if succeeded
|
||||
* 2 - update i2 module data
|
||||
*/
|
||||
//byte Dummy_Addr[] = { 0x42, 0x69 };
|
||||
|
||||
/* invert Value if set, save it to Value_tmp */
|
||||
byte Value_tmp = Value;
|
||||
if(Invert == true)
|
||||
/* Value comes from outputState[] which is only type byte (max 255) */
|
||||
Value_tmp = 255 - Value;
|
||||
|
||||
switch(OutputI2CindexId) {
|
||||
|
||||
/* I2C Output module 01 */
|
||||
case 1:
|
||||
switch(Mode) {
|
||||
case OUPUT_I2C_AIU_MODE_ADDR:
|
||||
return Output_I2C_01_MCP4725_Addr[AddrId];
|
||||
break;
|
||||
case OUPUT_I2C_AIU_MODE_INIT:
|
||||
return Output_I2C_01_MCP4725_Init(AddrId, PortId);
|
||||
break;
|
||||
case OUPUT_I2C_AIU_MODE_UPDATE:
|
||||
Output_I2C_01_MCP4725_Update(AddrId, PortId, Value_tmp);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
/* I2C Output module 02 */
|
||||
case 2:
|
||||
switch(Mode) {
|
||||
case OUPUT_I2C_AIU_MODE_ADDR:
|
||||
return Output_I2C_02_GP8403_Addr[AddrId];
|
||||
break;
|
||||
case OUPUT_I2C_AIU_MODE_INIT:
|
||||
return Output_I2C_02_GP8403_Init(AddrId, PortId);
|
||||
break;
|
||||
case OUPUT_I2C_AIU_MODE_UPDATE:
|
||||
Output_I2C_02_GP8403_Update(AddrId, PortId, Value_tmp);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
|
||||
/* 02 - dummy*/
|
||||
//case 2:
|
||||
|
||||
//switch(Mode) {
|
||||
//case 0:
|
||||
//return Dummy_Addr[AddrId];
|
||||
//break;
|
||||
//case 1:
|
||||
//return true;
|
||||
//break;
|
||||
//case 2:
|
||||
//true;
|
||||
//break;
|
||||
//}
|
||||
//break;
|
||||
|
||||
/* unknown i2c output module id */
|
||||
default:
|
||||
Log.error(F("%s OutputI2Cindex ID %d not found" CR), LogLoc, OutputI2CindexId);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
byte Output_GPIO_Init_Update(const byte GPIOindexId, const bool PWM, const bool Invert, const byte Mode, const byte Value = 0) {
|
||||
const static char LogLoc[] PROGMEM = "[Output:GPIO:Init_Update]";
|
||||
bool Value_bool = Value;
|
||||
switch(Mode) {
|
||||
|
||||
case OUTPUT_GPIO_IU_MODE_INIT:
|
||||
pinMode(GPIOindex[GPIOindexId].gpio, OUTPUT);
|
||||
|
||||
if(Invert == true) {
|
||||
digitalWrite(GPIOindex[GPIOindexId].gpio, 1 - Value_bool);
|
||||
} else {
|
||||
digitalWrite(GPIOindex[GPIOindexId].gpio, Value_bool);
|
||||
}
|
||||
return true;
|
||||
break;
|
||||
|
||||
case OUTPUT_GPIO_IU_MODE_UPDATE:
|
||||
if((PWM == false) || (GPIOindex[GPIOindexId].note == NO_PWM)) {
|
||||
if(Invert == true) {
|
||||
digitalWrite(GPIOindex[GPIOindexId].gpio, 1 - Value_bool);
|
||||
} else {
|
||||
digitalWrite(GPIOindex[GPIOindexId].gpio, Value);
|
||||
}
|
||||
} else {
|
||||
/* output inverted? */
|
||||
if(Invert == true) {
|
||||
/* when output is set to 0 (off), use digitalWrite LOW to prevent spikes */
|
||||
if(GPIOindex[GPIOindexId].gpio < 255) {
|
||||
analogWrite(GPIOindex[GPIOindexId].gpio, 255 - Value);
|
||||
} else {
|
||||
digitalWrite(GPIOindex[GPIOindexId].gpio, HIGH);
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s digitalWrite HIGH" CR), LogLoc);
|
||||
#endif
|
||||
}
|
||||
/* not inverted */
|
||||
} else {
|
||||
/* when output is set to 0 (off), use digitalWrite LOW to prevent spikes */
|
||||
if(GPIOindex[GPIOindexId].gpio < 255) {
|
||||
analogWrite(GPIOindex[GPIOindexId].gpio, Value);
|
||||
} else {
|
||||
digitalWrite(GPIOindex[GPIOindexId].gpio, LOW);
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s digitalWrite LOW" CR), LogLoc);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Output_Webcall_Init_Update(const byte OutputId, const bool Value = false) {
|
||||
const static char LogLoc[] PROGMEM = "[Output:Webcall:Init_Update]";
|
||||
|
||||
/* here we invert the value if set. First we hand it to a tmp var, Value_tmp */
|
||||
bool Value_tmp = Value;
|
||||
/* check if output has inverted flagged */
|
||||
if(config.system.output.invert[OutputId] == true)
|
||||
Value_tmp = 1 - Value;
|
||||
|
||||
|
||||
String url;
|
||||
url += F("http://");
|
||||
url += config.system.output.webcall_host[OutputId];
|
||||
url += F("/");
|
||||
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
|
||||
///* set timeout to three seconds */
|
||||
//#ifdef ESP8266
|
||||
///* on ESP8266 http.setTimeout accepts miliseconds */
|
||||
//http.setTimeout(3000);
|
||||
//#endif
|
||||
|
||||
//#ifdef ESP32
|
||||
///* on ESP32 http.setTimeout() accepts only seconds - https://github.com/espressif/arduino-esp32/issues/3732 */
|
||||
//http.setTimeout(3);
|
||||
//#endif
|
||||
|
||||
switch(Value_tmp) {
|
||||
/* turn on */
|
||||
case true:
|
||||
url += config.system.output.webcall_path_on[OutputId];
|
||||
break;
|
||||
|
||||
/* turn off */
|
||||
case false:
|
||||
url += config.system.output.webcall_path_off[OutputId];
|
||||
break;
|
||||
}
|
||||
|
||||
/* build request */
|
||||
http.begin(client, url);
|
||||
|
||||
/* fire request and check result, */
|
||||
int httpResponseCode = http.GET();
|
||||
/* if 200 , OK */
|
||||
if(httpResponseCode > 0) {
|
||||
#ifdef DEBUG2
|
||||
Log.verbose(F("%s GET %s (%d)" CR), LogLoc, url.c_str(), httpResponseCode);
|
||||
#endif
|
||||
return true;
|
||||
} else {
|
||||
#ifdef DEBUG2
|
||||
Log.verbose(F("%s FAILED GET %s (%d)" CR), LogLoc, url.c_str(), httpResponseCode);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Output_Check_PWM(const byte OutputId) {
|
||||
/* when we verify output is GPIO PWM OR */
|
||||
if(((config.system.output.type[OutputId] == OUTPUT_TYPE_GPIO) && (config.system.output.gpio_pwm[OutputId] == true) && (GPIOindex[config.system.output.type[OutputId]].note != NO_PWM)) ||
|
||||
/* Output is type I2C */
|
||||
(config.system.output.type[OutputId] == OUTPUT_TYPE_I2C)
|
||||
) {
|
||||
/* return true */
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Output_Device
|
||||
*
|
||||
* add, remove, (modify) config.grow.light[] objects
|
||||
*/
|
||||
void Output_Device_Grow_AddRemove(const byte OutputId, const byte mode) {
|
||||
/* switch on device type of the output
|
||||
*
|
||||
* Modes:
|
||||
* 0 - add - receive OutputId
|
||||
* 1 - remove - receive LightId
|
||||
* 2 - modify - receive LightId
|
||||
* */
|
||||
switch(config.system.output.device[OutputId]) {
|
||||
case OUTPUT_DEVICE_LIGHT:
|
||||
//byte LightId;
|
||||
switch(mode) {
|
||||
/* add */
|
||||
case 0:
|
||||
config.grow.light.configured[OutputId] = true;
|
||||
config.grow.light.output[OutputId] = OutputId;
|
||||
config.grow.light.sunriseHourVeg[OutputId] = 6;
|
||||
config.grow.light.sunriseMinuteVeg[OutputId] = 0;
|
||||
config.grow.light.sunsetHourVeg[OutputId] = 23;
|
||||
config.grow.light.sunsetMinuteVeg[OutputId] = 59;
|
||||
|
||||
config.grow.light.sunriseHourBloom[OutputId] = 6;
|
||||
config.grow.light.sunriseMinuteBloom[OutputId] = 0;
|
||||
config.grow.light.sunsetHourBloom[OutputId] = 18;
|
||||
config.grow.light.sunsetMinuteBloom[OutputId] = 0;
|
||||
|
||||
config.grow.light.power[OutputId] = 0;
|
||||
if(Output_Check_PWM(OutputId)) {
|
||||
config.grow.light.fade[OutputId] = true;
|
||||
} else {
|
||||
config.grow.light.fade[OutputId] = false;
|
||||
}
|
||||
|
||||
config.grow.light.fadeDuration[OutputId] = 30;
|
||||
//SaveConfig()
|
||||
break;
|
||||
|
||||
/* remove */
|
||||
case 1:
|
||||
/* get LightId for this Output */
|
||||
//for(byte i = 0; i < Max_Outputs; i++) {
|
||||
//if((config.grow.light.configured[i] == true) && (config.grow.light.output[i] == OutputId)) {
|
||||
//LightId = i;
|
||||
//break;
|
||||
//}
|
||||
//}
|
||||
config.grow.light.configured[OutputId] = 0;
|
||||
config.grow.light.output[OutputId] = 0;
|
||||
config.grow.light.sunriseHourVeg[OutputId] = 0;
|
||||
config.grow.light.sunriseMinuteVeg[OutputId] = 0;
|
||||
config.grow.light.sunsetHourVeg[OutputId] = 0;
|
||||
config.grow.light.sunsetMinuteVeg[OutputId] = 0;
|
||||
|
||||
config.grow.light.sunriseHourBloom[OutputId] = 0;
|
||||
config.grow.light.sunriseMinuteBloom[OutputId] = 0;
|
||||
config.grow.light.sunsetHourBloom[OutputId] = 0;
|
||||
config.grow.light.sunsetMinuteBloom[OutputId] = 0;
|
||||
|
||||
config.grow.light.power[OutputId] = 0;
|
||||
config.grow.light.fade[OutputId] = 0;
|
||||
|
||||
config.grow.light.fadeDuration[OutputId] = 0;
|
||||
//SaveConfig();
|
||||
break;
|
||||
|
||||
//case 2:
|
||||
//true;
|
||||
//break;
|
||||
}
|
||||
break;
|
||||
|
||||
case OUTPUT_DEVICE_FAN:
|
||||
case OUTPUT_DEVICE_HUMIDIFIER:
|
||||
case OUTPUT_DEVICE_DEHUMIDIFIER:
|
||||
case OUTPUT_DEVICE_HEATING:
|
||||
switch(mode) {
|
||||
case 0:
|
||||
config.grow.air.configured[OutputId] = true;
|
||||
config.grow.air.output[OutputId] = OutputId;
|
||||
config.grow.air.power[OutputId] = 0;
|
||||
config.grow.air.controlSensor[OutputId] = 255; // 255 means unconfigured, because SensorId begins at 0
|
||||
config.grow.air.controlRead[OutputId] = 255; // same here
|
||||
config.grow.air.controlMode[OutputId] = 0;
|
||||
config.grow.air.min[OutputId] = 0;
|
||||
config.grow.air.max[OutputId] = 0;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
config.grow.air.configured[OutputId] = false;
|
||||
config.grow.air.output[OutputId] = 0;
|
||||
config.grow.air.power[OutputId] = 0;
|
||||
config.grow.air.controlSensor[OutputId] = 0;
|
||||
config.grow.air.controlRead[OutputId] = 0;
|
||||
config.grow.air.controlMode[OutputId] = 0;
|
||||
config.grow.air.min[OutputId] = 0;
|
||||
config.grow.air.max[OutputId] = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case OUTPUT_DEVICE_PUMP:
|
||||
switch(mode) {
|
||||
case 0:
|
||||
config.grow.water.configured[OutputId] = true;
|
||||
config.grow.water.output[OutputId] = OutputId;
|
||||
config.grow.water.onTime[OutputId] = 0;
|
||||
config.grow.water.controlSensor[OutputId] = 255; // 255 means unconfigured, because SensorId begins at 0
|
||||
config.grow.water.controlRead[OutputId] = 255; // same here
|
||||
config.grow.water.controlMode[OutputId] = 0;
|
||||
config.grow.water.min[OutputId] = 0;
|
||||
config.grow.water.max[OutputId] = 0;
|
||||
config.grow.water.interval[OutputId] = 0;
|
||||
config.grow.water.intervalUnit[OutputId] = 0;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
config.grow.water.configured[OutputId] = false;
|
||||
config.grow.water.output[OutputId] = 0;
|
||||
config.grow.water.onTime[OutputId] = 0;
|
||||
config.grow.water.controlSensor[OutputId] = 0;
|
||||
config.grow.water.controlRead[OutputId] = 0;
|
||||
config.grow.water.controlMode[OutputId] = 0;
|
||||
config.grow.water.min[OutputId] = 0;
|
||||
config.grow.water.max[OutputId] = 0;
|
||||
config.grow.water.interval[OutputId] = 0;
|
||||
config.grow.water.intervalUnit[OutputId] = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
//case OUTPUT_DEVICE_HUMIDIFIER:
|
||||
//break;
|
||||
|
||||
//case OUTPUT_DEVICE_DEHUMIDIFIER:
|
||||
//break;
|
||||
|
||||
//case OUTPUT_DEVICE_HEATING:
|
||||
//break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Output main functions
|
||||
*
|
||||
*/
|
||||
void Output_Init() {
|
||||
/* initialize all configured outputs */
|
||||
const static char LogLoc[] PROGMEM = "[Output:Init]";
|
||||
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s == configured outputs ==" CR), LogLoc);
|
||||
#endif
|
||||
|
||||
/* interate through all available Output IDs */
|
||||
for(byte i = 0; i < Max_Outputs; i++) {
|
||||
/* if configured */
|
||||
if(config.system.output.type[i] > 0) {
|
||||
/* get the configured output type */
|
||||
switch(config.system.output.type[i]) {
|
||||
case OUTPUT_TYPE_GPIO:
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s Output ID %d: %s - Device %s (%d), GPIO %d (%d), Enabled %T, PWM %T, Invert %T" CR),
|
||||
LogLoc,
|
||||
i, config.system.output.name[i],
|
||||
Output_Device_descr[config.system.output.device[i]], config.system.output.device[i],
|
||||
GPIOindex[config.system.output.gpio[i]].gpio, config.system.output.gpio[i],
|
||||
config.system.output.enabled[i], config.system.output.gpio_pwm[i], config.system.output.invert[i]);
|
||||
#endif
|
||||
/* TODO implement gpio init */
|
||||
outputStatus[i] = Output_GPIO_Init_Update(config.system.output.gpio[i], config.system.output.gpio_pwm[i], config.system.output.invert[i], OUTPUT_GPIO_IU_MODE_INIT);
|
||||
|
||||
break;
|
||||
|
||||
case OUTPUT_TYPE_I2C:
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s Output ID %d: %s - Device %s (%d), I2C type %s (%d), I2C addr 0x%x (%d), Module port %d, Enabled %T, PWM %T, Invert %T" CR),
|
||||
LogLoc,
|
||||
i, config.system.output.name[i],
|
||||
Output_Device_descr[config.system.output.device[i]], config.system.output.device[i],
|
||||
OutputI2Cindex[config.system.output.i2c_type[i]].name, config.system.output.i2c_type[i],
|
||||
config.system.output.enabled[i], config.system.output.gpio_pwm[i], config.system.output.invert[i]);
|
||||
#endif
|
||||
outputStatus[i] = Output_I2C_Addr_Init_Update(config.system.output.i2c_type[i], config.system.output.i2c_addr[i], config.system.output.i2c_port[i], OUPUT_I2C_AIU_MODE_INIT, config.system.output.invert[i]);
|
||||
break;
|
||||
|
||||
case OUTPUT_TYPE_WEB:
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s Output ID %d: %s - Device %s (%d), Webcall host %s, Webcall Path ON '%s', Webcall Path OFF '%s', Enabled %T, PWM %T, Invert %T" CR),
|
||||
LogLoc,
|
||||
i, config.system.output.name[i],
|
||||
Output_Device_descr[config.system.output.device[i]], config.system.output.device[i],
|
||||
config.system.output.webcall_host[i], config.system.output.webcall_path_on[i], config.system.output.webcall_path_off[i],
|
||||
config.system.output.enabled[i], config.system.output.gpio_pwm[i], config.system.output.invert[i]);
|
||||
#endif
|
||||
/* TODO implement webcall init */
|
||||
outputStatus[i] = Output_Webcall_Init_Update(i);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.error(F("%s (%d) Output type %d not found" CR), LogLoc, i, config.system.output.type[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Output_Update() {
|
||||
const static char LogLoc[] PROGMEM = "[Output:Update]";
|
||||
#ifdef DEBUG2
|
||||
unsigned long mStart = millis();
|
||||
unsigned long mStop;
|
||||
Log.verbose(F("%s Start %u" CR), LogLoc, mStart);
|
||||
#endif
|
||||
/* interate through all available Output IDs */
|
||||
for(byte i = 0; i < Max_Outputs; i++) {
|
||||
/* if configured and enabled */
|
||||
if((config.system.output.type[i] > 0) && (config.system.output.enabled[i] == true)) {
|
||||
/* get the configured output type */
|
||||
switch(config.system.output.type[i]) {
|
||||
|
||||
/*******
|
||||
* GPIO
|
||||
* *****/
|
||||
case OUTPUT_TYPE_GPIO:
|
||||
/* update GPIO output */
|
||||
Output_GPIO_Init_Update(config.system.output.gpio[i], config.system.output.gpio_pwm[i], config.system.output.invert[i], OUTPUT_GPIO_IU_MODE_UPDATE, outputState[i]);
|
||||
|
||||
break;
|
||||
|
||||
/*******
|
||||
* I2C
|
||||
* *****/
|
||||
case OUTPUT_TYPE_I2C:
|
||||
/* perform I2C output update only, when outputStatus is OK (true) */
|
||||
if(outputStatus[i] == true)
|
||||
Output_I2C_Addr_Init_Update(config.system.output.i2c_type[i], config.system.output.i2c_addr[i], config.system.output.i2c_port[i], OUPUT_I2C_AIU_MODE_UPDATE, config.system.output.invert[i], outputState[i]);
|
||||
break;
|
||||
|
||||
/*******
|
||||
* WEB
|
||||
* ****/
|
||||
case OUTPUT_TYPE_WEB:
|
||||
/* check how often webcall output failed. if limit exceeded, do not update anymore */
|
||||
if((outputStatus[i] == false) && (outputWebcallFailed[i] > 5)) {
|
||||
/* if webcall fail counter has reached limit of 255, reset to 0
|
||||
* so we retry a call. */
|
||||
if(outputWebcallFailed[i] >= 255) {
|
||||
outputWebcallFailed[i] = 0;
|
||||
} else {
|
||||
/* increment webcall failed counter. */
|
||||
outputWebcallFailed[i]++;
|
||||
}
|
||||
} else {
|
||||
/* update webcall output */
|
||||
outputStatus[i] = Output_Webcall_Init_Update(i, outputState[i]);
|
||||
if(outputStatus[i] == false) {
|
||||
/* increment webcall failed counter */
|
||||
outputWebcallFailed[i]++;
|
||||
} else {
|
||||
/* otherwise set to 0 */
|
||||
outputWebcallFailed[i] = 0;
|
||||
}
|
||||
}
|
||||
true;
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.error(F("%s (%d) Output type %d not found" CR), LogLoc, i, config.system.output.type[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG2
|
||||
mStop = millis();
|
||||
Log.verbose(F("%s Stop %u (%u)" CR), LogLoc, mStop, mStop - mStart);
|
||||
#endif
|
||||
}
|
758
include/CanGrow_Sensor.h
Normal file
758
include/CanGrow_Sensor.h
Normal file
|
@ -0,0 +1,758 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_Sensor.h - sensor header file
|
||||
*
|
||||
*
|
||||
*
|
||||
* ADD A NEW SENSOR
|
||||
* ****************
|
||||
* If you want to add a new sensor, you have to to following things:
|
||||
*
|
||||
* Check what it's the last used SensorIndex ID. If it's Sensor 08, you have to
|
||||
* take 09 as next.
|
||||
*
|
||||
* Copy Sensor/00_Example.h to Sensor/09_YourSensor.h and rename everything in it from
|
||||
* "00_Example" to "09_YourSensor" and edit all the needed functions and variables to your needs.
|
||||
*
|
||||
* Add a new include line to CanGrow_Sensor.h (this file)
|
||||
* #include "Sensor/09_YourSensor.h"
|
||||
*
|
||||
* Add a new Entry to the SensorIndex Array, like:
|
||||
* ***** SensorIndex[] *****
|
||||
* ,{
|
||||
* // 9 - YourSensor
|
||||
* // Sensor Name
|
||||
* SENSOR_09_NAME,
|
||||
* {
|
||||
* // Sensor Readings
|
||||
* SENSOR_READ_TYPE_TEMP,
|
||||
* SENSOR_READ_TYPE_HUMIDITY,
|
||||
* SENSOR_READ_TYPE_RAW
|
||||
* },
|
||||
* // Maximal Sensor Units (most time the Sum of available Addresses)
|
||||
* sizeof(Sensor_09_YourSensor_Addr),
|
||||
* }
|
||||
* ************************
|
||||
*
|
||||
* If you are done with that, you have to add a new Switch case for the new Sensor ID
|
||||
* to Sensor_Addr_Init_Update() and Sensor_getValue() like:
|
||||
*
|
||||
* ***** Sensor_Addr_Init_Update() *****
|
||||
* // Sensor 09
|
||||
* case 9:
|
||||
* switch(mode) {
|
||||
* case 0:
|
||||
* return Sensor_09_YourSensor_Addr[AddrId];
|
||||
* break;
|
||||
*
|
||||
* case 1:
|
||||
* return Sensor_09_YourSensor_Init(AddrId);
|
||||
* break;
|
||||
*
|
||||
* case 2:
|
||||
* Sensor_09_YourSensor_Update(AddrId);
|
||||
* break;
|
||||
* }
|
||||
* break;
|
||||
* *************************************
|
||||
*
|
||||
* ***** Sensor_getValue() *****
|
||||
* // Sensor 09
|
||||
* case 9:
|
||||
* return Sensor_09_YourSensor[AddrId][ReadValId];
|
||||
* break;
|
||||
* *****************************
|
||||
*/
|
||||
|
||||
/* should come as dependency with all adafruit sensor libs. If not, see here:
|
||||
* https://github.com/adafruit/Adafruit_Sensor */
|
||||
#include <Adafruit_Sensor.h>
|
||||
|
||||
#include "Sensor/Sensor_Common.h"
|
||||
#include "Sensor/01_ADC_builtin.h"
|
||||
#include "Sensor/02_BME280.h"
|
||||
#include "Sensor/03_BME680.h"
|
||||
#include "Sensor/04_SHT3x.h"
|
||||
#include "Sensor/05_MLX90614.h"
|
||||
#include "Sensor/06_TCS34725.h"
|
||||
#include "Sensor/07_ADS1115.h"
|
||||
#include "Sensor/08_ADS1015.h"
|
||||
#include "Sensor/09_Chirp.h"
|
||||
#include "Sensor/10_CCS811.h"
|
||||
|
||||
/*
|
||||
* Sensor Todo list:
|
||||
*
|
||||
* - CCS811 CO2 sensor, will have type SENSOR_TYPE_I2C_WITH_GPIO, it needs signal on pin WAK
|
||||
* cheap - ~ 8€ on Aliexpress
|
||||
* - HX711 for weight sensor, this sensor needs two GPIOs for communication
|
||||
* - SCD30/40 CO2 sensor, expensive, >70€
|
||||
*/
|
||||
|
||||
|
||||
struct Sensor_Index {
|
||||
/*
|
||||
* Sensor Index
|
||||
* - name
|
||||
* - readings (array, up to 8 entries)
|
||||
* - 0 unset
|
||||
* - 1 Raw
|
||||
* - 2 Temp
|
||||
* - 3 Humidity
|
||||
* - 4 Moisture
|
||||
* - 5 Pressure
|
||||
* - 6 Gas restistance
|
||||
* - max units
|
||||
*
|
||||
*/
|
||||
const char name[16];
|
||||
const byte type;
|
||||
const byte read[Max_Sensors_Read];
|
||||
const byte max;
|
||||
const byte gpioMax;
|
||||
};
|
||||
|
||||
Sensor_Index SensorIndex[] {
|
||||
/*
|
||||
* Example:
|
||||
*
|
||||
* // 0 - Example
|
||||
* { SENSOR_00_NAME,
|
||||
* {
|
||||
* SENSOR_READ_TYPE_TEMP,
|
||||
* SENSOR_READ_TYPE_HUMIDITY,
|
||||
* SENSOR_READ_TYPE_RAW,
|
||||
* SENSOR_READ_TYPE_RAW
|
||||
* },
|
||||
* // max nr of sensor units by nr of available addresses
|
||||
* sizeof(Sensor_00_Example_Addr),
|
||||
* },
|
||||
*
|
||||
*/
|
||||
|
||||
/* 0 is for unset in config */
|
||||
{ "unset", 255, {
|
||||
{},
|
||||
}},
|
||||
|
||||
// 1 - internal ADC
|
||||
{ SENSOR_01_NAME,
|
||||
SENSOR_TYPE_INTADC,
|
||||
{
|
||||
SENSOR_READ_TYPE_RAW
|
||||
},
|
||||
SENSOR_01_MAX,
|
||||
},
|
||||
|
||||
// 2 - BME280
|
||||
{ SENSOR_02_NAME,
|
||||
SENSOR_TYPE_I2C,
|
||||
{
|
||||
SENSOR_READ_TYPE_TEMP,
|
||||
SENSOR_READ_TYPE_HUMIDITY,
|
||||
SENSOR_READ_TYPE_PRESSURE,
|
||||
SENSOR_READ_TYPE_ALTITUDE
|
||||
},
|
||||
// max nr of sensor units by nr of available addresses
|
||||
sizeof(Sensor_02_BME280_Addr),
|
||||
},
|
||||
|
||||
// 3 - BME680
|
||||
{ SENSOR_03_NAME,
|
||||
SENSOR_TYPE_I2C,
|
||||
{
|
||||
SENSOR_READ_TYPE_TEMP,
|
||||
SENSOR_READ_TYPE_HUMIDITY,
|
||||
SENSOR_READ_TYPE_PRESSURE,
|
||||
SENSOR_READ_TYPE_ALTITUDE,
|
||||
SENSOR_READ_TYPE_GAS_RESISTANCE
|
||||
},
|
||||
sizeof(Sensor_03_BME680_Addr),
|
||||
},
|
||||
|
||||
// 4 - SHT3x
|
||||
{ SENSOR_04_NAME,
|
||||
SENSOR_TYPE_I2C,
|
||||
{
|
||||
SENSOR_READ_TYPE_TEMP,
|
||||
SENSOR_READ_TYPE_HUMIDITY
|
||||
},
|
||||
sizeof(Sensor_04_SHT3X_Addr),
|
||||
},
|
||||
|
||||
// 5 - MLX90614
|
||||
{ SENSOR_05_NAME,
|
||||
SENSOR_TYPE_I2C,
|
||||
{
|
||||
/* Ambient temp */
|
||||
SENSOR_READ_TYPE_TEMP,
|
||||
/* Object temp */
|
||||
SENSOR_READ_TYPE_TEMP
|
||||
},
|
||||
sizeof(Sensor_05_MLX90614_Addr),
|
||||
},
|
||||
|
||||
// 6 - TCS34725
|
||||
{ SENSOR_06_NAME,
|
||||
SENSOR_TYPE_I2C,
|
||||
{
|
||||
SENSOR_READ_TYPE_COLOR_TEMP,
|
||||
SENSOR_READ_TYPE_LUX,
|
||||
SENSOR_READ_TYPE_COLOR_RED,
|
||||
SENSOR_READ_TYPE_COLOR_GREEN,
|
||||
SENSOR_READ_TYPE_COLOR_BLUE
|
||||
},
|
||||
sizeof(Sensor_06_TCS34725_Addr),
|
||||
},
|
||||
|
||||
{
|
||||
// 7 - ADS1115
|
||||
SENSOR_07_NAME,
|
||||
SENSOR_TYPE_I2C,
|
||||
{
|
||||
/* A0 */
|
||||
SENSOR_READ_TYPE_RAW,
|
||||
/* A1 */
|
||||
SENSOR_READ_TYPE_RAW,
|
||||
/* A2 */
|
||||
SENSOR_READ_TYPE_RAW,
|
||||
/* A3 */
|
||||
SENSOR_READ_TYPE_RAW
|
||||
|
||||
},
|
||||
sizeof(Sensor_07_ADS1115_Addr),
|
||||
},
|
||||
|
||||
{
|
||||
// 8 - ADS1015
|
||||
SENSOR_08_NAME,
|
||||
SENSOR_TYPE_I2C,
|
||||
{
|
||||
SENSOR_READ_TYPE_RAW,
|
||||
SENSOR_READ_TYPE_RAW,
|
||||
SENSOR_READ_TYPE_RAW,
|
||||
SENSOR_READ_TYPE_RAW
|
||||
|
||||
},
|
||||
sizeof(Sensor_08_ADS1015_Addr),
|
||||
},
|
||||
|
||||
{
|
||||
// 9 - I2C Chirp soilmoisture/temperature sensor
|
||||
SENSOR_09_NAME,
|
||||
SENSOR_TYPE_I2C,
|
||||
{
|
||||
// raw soilmoisture value
|
||||
SENSOR_READ_TYPE_RAW,
|
||||
// temperature
|
||||
SENSOR_READ_TYPE_TEMP,
|
||||
/* raw light value takes 3s to use, so we dont use it. if you need it,
|
||||
* uncomment it here and in Sensor/09_Chirp Sensor_09_Chirp_Update() */
|
||||
// SENSOR_READ_TYPE_RAW
|
||||
},
|
||||
sizeof(Sensor_09_Chirp_Addr),
|
||||
},
|
||||
|
||||
{
|
||||
/* 10 - CCS811 CO2 I2C sensor */
|
||||
SENSOR_10_NAME,
|
||||
SENSOR_TYPE_I2C,
|
||||
{
|
||||
/* CO2 as parts per million */
|
||||
SENSOR_READ_TYPE_PARTS_PER_MILLION,
|
||||
/* TVOC value (Total Volatile Organic Compouds)*/
|
||||
SENSOR_READ_TYPE_TVOC,
|
||||
},
|
||||
sizeof(Sensor_10_CCS811_Addr),
|
||||
}
|
||||
};
|
||||
|
||||
/* sum up of number of sensors. Dont forget to increment if you add one :) */
|
||||
const byte SensorIndex_length = 10;
|
||||
|
||||
byte Sensor_Addr_Init_Update(const byte SensorIndexId, const byte AddrId, const byte mode, const byte Gpio2 = 0) {
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:Addr_Init_Update]";
|
||||
/* Multi purpose function.
|
||||
*
|
||||
* Modes:
|
||||
* 0 - get the address as byte (i2c_addr index, gpio index)
|
||||
* 1 - init the sensor, returns true (1) if succeeded
|
||||
* 2 - update sensors data
|
||||
*
|
||||
* When using a sensor which is using bare GPIOs like int ADC, or some 1- or 2-Wire sensors
|
||||
* AddrId is used for the first GPIO and gpio2 for the second.
|
||||
* Maybe i come up later with a better idea, but for now...
|
||||
*/
|
||||
#ifdef DEBUG3
|
||||
if(mode > 0)
|
||||
Log.verbose(F("%s Mode: %d, SensorIndexId: %d, AddrId: %d" CR), LogLoc, mode, SensorIndexId, AddrId);
|
||||
#endif
|
||||
switch(SensorIndexId) {
|
||||
/*
|
||||
* Example:
|
||||
*
|
||||
* case 0:
|
||||
* if(!onlyReturn)
|
||||
* Sensor_00_Example_Init(AddrId);
|
||||
* return Sensor_00_Example_Addr[AddrId];
|
||||
* break;
|
||||
*/
|
||||
|
||||
/* Sensor 01 */
|
||||
/* Internal ADC is an exception. Its clearly not an I2C device, but as I let the ADC
|
||||
* have for both, 8266 and 32, configurable GPIOs, it kinda has.
|
||||
* AddrId with int ADC is GPIOindex[].type */
|
||||
case 1:
|
||||
switch(mode) {
|
||||
case 0:
|
||||
/* internal ADC does not has a address here */
|
||||
return 0;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
return Sensor_01_ADC_Init(AddrId);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
Sensor_01_ADC_Update(AddrId);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
/* Sensor 02 */
|
||||
case 2:
|
||||
switch(mode) {
|
||||
case 0:
|
||||
return Sensor_02_BME280_Addr[AddrId];
|
||||
break;
|
||||
|
||||
case 1:
|
||||
return Sensor_02_BME280_Init(AddrId);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
Sensor_02_BME280_Update(AddrId);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
/* Sensor 03 */
|
||||
case 3:
|
||||
switch(mode) {
|
||||
case 0:
|
||||
return Sensor_03_BME680_Addr[AddrId];
|
||||
break;
|
||||
|
||||
case 1:
|
||||
return Sensor_03_BME680_Init(AddrId);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
Sensor_03_BME680_Update(AddrId);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
/* Sensor 04 */
|
||||
case 4:
|
||||
switch(mode) {
|
||||
case 0:
|
||||
return Sensor_04_SHT3X_Addr[AddrId];
|
||||
break;
|
||||
|
||||
case 1:
|
||||
return Sensor_04_SHT3X_Init(AddrId);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
Sensor_04_SHT3X_Update(AddrId);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
/* Sensor 05 */
|
||||
case 5:
|
||||
switch(mode) {
|
||||
case 0:
|
||||
return Sensor_05_MLX90614_Addr[AddrId];
|
||||
break;
|
||||
|
||||
case 1:
|
||||
return Sensor_05_MLX90614_Init(AddrId);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
Sensor_05_MLX90614_Update(AddrId);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
/* Sensor 06 */
|
||||
case 6:
|
||||
switch(mode) {
|
||||
case 0:
|
||||
return Sensor_06_TCS34725_Addr[AddrId];
|
||||
break;
|
||||
|
||||
case 1:
|
||||
return Sensor_06_TCS34725_Init(AddrId);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
Sensor_06_TCS34725_Update(AddrId);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
/* Sensor 07 */
|
||||
case 7:
|
||||
switch(mode) {
|
||||
case 0:
|
||||
return Sensor_07_ADS1115_Addr[AddrId];
|
||||
break;
|
||||
|
||||
case 1:
|
||||
return Sensor_07_ADS1115_Init(AddrId);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
Sensor_07_ADS1115_Update(AddrId);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
/* Sensor 08 */
|
||||
case 8:
|
||||
switch(mode) {
|
||||
case 0:
|
||||
return Sensor_08_ADS1015_Addr[AddrId];
|
||||
break;
|
||||
|
||||
case 1:
|
||||
return Sensor_08_ADS1015_Init(AddrId);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
Sensor_08_ADS1015_Update(AddrId);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
/* Sensor 09 */
|
||||
case 9:
|
||||
switch(mode) {
|
||||
case 0:
|
||||
return Sensor_09_Chirp_Addr[AddrId];
|
||||
break;
|
||||
|
||||
case 1:
|
||||
return Sensor_09_Chirp_Init(AddrId);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
Sensor_09_Chirp_Update(AddrId);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
/* Sensor 10 */
|
||||
case 10:
|
||||
switch(mode) {
|
||||
case 0:
|
||||
return Sensor_10_CCS811_Addr[AddrId];
|
||||
break;
|
||||
|
||||
case 1:
|
||||
return Sensor_10_CCS811_Init(AddrId);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
Sensor_10_CCS811_Update(AddrId);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
/* unknown sensor id */
|
||||
default:
|
||||
Log.error(F("%s SensorIndex ID %d not found" CR), LogLoc, config.system.sensor.type[AddrId]);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
float Sensor_getValue(const byte SensorIndexId, const byte AddrId, const byte ReadValId = 0) {
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:getValue]";
|
||||
/* not the best solution, but solution for the moment
|
||||
* i hope i can come up in future with a way, where i do not have to
|
||||
* maintain three different places with huge switch cases
|
||||
* and return everything as float, even when it could be easy an int
|
||||
*
|
||||
* here we read the value ReadVal from the given SensorIndexId and its AddrId
|
||||
* In case of RAW readings, like from ADCs , There is an Index ReadValId as well
|
||||
*/
|
||||
switch(SensorIndexId) {
|
||||
|
||||
/* Sensor 01 */
|
||||
case 1:
|
||||
#ifdef ESP8266
|
||||
return Sensor_01_ADC[AddrId];
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
/* */
|
||||
return Sensor_01_ADC[Sensor_01_ADC_ArrId(AddrId)];
|
||||
#endif
|
||||
break;
|
||||
|
||||
/* Sensor 02 */
|
||||
case 2:
|
||||
return Sensor_02_BME280[AddrId][ReadValId];
|
||||
break;
|
||||
|
||||
/* Sensor 03 */
|
||||
case 3:
|
||||
return Sensor_03_BME680[AddrId][ReadValId];
|
||||
break;
|
||||
|
||||
/* Sensor 04 */
|
||||
case 4:
|
||||
return Sensor_04_SHT3X[AddrId][ReadValId];
|
||||
break;
|
||||
|
||||
/* Sensor 05 */
|
||||
case 5:
|
||||
return Sensor_05_MLX90614[AddrId][ReadValId];
|
||||
break;
|
||||
|
||||
/* Sensor 06 */
|
||||
case 6:
|
||||
return Sensor_06_TCS34725[AddrId][ReadValId];
|
||||
break;
|
||||
|
||||
/* Sensor 07 */
|
||||
case 7:
|
||||
return Sensor_07_ADS1115[AddrId][ReadValId];
|
||||
break;
|
||||
|
||||
/* Sensor 08 */
|
||||
case 8:
|
||||
return Sensor_08_ADS1015[AddrId][ReadValId];
|
||||
break;
|
||||
|
||||
/* Sensor 09 */
|
||||
case 9:
|
||||
return Sensor_09_Chirp[AddrId][ReadValId];
|
||||
break;
|
||||
|
||||
/* Sensor 10 */
|
||||
case 10:
|
||||
return Sensor_10_CCS811[AddrId][ReadValId];
|
||||
break;
|
||||
|
||||
/* unknown sensor id */
|
||||
default:
|
||||
Log.error(F("%s SensorIndex ID %d not found" CR), LogLoc, SensorIndexId);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* *********************************************************************************************
|
||||
* From here on you do not need to touch any code (hopefully) if you want to add a new sensor!
|
||||
* *********************************************************************************************
|
||||
*/
|
||||
|
||||
float Sensor_getCalibratedValue(const byte SensorId, const byte ReadId) {
|
||||
float valueRaw;
|
||||
float value;
|
||||
|
||||
|
||||
|
||||
/* if SensorId is configured and there is a reading on ReadingId */
|
||||
if((config.system.sensor.type[SensorId] > 0) && (SensorIndex[config.system.sensor.type[SensorId]].read[ReadId] > 0)) {
|
||||
/* first, get the raw / original value */
|
||||
if(SensorIndex[config.system.sensor.type[SensorId]].type == SENSOR_TYPE_INTADC) {
|
||||
valueRaw = Sensor_getValue(config.system.sensor.type[SensorId], config.system.sensor.gpio[SensorId][0], ReadId);
|
||||
} else if(SensorIndex[config.system.sensor.type[SensorId]].type == SENSOR_TYPE_I2C) {
|
||||
valueRaw = Sensor_getValue(config.system.sensor.type[SensorId], config.system.sensor.i2c_addr[SensorId], ReadId);
|
||||
}
|
||||
|
||||
/* if reading is RAW, check what to do with it */
|
||||
if(SensorIndex[config.system.sensor.type[SensorId]].read[ReadId] == SENSOR_READ_TYPE_RAW) {
|
||||
/* config.system.sensor.rawConvert
|
||||
* 0 - unconfigured, return raw value
|
||||
* 1 - soilmoisture, return percentage
|
||||
* 2 - other TBD */
|
||||
switch(config.system.sensor.rawConvert[SensorId][ReadId]) {
|
||||
/* soilmoisture as percentage */
|
||||
case SENSOR_CONVERT_RAW_TYPE_SOILMOISTURE:
|
||||
/* dont use map when both , low and high, are 0 - this causes a crash */
|
||||
if((config.system.sensor.low[SensorId][ReadId] > 0) || (config.system.sensor.high[SensorId][ReadId] > 0)) {
|
||||
/* use map to calculate percentage value */
|
||||
value = map(valueRaw, config.system.sensor.low[SensorId][ReadId], config.system.sensor.high[SensorId][ReadId], 0, 100);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
return value;
|
||||
break;
|
||||
|
||||
default:
|
||||
return valueRaw;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* if not a RAW value, return the value with the offset */
|
||||
return valueRaw + config.system.sensor.offset[SensorId][ReadId];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void Sensor_Log_Readings(const byte SensorIndexId , const byte AddrId) {
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:Log_Readings]";
|
||||
Log.verbose(F("%s Sensor %s (%d)" CR), LogLoc, SensorIndex[SensorIndexId].name, AddrId);
|
||||
/* iterate through the SensorIndex readings */
|
||||
for(byte j = 0; j < Max_Sensors_Read; j++) {
|
||||
/* if SensorIndex[].read[] > 0 (means there is a value to read) */
|
||||
if(SensorIndex[SensorIndexId].read[j] > 0 ) {
|
||||
if(SensorIndex[SensorIndexId].type == SENSOR_TYPE_INTADC) {
|
||||
Log.verbose(F("%s - %s: %F %s" CR), LogLoc, Sensor_Read_descr[SensorIndex[SensorIndexId].read[j]],
|
||||
Sensor_getValue(SensorIndexId, AddrId), Sensor_Read_unit[SensorIndex[SensorIndexId].read[j]]);
|
||||
} else {
|
||||
Log.verbose(F("%s - %s: %F %s" CR), LogLoc, Sensor_Read_descr[SensorIndex[SensorIndexId].read[j]],
|
||||
Sensor_getValue(SensorIndexId, AddrId, j), Sensor_Read_unit[SensorIndex[SensorIndexId].read[j]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sensor_Init() {
|
||||
/* main function that does initialize all configured sensors at once. called from setup()*/
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:Init]";
|
||||
/* Go through all configured sensors and initialize them */
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s == Sensor drivers ==" CR), LogLoc);
|
||||
|
||||
for(byte i = 1; i <= SensorIndex_length; i++) {
|
||||
Log.verbose(F("%s Sensor_Index %d, Name %s, Readings" CR), LogLoc, i, SensorIndex[i].name );
|
||||
|
||||
for(byte j = 0; j < Max_Sensors_Read; j++) {
|
||||
if(SensorIndex[i].read[j] > 0 ) {
|
||||
Log.verbose(F("%s %d: %S %S (%d)" CR), LogLoc, j, Sensor_Read_descr[SensorIndex[i].read[j]], Sensor_Read_unit[SensorIndex[i].read[j]], SensorIndex[i].read[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.verbose(F("%s == configured Sensors ==" CR), LogLoc);
|
||||
#endif
|
||||
|
||||
|
||||
/* iterate through configured sensors */
|
||||
for(byte i = 0; i < Max_Sensors; i++) {
|
||||
if(config.system.sensor.type[i] > 0) {
|
||||
/* if sensor type is internal ADC */
|
||||
if(SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_INTADC) {
|
||||
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s Sensor ID %d: %s - %s (GPIO ID %d, GPIO %d), offering" CR), LogLoc, i,config.system.sensor.name[i],
|
||||
SensorIndex[config.system.sensor.type[i]].name, config.system.sensor.gpio[i][0], GPIOindex[config.system.sensor.gpio[i][0]].gpio);
|
||||
#endif
|
||||
|
||||
/* initialize */
|
||||
sensorStatus[i] = Sensor_Addr_Init_Update(config.system.sensor.type[i], config.system.sensor.gpio[i][0], SENSOR_AIU_MODE_INIT);
|
||||
|
||||
#ifdef DEBUG
|
||||
/* when init was successful, list the sensor values */
|
||||
if(sensorStatus[i] == true) {
|
||||
Sensor_Log_Readings(config.system.sensor.type[i], config.system.sensor.gpio[i][0]);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
} else if(SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_I2C) {
|
||||
/* when SensorIndex[].type is == I2C sensor*/
|
||||
/* get only the I2C Address */
|
||||
byte Addr = Sensor_Addr_Init_Update(config.system.sensor.type[i], config.system.sensor.i2c_addr[i], SENSOR_AIU_MODE_ADDR);
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s Sensor ID %d: %s - %s (I2C %d, 0x%x), offering" CR), LogLoc, i,config.system.sensor.name[i],
|
||||
SensorIndex[config.system.sensor.type[i]].name, config.system.sensor.i2c_addr[i], Addr);
|
||||
#endif
|
||||
/* initialize */
|
||||
sensorStatus[i] = Sensor_Addr_Init_Update(config.system.sensor.type[i], config.system.sensor.i2c_addr[i], SENSOR_AIU_MODE_INIT);
|
||||
#ifdef DEBUG
|
||||
/* when init was successful, list the sensor values */
|
||||
if(sensorStatus[i] == true) {
|
||||
Sensor_Log_Readings(config.system.sensor.type[i], config.system.sensor.i2c_addr[i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Sensor_Update() {
|
||||
/* Update all configured sensors Values */
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:Update]";
|
||||
|
||||
#ifdef DEBUG2
|
||||
unsigned long mStart = millis();
|
||||
unsigned long mStop;
|
||||
Log.verbose(F("%s Start %u" CR), LogLoc, mStart);
|
||||
#endif
|
||||
|
||||
/* go through all possible existing Sensor configurations */
|
||||
for(byte i = 0; i < Max_Sensors; i++) {
|
||||
/* every configured one */
|
||||
if(config.system.sensor.type[i] > 0) {
|
||||
/* if internal ADC */
|
||||
if(SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_INTADC) {
|
||||
if(sensorStatus[i] == true) {
|
||||
#ifdef DEBUG2
|
||||
Log.verbose(F("%s (%d) %s: %s (%d)" CR), LogLoc, i, config.system.sensor.name[i], SensorIndex[config.system.sensor.type[i]].name, config.system.sensor.gpio[i][0]);
|
||||
#endif
|
||||
/* perform update of sensor values */
|
||||
Sensor_Addr_Init_Update(config.system.sensor.type[i], config.system.sensor.gpio[i][0], SENSOR_AIU_MODE_UPDATE);
|
||||
#ifdef DEBUG2
|
||||
Sensor_Log_Readings(config.system.sensor.type[i], config.system.sensor.gpio[i][0]);
|
||||
#endif
|
||||
}
|
||||
#ifdef DEBUG2
|
||||
else {
|
||||
Log.verbose(F("%s Sensor %d (%s, %d) not initialized." CR), LogLoc, i, SensorIndex[config.system.sensor.type[i]].name, Sensor_Addr_Init_Update(config.system.sensor.type[i], config.system.sensor.gpio[i][0], SENSOR_AIU_MODE_ADDR));
|
||||
}
|
||||
#endif
|
||||
/* Everything above 1 is an I2C sensor */
|
||||
} else if(SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_I2C) {
|
||||
if(sensorStatus[i] == true) {
|
||||
#ifdef DEBUG2
|
||||
Log.verbose(F("%s (%d) %s: %s (%d)" CR), LogLoc, i, config.system.sensor.name[i], SensorIndex[config.system.sensor.type[i]].name, config.system.sensor.i2c_addr[i]);
|
||||
#endif
|
||||
/* perform update of sensor values */
|
||||
Sensor_Addr_Init_Update(config.system.sensor.type[i], config.system.sensor.i2c_addr[i], SENSOR_AIU_MODE_UPDATE);
|
||||
#ifdef DEBUG2
|
||||
Sensor_Log_Readings(config.system.sensor.type[i], config.system.sensor.i2c_addr[i]);
|
||||
#endif
|
||||
}
|
||||
#ifdef DEBUG2
|
||||
else {
|
||||
Log.verbose(F("%s Sensor %d (%s, 0x%x) not initialized." CR), LogLoc, i, SensorIndex[config.system.sensor.type[i]].name, Sensor_Addr_Init_Update(config.system.sensor.type[i], config.system.sensor.i2c_addr[i], SENSOR_AIU_MODE_ADDR));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG2
|
||||
mStop = millis();
|
||||
Log.verbose(F("%s Stop %u (%u)" CR), LogLoc, mStop, mStop - mStart);
|
||||
#endif
|
||||
}
|
61
include/CanGrow_Timer.h
Normal file
61
include/CanGrow_Timer.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_Timer.h - timer header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Timer stuff
|
||||
*/
|
||||
|
||||
void Timer_Sensor() {
|
||||
Sensor_Update();
|
||||
}
|
||||
|
||||
void Timer_Output() {
|
||||
/* Update the outputs (switching GPIOs, sending webcall, etc) */
|
||||
Output_Update();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Timer_Control() {
|
||||
const static char LogLoc[] PROGMEM = "[Core:Timer_1s]";
|
||||
#ifdef DEBUG2
|
||||
Log.verbose(F("%s - trigger [Sensor:Update]" CR), LogLoc);
|
||||
#endif
|
||||
|
||||
/* Updating Light output states in memory */
|
||||
Control_Light();
|
||||
|
||||
/* Updating Air output states in memory */
|
||||
Control_Air();
|
||||
|
||||
/* Updating Water output sates in memory */
|
||||
Control_Water();
|
||||
}
|
||||
|
||||
void Timer_3s() {
|
||||
const static char LogLoc[] PROGMEM = "[Core:Timer_3s]";
|
||||
#ifdef DEBUG2
|
||||
Log.verbose(F("%s" CR), LogLoc);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void Timer_5s() {
|
||||
const static char LogLoc[] PROGMEM = "[Core:Timer_5s]";
|
||||
#ifdef DEBUG2
|
||||
Log.verbose(F("%s" CR), LogLoc);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TimeR_Init() {
|
||||
timer.setInterval(1000, Timer_Output);
|
||||
timer.setInterval(1000, Timer_Sensor);
|
||||
timer.setInterval(100, Timer_Control);
|
||||
|
||||
}
|
131
include/CanGrow_Webserver.h
Normal file
131
include/CanGrow_Webserver.h
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_Webserver.h - webserver header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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/Webserver_Common.h"
|
||||
#include "Webserver/Page_404.h"
|
||||
#include "Webserver/Page_root.h"
|
||||
#include "Webserver/Page_wifi.h"
|
||||
#include "Webserver/Page_system.h"
|
||||
#include "Webserver/Page_grow.h"
|
||||
|
||||
/*
|
||||
* include Api header files
|
||||
*/
|
||||
#include "Webserver/Api_sensor.h"
|
||||
|
||||
AsyncWebServer webserver(80);
|
||||
// load requestLogger middleware
|
||||
AsyncLoggingMiddleware requestLogger;
|
||||
|
||||
/*
|
||||
* setup all the webhandlers
|
||||
*/
|
||||
void Webserver_Init() {
|
||||
const static char LogLoc[] PROGMEM = "[Webserver]";
|
||||
Log.notice(F("%s initializing" CR), LogLoc);
|
||||
|
||||
|
||||
/* url handler definition */
|
||||
webserver.on("/", HTTP_GET, WebPage_root);
|
||||
webserver.on("/cangrow.css", HTTP_GET, WebFile_cangrow_CSS);
|
||||
webserver.on("/cangrow.js", HTTP_GET, WebFile_cangrow_JS);
|
||||
webserver.on("/favicon.ico", HTTP_GET, WebFile_favicon_ico);
|
||||
|
||||
webserver.on("/wifi/", HTTP_GET, WebPage_wifi);
|
||||
webserver.on("/wifi/", HTTP_POST, WebPage_wifi);
|
||||
webserver.on("/system/", HTTP_GET, WebPage_system);
|
||||
webserver.on("/system/", HTTP_POST, WebPage_system);
|
||||
|
||||
webserver.on("/system/update", HTTP_GET, WebPage_system_update);
|
||||
webserver.on("/system/update", HTTP_POST, WebPage_system_update, WebPage_system_update_ApplyUpdate);
|
||||
|
||||
webserver.on("/system/restart", HTTP_GET, WebPage_system_restart);
|
||||
webserver.on("/system/restart", HTTP_POST, WebPage_system_restart);
|
||||
|
||||
webserver.on("/system/wipe", HTTP_GET, WebPage_system_wipe);
|
||||
webserver.on("/system/wipe", HTTP_POST, WebPage_system_wipe);
|
||||
|
||||
webserver.on("/system/output/", HTTP_GET, WebPage_system_output);
|
||||
webserver.on("/system/output/", HTTP_POST, WebPage_system_output);
|
||||
|
||||
webserver.on("/system/output/add", HTTP_GET, WebPage_system_output_add);
|
||||
webserver.on("/system/output/add", HTTP_POST, WebPage_system_output_add);
|
||||
|
||||
webserver.on("/system/sensor/", HTTP_GET, WebPage_system_sensor);
|
||||
webserver.on("/system/sensor/", HTTP_POST, WebPage_system_sensor);
|
||||
|
||||
webserver.on("/system/sensor/add", HTTP_GET, WebPage_system_sensor_add);
|
||||
webserver.on("/system/sensor/add", HTTP_POST, WebPage_system_sensor_add);
|
||||
|
||||
webserver.on("/system/sensor/calibrate", HTTP_GET, WebPage_system_sensor_calibrate);
|
||||
webserver.on("/system/sensor/calibrate", HTTP_POST, WebPage_system_sensor_calibrate);
|
||||
|
||||
|
||||
/* grow */
|
||||
webserver.on("/grow/", HTTP_GET, WebPage_grow);
|
||||
webserver.on("/grow/", HTTP_POST, WebPage_grow);
|
||||
webserver.on("/grow/light/", HTTP_GET, WebPage_grow_light);
|
||||
webserver.on("/grow/light/", HTTP_POST, WebPage_grow_light);
|
||||
webserver.on("/grow/air/", HTTP_GET, WebPage_grow_air);
|
||||
webserver.on("/grow/air/", HTTP_POST, WebPage_grow_air);
|
||||
webserver.on("/grow/water/", HTTP_GET, WebPage_grow_water);
|
||||
webserver.on("/grow/water/", HTTP_POST, WebPage_grow_water);
|
||||
webserver.on("/grow/dashboard/", HTTP_GET, WebPage_grow_dashboard);
|
||||
webserver.on("/grow/dashboard/", HTTP_POST, WebPage_grow_dashboard);
|
||||
/* api */
|
||||
//webserver.on("/api/sensor", HTTP_GET, Api_sensor_data);
|
||||
webserver.on("/api/sensor/", HTTP_GET, Api_sensor_data);
|
||||
webserver.on("/api/sensor/raw", HTTP_GET, Api_sensor_data_raw);
|
||||
webserver.on("/api/sensor/driver", HTTP_GET, Api_sensor_driver);
|
||||
|
||||
|
||||
/* DEBUG only - offer config for direct download */
|
||||
#ifndef DEBUG
|
||||
webserver.serveStatic(CANGROW_CFG, LittleFS, CANGROW_CFG);
|
||||
#endif
|
||||
|
||||
/* 404 Error page */
|
||||
webserver.onNotFound(WebserverNotFound);
|
||||
|
||||
|
||||
// this activates the middleware
|
||||
if(config.system.httpLogSerial == true) {
|
||||
requestLogger.setOutput(Serial);
|
||||
Log.notice(F("%s serial logging: enabled" CR), LogLoc);
|
||||
webserver.addMiddleware(&requestLogger);
|
||||
} else {
|
||||
Log.notice(F("%s serial logging: disabled" CR), LogLoc);
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
Log.notice(F("%s call [wifi:ScanNetworks] to workaround empty scan results bug" CR), LogLoc);
|
||||
|
||||
WebPage_wifi_ScanNetworks();
|
||||
|
||||
webserver.begin();
|
||||
Log.notice(F("%s Ready to serve" CR), LogLoc);
|
||||
|
||||
}
|
||||
|
136
include/CanGrow_Wifi.h
Normal file
136
include/CanGrow_Wifi.h
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
*
|
||||
* include/CanGrow_Wifi.h - Wifi stuff header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
void Wifi_AP() {
|
||||
const static char LogLoc[] PROGMEM = "[WiFi:AP]";
|
||||
char randNr[5];
|
||||
itoa(random(9999), randNr, 10);
|
||||
//WiFi.softAPConfig(config.wifi.ip, config.wifi.gateway, config.wifi.netmask);
|
||||
IPAddress ip(192,168,4,20);
|
||||
IPAddress gateway(0,0,0,0);
|
||||
IPAddress netmask(255,255,255,0);
|
||||
WiFi.softAPConfig(ip, gateway, netmask);
|
||||
|
||||
/* when no ssid is configured, we assume here cangrow is in a fresh factory reset mode
|
||||
* when a ssid is already configured, we seem not to be able to connect to it. so we protect
|
||||
* our already configured cangrow controller with setting a temporary wifi ap password
|
||||
* and log it to serial. */
|
||||
if(strlen(config.wifi.ssid) > 0) {
|
||||
const char * password = RandomString();
|
||||
/* growName[64] + 8 */
|
||||
char ssid[20+5];
|
||||
strcpy(ssid, "CanGrow-FAILED-WIFI-");
|
||||
/* random maximum 4 digit number for ssid
|
||||
* https://arduino.stackexchange.com/a/42987*/
|
||||
|
||||
strcat(ssid, randNr);
|
||||
|
||||
Log.error(F("%s create access point" CR), LogLoc);
|
||||
|
||||
Log.error(F("%s SSID : %s" CR), LogLoc, ssid);
|
||||
Log.error(F("%s Password: %s" CR), LogLoc, password);
|
||||
WiFi.softAP(ssid, password);
|
||||
} else {
|
||||
char ssid[21+4];
|
||||
strcpy(ssid, CANGROW_DEFAULT_WIFI_SSID);
|
||||
//strcat(ssid, "-");
|
||||
/* random maximum 4 digit number for ssid
|
||||
* https://arduino.stackexchange.com/a/42987*/
|
||||
//strcat(ssid, randNr);
|
||||
/* start access point default password when being unconfigured */
|
||||
Log.notice(F("%s create access point" CR), LogLoc);
|
||||
Log.notice(F("%s SSID : %S" CR), LogLoc, ssid);
|
||||
Log.notice(F("%s Password: %S" CR), LogLoc, CANGROW_DEFAULT_WIFI_PASSWORD);
|
||||
WiFi.softAP(ssid, CANGROW_DEFAULT_WIFI_PASSWORD);
|
||||
//WiFi.softAP(CANGROW_DEFAULT_WIFI_SSID);
|
||||
}
|
||||
|
||||
|
||||
Log.notice(F("%s access point started." CR), LogLoc);
|
||||
Log.notice(F("%s IP : %s" CR), LogLoc, IP2Char(ip));
|
||||
Log.notice(F("%s Netmask : %s" CR), LogLoc, IP2Char(netmask));
|
||||
}
|
||||
|
||||
|
||||
void Wifi_Connect() {
|
||||
const static char LogLoc[] PROGMEM = "[WiFi:Connect]";
|
||||
Log.notice(F("%s connecting to SSID: %s" CR), LogLoc, config.wifi.ssid);
|
||||
|
||||
WiFi.begin(config.wifi.ssid, config.wifi.password);
|
||||
if(config.wifi.dhcp == false) {
|
||||
Log.notice(F("%s using static ip configuration:" CR), LogLoc);
|
||||
|
||||
Log.notice(F("%s IP : %s" CR), LogLoc, IP2Char(config.wifi.ip));
|
||||
Log.notice(F("%s Netmask: %s" CR), LogLoc, IP2Char(config.wifi.netmask));
|
||||
Log.notice(F("%s Gateway: %s" CR), LogLoc, IP2Char(config.wifi.gateway));
|
||||
Log.notice(F("%s DNS : %s" CR), LogLoc, IP2Char(config.wifi.dns));
|
||||
|
||||
WiFi.config(config.wifi.ip, config.wifi.dns, config.wifi.gateway, config.wifi.netmask);
|
||||
} else {
|
||||
Log.notice(F("%s using DHCP for ip configuration" CR), LogLoc);
|
||||
}
|
||||
|
||||
Log.notice("%s ", LogLoc);
|
||||
const byte max = 30;
|
||||
byte count = 0;
|
||||
// wait until WiFi connection is established
|
||||
while (count < max) {
|
||||
/* check connection stations */
|
||||
if(WiFi.status() != WL_CONNECTED) {
|
||||
/* if not connected, print dot and increment count */
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
count++;
|
||||
} else {
|
||||
/* if connected, set count to 10 to exit loop*/
|
||||
count = max+1;
|
||||
}
|
||||
}
|
||||
|
||||
/* check connection status. */
|
||||
if(WiFi.status() != WL_CONNECTED) {
|
||||
/* if connection failed, create AP */
|
||||
Log.error(F("FAILED! Fallback to AP mode" CR), LogLoc);
|
||||
WiFi.disconnect();
|
||||
/*
|
||||
* TODO / BUG
|
||||
*
|
||||
* On ESP32 there are no scan results shown in wifi tab, when connect to
|
||||
* a saved network failed and the esp created then its own network.
|
||||
*
|
||||
* without trying to connect it works fine, like when doing a factory reset.
|
||||
*
|
||||
* switch mode to softAP
|
||||
/* WiFi.mode(WIFI_AP_STA);
|
||||
*/
|
||||
|
||||
Wifi_AP();
|
||||
} else {
|
||||
Serial.println("CONNECTED!");
|
||||
if(config.wifi.dhcp == true) {
|
||||
Log.notice(F("%s DHCP offered ip configuration:" CR), LogLoc);
|
||||
Log.notice(F("%s IP : %s" CR), LogLoc, IP2Char(WiFi.localIP()));
|
||||
Log.notice(F("%s Netmask: %s" CR), LogLoc, IP2Char(WiFi.subnetMask()));
|
||||
Log.notice(F("%s Gateway: %s" CR), LogLoc, IP2Char(WiFi.gatewayIP()));
|
||||
Log.notice(F("%s DNS : %s" CR), LogLoc, IP2Char(WiFi.dnsIP()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Wifi_Init() {
|
||||
const static char LogLoc[] PROGMEM = "[WiFi:Init]";
|
||||
Log.notice(F("%s" CR), LogLoc);
|
||||
|
||||
if(strlen(config.wifi.ssid) == 0) {
|
||||
Log.notice(F("%s config.wifi.ssid is unset" CR), LogLoc);
|
||||
Wifi_AP();
|
||||
} else {
|
||||
Wifi_Connect();
|
||||
}
|
||||
}
|
84
include/Output/Output_Common.h
Normal file
84
include/Output/Output_Common.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
*
|
||||
* include/Output/Output_Common.h - Output common header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Output Type
|
||||
*/
|
||||
|
||||
// How many output types exist
|
||||
const byte OUTPUT_TYPE__TOTAL = 3;
|
||||
|
||||
const byte OUTPUT_TYPE_GPIO = 1;
|
||||
const byte OUTPUT_TYPE_I2C = 2;
|
||||
const byte OUTPUT_TYPE_WEB = 3;
|
||||
|
||||
const char OUTPUT_TYPE_GPIO_descr[] PROGMEM = {"GPIO"};
|
||||
const char OUTPUT_TYPE_I2C_descr[] PROGMEM = {"I2C"};
|
||||
const char OUTPUT_TYPE_WEB_descr[] PROGMEM = {"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_GPIO_Addr_Init_Update() modes */
|
||||
const byte OUTPUT_GPIO_IU_MODE_INIT = 0;
|
||||
const byte OUTPUT_GPIO_IU_MODE_UPDATE = 1;
|
||||
|
||||
/* Output_Webcall_Addr_Init_Update() modes */
|
||||
const byte OUTPUT_WEB_IU_MODE_INIT = 0;
|
||||
const byte OUTPUT_WEB_IU_MODE_UPDATE = 1;
|
||||
|
||||
|
||||
/*
|
||||
* OutputI2C types / modules
|
||||
*/
|
||||
const byte OUTPUT_TYPE_I2C_MAX_PORTS = 2;
|
||||
/* Total number of I2C PORT Types */
|
||||
const byte OUTPUT_TYPE_I2C_PORT__TOTAL = 1;
|
||||
/* port type for percentage. Those ports receive an int from 0 up to 100 to set their output value */
|
||||
const byte OUTPUT_TYPE_I2C_PORT_BYTE = 1;
|
||||
|
||||
/* Output_I2C_Addr_Init_Update() modes */
|
||||
const byte OUPUT_I2C_AIU_MODE_ADDR = 0;
|
||||
const byte OUPUT_I2C_AIU_MODE_INIT = 1;
|
||||
const byte OUPUT_I2C_AIU_MODE_UPDATE = 2;
|
||||
|
||||
|
||||
/*
|
||||
* Output Device
|
||||
*/
|
||||
// 0 is unconfigured
|
||||
const byte OUTPUT_DEVICE__TOTAL = 6;
|
||||
|
||||
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;
|
||||
|
||||
const char OUTPUT_DEVICE_LIGHT_descr[] PROGMEM = {"💡 Light"};
|
||||
const char OUTPUT_DEVICE_FAN_descr[] PROGMEM = {"🌀 Fan"};
|
||||
const char OUTPUT_DEVICE_PUMP_descr[] PROGMEM = {"💧 Pump"};
|
||||
const char OUTPUT_DEVICE_HUMIDIFIER_descr[] PROGMEM = {"🌀 Humidifier"};
|
||||
const char OUTPUT_DEVICE_DEHUMIDIFIER_descr[] PROGMEM = {"🌀 Dehumidifier"};
|
||||
const char OUTPUT_DEVICE_HEATING_descr[] PROGMEM = {"🌀 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,
|
||||
};
|
45
include/Output/Output_I2C_01_MCP4725.h
Normal file
45
include/Output/Output_I2C_01_MCP4725.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
*
|
||||
* include/Output/OutputI2C_01_MCP4725.h - sensor header for I2C Output MCP4725 sensor
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Adafruit_MCP4725.h>
|
||||
|
||||
#define OUTPUT_I2C_01_NAME "MCP4725"
|
||||
|
||||
const byte Output_I2C_01_MCP4725_Addr[] = { 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67 };
|
||||
|
||||
Adafruit_MCP4725 MCP4725[sizeof(Output_I2C_01_MCP4725_Addr)];
|
||||
|
||||
const byte Output_I2C_01_MCP4725_Ports = 1;
|
||||
|
||||
void Output_I2C_01_MCP4725_Update(const byte AddrId, const byte PortId, const byte Value) {
|
||||
/* Update Output Port of I2C Module */
|
||||
const static char LogLoc[] PROGMEM = "[Output:I2C:01_MCP4725:Update]";
|
||||
|
||||
/* 'Value' , which comes from outputState[], is byte, so 0-255. So we need to map this to the MCPs 0-4095 */
|
||||
MCP4725[AddrId].setVoltage(map(Value, 0, 255, 0, 4095), false);
|
||||
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s 0x%x Port %d, Value %d, MCP4725_Value %d" CR), LogLoc, Output_I2C_01_MCP4725_Addr[AddrId], PortId, Value, map(Value, 0, 255, 0, 4095));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
bool Output_I2C_01_MCP4725_Init(const byte AddrId, const byte PortId) {
|
||||
/* Initialize I2C Module, return true when successful */
|
||||
const static char LogLoc[] PROGMEM = "[Output:I2C:01_MCP4725:Init]";
|
||||
bool returnCode;
|
||||
if(MCP4725[AddrId].begin(Output_I2C_01_MCP4725_Addr[AddrId])) {
|
||||
Log.notice(F("%s found at addr 0x%x" CR), LogLoc, Output_I2C_01_MCP4725_Addr[AddrId]);
|
||||
Output_I2C_01_MCP4725_Update(AddrId, PortId, 50);
|
||||
returnCode = true;
|
||||
} else {
|
||||
Log.error(F("%s FAILED! Not found at addr 0x%x" CR), LogLoc, Output_I2C_01_MCP4725_Addr[AddrId]);
|
||||
returnCode = false;
|
||||
}
|
||||
return returnCode;
|
||||
}
|
52
include/Output/Output_I2C_02_GP8403.h
Normal file
52
include/Output/Output_I2C_02_GP8403.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
*
|
||||
* include/Output/OutputI2C_Output_I2C_02_GP8403.h - sensor header for I2C Output GP8403 (DFR Gravity) sensor
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <DFRobot_GP8XXX.h>
|
||||
|
||||
#define OUTPUT_I2C_02_NAME "GP8403"
|
||||
|
||||
const byte Output_I2C_02_GP8403_Addr[] = { 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F };
|
||||
|
||||
DFRobot_GP8403 GP8403[sizeof(Output_I2C_02_GP8403_Addr)];
|
||||
|
||||
const byte Output_I2C_02_GP8403_Ports = 2;
|
||||
|
||||
|
||||
void Output_I2C_02_GP8403_Update(const byte AddrId, const byte PortId, const byte Value) {
|
||||
/* Update Output Port of I2C Module */
|
||||
const static char LogLoc[] PROGMEM = "[Output:I2C:02_GP8403:Update]";
|
||||
|
||||
/* 'Value' , which comes from outputState[], is byte, so 0-255. So we need to map this to the GP8403 0-4095 */
|
||||
GP8403[AddrId].setDACOutVoltage(map(Value, 0, 255, 0, 4095), PortId);
|
||||
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s 0x%x Port %d, Value %d, GP8403_Value %d" CR), LogLoc, Output_I2C_02_GP8403_Addr[AddrId], PortId, Value, map(Value, 0, 255, 0, 4095));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
bool Output_I2C_02_GP8403_Init(const byte AddrId, const byte PortId) {
|
||||
/* Initialize I2C Module, return true when successful */
|
||||
const static char LogLoc[] PROGMEM = "[Output:I2C:02_GP8403:Init]";
|
||||
bool returnCode;
|
||||
|
||||
/* Overwrite the default address of the library 0x58 with configured one */
|
||||
GP8403[AddrId] = DFRobot_GP8403(Output_I2C_02_GP8403_Addr[AddrId]);
|
||||
|
||||
if(GP8403[AddrId].begin() == 0) {
|
||||
/* Set output to 0-10V - this is what most grow devices are using as control standard */
|
||||
GP8403[AddrId].setDACOutRange(GP8403[AddrId].eOutputRange10V);
|
||||
Log.notice(F("%s found at addr 0x%x" CR), LogLoc, Output_I2C_02_GP8403_Addr[AddrId]);
|
||||
Output_I2C_02_GP8403_Update(AddrId, PortId, 0);
|
||||
returnCode = true;
|
||||
} else {
|
||||
Log.error(F("%s FAILED! Not found at addr 0x%x" CR), LogLoc, Output_I2C_02_GP8403_Addr[AddrId]);
|
||||
returnCode = false;
|
||||
}
|
||||
return returnCode;
|
||||
}
|
46
include/Sensor/00_Example.h
Normal file
46
include/Sensor/00_Example.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
*
|
||||
* include/Sensor/00_Example.h - example sensor header I2C device
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Adafruit_WhateverLib.h>
|
||||
|
||||
#define SENSOR_00_NAME "Example sensor"
|
||||
|
||||
const byte Sensor_00_Example_Addr[] = { 0x00, 0x01 };
|
||||
|
||||
Adafruit_WhateverLib Whatever[sizeof(Sensor_00_Example_Addr)];
|
||||
|
||||
/* Create main data array specifying max amount of readings */
|
||||
float Sensor_00_Example[sizeof(Sensor_00_Example_Addr)][4];
|
||||
|
||||
void Sensor_00_Example_Update(const byte AddrId) {
|
||||
/* keep the same order as in SensorIndex[].read[] !! */
|
||||
Sensor_00_Example[AddrId][0] = Whatever[AddrId].temperature();
|
||||
Sensor_00_Example[AddrId][1] = Whatever[AddrId].humidity();
|
||||
Sensor_00_Example[AddrId][2] = Whatever[AddrId].raw1();
|
||||
Sensor_00_Example[AddrId][3] = Whatever[AddrId].raw2();
|
||||
}
|
||||
|
||||
bool Sensor_00_Example_Init(const byte AddrId) {
|
||||
/* Sensor Init function
|
||||
*
|
||||
* returns true (1) when Init was successful
|
||||
* returns false (0) if not.
|
||||
*/
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:00_Example:Init]";
|
||||
bool returnCode;
|
||||
|
||||
if(Whatever[AddrId].begin(Sensor_00_Example_Addr[AddrId])) {
|
||||
Log.notice(F("%s found at addr 0x%x" CR), LogLoc, Sensor_00_Example_Addr[AddrId]);
|
||||
Sensor_00_Example_Update(AddrId);
|
||||
returnCode = true;
|
||||
} else {
|
||||
Log.error(F("%s FAILED! Not found at addr 0x%x" CR), LogLoc, Sensor_00_Example_Addr[AddrId]);
|
||||
returnCode = false;
|
||||
}
|
||||
return returnCode;
|
||||
}
|
112
include/Sensor/01_ADC_builtin.h
Normal file
112
include/Sensor/01_ADC_builtin.h
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
*
|
||||
* include/Sensor/01_ADC_builtin.h - sensor header for builtin ADC
|
||||
*
|
||||
*
|
||||
* "Driver" for the internal ADC of the ESP8266 and ESP32
|
||||
*
|
||||
* Bit dirty hacky workaround to support both boards ADC
|
||||
*
|
||||
* ESP8266 only has one ADC onboard. For "Multiplexing" I have added
|
||||
* the to "add a GPIO" to it. It simply turns on the given GPIO for
|
||||
* 100ms, and then turns it off.
|
||||
* This is kinda a poor (wo)mans multiplexer. Control the supply voltage
|
||||
* of your analog sensor with the GPIO, put a diode to the AOUT and enjoy.
|
||||
*
|
||||
* You can theoretically use all available pins as "multiplexer", thats why
|
||||
* the Sensor_01_ADC[] array is as large as GPIOindex_length
|
||||
* ************
|
||||
*
|
||||
* ESP32 has a bunch of ADCs onboard. So it is not needed to go this hacky way,
|
||||
* we can just use all the nice ADCs available.
|
||||
*
|
||||
* in GPIOindex.note we get the info if the GPIO is an ADC or not
|
||||
* INT_ADC and INPUT_ONLY tells us this.
|
||||
* To save memory, I build an own "index" for the available
|
||||
* ADCs. Thats wahat Sensor_01_ADC_ArrId() is for.
|
||||
*
|
||||
* It returns which Index / slot in the array the given GPIO ID
|
||||
* from GPIOindex has.
|
||||
*/
|
||||
|
||||
#define SENSOR_01_NAME "ADC builtin"
|
||||
|
||||
/* Create main data array specifying max amount of readings */
|
||||
#ifdef ESP8266
|
||||
const byte SENSOR_01_MAX = GPIOindex_length + 1;
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
/* indexing function for our ADC GPIO pins we could theoretically all use */
|
||||
byte Sensor_01_ADC_ArrId(const byte GPIOid) {
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:01_ADC:Slot]";
|
||||
byte count = 0;
|
||||
//Log.verbose(F("%s GPIO %d (%d) START" CR), LogLoc, GPIOindex[GPIOid].gpio, GPIOid);
|
||||
for(byte i = 1; i <= GPIOindex_length; i++) {
|
||||
//Log.verbose(F("%s GPIO %d (%d) NOTE %d - %S" CR), LogLoc, GPIOindex[GPIOid].gpio, GPIOid, GPIOindex[i].note, GPIO_Index_note_descr[GPIOindex[i].note]);
|
||||
if((GPIOindex[i].note == INPUT_ONLY) || (GPIOindex[i].note == INT_ADC)) {
|
||||
if(i == GPIOid) {
|
||||
//Log.verbose(F("%s ??? GPIO %d (%d) i %d" CR), LogLoc, GPIOindex[GPIOid].gpio, GPIOid, i);
|
||||
return count;
|
||||
} else {
|
||||
count++;
|
||||
//Log.verbose(F("%s GPIO %d (%d) i %d count" CR), LogLoc, GPIOindex[GPIOid].gpio, GPIOid, i, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* this dumb, but yeah - i counted the avail ADC manually (INT_ADC or INPUT_ONLY) */
|
||||
const byte SENSOR_01_MAX = 6;
|
||||
#endif
|
||||
|
||||
int Sensor_01_ADC[SENSOR_01_MAX];
|
||||
|
||||
void Sensor_01_ADC_Update(const byte GPIOid) {
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:01_ADC:Update]";
|
||||
#ifdef ESP8266
|
||||
if(GPIOid > 0) {
|
||||
//digitalWrite(GPIOindex[GPIOid].gpio, HIGH);
|
||||
//Log.notice(F("%s GPIO %d (%d) delay ON" CR), LogLoc, GPIOindex[GPIOid].gpio, GPIOid);
|
||||
digitalWrite(GPIOindex[GPIOid].gpio, HIGH);
|
||||
delay(50);
|
||||
Sensor_01_ADC[GPIOid] = analogRead(A0);
|
||||
digitalWrite(GPIOindex[GPIOid].gpio, LOW);
|
||||
//Log.notice(F("%s GPIO %d (%d) delay OFF" CR), LogLoc, GPIOindex[GPIOid].gpio, GPIOid);
|
||||
} else {
|
||||
//Log.notice(F("%s GPIO %d (%d) READ" CR), LogLoc, GPIOindex[GPIOid].gpio, GPIOid);
|
||||
Sensor_01_ADC[GPIOid] = analogRead(A0);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
byte slot = Sensor_01_ADC_ArrId(GPIOid);
|
||||
//Log.notice(F("%s GPIO %d (%d) READ - slot %d" CR), LogLoc, GPIOindex[GPIOid].gpio, GPIOid, slot);
|
||||
Sensor_01_ADC[slot] = analogRead(GPIOindex[GPIOid].gpio);
|
||||
//Log.notice(F("%s GPIO %d (%d) READ - slot %d Val %d" CR), LogLoc, GPIOindex[GPIOid].gpio, GPIOid, slot, Sensor_01_ADC[slot]);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
bool Sensor_01_ADC_Init(const byte GPIOid) {
|
||||
/* Sensor Init function
|
||||
*
|
||||
* returns true (1) when Init was successful
|
||||
* returns false (0) if not.
|
||||
*/
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:01_ADC:Init]";
|
||||
|
||||
#ifdef ESP8266
|
||||
//Log.notice(F("%s setting GPIO ID %d (%d) as OUTPUT for internal ADC" CR), LogLoc, GPIOid, GPIOindex[GPIOid].gpio);
|
||||
pinMode(GPIOindex[GPIOid].gpio, OUTPUT);
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
//Log.notice(F("%s GPIO ID %d (%d) as INPUT for internal ADC slot %d" CR), LogLoc, GPIOid, GPIOindex[GPIOid].gpio, Sensor_01_ADC_ArrId(GPIOid));
|
||||
//pinMode(GPIOindex[GPIOid].gpio, INPUT);
|
||||
#endif
|
||||
|
||||
Sensor_01_ADC_Update(GPIOid);
|
||||
return true;
|
||||
}
|
54
include/Sensor/02_BME280.h
Normal file
54
include/Sensor/02_BME280.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
*
|
||||
* include/Sensor/00_ADC_builtin.h - sensor header for BME280 I2C sensor
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <Adafruit_BME280.h>
|
||||
|
||||
|
||||
#define SENSOR_02_NAME "BME280"
|
||||
//#define SENSOR_02_MAXUNITS 2
|
||||
|
||||
/* available addresses in byte array, default is at 0 */
|
||||
const byte Sensor_02_BME280_Addr[] = { 0x76, 0x77 };
|
||||
|
||||
Adafruit_BME280 BME280[sizeof(Sensor_02_BME280_Addr)];
|
||||
|
||||
/* creation of BME280 Value Struct, as many as addresses */
|
||||
//Sensor_02_BME280 Sensor_02_BME280_Data[sizeof(Sensor_02_BME280_Addr)];
|
||||
|
||||
/* main data array */
|
||||
float Sensor_02_BME280[sizeof(Sensor_02_BME280_Addr)][4];
|
||||
|
||||
void Sensor_02_BME280_Update(const byte AddrId) {
|
||||
/* Temp */
|
||||
Sensor_02_BME280[AddrId][0] = BME280[AddrId].readTemperature();
|
||||
/* Humidity */
|
||||
Sensor_02_BME280[AddrId][1] = BME280[AddrId].readHumidity();
|
||||
/* Pressure */
|
||||
Sensor_02_BME280[AddrId][2] = BME280[AddrId].readPressure() / 1000.00;
|
||||
/* Altitude */
|
||||
Sensor_02_BME280[AddrId][3] = BME280[AddrId].readAltitude(SEALEVELPRESSURE_HPA);
|
||||
}
|
||||
|
||||
|
||||
bool Sensor_02_BME280_Init(const byte AddrId) {
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:02_BME280:Init]";
|
||||
bool returnCode;
|
||||
//Log.notice(F("%s Init at addr 0x%x (%d)" CR), LogLoc, Sensor_02_BME280_Addr[AddrId], AddrId);
|
||||
if(BME280[AddrId].begin(Sensor_02_BME280_Addr[AddrId])) {
|
||||
Log.notice(F("%s found at addr 0x%x" CR), LogLoc, Sensor_02_BME280_Addr[AddrId]);
|
||||
//Log.notice(F("%s Temp: %F°C Humidity: %F % Pressure: %FhPa, Appr. Altitude %Fm" CR), LogLoc, BME280[AddrId].readTemperature(), BME280[AddrId].readHumidity(), BME280[AddrId].readPressure() / 1000.00, BME280[AddrId].readAltitude(SEALEVELPRESSURE_HPA));
|
||||
Sensor_02_BME280_Update(AddrId);
|
||||
returnCode = true;
|
||||
} else {
|
||||
Log.error(F("%s FAILED! Not found at addr 0x%x" CR), LogLoc, Sensor_02_BME280_Addr[AddrId]);
|
||||
returnCode = false;
|
||||
}
|
||||
|
||||
return returnCode;
|
||||
}
|
98
include/Sensor/03_BME680.h
Normal file
98
include/Sensor/03_BME680.h
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
*
|
||||
* include/Sensor/00_ADC_builtin.h - sensor header for BME680 I2C sensor
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <Adafruit_BME680.h>
|
||||
|
||||
#define SENSOR_03_NAME "BME680"
|
||||
|
||||
/* available addresses in byte array, default is at 0 */
|
||||
const byte Sensor_03_BME680_Addr[] = { 0x77, 0x76 };
|
||||
|
||||
Adafruit_BME680 BME680[sizeof(Sensor_03_BME680_Addr)];
|
||||
|
||||
unsigned long BME680_endtime[sizeof(Sensor_03_BME680_Addr)];
|
||||
|
||||
/*struct Sensor_03_BME680 {
|
||||
float humidity;
|
||||
float temperature;
|
||||
float pressure;
|
||||
float altitude;
|
||||
float gas_resistance;
|
||||
};
|
||||
*/
|
||||
/* creation of BME680 Value Struct, as many as addresses */
|
||||
/*Sensor_03_BME680 Sensor_03_BME680_Data[sizeof(Sensor_03_BME680_Addr)];*/
|
||||
|
||||
float Sensor_03_BME680[sizeof(Sensor_03_BME680_Addr)][5];
|
||||
|
||||
/* for async read of BME680 we need to trigger a new reading cycle (as adafruit doc says) */
|
||||
void Sensor_03_BME680_BeginReading(const byte AddrId) {
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:03_BME680:BeginReading]";
|
||||
|
||||
#ifdef DEBUG3
|
||||
Log.warning(F("%s Start reading %u , finishing %u (0x%x)" CR), LogLoc, millis(), BME680_endtime[AddrId], Sensor_03_BME680_Addr[AddrId]);
|
||||
#endif
|
||||
|
||||
// Tell BME680 to begin measurement.
|
||||
BME680_endtime[AddrId] = BME680[AddrId].beginReading();
|
||||
if(BME680_endtime[AddrId] == 0) {
|
||||
Log.warning(F("%s Failed to begin reading (0x%x)" CR), LogLoc, Sensor_03_BME680_Addr[AddrId]);
|
||||
}
|
||||
}
|
||||
|
||||
void Sensor_03_BME680_Update(const byte AddrId) {
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:03_BME680:Update]";
|
||||
|
||||
#ifdef DEBUG3
|
||||
Log.warning(F("%s Start reading %u , finishing %u (0x%x)" CR), LogLoc, millis(), BME680_endtime[AddrId], Sensor_03_BME680_Addr[AddrId]);
|
||||
#endif
|
||||
|
||||
if(!BME680[AddrId].endReading()) {
|
||||
Log.warning(F("%s Failed to complete reading (0x%x)" CR), LogLoc, Sensor_03_BME680_Addr[AddrId]);
|
||||
return;
|
||||
}
|
||||
|
||||
Sensor_03_BME680[AddrId][0] = BME680[AddrId].readTemperature();
|
||||
Sensor_03_BME680[AddrId][1] = BME680[AddrId].readHumidity();
|
||||
Sensor_03_BME680[AddrId][2] = BME680[AddrId].readPressure() / 1000;
|
||||
Sensor_03_BME680[AddrId][3] = BME680[AddrId].readAltitude(SEALEVELPRESSURE_HPA);
|
||||
Sensor_03_BME680[AddrId][4] = BME680[AddrId].gas_resistance / 1000.0;
|
||||
|
||||
/* begin new reading cycle */
|
||||
Sensor_03_BME680_BeginReading(AddrId);
|
||||
|
||||
}
|
||||
|
||||
bool Sensor_03_BME680_Init(const byte AddrId) {
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:03_BME680:Init]";
|
||||
bool returnCode;
|
||||
//Log.notice(F("%s Init at addr 0x%x (%d)" CR), LogLoc, Sensor_03_BME680_Addr[AddrId], AddrId);
|
||||
if(BME680[AddrId].begin(Sensor_03_BME680_Addr[AddrId])) {
|
||||
Log.notice(F("%s found at addr 0x%x" CR), LogLoc, Sensor_03_BME680_Addr[AddrId]);
|
||||
|
||||
// Set up oversampling and filter initialization
|
||||
BME680[AddrId].setTemperatureOversampling(BME680_OS_8X);
|
||||
BME680[AddrId].setHumidityOversampling(BME680_OS_2X);
|
||||
BME680[AddrId].setPressureOversampling(BME680_OS_4X);
|
||||
BME680[AddrId].setIIRFilterSize(BME680_FILTER_SIZE_3);
|
||||
BME680[AddrId].setGasHeater(320, 150); // 320*C for 150 ms
|
||||
|
||||
/* start to do readings here, like shown in async example
|
||||
* https://github.com/adafruit/Adafruit_BME680/blob/master/examples/bme680async/bme680async.ino */
|
||||
Sensor_03_BME680_BeginReading(AddrId);
|
||||
|
||||
Sensor_03_BME680_Update(AddrId);
|
||||
returnCode = true;
|
||||
} else {
|
||||
Log.error(F("%s FAILED! Not found at addr 0x%x" CR), LogLoc, Sensor_03_BME680_Addr[AddrId]);
|
||||
returnCode = false;
|
||||
}
|
||||
return returnCode;
|
||||
}
|
||||
|
43
include/Sensor/04_SHT3x.h
Normal file
43
include/Sensor/04_SHT3x.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
*
|
||||
* include/Sensor/04_SHT3X.h - SHT3X I2C temp/humidity sensor
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Adafruit_SHT31.h>
|
||||
|
||||
#define SENSOR_04_NAME "SHT3x"
|
||||
|
||||
const byte Sensor_04_SHT3X_Addr[] = { 0x44, 0x45 };
|
||||
|
||||
Adafruit_SHT31 SHT3X[sizeof(Sensor_04_SHT3X_Addr)];
|
||||
|
||||
/* Create main data array specifying max amount of readings */
|
||||
float Sensor_04_SHT3X[sizeof(Sensor_04_SHT3X_Addr)][2];
|
||||
|
||||
void Sensor_04_SHT3X_Update(const byte AddrId) {
|
||||
Sensor_04_SHT3X[AddrId][0] = SHT3X[AddrId].readTemperature();
|
||||
Sensor_04_SHT3X[AddrId][1] = SHT3X[AddrId].readHumidity();
|
||||
|
||||
}
|
||||
|
||||
bool Sensor_04_SHT3X_Init(const byte AddrId) {
|
||||
/* Sensor Init function
|
||||
*
|
||||
* returns true (1) when Init was successful
|
||||
* returns false (0) if not.
|
||||
*/
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:04_SHT3X:Init]";
|
||||
bool returnCode;
|
||||
if(SHT3X[AddrId].begin(Sensor_04_SHT3X_Addr[AddrId])) {
|
||||
Log.notice(F("%s found at addr 0x%x" CR), LogLoc, Sensor_04_SHT3X_Addr[AddrId]);
|
||||
Sensor_04_SHT3X_Update(AddrId);
|
||||
returnCode = true;
|
||||
} else {
|
||||
Log.error(F("%s FAILED! Not found at addr 0x%x" CR), LogLoc, Sensor_04_SHT3X_Addr[AddrId]);
|
||||
returnCode = false;
|
||||
}
|
||||
return returnCode;
|
||||
}
|
44
include/Sensor/05_MLX90614.h
Normal file
44
include/Sensor/05_MLX90614.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
*
|
||||
* include/Sensor/05_MLX90614.h - MLX90614 I2C IR temp sensor
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Adafruit_MLX90614.h>
|
||||
|
||||
#define SENSOR_05_NAME "MLX90614"
|
||||
|
||||
const byte Sensor_05_MLX90614_Addr[] = { 0x5A, 0x5B, 0x5C, 0x5D };
|
||||
|
||||
Adafruit_MLX90614 MLX90614[sizeof(Sensor_05_MLX90614_Addr)];
|
||||
|
||||
/* Create main data array specifying max amount of readings */
|
||||
float Sensor_05_MLX90614[sizeof(Sensor_05_MLX90614_Addr)][2];
|
||||
|
||||
void Sensor_05_MLX90614_Update(const byte AddrId) {
|
||||
/* keep the same order as in SensorIndex[].read[] !! */
|
||||
Sensor_05_MLX90614[AddrId][0] = MLX90614[AddrId].readAmbientTempC();
|
||||
Sensor_05_MLX90614[AddrId][1] = MLX90614[AddrId].readObjectTempC();
|
||||
}
|
||||
|
||||
bool Sensor_05_MLX90614_Init(const byte AddrId) {
|
||||
/* Sensor Init function
|
||||
*
|
||||
* returns true (1) when Init was successful
|
||||
* returns false (0) if not.
|
||||
*/
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:05_MLX90614:Init]";
|
||||
bool returnCode;
|
||||
|
||||
if(MLX90614[AddrId].begin(Sensor_05_MLX90614_Addr[AddrId])) {
|
||||
Log.notice(F("%s found at addr 0x%x - emissivity set to %F" CR), LogLoc, Sensor_05_MLX90614_Addr[AddrId], MLX90614[AddrId].readEmissivity());
|
||||
Sensor_05_MLX90614_Update(AddrId);
|
||||
returnCode = true;
|
||||
} else {
|
||||
Log.error(F("%s FAILED! Not found at addr 0x%x" CR), LogLoc, Sensor_05_MLX90614_Addr[AddrId]);
|
||||
returnCode = false;
|
||||
}
|
||||
return returnCode;
|
||||
}
|
72
include/Sensor/06_TCS34725.h
Normal file
72
include/Sensor/06_TCS34725.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
*
|
||||
* include/Sensor/06_TCS34725.h - header for I2C color sensor TCS34725
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
//#include "TCS34725.h"
|
||||
|
||||
#include "Adafruit_TCS34725.h"
|
||||
|
||||
#define SENSOR_06_NAME "TCS34725"
|
||||
|
||||
const byte Sensor_06_TCS34725_Addr[] = { 0x29 };
|
||||
|
||||
Adafruit_TCS34725 TCS34725[sizeof(Sensor_06_TCS34725_Addr)];
|
||||
/* This library causes a 240ms (or greater if chosen) delay when reading the values from the sensor
|
||||
* this is not optimal, and there are libs workarounding this behaviour.
|
||||
* But unfortunatelly the other libs wont connect successful by i2c to the sensor,
|
||||
* which only the adafruit lib does reliably 240MS integration time and 4x gain
|
||||
* seems to be the sweet spot between delay and value resolutin */
|
||||
|
||||
|
||||
/* Create main data array specifying max amount of readings */
|
||||
float Sensor_06_TCS34725[sizeof(Sensor_06_TCS34725_Addr)][5];
|
||||
|
||||
void Sensor_06_TCS34725_Update(const byte AddrId) {
|
||||
uint16_t r, g, b, c, colorTemp, lux;
|
||||
|
||||
TCS34725[AddrId].getRawData(&r, &g, &b, &c);
|
||||
colorTemp = TCS34725[AddrId].calculateColorTemperature_dn40(r, g, b, c);
|
||||
lux = TCS34725[AddrId].calculateLux(r, g, b);
|
||||
Sensor_06_TCS34725[AddrId][0] = colorTemp;
|
||||
Sensor_06_TCS34725[AddrId][1] = lux;
|
||||
Sensor_06_TCS34725[AddrId][2] = r;
|
||||
Sensor_06_TCS34725[AddrId][3] = g;
|
||||
Sensor_06_TCS34725[AddrId][4] = b;
|
||||
|
||||
}
|
||||
|
||||
bool Sensor_06_TCS34725_Init(const byte AddrId) {
|
||||
/* Sensor Init function
|
||||
*
|
||||
* returns true (1) when Init was successful
|
||||
* returns false (0) if not.
|
||||
*/
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:06_TCS34725:Init]";
|
||||
bool returnCode;
|
||||
|
||||
if(TCS34725[AddrId].begin()) {
|
||||
/* Here I hardcoded here the values for Integration time and Gain.
|
||||
* For calibration I used my desk lamp and a lux smartphone app.
|
||||
* I fooled around until the smartphone app reading was kinda the
|
||||
* same as the TCS34725 ones. Yay!
|
||||
*
|
||||
* Comes out TCS34725_INTEGRATIONTIME_240MS and TCS34725_GAIN_16X
|
||||
* seem to be good values. Smartphone reading of my desk lamp is
|
||||
* 3507lx and on the exakt same spot, height, angle and so on the
|
||||
* TCS34725 measures 3487lx. I guess this is fine. */
|
||||
|
||||
Log.notice(F("%s found at addr 0x%x - Integration time: 240ms Gain: 16x" CR), LogLoc, Sensor_06_TCS34725_Addr[AddrId]);
|
||||
TCS34725[AddrId].setIntegrationTime(TCS34725_INTEGRATIONTIME_240MS);
|
||||
TCS34725[AddrId].setGain(TCS34725_GAIN_16X);
|
||||
Sensor_06_TCS34725_Update(AddrId);
|
||||
returnCode = true;
|
||||
} else {
|
||||
Log.error(F("%s FAILED! Not found at addr 0x%x" CR), LogLoc, Sensor_06_TCS34725_Addr[AddrId]);
|
||||
returnCode = false;
|
||||
}
|
||||
return returnCode;
|
||||
}
|
38
include/Sensor/07_ADS1115.h
Normal file
38
include/Sensor/07_ADS1115.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
*
|
||||
* include/Sensor/07_ADS1115.h - ADS1115 16 bit ADC I2C driver
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Adafruit_ADS1X15.h>
|
||||
|
||||
#define SENSOR_07_NAME "ADS1115"
|
||||
|
||||
|
||||
const byte Sensor_07_ADS1115_Addr[] = { 0x48, 0x49, 0x4A, 0x4B };
|
||||
|
||||
Adafruit_ADS1115 ADS1115[sizeof(Sensor_07_ADS1115_Addr)];
|
||||
|
||||
int Sensor_07_ADS1115[sizeof(Sensor_07_ADS1115_Addr)][4];
|
||||
|
||||
void Sensor_07_ADS1115_Update(const byte AddrId) {
|
||||
for(byte i = 0; i < 4; i++) {
|
||||
Sensor_07_ADS1115[AddrId][i] = ADS1115[AddrId].readADC_SingleEnded(i);
|
||||
}
|
||||
}
|
||||
|
||||
bool Sensor_07_ADS1115_Init(const byte AddrId) {
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:07_ADS1115:Init]";
|
||||
bool returnCode;
|
||||
if(ADS1115[AddrId].begin(Sensor_07_ADS1115_Addr[AddrId])) {
|
||||
Log.notice(F("%s found at addr 0x%x" CR), LogLoc, Sensor_07_ADS1115_Addr[AddrId]);
|
||||
Sensor_07_ADS1115_Update(AddrId);
|
||||
returnCode = true;
|
||||
} else {
|
||||
Log.error(F("%s FAILED! Not found at addr 0x%x" CR), LogLoc, Sensor_07_ADS1115_Addr[AddrId]);
|
||||
returnCode = false;
|
||||
}
|
||||
return returnCode;
|
||||
}
|
41
include/Sensor/08_ADS1015.h
Normal file
41
include/Sensor/08_ADS1015.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
*
|
||||
* include/Sensor/08_ADS1015.h - ADS1115 16 bit ADC I2C driver
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* #include <Adafruit_ADS1X15.h>
|
||||
* This already got included in Sensor_07_ADS1115.h
|
||||
*/
|
||||
|
||||
#define SENSOR_08_NAME "ADS1015"
|
||||
|
||||
|
||||
const byte Sensor_08_ADS1015_Addr[] = { 0x48, 0x49, 0x4A, 0x4B };
|
||||
|
||||
Adafruit_ADS1015 ADS1015[sizeof(Sensor_08_ADS1015_Addr)];
|
||||
|
||||
int Sensor_08_ADS1015[sizeof(Sensor_08_ADS1015_Addr)][4];
|
||||
|
||||
void Sensor_08_ADS1015_Update(const byte AddrId) {
|
||||
for(byte i = 0; i < 4; i++) {
|
||||
Sensor_08_ADS1015[AddrId][i] = ADS1015[AddrId].readADC_SingleEnded(i);
|
||||
}
|
||||
}
|
||||
|
||||
bool Sensor_08_ADS1015_Init(const byte AddrId) {
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:08_ADS1015:Init]";
|
||||
bool returnCode;
|
||||
if(ADS1015[AddrId].begin(Sensor_08_ADS1015_Addr[AddrId])) {
|
||||
Log.notice(F("%s found at addr 0x%x" CR), LogLoc, Sensor_08_ADS1015_Addr[AddrId]);
|
||||
Sensor_08_ADS1015_Update(AddrId);
|
||||
returnCode = true;
|
||||
} else {
|
||||
Log.error(F("%s FAILED! Not found at addr 0x%x" CR), LogLoc, Sensor_08_ADS1015_Addr[AddrId]);
|
||||
returnCode = false;
|
||||
}
|
||||
return returnCode;
|
||||
}
|
88
include/Sensor/09_Chirp.h
Normal file
88
include/Sensor/09_Chirp.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
*
|
||||
* include/Sensor/09_Chirp.h - example sensor header I2C device
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <I2CSoilMoistureSensor.h>
|
||||
|
||||
#define SENSOR_09_NAME "I2C-Chirp"
|
||||
|
||||
const byte Sensor_09_Chirp_Addr[] = { 0x20, 0x21, 0x22, 0x23 };
|
||||
|
||||
I2CSoilMoistureSensor Chirp[sizeof(Sensor_09_Chirp_Addr)];
|
||||
|
||||
/* Create main data array specifying max amount of readings */
|
||||
float Sensor_09_Chirp[sizeof(Sensor_09_Chirp_Addr)][3];
|
||||
|
||||
void Sensor_09_Chirp_Update(const byte AddrId) {
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:09_Chirp:Update]";
|
||||
|
||||
#ifdef DEBUG
|
||||
unsigned long mStart = millis();
|
||||
unsigned long mStop;
|
||||
Log.verbose(F("%s Start %u" CR), LogLoc, mStart);
|
||||
#endif
|
||||
|
||||
/* keep the same order as in SensorIndex[].read[] !! */
|
||||
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s capacitance (%u)" CR), LogLoc, millis());
|
||||
#endif
|
||||
Sensor_09_Chirp[AddrId][0] = Chirp[AddrId].getCapacitance();
|
||||
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s temperature (%u)" CR), LogLoc, millis());
|
||||
#endif
|
||||
Sensor_09_Chirp[AddrId][1] = Chirp[AddrId].getTemperature()/(float)10;
|
||||
|
||||
/* light sensor is disabled, because it takes 3s to read, which is just too much */
|
||||
//#ifndef DEBUG
|
||||
//Log.verbose(F("%s light (%u)" CR), LogLoc, millis());
|
||||
//#endif
|
||||
//Sensor_09_Chirp[AddrId][2] = Chirp[AddrId].getLight(true);
|
||||
|
||||
Chirp[AddrId].sleep();
|
||||
|
||||
|
||||
#ifdef DEBUG
|
||||
mStop = millis();
|
||||
Log.verbose(F("%s Stop %u (%u)" CR), LogLoc, mStop, mStop - mStart);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Sensor_09_Chirp_Init(const byte AddrId) {
|
||||
/* Sensor Init function
|
||||
*
|
||||
* returns true (1) when Init was successful
|
||||
* returns false (0) if not.
|
||||
*/
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:09_Chirp:Init]";
|
||||
bool returnCode;
|
||||
|
||||
/* manually check if I2C address answers on bus, i2c chirp lib does not return a value */
|
||||
Wire.beginTransmission(Sensor_09_Chirp_Addr[AddrId]);
|
||||
short i2cError = Wire.endTransmission();
|
||||
|
||||
/* when i2c sensor answered to our previous init request */
|
||||
if(i2cError == 0) {
|
||||
Log.notice(F("%s found at addr 0x%x" CR), LogLoc, Sensor_09_Chirp_Addr[AddrId]);
|
||||
|
||||
#ifdef ESP8266
|
||||
/* maybe its not the best idea to place it here, but for the moment.. */
|
||||
Wire.setClockStretchLimit(2500);
|
||||
#endif
|
||||
|
||||
/* change chirp library I2C address, it will also trigger .begin() afterwards */
|
||||
Chirp[AddrId].changeSensor(Sensor_09_Chirp_Addr[AddrId], false);
|
||||
|
||||
Sensor_09_Chirp_Update(AddrId);
|
||||
returnCode = true;
|
||||
} else {
|
||||
Log.error(F("%s FAILED! Not found at addr 0x%x" CR), LogLoc, Sensor_09_Chirp_Addr[AddrId]);
|
||||
returnCode = false;
|
||||
}
|
||||
return returnCode;
|
||||
}
|
58
include/Sensor/10_CCS811.h
Normal file
58
include/Sensor/10_CCS811.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
*
|
||||
* include/Sensor/10_CCS811_.h - CCS811 CO2 I2C sensor
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Adafruit_CCS811.h"
|
||||
|
||||
#define SENSOR_10_NAME "CCS811"
|
||||
|
||||
const byte Sensor_10_CCS811_Addr[] = { 0x5a, 0x5b };
|
||||
|
||||
Adafruit_CCS811 CCS811[sizeof(Sensor_10_CCS811_Addr)];
|
||||
|
||||
/* Create main data array specifying max amount of readings */
|
||||
float Sensor_10_CCS811[sizeof(Sensor_10_CCS811_Addr)][4];
|
||||
|
||||
void Sensor_10_CCS811_Update(const byte AddrId) {
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:10_CCS811:Update]";
|
||||
if(CCS811[AddrId].available()){
|
||||
if(!CCS811[AddrId].readData()){
|
||||
/* keep the same order as in SensorIndex[].read[] !! */
|
||||
/* CO2 in ppm */
|
||||
Sensor_10_CCS811[AddrId][0] = CCS811[AddrId].geteCO2();
|
||||
/* TVOC (Total Volatile Organic Compouds) */
|
||||
Sensor_10_CCS811[AddrId][1] = CCS811[AddrId].getTVOC();
|
||||
}
|
||||
#ifndef DEBUG
|
||||
else {
|
||||
Log.error(F("%s 0x%x ERROR getting new data" CR), LogLoc, Sensor_10_CCS811_Addr[AddrId]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
bool Sensor_10_CCS811_Init(const byte AddrId) {
|
||||
/* Sensor Init function
|
||||
*
|
||||
* returns true (1) when Init was successful
|
||||
* returns false (0) if not.
|
||||
*/
|
||||
const static char LogLoc[] PROGMEM = "[Sensor:10_CCS811:Init]";
|
||||
bool returnCode;
|
||||
|
||||
if(CCS811[AddrId].begin(Sensor_10_CCS811_Addr[AddrId])) {
|
||||
Log.notice(F("%s found at addr 0x%x" CR), LogLoc, Sensor_10_CCS811_Addr[AddrId]);
|
||||
Sensor_10_CCS811_Update(AddrId);
|
||||
returnCode = true;
|
||||
} else {
|
||||
Log.error(F("%s FAILED! Not found at addr 0x%x" CR), LogLoc, Sensor_10_CCS811_Addr[AddrId]);
|
||||
returnCode = false;
|
||||
}
|
||||
return returnCode;
|
||||
}
|
152
include/Sensor/Sensor_Common.h
Normal file
152
include/Sensor/Sensor_Common.h
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
*
|
||||
* include/Sensor/Common.h - common sensor header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Common used consts and variables, used within the Sensor header for example
|
||||
*/
|
||||
|
||||
// for bme280 and bme680
|
||||
#define SEALEVELPRESSURE_HPA (1013.25)
|
||||
|
||||
/* sensor types, int ADC, i2c, one wire , two wire, ...*/
|
||||
const byte SENSOR_TYPE__TOTAL = 5;
|
||||
|
||||
const byte SENSOR_TYPE_INTADC = 0;
|
||||
const byte SENSOR_TYPE_I2C = 1;
|
||||
const byte SENSOR_TYPE_ONEWIRE = 2;
|
||||
const byte SENSOR_TYPE_TWOWIRE = 3;
|
||||
const byte SENSOR_TYPE_I2C_WITH_GPIO = 4;
|
||||
|
||||
/* How many different read types exists */
|
||||
const byte SENSOR_READ_TYPE__TOTAL = 14;
|
||||
|
||||
|
||||
const byte SENSOR_READ_TYPE_RAW = 1;
|
||||
const char SENSOR_READ_TYPE_RAW_descr[] PROGMEM = {"Raw value"};
|
||||
const char SENSOR_READ_TYPE_RAW_unit[] PROGMEM = {""};
|
||||
|
||||
const byte SENSOR_READ_TYPE_TEMP = 2;
|
||||
const char SENSOR_READ_TYPE_TEMP_descr[] PROGMEM = {"Temperature"};
|
||||
const char SENSOR_READ_TYPE_TEMP_unit[] PROGMEM = {"°C"};
|
||||
|
||||
const byte SENSOR_READ_TYPE_HUMIDITY = 3;
|
||||
const char SENSOR_READ_TYPE_HUMIDITY_descr[] PROGMEM = {"Humidity"};
|
||||
const char SENSOR_READ_TYPE_HUMIDITY_unit[] PROGMEM = {"%"};
|
||||
|
||||
const byte SENSOR_READ_TYPE_SOILMOISTURE = 4;
|
||||
const char SENSOR_READ_TYPE_SOILMOISTURE_descr[] PROGMEM = {"Moisture"};
|
||||
const char SENSOR_READ_TYPE_SOILMOISTURE_unit[] PROGMEM = {"%"};
|
||||
|
||||
const byte SENSOR_READ_TYPE_PRESSURE = 5;
|
||||
const char SENSOR_READ_TYPE_PRESSURE_descr[] PROGMEM = {"Pressure"};
|
||||
const char SENSOR_READ_TYPE_PRESSURE_unit[] PROGMEM = {"Pa"};
|
||||
|
||||
const byte SENSOR_READ_TYPE_ALTITUDE = 6;
|
||||
const char SENSOR_READ_TYPE_ALTITUDE_descr[] PROGMEM = {"Altitude"};
|
||||
const char SENSOR_READ_TYPE_ALTITUDE_unit[] PROGMEM = {"m"};
|
||||
|
||||
const byte SENSOR_READ_TYPE_GAS_RESISTANCE = 7;
|
||||
const char SENSOR_READ_TYPE_GAS_RESISTANCE_descr[] PROGMEM = {"Gas resistance"};
|
||||
const char SENSOR_READ_TYPE_GAS_RESISTANCE_unit[] PROGMEM = {"KOhm"};
|
||||
|
||||
const byte SENSOR_READ_TYPE_COLOR_TEMP = 8;
|
||||
const char SENSOR_READ_TYPE_COLOR_TEMP_descr[] PROGMEM = {"Color temperature"};
|
||||
const char SENSOR_READ_TYPE_COLOR_TEMP_unit[] PROGMEM = {"K"};
|
||||
|
||||
const byte SENSOR_READ_TYPE_LUX = 9;
|
||||
const char SENSOR_READ_TYPE_LUX_descr[] PROGMEM = {"Lux"};
|
||||
const char SENSOR_READ_TYPE_LUX_unit[] PROGMEM = {"lx"};
|
||||
|
||||
const byte SENSOR_READ_TYPE_COLOR_RED = 10;
|
||||
const char SENSOR_READ_TYPE_COLOR_RED_descr[] PROGMEM = {"Color red"};
|
||||
const char SENSOR_READ_TYPE_COLOR_RED_unit[] PROGMEM = {""};
|
||||
|
||||
const byte SENSOR_READ_TYPE_COLOR_GREEN = 11;
|
||||
const char SENSOR_READ_TYPE_COLOR_GREEN_descr[] PROGMEM = {"Color green"};
|
||||
const char SENSOR_READ_TYPE_COLOR_GREEN_unit[] PROGMEM = {""};
|
||||
|
||||
const byte SENSOR_READ_TYPE_COLOR_BLUE = 12;
|
||||
const char SENSOR_READ_TYPE_COLOR_BLUE_descr[] PROGMEM = {"Color blue"};
|
||||
const char SENSOR_READ_TYPE_COLOR_BLUE_unit[] PROGMEM = {""};
|
||||
|
||||
const byte SENSOR_READ_TYPE_PARTS_PER_MILLION = 13;
|
||||
const char SENSOR_READ_TYPE_PARTS_PER_MILLION_descr[] PROGMEM = {"Part per million"};
|
||||
const char SENSOR_READ_TYPE_PARTS_PER_MILLION_unit[] PROGMEM = {"ppm"};
|
||||
|
||||
const byte SENSOR_READ_TYPE_TVOC = 14;
|
||||
const char SENSOR_READ_TYPE_TVOC_descr[] PROGMEM = {"TVOC"};
|
||||
const char SENSOR_READ_TYPE_TVOC_unit[] PROGMEM = {""};
|
||||
|
||||
|
||||
const char * Sensor_Read_descr[] = {
|
||||
NULL, // 0 is unset
|
||||
SENSOR_READ_TYPE_RAW_descr,
|
||||
SENSOR_READ_TYPE_TEMP_descr,
|
||||
SENSOR_READ_TYPE_HUMIDITY_descr,
|
||||
SENSOR_READ_TYPE_SOILMOISTURE_descr,
|
||||
SENSOR_READ_TYPE_PRESSURE_descr,
|
||||
SENSOR_READ_TYPE_ALTITUDE_descr,
|
||||
SENSOR_READ_TYPE_GAS_RESISTANCE_descr,
|
||||
SENSOR_READ_TYPE_COLOR_TEMP_descr,
|
||||
SENSOR_READ_TYPE_LUX_descr,
|
||||
SENSOR_READ_TYPE_COLOR_RED_descr,
|
||||
SENSOR_READ_TYPE_COLOR_GREEN_descr,
|
||||
SENSOR_READ_TYPE_COLOR_BLUE_descr,
|
||||
SENSOR_READ_TYPE_PARTS_PER_MILLION_descr,
|
||||
SENSOR_READ_TYPE_TVOC_descr
|
||||
};
|
||||
|
||||
const char * Sensor_Read_unit[] = {
|
||||
NULL, // 0 is unset
|
||||
SENSOR_READ_TYPE_RAW_unit,
|
||||
SENSOR_READ_TYPE_TEMP_unit,
|
||||
SENSOR_READ_TYPE_HUMIDITY_unit,
|
||||
SENSOR_READ_TYPE_SOILMOISTURE_unit,
|
||||
SENSOR_READ_TYPE_PRESSURE_unit,
|
||||
SENSOR_READ_TYPE_ALTITUDE_unit,
|
||||
SENSOR_READ_TYPE_GAS_RESISTANCE_unit,
|
||||
SENSOR_READ_TYPE_COLOR_TEMP_unit,
|
||||
SENSOR_READ_TYPE_LUX_unit,
|
||||
SENSOR_READ_TYPE_COLOR_RED_unit,
|
||||
SENSOR_READ_TYPE_COLOR_GREEN_unit,
|
||||
SENSOR_READ_TYPE_COLOR_BLUE_unit,
|
||||
SENSOR_READ_TYPE_PARTS_PER_MILLION_unit,
|
||||
SENSOR_READ_TYPE_TVOC_unit
|
||||
};
|
||||
|
||||
|
||||
/* How many different read convert types exists */
|
||||
const byte SENSOR_CONVERT_RAW_TYPE__TOTAL = 1;
|
||||
|
||||
const byte SENSOR_CONVERT_RAW_TYPE_SOILMOISTURE = 1;
|
||||
const char SENSOR_CONVERT_RAW_TYPE_SOILMOISTURE_descr[] PROGMEM = {"Soilmoisture"};
|
||||
const char SENSOR_CONVERT_RAW_TYPE_SOILMOISTURE_unit[] PROGMEM = {"%"};
|
||||
|
||||
//const byte SENSOR_CONVERT_RAW_TYPE_OTHER = 2;
|
||||
//const char SENSOR_CONVERT_RAW_TYPE_OTHER_descr[] PROGMEM = {"Other"};
|
||||
//const char SENSOR_CONVERT_RAW_TYPE_OTHER_unit[] PROGMEM = {"n/a"};
|
||||
|
||||
|
||||
const char * Sensor_Convert_Raw_descr[] = {
|
||||
NULL, // 0 is unset
|
||||
SENSOR_CONVERT_RAW_TYPE_SOILMOISTURE_descr,
|
||||
//SENSOR_CONVERT_RAW_TYPE_OTHER_descr
|
||||
};
|
||||
|
||||
const char * Sensor_Convert_Raw_unit[] = {
|
||||
NULL, // 0 is unset
|
||||
SENSOR_CONVERT_RAW_TYPE_SOILMOISTURE_unit,
|
||||
//SENSOR_CONVERT_RAW_TYPE_OTHER_unit
|
||||
};
|
||||
|
||||
// Addr_Init_Update modes
|
||||
const byte SENSOR_AIU_MODE_ADDR = 0;
|
||||
const byte SENSOR_AIU_MODE_INIT = 1;
|
||||
const byte SENSOR_AIU_MODE_UPDATE = 2;
|
124
include/Webserver/Api_sensor.h
Normal file
124
include/Webserver/Api_sensor.h
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Api_Sensor.h - Sensor API header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
void Api_sensor_data(AsyncWebServerRequest* request) {
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot().to<JsonObject>();
|
||||
//root["hello"] = "world";
|
||||
for(byte i = 0 ; i < Max_Sensors ; i++) {
|
||||
if(config.system.sensor.type[i] > 0) {
|
||||
|
||||
JsonObject objSensor = root["sensor"].add<JsonObject>();
|
||||
objSensor["id"] = i;
|
||||
objSensor["name"] = config.system.sensor.name[i];
|
||||
objSensor["type"] = SensorIndex[config.system.sensor.type[i]].name;
|
||||
objSensor["status"] = sensorStatus[i];
|
||||
if(SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_I2C)
|
||||
objSensor["i2c_addr"] = "0x" + String(Sensor_Addr_Init_Update(config.system.sensor.type[i], config.system.sensor.i2c_addr[i], SENSOR_AIU_MODE_ADDR), HEX);
|
||||
if(SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_INTADC)
|
||||
objSensor["gpio"] = GPIOindex[config.system.sensor.gpio[i][0]].gpio;
|
||||
|
||||
for(byte j = 0; j < Max_Sensors_Read; j++) {
|
||||
if(SensorIndex[config.system.sensor.type[i]].read[j] > 0) {
|
||||
|
||||
JsonObject objReading = objSensor["reading"].add<JsonObject>();
|
||||
|
||||
/* when for a RAW reading rawConvert is set, return the converted description and unit */
|
||||
if((SensorIndex[config.system.sensor.type[i]].read[j] == SENSOR_READ_TYPE_RAW) && (config.system.sensor.rawConvert[i][j] > 0)) {
|
||||
objReading["descr"] = FPSTR(Sensor_Convert_Raw_descr[config.system.sensor.rawConvert[i][j]]);
|
||||
objReading["unit"] = FPSTR(Sensor_Convert_Raw_unit[config.system.sensor.rawConvert[i][j]]);
|
||||
} else {
|
||||
objReading["descr"] = FPSTR(Sensor_Read_descr[SensorIndex[config.system.sensor.type[i]].read[j]]);
|
||||
objReading["unit"] = FPSTR(Sensor_Read_unit[SensorIndex[config.system.sensor.type[i]].read[j]]);
|
||||
}
|
||||
|
||||
/* read RAW values
|
||||
when internal ADC */
|
||||
if(SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_INTADC) {
|
||||
objReading["raw"] = Sensor_getValue( config.system.sensor.type[i], config.system.sensor.gpio[i][0]);
|
||||
} else if(SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_I2C) {
|
||||
objReading["raw"] = Sensor_getValue( config.system.sensor.type[i], config.system.sensor.i2c_addr[i], j);
|
||||
}
|
||||
|
||||
objReading["value"] = Sensor_getCalibratedValue(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
|
||||
void Api_sensor_data_raw(AsyncWebServerRequest* request) {
|
||||
/* Api_sensor_data_raw returns the raw reading value of a specific reading of a sensor
|
||||
* you can call it with GET http://<IP>/api/sensor/raw?sensor=1&reading=2*/
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot().to<JsonObject>();
|
||||
//root["hello"] = "world";
|
||||
|
||||
if((request->hasParam("sensor")) && (request->hasParam("reading"))) {
|
||||
const AsyncWebParameter* paramSensor = request->getParam("sensor");
|
||||
byte sensorId = paramSensor->value().toInt();
|
||||
|
||||
const AsyncWebParameter* paramReading = request->getParam("reading");
|
||||
byte readingId = paramReading->value().toInt();
|
||||
|
||||
root["sensorId"] = sensorId;
|
||||
root["readingId"] = readingId;
|
||||
|
||||
/* when reading is RAW */
|
||||
if(SensorIndex[config.system.sensor.type[sensorId]].read[readingId] == SENSOR_READ_TYPE_RAW) {
|
||||
/* when internal ADC */
|
||||
if(SensorIndex[config.system.sensor.type[sensorId]].type == SENSOR_TYPE_INTADC) {
|
||||
root["value"] = Sensor_getValue( config.system.sensor.type[sensorId], config.system.sensor.gpio[sensorId][0]);
|
||||
} else if(SensorIndex[config.system.sensor.type[sensorId]].type == SENSOR_TYPE_I2C) {
|
||||
root["value"] = Sensor_getValue( config.system.sensor.type[sensorId], config.system.sensor.i2c_addr[sensorId], readingId);
|
||||
}
|
||||
} else {
|
||||
root["msg"] = String(F("not a RAW reading"));
|
||||
}
|
||||
} else {
|
||||
root["msg"] = String(F("sensor or reading not given"));
|
||||
}
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void Api_sensor_driver(AsyncWebServerRequest* request) {
|
||||
/* Api_sensor_data_raw returns the raw reading value of a specific reading of a sensor
|
||||
* you can call it with GET http://<IP>/api/sensor/raw?sensor=1&reading=2*/
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot().to<JsonObject>();
|
||||
//root["hello"] = "world";
|
||||
|
||||
root["drivers"] = SensorIndex_length;
|
||||
root["maxReadings"] = Max_Sensors_Read;
|
||||
|
||||
/* empty driver because 0 is unconfigured */
|
||||
JsonObject objSensor = root["sensor"].add<JsonObject>();
|
||||
for(byte i = 1; i <= SensorIndex_length; i++) {
|
||||
//Log.verbose(F("%s Sensor_Index %d, Name %s, Readings" CR), LogLoc, i, SensorIndex[i].name );
|
||||
JsonObject objSensor = root["sensor"].add<JsonObject>();
|
||||
objSensor["index"] = i;
|
||||
objSensor["name"] = FPSTR(SensorIndex[i].name);
|
||||
for(byte j = 0; j < Max_Sensors_Read; j++) {
|
||||
if(SensorIndex[i].read[j] > 0 ) {
|
||||
//Log.verbose(F("%s %d: %s (%d %d)" CR), LogLoc, j, Sensor_Read_descr[SensorIndex[i].read[j]], SensorIndex[i].read[j], Sensor_Read_unit[SensorIndex[i].read[j]], SensorIndex[i].read[j]);
|
||||
JsonObject objReading = objSensor["reading"].add<JsonObject>();
|
||||
objReading["index"] = j;
|
||||
objReading["descr"] = FPSTR(Sensor_Read_descr[SensorIndex[i].read[j]]);
|
||||
objReading["unit"] = FPSTR(Sensor_Read_unit[SensorIndex[i].read[j]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
257
include/Webserver/File_cangrow_CSS.h
Normal file
257
include/Webserver/File_cangrow_CSS.h
Normal file
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/File_cangrow_CSS.h - /cangrow.css header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 1px;
|
||||
border-width: 0;
|
||||
color: #262B27;
|
||||
background-color: #262B27;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-style: inset;
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.sensorReading {
|
||||
font-style: italic;
|
||||
color: #64AA6D;
|
||||
}
|
||||
|
||||
.helpbox {
|
||||
font-size: 0.8em;
|
||||
margin-left: 15px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.nav {
|
||||
background: #333;
|
||||
/*width: 100; */
|
||||
margin: auto;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
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;
|
||||
}
|
||||
|
||||
.force_hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.visible {
|
||||
display: inline;
|
||||
/*justify-content: center!important;*/
|
||||
}
|
||||
/* a disabled class */
|
||||
a.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
@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) {
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/css"), File_cangrow_CSS);
|
||||
response->addHeader(F("Cache-control"), F("max-age=600"));
|
||||
request->send(response);
|
||||
//request->send_P(200, "text/css", File_cangrow_CSS);
|
||||
}
|
241
include/Webserver/File_cangrow_JS.h
Normal file
241
include/Webserver/File_cangrow_JS.h
Normal file
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/File_cangrow_JS.h - /cangrow.js header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
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 + '?');
|
||||
}
|
||||
|
||||
function SystemOutputAddselectRequired(selectId) {
|
||||
let selVal = document.getElementById(selectId).value;
|
||||
//hideAllClass('hidden');
|
||||
console.log('selectReq Status: ' + selVal);
|
||||
switch(selVal) {
|
||||
case '1':
|
||||
document.getElementById('gpio').required = true;
|
||||
document.getElementById('gpio_pwm').required = true;
|
||||
|
||||
document.getElementById('i2c_type').required = false;
|
||||
document.getElementById('i2c_addr').required = false;
|
||||
document.getElementById('i2c_port').required = false;
|
||||
document.getElementById('webcall_host').required = false;
|
||||
document.getElementById('webcall_path_on').required = false;
|
||||
document.getElementById('webcall_path_off').required = false;
|
||||
break;
|
||||
|
||||
case '2':
|
||||
document.getElementById('gpio').required = false;
|
||||
document.getElementById('gpio_pwm').required = false;
|
||||
|
||||
document.getElementById('i2c_type').required = true;
|
||||
document.getElementById('i2c_addr').required = true;
|
||||
document.getElementById('i2c_port').required = true;
|
||||
document.getElementById('webcall_host').required = false;
|
||||
document.getElementById('webcall_path_on').required = false;
|
||||
document.getElementById('webcall_path_off').required = false;
|
||||
break;
|
||||
|
||||
case '3':
|
||||
document.getElementById('gpio').required = false;
|
||||
document.getElementById('gpio_pwm').required = false;
|
||||
|
||||
document.getElementById('i2c_type').required = false;
|
||||
document.getElementById('i2c_addr').required = false;
|
||||
document.getElementById('i2c_port').required = false;
|
||||
document.getElementById('webcall_host').required = true;
|
||||
document.getElementById('webcall_path_on').required = true;
|
||||
document.getElementById('webcall_path_off').required = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/67412019
|
||||
function SystemOutputAdd_replaceI2cAddr(selectId, replaceId) {
|
||||
let sel = document.querySelector('#' + replaceId);
|
||||
let selVal = document.getElementById(selectId).value;
|
||||
// Remove existing options
|
||||
Array.from(sel).forEach((option) => {
|
||||
sel.removeChild(option)
|
||||
});
|
||||
// get or set your new options here.
|
||||
if(selVal) {
|
||||
addr[selVal].map((optionData) => {
|
||||
let opt = document.createElement('option');
|
||||
let PortsUsed = 0;
|
||||
let label = optionData[0];
|
||||
opt.value = optionData[1];
|
||||
// iterate through i2c modules available ports
|
||||
for(i = 0; i < optionData[2].length; i++) {
|
||||
if(optionData[2][i] > 0) {
|
||||
PortsUsed++;
|
||||
}
|
||||
}
|
||||
if(PortsUsed >= optionData[2].length) {
|
||||
opt.disabled = true;
|
||||
label = label + ' (used)';
|
||||
}
|
||||
opt.appendChild(document.createTextNode(label));
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
SystemOutputAdd_replaceI2cPort('i2c_type', 'i2c_addr', 'i2c_port');
|
||||
}
|
||||
|
||||
}
|
||||
//////////////////////////////////////
|
||||
function SystemOutputAdd_replaceI2cPort(selectTypeId, selectAddrId, replaceId) {
|
||||
let repl = document.querySelector('#' + replaceId);
|
||||
let selValType = document.getElementById(selectTypeId).value;
|
||||
let selValAddr = document.getElementById(selectAddrId).value;
|
||||
// Remove existing options
|
||||
Array.from(repl).forEach((option) => {
|
||||
repl.removeChild(option)
|
||||
});
|
||||
if(selValAddr) {
|
||||
console.log('true');
|
||||
// iterate through i2c modules available ports
|
||||
for(i = 0; i < addr[selValType][selValAddr][2].length; i++) {
|
||||
let opt = document.createElement('option');
|
||||
let label = 'Port ' + i;
|
||||
opt.value = i;
|
||||
if(addr[selValType][selValAddr][2][i] > 0) {
|
||||
label = label + ' (used)';
|
||||
opt.disabled = true;
|
||||
}
|
||||
opt.appendChild(document.createTextNode(label));
|
||||
repl.appendChild(opt);
|
||||
console.log('PortID ' + i + ' Port sum ' + addr[selValType][selValAddr][2].length);
|
||||
}
|
||||
} else {
|
||||
let opt = document.createElement('option');
|
||||
opt.appendChild(document.createTextNode('n/a'));
|
||||
opt.disabled = true;
|
||||
repl.appendChild(opt);
|
||||
}
|
||||
}
|
||||
//javascript is my passion
|
||||
|
||||
|
||||
function SystemSensorAddGpioI2cSel(selectId) {
|
||||
let selVal = document.getElementById(selectId).value;
|
||||
hideAllClass('hidden');
|
||||
if(selVal == 1) {
|
||||
document.getElementById('type_1').style.display = 'inline';
|
||||
document.getElementById('i2c_addr').required = false;
|
||||
if(ESP == '32') {
|
||||
document.getElementById('gpio').required = true;
|
||||
}
|
||||
} else if(selVal > 1) {
|
||||
document.getElementById('type_2').style.display = 'inline';
|
||||
document.getElementById('i2c_addr').required = true;
|
||||
if(ESP == '32') {
|
||||
document.getElementById('gpio').required = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertDateToEpoch(src, dst) {
|
||||
var val = document.getElementById(src).value ;
|
||||
document.getElementById(dst).value = new Date(val).getTime() / 1000;
|
||||
}
|
||||
|
||||
|
||||
function GrowSelectControlSensorRead(selectId, inputSensor, inputRead) {
|
||||
let selVal = document.getElementById(selectId).value;
|
||||
let sensor = selVal.split(':')[0];
|
||||
let read = selVal.split(':')[1];
|
||||
document.getElementById(inputSensor).value = sensor;
|
||||
document.getElementById(inputRead).value = read;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function GetSensorJson(callback) {
|
||||
let path = '/api/sensor/';
|
||||
//let path = '/api/sensor/raw_' + sensor + '_' + reading;
|
||||
var xobj = new XMLHttpRequest();
|
||||
xobj.overrideMimeType('application/json');
|
||||
xobj.open('GET', path, true);
|
||||
xobj.onreadystatechange = function() {
|
||||
if (xobj.readyState == 4 && xobj.status == "200") {
|
||||
callback(xobj.responseText);
|
||||
}
|
||||
}
|
||||
xobj.send(null);
|
||||
}
|
||||
|
||||
|
||||
/* propably not the best place, but this as global as it can get i guess */
|
||||
var SensorJson;
|
||||
function SensorJsonRefresh() {
|
||||
GetSensorJson(function(response) {
|
||||
/* needs to be a global */
|
||||
SensorJson = JSON.parse(response);
|
||||
});
|
||||
//console.log('Refresh SensorJson');
|
||||
}
|
||||
|
||||
|
||||
function rawRefresh(sensor, reading, id) {
|
||||
let element = id + sensor + '-' + reading;
|
||||
document.getElementById(element).textContent = SensorJson.sensor[sensor].reading[reading].raw;
|
||||
//console.log(SensorJson.sensor[sensor].reading[reading].raw);
|
||||
//console.log('sensor:' + sensor + ';reading:' + reading + ';id:' + id + ';element:' + element);
|
||||
}
|
||||
|
||||
|
||||
function sensorRefresh(sensor, reading, id) {
|
||||
let element = id + sensor + '-' + reading;
|
||||
document.getElementById(element).textContent = SensorJson.sensor[sensor].reading[reading].value + ' ' + SensorJson.sensor[sensor].reading[reading].unit;
|
||||
//console.log(SensorJson.sensor[sensor].reading[reading].value + SensorJson.sensor[sensor].reading[reading].unit);
|
||||
//console.log('sensor:' + sensor + ';reading:' + reading + ';id:' + id + ';element:' + element);
|
||||
}
|
||||
|
||||
)";
|
||||
|
||||
void WebFile_cangrow_JS(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/javascript"), File_cangrow_JS);
|
||||
response->addHeader(F("Cache-control"), F("max-age=600"));
|
||||
request->send(response);
|
||||
//request->send_P(200, "text/javascript", File_cangrow_JS);
|
||||
}
|
38
include/Webserver/File_favicon_ico.h
Normal file
38
include/Webserver/File_favicon_ico.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
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, F("image/x-icon"), File_favicon_ico_gz, File_favicon_ico_gz_len);
|
||||
response->addHeader(F("Content-Encoding"), F("gzip"));
|
||||
response->addHeader(F("Cache-control"), F("max-age=600"));
|
||||
request->send(response);
|
||||
}
|
9
include/Webserver/Footer.h
Normal file
9
include/Webserver/Footer.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/footer_HTML.h - footer page HTML header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const char Footer_HTML[] PROGMEM = R"(<div class='footer'><span>Build: %CGBUILD%</span></div></div></body></html>)";
|
28
include/Webserver/Header.h
Normal file
28
include/Webserver/Header.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/header_HTML.h - header page HTML header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
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'>%TIME%</span></li>
|
||||
<li><a href='https://git.la10cy.net/DeltaLima/CanGrow' target='_blank'>CanGrow v%CGVER%</a></li>
|
||||
</ul>
|
||||
<div class='center'>
|
||||
%NEED_RESTART%)";
|
26
include/Webserver/Page_404.h
Normal file
26
include/Webserver/Page_404.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 404 error page begins
|
||||
*/
|
||||
|
||||
// 404 page is a good page template btw
|
||||
const char Page_404_HTML[] PROGMEM = R"EOF(%HEADER%
|
||||
<div class='warnmsg'><h1>❗ ️ 404 - not found</h1></div>
|
||||
%FOOTER% )EOF";
|
||||
|
||||
/* processor */
|
||||
String Proc_WebPage_404(const String& var) {
|
||||
if(TestHeaderFooter(var)) {
|
||||
return AddHeaderFooter(var);
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/mathieucarbou/ESPAsyncWebServer/blob/main/examples/SimpleServer/SimpleServer.ino
|
||||
void WebserverNotFound(AsyncWebServerRequest* request) {
|
||||
request->send_P(404, TEXT_HTML, Page_404_HTML, Proc_WebPage_404);
|
||||
}
|
||||
|
||||
/*
|
||||
* 404 error page ends
|
||||
*/
|
1000
include/Webserver/Page_grow.h
Normal file
1000
include/Webserver/Page_grow.h
Normal file
File diff suppressed because it is too large
Load diff
102
include/Webserver/Page_grow_HTML.h
Normal file
102
include/Webserver/Page_grow_HTML.h
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_grow_HTML.h - grow page HTML header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/* submenu SUBNAV */
|
||||
const char Page_grow_HTML_SUBNAV[] PROGMEM = R"(<ul class='subnav'>
|
||||
<li><a class='%ACTIVE_SUBNAV_GENERAL%' href='/grow/'>🛠️ General</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_LIGHT%' href='/grow/light/'>💡 Light</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_AIR%' href='/grow/air/'>🌀 Air</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_WATER%' href='/grow/water/'>💧 Water</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_DASHBOARD%' href='/grow/dashboard/' >🖥️ Dashboard</a></li>
|
||||
</ul>)";
|
||||
|
||||
/* /grow/ main page */
|
||||
const char Page_grow_HTML[] PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
%SAVE_MSG%
|
||||
<p>here you can set grow stuff<br></p><form method='post' action='/grow/'>
|
||||
|
||||
<u>Grow name:</u><br>
|
||||
<input type='text' name='name' maxlength='31' value='%GROWNAME%' required><br>
|
||||
<input type='hidden' id='start' name='start' value='%GROWSTART_EPOCH%' required>
|
||||
<u>Grow start date:</u><br>
|
||||
<input type='date' id='GrowStart_sel' onChange='convertDateToEpoch("GrowStart_sel", "start");' value='%GROWSTART%' ><br>
|
||||
<u>Vegetation duration:</u><br>
|
||||
<input class='inputShort' type='number' name='daysVeg' min='0' max='255' value='%DAYS_VEG%' required> Days<br>
|
||||
<u>Bloom duration:</u><br>
|
||||
<input class='inputShort' type='number' name='daysBloom' min='0' max='255' value='%DAYS_BLOOM%' required> Days<br>
|
||||
|
||||
<br>
|
||||
<input type='submit' value='💾 Save settings'>
|
||||
</form>
|
||||
%FOOTER% )";
|
||||
|
||||
/* /grow/light/ page */
|
||||
const char Page_grow_light_HTML[] PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
%SAVE_MSG%
|
||||
<p>here you can set light stuff<br></p>
|
||||
|
||||
|
||||
%LIGHT%
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
%FOOTER% )";
|
||||
|
||||
|
||||
/* /grow/air/ page */
|
||||
const char Page_grow_air_HTML[] PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
%SAVE_MSG%
|
||||
<p>here you can set air stuff<br></p>
|
||||
|
||||
|
||||
%AIR%
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
%FOOTER% )";
|
||||
|
||||
/* /grow/water/ page */
|
||||
const char Page_grow_water_HTML[] PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
%SAVE_MSG%
|
||||
<p>here you can set water stuff<br></p>
|
||||
|
||||
|
||||
%WATER%
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
%FOOTER% )";
|
||||
|
||||
/* /grow/dashboard/ page */
|
||||
const char Page_grow_dashboard_HTML[] PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
%SAVE_MSG%
|
||||
<p>here you can set dashboard stuff<br></p>
|
||||
|
||||
|
||||
%DASHBOARD%
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
%FOOTER% )";
|
28
include/Webserver/Page_root.h
Normal file
28
include/Webserver/Page_root.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_root.h - root page header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#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);
|
||||
}
|
13
include/Webserver/Page_root_HTML.h
Normal file
13
include/Webserver/Page_root_HTML.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_root_HTML.h - root page HTML header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
const char Page_root_HTML[] PROGMEM = R"EOF(%HEADER%
|
||||
<h2>🌱 Hello world!</h2>
|
||||
<a href='/api/sensor/'>Sensor data -> /api/sensor/</a>
|
||||
%FOOTER% )EOF";
|
1721
include/Webserver/Page_system.h
Normal file
1721
include/Webserver/Page_system.h
Normal file
File diff suppressed because it is too large
Load diff
396
include/Webserver/Page_system_HTML.h
Normal file
396
include/Webserver/Page_system_HTML.h
Normal file
|
@ -0,0 +1,396 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_system_HTML.h - system settings page HTML header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/* submenu SUBNAV */
|
||||
const char Page_system_HTML_SUBNAV[] PROGMEM = R"(<ul class='subnav'>
|
||||
<li><a class='%ACTIVE_SUBNAV_GENERAL%' href='/system/'>🛠️ General</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_SENSOR%' href='/system/sensor/'>🌡️ Sensor</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_OUTPUT%' href='/system/output/'>⚡ Output</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_UPDATE%' href='/system/update'>🔄 Firmware update</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_RESTART%' href='/system/restart' >🔁 System restart</a></li>
|
||||
<li><a class='%ACTIVE_SUBNAV_WIPE%' href='/system/wipe' >💣 Factory reset</a></li>
|
||||
</ul>)";
|
||||
|
||||
/* /system main page */
|
||||
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='%NTPOFFSET%' required> Hours<br>
|
||||
|
||||
<u>Maintenance duration</u>:<br> <input class='inputShort' type='number' name='maintenanceDuration' min='0' max='900' value='%MAINTDUR%' required> Seconds<br>
|
||||
|
||||
<u>ESP32-Cam IP (optional)</u>:<br>
|
||||
<input type='text' name='esp32cam' maxlength='16' value='%ESP32CAM%' ><br>
|
||||
|
||||
<u>HTTP log to serial</u>:<br>
|
||||
<select name='httpLogSerial' required>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%HTTPLOGSERIAL%
|
||||
</select><br>
|
||||
|
||||
<u>I2C RTC</u>:<br>
|
||||
%RTC_STATUS%<select name='rtc' required>
|
||||
<option value='0' selected >---</option>
|
||||
%RTC_AVAILABLE%
|
||||
</select><br>
|
||||
|
||||
<u>Save time to LittleFS</u>:<br>
|
||||
<select name='time2fs' required>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%TIME2FS%
|
||||
</select><br>
|
||||
|
||||
<u>PWM Frequency</u>:<br>
|
||||
<input type='number' name='pwmFreq' min='0' max='65535' value='%PWMFREQ%'>
|
||||
</select><br>
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
<input type='submit' value='💾 Save settings'>
|
||||
</form>
|
||||
%FOOTER% )";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* 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='20;url=/' /></head>
|
||||
<body><h1>Successfully updated!
|
||||
<br>Restarting...</h1><br>
|
||||
Redirecting to "/" in 20 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...<br><span style='helpbox'>Redirecting to root page in 20 seconds.</span>)";
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* 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 %ADD_DISABLED%' href='/system/output/add'>➕ Add output</a>
|
||||
<table class='centered'>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Device</th>
|
||||
<th> </th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
%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?%EDIT_MODE%'>
|
||||
<input type='hidden' name='outputId' value='%OUTPUT_ID%' />
|
||||
|
||||
<u>Type</u>:<br>
|
||||
<select id='type_sel' name='type' onchange="showSelect('type_sel', 'type_', 'hidden'); SystemOutputAddselectRequired('type_sel');" 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='32' value='%OUTPUT_NAME%' required><br>
|
||||
|
||||
<u>Enable</u>:<br>
|
||||
<select name='enabled' required>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%OUTPUT_ENABLED%
|
||||
</select><br>
|
||||
|
||||
<u>Invert</u>:<br>
|
||||
<select name='invert' required>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%INVERT%
|
||||
</select><br>
|
||||
|
||||
|
||||
<div class='hidden %CLASS_TYPE_1%' id='type_1'>
|
||||
<u>GPIO</u>:<br>
|
||||
<select id='gpio' name='gpio'>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%GPIO_INDEX%
|
||||
</select><br>
|
||||
|
||||
<u>GPIO PWM</u>:<br>
|
||||
<select id='gpio_pwm' name='gpio_pwm' >
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%GPIO_PWM%
|
||||
</select><br>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class='hidden ' id='type_2'>
|
||||
<u>I2C type</u>:<br>
|
||||
<select id='i2c_type' name='i2c_type' onchange="SystemOutputAdd_replaceI2cAddr('i2c_type', 'i2c_addr');">
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%I2C_TYPE%
|
||||
</select><br>
|
||||
<u>I2C address</u>:<br>
|
||||
<select id='i2c_addr' name='i2c_addr' onchange="SystemOutputAdd_replaceI2cPort('i2c_type', 'i2c_addr', 'i2c_port');">
|
||||
<option value='' selected >---</option>
|
||||
</select><br>
|
||||
<u>I2C module port</u>:<br>
|
||||
<select id='i2c_port' name='i2c_port'>
|
||||
<option value='' selected >---</option>
|
||||
</select><br>
|
||||
</div>
|
||||
|
||||
<div class='hidden %CLASS_TYPE_3%' id='type_3'>
|
||||
<u>Webcall host</u>:<br>
|
||||
<input id='webcall_host' type='text' name='webcall_host' maxlength='32' value='%WEBCALL_HOST%' ><br>
|
||||
|
||||
<u>Webcall path 'on'</u>:<br>
|
||||
<input id='webcall_path_on' type='text' name='webcall_path_on' maxlength='32' value='%WEBCALL_PATH_ON%' ><br>
|
||||
|
||||
<u>Webcall path 'off'</u>:<br>
|
||||
<input id='webcall_path_off' type='text' name='webcall_path_off' maxlength='32' value='%WEBCALL_PATH_OFF%' ><br>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<input type='submit' value='💾 Save settings'>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
var addr = [ [] ,
|
||||
%REPLACE_I2CADDR_JS% ];
|
||||
|
||||
%I2C_SAVED%
|
||||
</script>
|
||||
|
||||
|
||||
%FOOTER% )";
|
||||
|
||||
const char Page_system_output_add_HTML_NO_ID_AVAILABLE[] PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
<h3>You cannot create more outputs, limit reached.</h3>
|
||||
%FOOTER% )";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Subpage sensor
|
||||
*/
|
||||
const char Page_system_sensor_HTML[] PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
%SAVE_MSG%
|
||||
<a class='button %ADD_DISABLED%' href='/system/sensor/add'>➕ Add sensor</a>
|
||||
<table class='centered'>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
</tr>
|
||||
%TR_TD%
|
||||
</table>
|
||||
%FOOTER% )";
|
||||
|
||||
/*******************************************************************************
|
||||
* Subpage sensor add
|
||||
*/
|
||||
const char Page_system_sensor_add_HTML[] PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
<h3>%ACTION% sensor ID %SENSOR_ID%</h3>
|
||||
%SAVE_MSG%
|
||||
|
||||
<p>Add/Edit CanGrow sensor.</p>
|
||||
<form method='post' action='/system/sensor/add'>
|
||||
<input type='hidden' name='sensorId' value='%SENSOR_ID%' />
|
||||
<u>Type</u>:<br>
|
||||
<select id='type_sel' name='type' onchange="SystemSensorAddGpioI2cSel('type_sel'); SystemSensor_replaceAddr('type_sel', 'i2c_addr');" required>
|
||||
<option disabled value='' selected hidden>---</option>
|
||||
%SENSOR_TYPE%
|
||||
</select><br>
|
||||
|
||||
|
||||
|
||||
<u>Name</u>:<br>
|
||||
<input type='text' name='name' maxlength='32' value='%SENSOR_NAME%' required><br>
|
||||
|
||||
|
||||
<div class='hidden %CLASS_TYPE_1%' id='type_1'>
|
||||
<u>GPIO</u>:<br>
|
||||
<select id='gpio' name='gpio'>
|
||||
<option value='' selected >---</option>
|
||||
%GPIO_INDEX%
|
||||
</select><br>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='hidden %CLASS_TYPE_2%' id='type_2'>
|
||||
<u>I2C address</u>:<br>
|
||||
<select id='i2c_addr' name='i2c_addr' required>
|
||||
<option value='' selected >---</option>
|
||||
</select><br>
|
||||
</div>
|
||||
|
||||
<div class='hidden %CLASS_TYPE_3% %CLASS_TYPE_1%' id='type_3'>
|
||||
<span>Special 3</span>
|
||||
</div>
|
||||
|
||||
<!-- Sensor reading calibration -->
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
<input type='submit' value='💾 Save settings'>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
var ESP = '%ESP_PLATFORM%';
|
||||
// https://stackoverflow.com/a/67412019
|
||||
function SystemSensor_replaceAddr(selectId, replaceId) {
|
||||
var sel = document.querySelector('#' + replaceId);
|
||||
let selVal = document.getElementById(selectId).value;
|
||||
// Remove existing options
|
||||
Array.from(sel).forEach((option) => {
|
||||
sel.removeChild(option)
|
||||
})
|
||||
|
||||
var addr = [ [] ,
|
||||
%REPLACE_I2CADDR_JS%
|
||||
]
|
||||
|
||||
addr[selVal].map((optionData) => {
|
||||
let opt = document.createElement('option')
|
||||
opt.appendChild(document.createTextNode(optionData[0]));
|
||||
opt.value = optionData[1]
|
||||
if(optionData[2] > 0) {
|
||||
opt.disabled = true
|
||||
}
|
||||
sel.appendChild(opt);
|
||||
})
|
||||
}
|
||||
|
||||
%I2C_SAVED%
|
||||
</script>
|
||||
|
||||
%FOOTER% )";
|
||||
|
||||
const char Page_system_sensor_add_HTML_NO_ID_AVAILABLE[] PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
<h3>You cannot create more sensors, limit reached.</h3>
|
||||
%FOOTER% )";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Subpage sensor calibrate
|
||||
*/
|
||||
const char Page_system_sensor_calibrate_HTML[] PROGMEM = R"(%HEADER%
|
||||
%SUBNAV%
|
||||
<h3>🎛️ Calibrate sensor ID %SENSOR_ID% (%SENSOR_NAME%)</h3>
|
||||
%SAVE_MSG%
|
||||
|
||||
<p>Calibrate CanGrow sensor.</p>
|
||||
|
||||
|
||||
%SENSOR_READING%
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
||||
|
||||
%FOOTER% )";
|
197
include/Webserver/Page_wifi.h
Normal file
197
include/Webserver/Page_wifi.h
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_wifi.h - wifi page header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "Page_wifi_HTML.h"
|
||||
|
||||
String WebPage_wifi_ScanNetworks() {
|
||||
const static char LogLoc[] PROGMEM= "[Webserver:wifi:ScanNetworks]";
|
||||
String html;
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s scanning for available networks:" CR), LogLoc);
|
||||
#endif
|
||||
// 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 += F("<option value='");
|
||||
html += WiFi.SSID(i);
|
||||
html += F("'>");
|
||||
html += WiFi.SSID(i);
|
||||
html += F("</option>");
|
||||
/* dirty hack, arduino-log somehow destroys wifi names in output
|
||||
/* so i have to print them oldschool with Serial.println
|
||||
*/
|
||||
#ifdef DEBUG
|
||||
Log.verbose(F("%s - "), LogLoc);
|
||||
Serial.println(WiFi.SSID(i));
|
||||
#endif
|
||||
}
|
||||
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) {
|
||||
const static char LogLoc[] PROGMEM = "[Webserver:wifi]";
|
||||
|
||||
if(request->method() == HTTP_POST) {
|
||||
if(request->hasParam("config.wifi.ssid", true)) {
|
||||
const AsyncWebParameter* p_ssid = request->getParam("config.wifi.ssid", true);
|
||||
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);
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
config.wifi.dhcp = p_dhcp->value().toInt();
|
||||
}
|
||||
|
||||
if(SaveConfig()) {
|
||||
// we need a restart to apply the new settings
|
||||
needRestart = true;
|
||||
Log.notice(F("%s config saved" CR), LogLoc);
|
||||
request->send_P(200, TEXT_HTML, Page_wifi_HTML, Proc_WebPage_wifi_POST);
|
||||
} else {
|
||||
Log.error(F("%s ERROR while saving config" CR), LogLoc);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
72
include/Webserver/Page_wifi_HTML.h
Normal file
72
include/Webserver/Page_wifi_HTML.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Page_wifi_HTML.h - wifi page HTML header file
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
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>)";
|
||||
|
||||
|
190
include/Webserver/Webserver_Common.h
Normal file
190
include/Webserver/Webserver_Common.h
Normal file
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Common.h - header file with common webserver functions
|
||||
* HTML header or footer to a String()
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Webserver_Common_HTML.h"
|
||||
|
||||
/*
|
||||
* global char constants for various HTML tags and stuff
|
||||
*
|
||||
*/
|
||||
|
||||
/* return type */
|
||||
const char TEXT_HTML[] PROGMEM = "text/html";
|
||||
|
||||
|
||||
/*
|
||||
* TestHeaderFooter - checks if the given var from the webserver processor
|
||||
* is actual a template variable from header or footer.
|
||||
*/
|
||||
bool TestHeaderFooter(const String& var) {
|
||||
const static char LogLoc[] PROGMEM = "[Webserver:Common:TestHeaderFooter]";
|
||||
#ifdef DEBUG3
|
||||
Log.verbose(F("%s var: %s" CR), LogLoc, var);
|
||||
#endif
|
||||
if(
|
||||
(var == "HEADER") ||
|
||||
(var == "FOOTER") ||
|
||||
(var == "CGVER") ||
|
||||
(var == "CGBUILD") ||
|
||||
(var == "GROWNAME") ||
|
||||
(var == "CANGROW_CSS") ||
|
||||
(var == "TIME") ||
|
||||
(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 = F("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.name);
|
||||
} else if(var == "CANGROW_CSS") {
|
||||
return String(File_cangrow_CSS);
|
||||
} else if(var == "TIME") {
|
||||
return Str_TimeNow();
|
||||
} 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, bool input = false) {
|
||||
|
||||
String gpioIndex_html;
|
||||
// iterate through through all available GPIOs in index
|
||||
for(byte i = 1; i <= GPIOindex_length; i++) {
|
||||
bool gpioUsed = Check_GPIOindex_Used(i);
|
||||
|
||||
gpioIndex_html += F("<option value='");
|
||||
gpioIndex_html += i;
|
||||
gpioIndex_html += F("'");
|
||||
// set disabled option for gpio which are already in use or incompatible // or only inputs when configuring sensor ADC
|
||||
// || ( (input == true) && ((GPIOindex[i].note != INPUT_ONLY) || (GPIOindex[i].note != INT_ADC)) )
|
||||
/* when GPIO is already in use AND not selected OR
|
||||
* input is false AND GPIO is Input only OR
|
||||
* input is true AND GPIO is not INT_ADC AND not INPUT_only*/
|
||||
if( ((gpioUsed == true) && (i != selectId)) || ((input == false) && (GPIOindex[i].note == INPUT_ONLY))
|
||||
#ifdef ESP32
|
||||
/* If we are on ESP32, we check our input GPIOs - we dont need this on ESP8266, as it only has 1 ADC */
|
||||
|| ((input == true) && ((GPIOindex[i].note != INT_ADC) && GPIOindex[i].note != INPUT_ONLY))
|
||||
#endif
|
||||
) {
|
||||
//|| ((input == true) && ((GPIOindex[i].note != INPUT_ONLY) || (GPIOindex[i].note != INT_ADC) ))
|
||||
gpioIndex_html += F(" disabled");
|
||||
}
|
||||
|
||||
if(i == selectId) {
|
||||
gpioIndex_html += F(" selected");
|
||||
}
|
||||
gpioIndex_html += F(">GPIO ");
|
||||
gpioIndex_html += GPIOindex[i].gpio;
|
||||
//add gpio note if there is some
|
||||
//if(GPIOindex[i].note > 0) {
|
||||
gpioIndex_html += F(" ");
|
||||
gpioIndex_html += FPSTR(GPIO_Index_note_descr[GPIOindex[i].note]);
|
||||
|
||||
// disable output incompatible gpio
|
||||
if((GPIOindex[i].note == INPUT_ONLY) && (input == false)) {
|
||||
gpioIndex_html += F(" (N/A)");
|
||||
// add USED if gpio is already in use
|
||||
} else if((gpioUsed == true) && (i != selectId)) {
|
||||
gpioIndex_html += F(" (used)");
|
||||
}
|
||||
gpioIndex_html += F("</option>");
|
||||
}
|
||||
return gpioIndex_html;
|
||||
}
|
||||
|
||||
|
||||
String Html_SelectOpt_bool(byte selectVal = 255, String trueStr = "Yes", String falseStr = "No") {
|
||||
String html;
|
||||
html += F("<option value='1'");
|
||||
html += ((selectVal > 0) && (selectVal < 255)) ? F(" selected") : F("");
|
||||
html += F(">");
|
||||
html += trueStr;
|
||||
html += F("</option>");
|
||||
|
||||
html += F("<option value='0'");
|
||||
html += (selectVal == 0) ? F(" selected") : F("");
|
||||
html += F(">");
|
||||
html += falseStr;
|
||||
html += F("</option>");
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
String Html_SelectOpt_array(byte total, const char * descr[] , byte selectVal = 255) {
|
||||
const static char LogLoc[] PROGMEM= "[Webserver:Common:Html_Select_Opt_array]";
|
||||
String html;
|
||||
// go through all available array entries, skip 0 because it means unconfigured
|
||||
for(byte i = 1; i <= total; i++) {
|
||||
//Log.notice(F("%s i: %d selectVal: %d descr: %S" CR), LogLoc, i, selectVal, descr[i]);
|
||||
html += F("<option value='");
|
||||
html += i;
|
||||
html += F("'");
|
||||
if(i == selectVal) {
|
||||
html += F(" selected");
|
||||
}
|
||||
html += F(">");
|
||||
// use FPSTR because our descr is stored in PROGMEM
|
||||
html += FPSTR(descr[i]);
|
||||
html += F("</option>");
|
||||
}
|
||||
return html;
|
||||
}
|
26
include/Webserver/Webserver_Common_HTML.h
Normal file
26
include/Webserver/Webserver_Common_HTML.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
*
|
||||
* include/Webserver/Common_HTML.h - header file with common HTML snippets
|
||||
* HTML header or footer to a String()
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// 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";
|
Loading…
Add table
Reference in a new issue