// Global defines
#define __JUMP_TO_ENTRY_POINT asm volatile ("jmp 0")
#define __VERSION_INFO 922
const uint32_t CommunicationBaud = 19200;
const uint16_t JSONBufferLen = 1024;
// Arduino Mega Code
#ifdef ARDUINO_AVR_MEGA2560
// ======= REGION: Defines =======
// Communication
#define USBSerial Serial
#define ESPSerial Serial2
#define GPSSerial Serial3
const uint32_t USBSerialBaud = 115200;
const uint32_t GPSSerialBaud = 9600;
// JSON
#include <ArduinoJson.h>
StaticJsonDocument<JSONBufferLen> SensorJSON;
StaticJsonDocument<JSONBufferLen> TempSensorJSON;
StaticJsonDocument<JSONBufferLen> BarometerJSON;
// LCD
// #define USE_LCD_2004
#define USE_LCD_2004_I2C
const unsigned char lcd_registerSelect = 12;
const unsigned char lcd_enable = 11;
const unsigned char lcd_d4 = 4;
const unsigned char lcd_d5 = 5;
const unsigned char lcd_d6 = 6;
const unsigned char lcd_d7 = 7;
const byte* lcd_i2c_address = 0x27;
const int8_t lcdColumns = 20;
const int8_t lcdRows = 4;
#if defined(USE_LCD_2004)
#include <LiquidCrystal.h>
LiquidCrystal lcd(lcd_registerSelect, lcd_enable, lcd_d4, lcd_d5, lcd_d6, lcd_d7);
#elif defined(USE_LCD_2004_I2C)
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(lcd_i2c_address, lcdColumns, lcdRows);
#endif
// RTC
int8_t records[3];
const int8_t monthDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
const uint16_t UnixStartYear = 1970;
const uint16_t Last32BitYear = 2038;
#define USE_DS1307
// #define USE_DS1307
#if defined(USE_DS1302)
#include <RtcDS1302.h>
RtcDateTime dt;
const uint8_t rtcDataPin = 41;
const uint8_t rtcClockPin = 43;
const uint8_t rtcResetPin = 39;
ThreeWire myWire(rtcDataPin, rtcClockPin, rtcResetPin);
RtcDS1302<ThreeWire> Rtc(myWire);
#elif defined(USE_DS1307)
#include <RtcDS1307.h>
#include <Wire.h>
RtcDateTime dt;
RtcDS1307<TwoWire> Rtc(Wire);
#endif
// Buttons
const uint8_t buttonAmount = 3;
const int8_t buttonPins[buttonAmount] = { 2, 3, 19 };
const bool canHoldButton[buttonAmount] = { true, false, true };
const long debounceTime = 150;
const int16_t holdTicksForFastForward = 200;
const int16_t ticksPerFastForward = 100;
const int16_t holdTicksForMEGAFastForward = 100;
const int16_t ticksPerMEGAFastForward = 10;
bool canMegaFastForward;
#define _previousBTN 2
#define _selectBTN 1
#define _nextBTN 0
volatile bool buttonState[buttonAmount];
volatile long buttonDebounce[buttonAmount];
volatile long buttonHeldFor[buttonAmount];
// Sensors
const uint8_t sensorAmount = 5;
const int sensorADCStart = A0;
int sensorADCResult[sensorAmount];
const uint8_t relayAmount = sensorAmount;
const uint8_t relayPins[relayAmount] = { 62, 63, 64, 65, 66 };
bool relayStatus[relayAmount];
const int defaultRelayState[relayAmount] = { LOW, LOW, LOW, LOW, LOW };
const int8_t RelayPercentResolution = 5;
int8_t relayPercentages[relayAmount];
const uint8_t relayBottleneckFix = 2;
// Temperature
#include <DHT.h>
#define DHTTYPE DHT11
// #define DHTTYPE DHT22
const uint8_t tempSensorAmount = 2;
const uint8_t outsideTempSensorPin = 29;
const uint8_t insideTempSensorPin = 24;
DHT tempSensors[tempSensorAmount] = {
DHT(outsideTempSensorPin, DHTTYPE),
DHT(insideTempSensorPin, DHTTYPE)
};
float tempSensorTemperature[tempSensorAmount];
float tempSensorHumidity[tempSensorAmount];
const char* tempChannels[tempSensorAmount] = { "OUTSIDE", "INSIDE" };
// GPS
#include <TinyGPSPlus.h>
TinyGPSPlus gps;
struct Location {
double latitude;
double longitude;
const char* city;
const char* country;
};
Location locations[] = {
{42.020878, 23.094331, "Blagoevgrad", "Bulgaria"},
{42.504807, 27.462645, "Burgas", "Bulgaria"},
{43.214065, 27.914734, "Varna", "Bulgaria"},
{43.075689, 25.617153, "Veliko Tarnovo", "Bulgaria"},
{43.996172, 22.867932, "Vidin", "Bulgaria"},
{43.210254, 23.552883, "Vratsa", "Bulgaria"},
{42.874232, 25.318677, "Gabrovo", "Bulgaria"},
{43.572601, 27.827249, "Dobrich", "Bulgaria"},
{41.633855, 25.377708, "Kardzhali", "Bulgaria"},
{42.286895, 22.693930, "Kyustendil", "Bulgaria"},
{43.136965, 24.714193, "Lovech", "Bulgaria"},
{43.408531, 23.225718, "Montana", "Bulgaria"},
{42.192782, 24.333556, "Pazardzhik", "Bulgaria"},
{42.605205, 23.037835, "Pernik", "Bulgaria"},
{43.417053, 24.606692, "Pleven", "Bulgaria"},
{42.135419, 24.745287, "Plovdiv", "Bulgaria"},
{43.533686, 26.541122, "Razgrad", "Bulgaria"},
{43.835586, 25.965648, "Ruse", "Bulgaria"},
{44.114743, 27.267184, "Silistra", "Bulgaria"},
{42.681669, 26.322874, "Sliven", "Bulgaria"},
{41.577441, 24.701105, "Smolyan", "Bulgaria"},
{42.697724, 23.321869, "Sofia", "Bulgaria"},
{42.425792, 25.634470, "Stara Zagora", "Bulgaria"},
{43.249365, 26.572728, "Targovishte", "Bulgaria"},
{41.934448, 25.555442, "Haskovo", "Bulgaria"},
{43.271252, 26.936136, "Shumen", "Bulgaria"},
{42.484218, 26.503505, "Yambol", "Bulgaria"}
};
const Location defaultUnknownLocation = {0.0, 0.0, "Unknown", "Unknown"};
Location lastLocation = defaultUnknownLocation;
const long maxKilometerRange = 250;
const double minDifferenceForUpdate = 0.001;
int16_t locScrollIndex = -2;
bool locScrollInverted;
double lastGPSLat;
double lastGPSLng;
long memorySaves = 0;
#define NOT_CONNECTED 0
#define LIMITED_DATA 1
#define CONNECTED 2
byte gpsStatus;
// Barometer
#include <Wire.h>
#include <Adafruit_BMP280.h>
const byte* barometer_i2c_address = 0x76;
Adafruit_BMP280 bmp;
Adafruit_Sensor* bmp_temp = bmp.getTemperatureSensor();
Adafruit_Sensor* bmp_pressure = bmp.getPressureSensor();
double barometerTemp;
double barometerPressure;
// Control
const uint8_t controlsAmount = 2;
const uint8_t controlPins[controlsAmount] = { 33, 35 };
bool controlStatus[controlsAmount];
const int defaultControlRelayState[controlsAmount] = { HIGH, HIGH };
#define VENT_RELAY 0
#define LIGHTS_RELAY 1
typedef int8_t ControlMode;
const int8_t MaxControls = 2;
#define CONTROL_MODE_PROGRAM 0
#define CONTROL_MODE_ALWAYS_ON 1
#define CONTROL_MODE_ALWAYS_OFF 2
ControlMode VentMode;
ControlMode LightsMode;
const long VentTimeResolution = 10;
long VentilationTimer;
long VentilationTimeStamp;
bool ventilationState = true;
int16_t ventilationActiveFor;
int16_t ventilationInactiveFor;
const long maxVentilationWait = 5000;
const int8_t LightsTimeResolution = 5;
bool lightsState;
int8_t LightsStartHour;
int8_t LightsEndHour;
int8_t LightsStartMin;
int8_t LightsEndMin;
// Menu Data
const uint8_t menusAmount = 8;
const char* menuNames[menusAmount] = {
"Soil Sensors ",
"Temp & Humidity ",
"Date & Time ",
"GPS Satelite ",
"Barometer ",
"Storage ",
"Control ",
"Software Info "
};
#define _menuMain -1
#define _menuSensor 0
#define _menuTemp 1
#define _menuRTC 2
#define _menuGPS 3
#define _menuBarometer 4
#define _menuStorage 5
#define _menuControl 6
#define _menuSoftwareInfo 7
const bool contextMenuAllowsFastForward[menusAmount] = {
true,
true,
true,
false,
false,
true,
true,
false
};
const bool contextMenuIdleRefresh[menusAmount] = {
true,
true,
true,
true,
true,
false,
true,
false
};
int8_t contextMenu = -1;
int8_t currentTab = 0;
int8_t currentTabOffset = 0;
const int maxChannels = 3;
int8_t currentChannel[maxChannels];
int8_t channelSelectMode = -1;
int8_t subMenuEntered = -1;
const int8_t maxTabsMainMenu = menusAmount;
const int8_t maxTabsSensorMenu = 3;
const int8_t maxTabsTempMenu = 2;
const int8_t maxTabsRTCMenu = 3;
const int8_t maxTabsGPSMenu = 1;
const int8_t maxTabsBarometerMenu = 1;
const int8_t maxTabsStorageMenu = 3;
const int8_t maxTabsControlMenu = 4;
const int8_t maxTabsSoftwareInfoMenu = 2;
const int8_t maxSubTabsRTCMenu = 5;
const int8_t maxSubTabsProgramMenu = 2;
const int8_t maxSubTabsVentMenu = 3;
const int8_t maxSubTabsLightsMenu = 3;
const int8_t maxTabs[maxTabsMainMenu + 1] = {
maxTabsMainMenu,
maxTabsSensorMenu,
maxTabsTempMenu,
maxTabsRTCMenu,
maxTabsGPSMenu,
maxTabsBarometerMenu,
maxTabsStorageMenu,
maxTabsControlMenu,
maxTabsSoftwareInfoMenu
};
typedef bool Direction;
#define BACKWARD false
#define FORWARD true
// EEPROM
#include <EEPROM.h>
const uintptr_t GPSLatAddress = 0x010;
const uintptr_t GPSLngAddress = 0x018;
double eepromGPSLat;
double eepromGPSLng;
const uintptr_t RelayPercentageAddress = 0x300;
uint8_t eepromRelayPercentages[relayAmount];
const uintptr_t SDCardSaveTimeAddress = 0x120;
long eepromSDCardSaveTime;
const uintptr_t ControlModeVentAddress = 0x501;
const uintptr_t ControlmodeLightsAddress = 0x502;
ControlMode eepromControlModeVent;
ControlMode eepromControlModeLights;
const uintptr_t VentilationActiveAddress = 0x212;
const uintptr_t VentilationInactiveAddress = 0x216;
int16_t eepromVentilationActiveTime;
int16_t eepromVentilationInactiveTime;
const uintptr_t LightsStartHourAddress = 0x402;
const uintptr_t LightsEndHourAddress = 0x404;
const uintptr_t LightsStartMinAddress = 0x406;
const uintptr_t LightsEndMinAddress = 0x408;
int8_t eepromLightsStartHour;
int8_t eepromLightsEndHour;
int8_t eepromLightsStartMin;
int8_t eepromLightsEndMin;
// SD
#include <SPI.h>
#include <SD.h>
const uint8_t sdChipSelect = 53;
bool sdCardAvailable = false;
long SDCardSaveTime;
long lastTimeSaved = SDCardSaveTime;
const long SDCardSaveTimeResolution = 250;
const long MaxSDCardSaveSeconds = 7200;
// ======= END REGION =======
// ======= REGION: Entry Point =======
void setup(void){
// Initialize Serial
USBSerial.begin(USBSerialBaud);
ESPSerial.begin(CommunicationBaud);
GPSSerial.begin(GPSSerialBaud);
USBSerial.print("Loading Arduino Greenhouse v0.");
USBSerial.println(__VERSION_INFO);
// Initialize LCD
#if defined(USE_LCD_2004_I2C)
lcd.init();
lcd.backlight();
#else
lcd.begin(lcdColumns, lcdRows);
#endif
// Initialize RTC
Rtc.Begin();
dt = Rtc.GetDateTime();
// Initialize Buttons
for (uint8_t i = 0; i < buttonAmount; i++){
pinMode(buttonPins[i], INPUT_PULLUP);
}
// Initialize Sensors
for (uint8_t i = 0; i < sensorAmount; i++){
sensorADCResult[i] = 1023;
}
// Initialize Temp Sensors
for (uint8_t i = 0; i < tempSensorAmount; i++){
tempSensors[i].begin();
}
// Initialize Control Relays
for (uint8_t i = 0; i < controlsAmount; i++){
pinMode(controlPins[i], OUTPUT);
digitalWrite(controlPins[i], defaultControlRelayState[i] == LOW ? controlStatus[i] : !controlStatus[i]);
}
// Initialize Sensor Relays
for (uint8_t i = 0; i < relayAmount; i++){
EEPROM.get(RelayPercentageAddress + i, eepromRelayPercentages[i]);
relayPercentages[i] = max(0, min(eepromRelayPercentages[i], 100));
pinMode(relayPins[i], OUTPUT);
digitalWrite(relayPins[i], defaultRelayState[i]);
}
// Initialize Barometer
bmp.begin(barometer_i2c_address);
bmp.setSampling(
Adafruit_BMP280::MODE_NORMAL,
Adafruit_BMP280::SAMPLING_X2,
Adafruit_BMP280::SAMPLING_X16,
Adafruit_BMP280::FILTER_X16,
Adafruit_BMP280::STANDBY_MS_500
);
// Initialize GPS
EEPROM.get(GPSLatAddress, eepromGPSLat);
EEPROM.get(GPSLngAddress, eepromGPSLng);
lastGPSLat = eepromGPSLat;
lastGPSLng = eepromGPSLng;
// Initialize Control Memory
EEPROM.get(ControlModeVentAddress, eepromControlModeVent);
EEPROM.get(ControlmodeLightsAddress, eepromControlModeLights);
VentMode = eepromControlModeVent;
LightsMode = eepromControlModeLights;
// Initialize Ventilation
EEPROM.get(VentilationActiveAddress, eepromVentilationActiveTime);
EEPROM.get(VentilationInactiveAddress, eepromVentilationInactiveTime);
if (eepromVentilationActiveTime > maxVentilationWait) eepromVentilationActiveTime = maxVentilationWait;
if (eepromVentilationInactiveTime > maxVentilationWait) eepromVentilationInactiveTime = maxVentilationWait;
if (eepromVentilationActiveTime < VentTimeResolution || eepromVentilationActiveTime % VentTimeResolution != 0) eepromVentilationActiveTime = VentTimeResolution;
if (eepromVentilationInactiveTime < VentTimeResolution || eepromVentilationInactiveTime % VentTimeResolution != 0) eepromVentilationInactiveTime = VentTimeResolution;
ventilationActiveFor = eepromVentilationActiveTime;
ventilationInactiveFor = eepromVentilationInactiveTime;
// Initialize Lights
EEPROM.get(LightsStartHourAddress, eepromLightsStartHour);
EEPROM.get(LightsEndHourAddress, eepromLightsEndHour);
EEPROM.get(LightsStartMinAddress, eepromLightsStartMin);
EEPROM.get(LightsEndMinAddress, eepromLightsEndMin);
if (eepromLightsStartHour < 0 || eepromLightsStartHour > 23) eepromLightsStartHour = 0;
if (eepromLightsEndHour < 0 || eepromLightsEndHour > 23) eepromLightsEndHour = 0;
if (eepromLightsStartMin < 0 || eepromLightsStartMin > 59) eepromLightsStartMin = 0;
if (eepromLightsEndMin < 0 || eepromLightsEndMin > 59) eepromLightsEndMin = 0;
while (eepromLightsStartMin % LightsTimeResolution != 0 && eepromLightsStartMin > 0) eepromLightsStartMin--;
while (eepromLightsEndMin % LightsTimeResolution != 0 && eepromLightsEndMin > 0) eepromLightsEndMin--;
LightsStartHour = eepromLightsStartHour;
LightsEndHour = eepromLightsEndHour;
LightsStartMin = eepromLightsStartMin;
LightsEndMin = eepromLightsEndMin;
// Initialize SD Card
EEPROM.get(SDCardSaveTimeAddress, eepromSDCardSaveTime);
if (eepromSDCardSaveTime < 0 || eepromSDCardSaveTime % SDCardSaveTimeResolution != 0) eepromSDCardSaveTime = 0;
if (eepromSDCardSaveTime > MaxSDCardSaveSeconds) eepromSDCardSaveTime = MaxSDCardSaveSeconds;
SDCardSaveTime = eepromSDCardSaveTime;
// Load Main Menu
changeMenu(_menuMain, -1);
}
void loop(void){
handleButtons();
readGPS();
readBarometer();
if (dt != Rtc.GetDateTime()){
dt = Rtc.GetDateTime();
idleRefreshMenu();
backgroundTasks();
}
}
// ======= END REGION =======
// ======= REGION: General Commands =======
const char* getDateTime(){
static char datestring[26];
snprintf_P(datestring,
sizeof(datestring) / sizeof(datestring[0]),
PSTR("%02u/%02u/%04u %02u:%02u:%02u"),
dt.Day(),
dt.Month(),
dt.Year(),
dt.Hour(),
dt.Minute(),
dt.Second() );
return datestring;
}
int8_t getDaysInMonth(){
if (records[1] == 1 && (((records[2] + UnixStartYear) % 4 == 0 && (records[2] + UnixStartYear) % 100 != 0) || ((records[2] + UnixStartYear) % 400 == 0)))
return 29;
return monthDays[records[1]];
}
void setNewRTCDateTime(){
int16_t convertRecord[3];
convertRecord[0] = records[0];
convertRecord[1] = records[1];
convertRecord[2] = records[2];
if (subMenuEntered == 0){
convertRecord[0] += 1;
convertRecord[1] += 1;
convertRecord[2] += UnixStartYear;
}
RtcDateTime newdt;
switch (subMenuEntered){
case 0:
newdt = RtcDateTime(
convertRecord[2],
convertRecord[1],
convertRecord[0],
dt.Hour(),
dt.Minute(),
dt.Second()
);
break;
case 1:
newdt = RtcDateTime(
dt.Year(),
dt.Month(),
dt.Day(),
convertRecord[0],
convertRecord[1],
convertRecord[2]
);
break;
}
Rtc.SetDateTime(newdt);
dt = Rtc.GetDateTime();
}
void readGPS(){
while (GPSSerial.available() > 0){
if (gps.encode(GPSSerial.read())){
if (gps.location.isValid()){
gpsStatus = CONNECTED;
lastGPSLat = gps.location.lat();
lastGPSLng = gps.location.lng();
if (abs(eepromGPSLat - lastGPSLat) > minDifferenceForUpdate || abs(eepromGPSLng - lastGPSLng) > minDifferenceForUpdate){
memorySaves++;
eepromGPSLat = lastGPSLat;
eepromGPSLng = lastGPSLng;
EEPROM.put(GPSLatAddress, eepromGPSLat);
EEPROM.put(GPSLngAddress, eepromGPSLng);
}
}
else gpsStatus = gps.date.isValid() ? LIMITED_DATA : NOT_CONNECTED;
}
}
}
void readBarometer(){
sensors_event_t temp_event, pressure_event;
bmp_temp->getEvent(&temp_event);
bmp_pressure->getEvent(&pressure_event);
barometerTemp = temp_event.temperature;
barometerPressure = pressure_event.pressure;
}
bool triggerControlRelay(int id, bool status){
controlStatus[id] = status;
digitalWrite(controlPins[id], defaultControlRelayState[id] == LOW ? controlStatus[id] : !controlStatus[id]);
return true;
}
void updateVentilation(){
VentilationTimeStamp = ventilationState ? ventilationActiveFor : ventilationInactiveFor;
VentilationTimeStamp *= 60;
VentilationTimer++;
switch (VentMode){
case CONTROL_MODE_ALWAYS_ON: ventilationState = true; break;
case CONTROL_MODE_ALWAYS_OFF: ventilationState = false; break;
default:
if (VentilationTimer >= VentilationTimeStamp){
ventilationState = !ventilationState;
VentilationTimer = 0;
}
break;
}
triggerControlRelay(VENT_RELAY, ventilationState);
}
bool updateAllOfTheLights(){
switch (LightsMode){
case CONTROL_MODE_ALWAYS_ON: lightsState = true; break;
case CONTROL_MODE_ALWAYS_OFF: lightsState = false; break;
default:
int16_t dtoi_from = LightsStartHour * 100 + LightsStartMin;
int16_t dtoi_to = LightsEndHour * 100 + LightsEndMin;
int16_t dtoi_dt = dt.Hour() * 100 + dt.Minute();
if (dtoi_from != dtoi_to){
if (dtoi_from < dtoi_to) lightsState = dtoi_dt >= dtoi_from && dtoi_dt < dtoi_to;
else lightsState = (dtoi_dt >= dtoi_from && dtoi_dt <= 2359) || (dtoi_dt >= 0000 && dtoi_dt < dtoi_to);
}
break;
}
return triggerControlRelay(LIGHTS_RELAY, lightsState);
}
bool backgroundTasks(){
for (uint8_t i = 0; i < sensorAmount; i++){
sensorADCResult[i] = analogRead(sensorADCStart + i);
handleButtons();
}
for (uint8_t i = 0; i < relayAmount; i++){
int16_t moisture = map(sensorADCResult[i], 0, 1023, 100, 0);
int16_t threshold = relayPercentages[i];
bool enable = moisture + (relayBottleneckFix - 1) < threshold;
bool disable = moisture - (relayBottleneckFix - 1) > threshold;
if (enable) digitalWrite(relayPins[i], defaultRelayState[i] == LOW ? HIGH : LOW);
if (disable) digitalWrite(relayPins[i], defaultRelayState[i] == LOW ? LOW : HIGH);
handleButtons();
}
for (uint8_t i = 0; i < tempSensorAmount; i++){
tempSensorTemperature[i] = tempSensors[i].readTemperature();
tempSensorHumidity[i] = tempSensors[i].readHumidity();
if (isnan(tempSensorTemperature[i])) tempSensorTemperature[i] = 0.0f;
if (isnan(tempSensorHumidity[i])) tempSensorHumidity[i] = 0.0f;
handleButtons();
}
lastLocation = findClosestLocation(lastGPSLat, lastGPSLng);
handleButtons();
updateVentilation();
handleButtons();
updateAllOfTheLights();
handleButtons();
sdCardAvailable = SD.begin(sdChipSelect);
lastTimeSaved++;
handleButtons();
if (sdCardAvailable){
if (lastTimeSaved >= SDCardSaveTime){
lastTimeSaved = 0;
saveAllSDCardData();
}
}
else lastTimeSaved = SDCardSaveTime;
handleButtons();
if (ESPSerial){
sendSensorData();
handleButtons();
sendTemperatureData();
handleButtons();
sendBarometerData();
handleButtons();
}
return true;
}
bool saveAllSDCardData(){
if (sdCardAvailable){
String saveFileName;
bool headerInitialized;
char buffer[16];
snprintf(buffer, sizeof(buffer), "%02d-%02d-%02d", dt.Day(), dt.Month(), dt.Year() % 100);
saveFileName = String(buffer) + ".csv";
if (SD.exists(saveFileName)) headerInitialized = true;
File saveFile = SD.open(saveFileName, FILE_WRITE);
if (saveFile){
if (!headerInitialized){
saveFile.print("DateTime;;");
for (uint8_t i = 0; i < sensorAmount; i++){
saveFile.print("Sensor ");
saveFile.print(i + 1);
saveFile.print(";Moisture;Voltage;;");
}
for (uint8_t j = 0; j < tempSensorAmount; j++){
saveFile.print("Temp CH ");
saveFile.print(tempChannels[j]);
saveFile.print(";Temperature;Humidity;;");
}
saveFile.print("GPS;latitude;longitude;;Barometer;Temp;Pressure;;Control;Ventilation;Lights");
saveFile.println('\n');
}
saveFile.print(getDateTime());
saveFile.print(';');
saveFile.print(';');
for (uint8_t i = 0; i < sensorAmount; i++){
saveFile.print(';');
saveFile.print(map(sensorADCResult[i], 0, 1023, 100, 0));
saveFile.print('%');
saveFile.print(';');
saveFile.print((sensorADCResult[i] / 1.023) * 5);
saveFile.print("mV");
saveFile.print(';');
saveFile.print(';');
}
for (uint8_t j = 0; j < tempSensorAmount; j++){
saveFile.print(';');
saveFile.print(tempSensorTemperature[j]);
saveFile.print('C');
saveFile.print(';');
saveFile.print(tempSensorHumidity[j]);
saveFile.print('%');
saveFile.print(';');
saveFile.print(';');
}
saveFile.print(';');
saveFile.print(lastGPSLat, 6);
saveFile.print(';');
saveFile.print(lastGPSLng, 6);
saveFile.print(';');
saveFile.print(';');
saveFile.print(';');
saveFile.print(barometerTemp, 2);
saveFile.print('C');
saveFile.print(';');
saveFile.print(barometerPressure, 2);
saveFile.print("hPa");
saveFile.print(';');
saveFile.print(';');
saveFile.print(';');
saveFile.print(controlStatus[VENT_RELAY] ? "ON " : "OFF ");
saveFile.print("(");
saveFile.print(VentMode == CONTROL_MODE_ALWAYS_ON ? "ON" : VentMode == CONTROL_MODE_ALWAYS_OFF ? "OFF" : "PROG");
saveFile.print(")");
saveFile.print(';');
saveFile.print(controlStatus[LIGHTS_RELAY] ? "ON " : "OFF ");
saveFile.print("(");
saveFile.print(LightsMode == CONTROL_MODE_ALWAYS_ON ? "ON" : LightsMode == CONTROL_MODE_ALWAYS_OFF ? "OFF" : "PROG");
saveFile.print(")");
saveFile.println();
saveFile.close();
return true;
}
}
return false;
}
void sendSensorData(){
JsonArray sensorsArray = SensorJSON.createNestedArray("sensors");
for (uint8_t i = 0; i < sensorAmount; i++){
JsonObject sensor = sensorsArray.createNestedObject();
sensor["id"] = i + 1;
sensor["percentage"] = map(sensorADCResult[i], 0, 1023, 100, 0);
sensor["voltage"] = (sensorADCResult[i] / 1.023) * 5;
}
String output;
serializeJson(SensorJSON, output);
ESPSerial.println(output);
}
void sendTemperatureData(){
JsonArray tempSensorsArray = TempSensorJSON.createNestedArray("tempsensors");
for (uint8_t i = 0; i < tempSensorAmount; i++){
JsonObject tempsensor = tempSensorsArray.createNestedObject();
tempsensor["channel"] = tempChannels[i];
tempsensor["temperature"] = tempSensorTemperature[i];
tempsensor["humidity"] = tempSensorHumidity[i];
}
String output;
serializeJson(TempSensorJSON, output);
ESPSerial.println(output);
}
void sendBarometerData(){
JsonArray barometerArray = BarometerJSON.createNestedArray("barometer");
JsonObject barometer = barometerArray.createNestedObject();
barometer["temp"] = barometerTemp;
barometer["pressure"] = barometerPressure;
String output;
serializeJson(BarometerJSON, output);
ESPSerial.println(output);
}
// ======= END REGION =======
// ======= REGION: Button Handlers =======
void handleButtons(){
for (uint8_t i = 0; i < buttonAmount; i++){
int btnState = digitalRead(buttonPins[i]);
if (btnState == LOW) buttonHeldFor[i]++;
else buttonHeldFor[i] = -1;
if ((contextMenu < 0 || contextMenuAllowsFastForward[contextMenu])
&& canHoldButton[i] && buttonHeldFor[i] > (canMegaFastForward ? holdTicksForMEGAFastForward : holdTicksForFastForward)
&& buttonHeldFor[i] % (canMegaFastForward ? ticksPerMEGAFastForward : ticksPerFastForward) == 0){
buttonPressed(i);
return;
}
if (btnState == LOW && !buttonState[i] && millis() - buttonDebounce[i] > debounceTime){
buttonState[i] = true;
buttonDebounce[i] = millis();
buttonPressed(i);
}else if (btnState == HIGH){
buttonState[i] = false;
}
}
}
void buttonPressed(int8_t id){
switch (id){
case _previousBTN:
case _nextBTN:
buttonPressed_dir(id);
break;
case _selectBTN:
buttonPressed_select();
break;
}
}
void buttonPressed_dir(int8_t& id){
Direction _DIR = id == _previousBTN ? BACKWARD : FORWARD;
switch (contextMenu){
case _menuSensor:
if (channelSelectMode == 1){
relayPercentages[currentChannel[0]] += (_DIR == BACKWARD ? -RelayPercentResolution : RelayPercentResolution);
if (relayPercentages[currentChannel[0]] < 0) relayPercentages[currentChannel[0]] = 0;
if (relayPercentages[currentChannel[0]] > 100) relayPercentages[currentChannel[0]] = 100;
refreshMenu();
}
else channelSelectMenuScrolling(_DIR, sensorAmount);
break;
case _menuTemp:
channelSelectMenuScrolling(_DIR, tempSensorAmount);
break;
case _menuRTC:
if (subMenuEntered >= 0){
if (channelSelectMode >= 0){
int8_t highBound;
if (subMenuEntered == 0) switch (channelSelectMode){
case 0: highBound = getDaysInMonth() - 1; break;
case 1: highBound = 11; break;
case 2: highBound = (Last32BitYear - 1) - UnixStartYear; break;
}
if (subMenuEntered == 1) switch (channelSelectMode){
case 0: highBound = 23; break;
case 1: case 2: highBound = 59; break;
}
records[currentTab] += (_DIR == BACKWARD ? -1 : 1);
if (records[currentTab] < 0) records[currentTab] = highBound;
if (records[currentTab] > highBound) records[currentTab] = 0;
if (subMenuEntered == 0 && records[0] > getDaysInMonth() - 1) records[0] = getDaysInMonth() - 1;
refreshMenu();
}
else regularMenuScrolling(_DIR, maxSubTabsRTCMenu);
}
else regularMenuScrolling(_DIR, maxTabs[contextMenu + 1]);
break;
case _menuGPS:
case _menuBarometer:
break;
case _menuStorage:
if (channelSelectMode == 0){
eepromSDCardSaveTime += (_DIR == BACKWARD ? -SDCardSaveTimeResolution : SDCardSaveTimeResolution);
if (eepromSDCardSaveTime < 0) eepromSDCardSaveTime = 0;
if (eepromSDCardSaveTime > MaxSDCardSaveSeconds) eepromSDCardSaveTime = MaxSDCardSaveSeconds;
refreshMenu();
}
else regularMenuScrolling(_DIR, maxTabs[contextMenu + 1]);
break;
case _menuControl:
switch (subMenuEntered){
case 0:
regularMenuScrolling(_DIR, maxSubTabsProgramMenu);
break;
case 1:
switch (channelSelectMode){
case 0:
eepromVentilationActiveTime += (_DIR == BACKWARD ? -VentTimeResolution : VentTimeResolution);
if (eepromVentilationActiveTime < VentTimeResolution) eepromVentilationActiveTime = VentTimeResolution;
if (eepromVentilationActiveTime > maxVentilationWait) eepromVentilationActiveTime = maxVentilationWait;
refreshMenu();
break;
case 1:
eepromVentilationInactiveTime += (_DIR == BACKWARD ? -VentTimeResolution : VentTimeResolution);
if (eepromVentilationInactiveTime < VentTimeResolution) eepromVentilationInactiveTime = VentTimeResolution;
if (eepromVentilationInactiveTime > maxVentilationWait) eepromVentilationInactiveTime = maxVentilationWait;
refreshMenu();
break;
default:
regularMenuScrolling(_DIR, maxSubTabsVentMenu);
break;
}
break;
case 2:
switch (channelSelectMode){
case 0:
eepromLightsStartMin += (_DIR == BACKWARD ? -LightsTimeResolution : LightsTimeResolution);
if (eepromLightsStartMin < 0){
eepromLightsStartMin = (60 - LightsTimeResolution);
eepromLightsStartHour--;
}
if (eepromLightsStartMin > 59){
eepromLightsStartMin = 0;
eepromLightsStartHour++;
}
if (eepromLightsStartHour < 0) eepromLightsStartHour = 23;
if (eepromLightsStartHour > 23) eepromLightsStartHour = 0;
refreshMenu();
break;
case 1:
eepromLightsEndMin += (_DIR == BACKWARD ? -LightsTimeResolution : LightsTimeResolution);
if (eepromLightsEndMin < 0){
eepromLightsEndMin = (60 - LightsTimeResolution);
eepromLightsEndHour--;
}
if (eepromLightsEndMin > 59){
eepromLightsEndMin = 0;
eepromLightsEndHour++;
}
if (eepromLightsEndHour < 0) eepromLightsEndHour = 23;
if (eepromLightsEndHour > 23) eepromLightsEndHour = 0;
refreshMenu();
break;
default:
regularMenuScrolling(_DIR, maxSubTabsLightsMenu);
break;
}
break;
default:
switch (channelSelectMode){
case 0:
eepromControlModeVent += (_DIR == BACKWARD ? -1 : 1);
if (eepromControlModeVent < 0) eepromControlModeVent = MaxControls;
if (eepromControlModeVent > MaxControls) eepromControlModeVent = 0;
VentMode = eepromControlModeVent;
refreshMenu();
break;
case 1:
eepromControlModeLights += (_DIR == BACKWARD ? -1 : 1);
if (eepromControlModeLights < 0) eepromControlModeLights = MaxControls;
if (eepromControlModeLights > MaxControls) eepromControlModeLights = 0;
LightsMode = eepromControlModeLights;
refreshMenu();
break;
default:
regularMenuScrolling(_DIR, maxTabs[contextMenu + 1]);
break;
break;
}
}
break;
default:
regularMenuScrolling(_DIR, maxTabs[contextMenu + 1]);
break;
}
}
void buttonPressed_select(){
switch (contextMenu){
case _menuMain:
changeMenu(currentTab, -1);
break;
case _menuSensor:
if (currentTab == 0 || currentTab == 1){
if (channelSelectMode < 0) channelSelectMode = currentTab;
else {
channelSelectMode = -1;
if (currentTab == 1){
eepromRelayPercentages[currentChannel[0]] = relayPercentages[currentChannel[0]];
EEPROM.put(RelayPercentageAddress + currentChannel[0], eepromRelayPercentages[currentChannel[0]]);
}
}
refreshMenu();
}
else if (currentTab == 2) changeMenu(_menuMain, -1);
break;
case _menuTemp:
if (currentTab == 0){
if (channelSelectMode < 0) channelSelectMode = 0;
else channelSelectMode = -1;
refreshMenu();
}
else if (currentTab == 1) changeMenu(_menuMain, -1);
break;
case _menuRTC:
if (subMenuEntered < 0){
switch (currentTab){
case 0:
records[0] = dt.Day() - 1;
records[1] = dt.Month() - 1;
records[2] = dt.Year() - UnixStartYear;
changeMenu(_menuRTC, 0);
break;
case 1:
records[0] = dt.Hour();
records[1] = dt.Minute();
records[2] = dt.Second();
changeMenu(_menuRTC, 1);
break;
case 2:
changeMenu(_menuMain, -1);
break;
}
}else {
switch (currentTab){
case 0:
case 1:
case 2:
if (channelSelectMode < 0) channelSelectMode = currentTab;
else channelSelectMode = -1;
refreshMenu();
break;
case 3:
setNewRTCDateTime();
changeMenu(_menuRTC, -1);
break;
default:
changeMenu(_menuRTC, -1);
break;
}
}
break;
case _menuStorage:
if (currentTab == 0){
if (channelSelectMode < 0) channelSelectMode = 0;
else channelSelectMode = -1;
refreshMenu();
}else {
if (currentTab == 1){
EEPROM.put(SDCardSaveTimeAddress, eepromSDCardSaveTime);
SDCardSaveTime = eepromSDCardSaveTime;
lastTimeSaved = SDCardSaveTime;
}
else eepromSDCardSaveTime = SDCardSaveTime;
changeMenu(_menuMain, -1);
}
break;
case _menuControl:
switch (subMenuEntered){
case 0:
changeMenu(_menuControl, currentTab + 1);
refreshMenu();
break;
case 1:
if (currentTab == 0 || currentTab == 1){
if (channelSelectMode < 0) channelSelectMode = currentTab;
else {
channelSelectMode = -1;
switch (currentTab){
case 0:
EEPROM.put(VentilationActiveAddress, eepromVentilationActiveTime);
ventilationActiveFor = eepromVentilationActiveTime;
break;
case 1:
EEPROM.put(VentilationInactiveAddress, eepromVentilationInactiveTime);
ventilationInactiveFor = eepromVentilationInactiveTime;
break;
}
}
refreshMenu();
}
else changeMenu(_menuControl, -1);
break;
case 2:
if (currentTab == 0 || currentTab == 1){
if (channelSelectMode < 0) channelSelectMode = currentTab;
else {
channelSelectMode = -1;
switch (currentTab){
case 0:
EEPROM.put(LightsStartHourAddress, eepromLightsStartHour);
EEPROM.put(LightsStartMinAddress, eepromLightsStartMin);
LightsStartHour = eepromLightsStartHour;
LightsStartMin = eepromLightsStartMin;
break;
case 1:
EEPROM.put(LightsEndHourAddress, eepromLightsEndHour);
EEPROM.put(LightsEndMinAddress, eepromLightsEndMin);
LightsEndHour = eepromLightsEndHour;
LightsEndMin = eepromLightsEndMin;
break;
}
}
refreshMenu();
}
else changeMenu(_menuControl, -1);
break;
default:
if (currentTab == 0 || currentTab == 1){
if (channelSelectMode < 0) channelSelectMode = currentTab;
else {
channelSelectMode = -1;
switch (currentTab){
case 0: EEPROM.put(ControlModeVentAddress, eepromControlModeVent); break;
case 1: EEPROM.put(ControlmodeLightsAddress, eepromControlModeLights); break;
}
}
}else if (currentTab == 2){
changeMenu(_menuControl, 0);
refreshMenu();
}else changeMenu(_menuMain, -1);
break;
}
break;
case _menuSoftwareInfo:
if (currentTab == 0) restartSystem();
else changeMenu(_menuMain, -1);
break;
default:
changeMenu(_menuMain, -1);
break;
}
}
void regularMenuScrolling(Direction direction, const int8_t& tabs){
if (direction == BACKWARD){
if (currentTab > 0){
currentTab--;
}else {
currentTab = tabs - 1;
if (tabs > lcdRows) currentTabOffset = currentTab - (lcdRows - 1);
}
if (currentTab < currentTabOffset){
currentTabOffset = currentTab;
}
}else if (direction == FORWARD){
if (currentTab < tabs - 1){
currentTab++;
}else {
currentTab = 0;
currentTabOffset = 0;
}
if (currentTab >= currentTabOffset + lcdRows){
currentTabOffset++;
}
}
refreshMenu();
}
void channelSelectMenuScrolling(Direction direction, const uint8_t& itemAmount){
if (channelSelectMode >= 0){
currentChannel[channelSelectMode] += direction ? 1 : -1;
if (direction == BACKWARD && currentChannel[channelSelectMode] < 0) currentChannel[channelSelectMode] = itemAmount - 1;
if (direction == FORWARD && currentChannel[channelSelectMode] >= itemAmount) currentChannel[channelSelectMode] = 0;
refreshMenu();
}
else regularMenuScrolling(direction, maxTabs[contextMenu + 1]);
}
// ======= END REGION =======
// ======= REGION: Menus =======
// Main Menu
void idleRefreshMenu(){
if (contextMenuIdleRefresh[contextMenu]) refreshMenu();
}
void refreshMenu(){
canMegaFastForward = false;
switch (contextMenu){
case _menuMain: refreshMainMenu(); break;
case _menuSensor: refreshSensorStats(); break;
case _menuTemp: refreshTemperature(); break;
case _menuRTC: refreshRTCMenu(); break;
case _menuGPS: refreshGPSMenu(); break;
case _menuBarometer: refreshBarometerMenu(); break;
case _menuStorage: refreshStorageMenu(); break;
case _menuControl: refreshControlMenu(); break;
case _menuSoftwareInfo: refreshSoftwareInfoMenu(); break;
}
}
void changeMenu(int8_t id, int8_t subMenu){
lcd.clear();
contextMenu = id;
currentTab = 0;
currentTabOffset = 0;
for (int i = 0; i < maxChannels; i++) currentChannel[i] = 0;
channelSelectMode = -1;
subMenuEntered = subMenu;
locScrollIndex = -2;
locScrollInverted = false;
refreshMenu();
}
void refreshMainMenu(){
for (uint8_t i = 0; i < lcdRows; i++){
lcd.setCursor(0, i);
lcd.print(currentTab == currentTabOffset + i ? "> " : " ");
lcd.print(menuNames[currentTabOffset + i]);
}
}
// Sensor Menu
void refreshSensorStats(){
if (channelSelectMode == 1) canMegaFastForward = true;
int16_t percentage = map(sensorADCResult[currentChannel[0]], 0, 1023, 100, 0);
float voltageDrop = (sensorADCResult[currentChannel[0]] / 1.023) * 5;
lcd.setCursor(0, 0);
lcd.print("Soil: ");
lcd.print(percentage);
lcd.print("%");
if (percentage < 100) lcd.print(" ");
if (percentage < 10) lcd.print(" ");
lcd.print(" ");
lcd.print(voltageDrop);
lcd.print("mV");
if (voltageDrop < 1000) lcd.print(" ");
if (voltageDrop < 100) lcd.print(" ");
if (voltageDrop < 10) lcd.print(" ");
lcd.setCursor(0, 1);
if (currentTab == 0) lcd.print("> ");
if (channelSelectMode == 0) lcd.print("< ");
lcd.print("Sensor ");
lcd.print(currentChannel[0] + 1);
if (channelSelectMode == 0) lcd.print(" >");
else lcd.print(" ");
lcd.print(" ");
lcd.setCursor(0, 2);
if (currentTab == 1) lcd.print("> ");
lcd.print("Level: ");
if (channelSelectMode == 1) lcd.print("< ");
lcd.print(relayPercentages[currentChannel[0]]);
lcd.print("%");
if (channelSelectMode == 1) lcd.print(" >");
else lcd.print(" ");
lcd.print(" ");
lcd.setCursor(0, 3);
if (currentTab == 2) lcd.print("> ");
lcd.print("Go Back ");
}
// Temp Menu
void refreshTemperature(){
lcd.setCursor(0, 0);
lcd.print("Temp: ");
lcd.print(tempSensorTemperature[currentChannel[0]]);
lcd.print((char)223);
lcd.print("C ");
lcd.setCursor(0, 1);
lcd.print("Hum: ");
lcd.print(tempSensorHumidity[currentChannel[0]]);
lcd.print("% ");
lcd.setCursor(0, 2);
if (currentTab == 0) lcd.print("> ");
lcd.print("CH: ");
if (channelSelectMode == 0) lcd.print("< ");
lcd.print(tempChannels[currentChannel[0]]);
if (channelSelectMode == 0) lcd.print(" >");
else lcd.print(" ");
lcd.print(" ");
lcd.setCursor(0, 3);
if (currentTab == 1) lcd.print("> ");
lcd.print("Go Back ");
}
// RTC Menu
void refreshRTCMenu(){
if (subMenuEntered < 0){
lcd.setCursor(0, 0);
lcd.print(getDateTime());
for (uint8_t i = 1; i < lcdRows; i++){
lcd.setCursor(0, i);
lcd.print((currentTab == currentTabOffset + i - 1) ? "> " : " ");
switch (i){
case 1: lcd.print("Change Date"); break;
case 2: lcd.print("Change Time"); break;
case 3: lcd.print("Go Back "); break;
}
}
}else {
if (channelSelectMode >= 0) canMegaFastForward = true;
int16_t convertRecord[3];
convertRecord[0] = records[0];
convertRecord[1] = records[1];
convertRecord[2] = records[2];
if (subMenuEntered == 0){
convertRecord[0] += 1;
convertRecord[1] += 1;
convertRecord[2] += UnixStartYear;
}
lcd.setCursor(0, 0);
if (subMenuEntered == 0) lcd.print("Date: ");
else lcd.print("Time: ");
if (channelSelectMode == 0) lcd.print("<");
if (convertRecord[0] < 10) lcd.print("0");
lcd.print(convertRecord[0]);
if (channelSelectMode == 0) lcd.print(">");
lcd.print(subMenuEntered == 0 ? "/" : ":");
if (channelSelectMode == 1) lcd.print("<");
if (convertRecord[1] < 10) lcd.print("0");
lcd.print(convertRecord[1]);
if (channelSelectMode == 1) lcd.print(">");
lcd.print(subMenuEntered == 0 ? "/" : ":");
if (channelSelectMode == 2) lcd.print("<");
if (convertRecord[2] < 10) lcd.print("0");
lcd.print(convertRecord[2]);
if (channelSelectMode == 2) lcd.print(">");
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print(" ");
if (channelSelectMode >= 0) lcd.print(" ");
if (currentTab == 0) lcd.print("^^");
else lcd.print(" ");
lcd.print(" ");
if (currentTab == 1) lcd.print("^^");
else lcd.print(" ");
lcd.print(" ");
if (currentTab == 2) lcd.print("^^");
else lcd.print(" ");
if (currentTab == 2 && subMenuEntered == 0) lcd.print("^^");
else lcd.print(" ");
lcd.print(" ");
lcd.setCursor(0, 2);
if (currentTab == 3) lcd.print("> ");
lcd.print("Save & Go Back ");
lcd.setCursor(0, 3);
if (currentTab == 4) lcd.print("> ");
lcd.print("Go Back ");
}
}
// GPS Menu
void refreshGPSMenu(){
lcd.setCursor(0, 0);
lcd.print("Sat: ");
lcd.print(getGPSStatus());
lcd.print(memorySaves % 1000);
lcd.print(")");
lcd.setCursor(0, 1);
lcd.print(lastGPSLat, 6);
lcd.print(", ");
lcd.print(lastGPSLng, 6);
lcd.setCursor(0, 2);
lcd.print("[");
const char* city = lastLocation.city;
const char* country = lastLocation.country;
String scrollText = String(city) + ", " + String(country);
int16_t scrollTextLength = scrollText.length();
const int8_t contentWidth = lcdColumns - 2;
if (scrollTextLength <= contentWidth){
String leftPadding = "";
for (int8_t i = 0; i < ((contentWidth - scrollTextLength) / 2); i++) leftPadding += " ";
lcd.print(leftPadding + scrollText);
}else {
if (locScrollIndex >= scrollTextLength - contentWidth) locScrollInverted = true;
if (locScrollIndex <= 0) locScrollInverted = false;
locScrollIndex += locScrollInverted ? -1 : 1;
String visibleText = scrollText.substring(max(locScrollIndex, 0), max(locScrollIndex, 0) + contentWidth);
lcd.print(visibleText);
}
lcd.setCursor(19, 2);
lcd.print("]");
lcd.setCursor(0, 3);
lcd.print("> Go Back");
}
const char* getGPSStatus(){
return gpsStatus == 2 ? "CONNECTED (" : gpsStatus == 1 ? "LOADING.. (" : "NO_SIGNAL (";
}
float calculateDistance(float lat1, float lon1, float lat2, float lon2){
float dLat = radians(lat2 - lat1);
float dLon = radians(lon2 - lon1);
lat1 = radians(lat1);
lat2 = radians(lat2);
float a = sin(dLat / 2) * sin(dLat / 2) +
sin(dLon / 2) * sin(dLon / 2) * cos(lat1) * cos(lat2);
float c = 2 * atan2(sqrt(a), sqrt(1 - a));
return 6371.0 * c;
}
Location findClosestLocation(float lat, float lon){
float closestDistance = 999999.0;
Location closestLocation = defaultUnknownLocation;
for (int i = 0; i < sizeof(locations) / sizeof(locations[0]); i++){
float distance = calculateDistance(lat, lon, locations[i].latitude, locations[i].longitude);
if (distance < closestDistance && distance < maxKilometerRange){
closestDistance = distance;
closestLocation = locations[i];
}
}
return closestLocation;
}
// Barometer Menu
void refreshBarometerMenu(){
lcd.setCursor(0, 0);
lcd.print("Temp: ");
lcd.print(barometerTemp, 2);
lcd.print(" ");
lcd.print((char)223);
lcd.print("C ");
lcd.setCursor(0, 1);
lcd.print("Prs: ");
lcd.print(barometerPressure, 2);
lcd.print(" hPa ");
lcd.setCursor(0, 3);
lcd.print("> Go Back");
}
// Storage Menu
void refreshStorageMenu(){
if (channelSelectMode >= 0) canMegaFastForward = true;
lcd.setCursor(0, 0);
if (currentTab == 0) lcd.print("> ");
lcd.print("Save data every:");
if (currentTab != 0) lcd.print(" ");
lcd.setCursor(0, 1);
if (currentTab == 0) lcd.print("> ");
if (channelSelectMode == 0) lcd.print("< ");
lcd.print(eepromSDCardSaveTime);
lcd.print(" seconds");
if (channelSelectMode == 0) lcd.print(" >");
else lcd.print(" ");
lcd.print(" ");
lcd.setCursor(0, 2);
if (currentTab == 1) lcd.print("> ");
lcd.print("Save & Go Back ");
lcd.setCursor(0, 3);
if (currentTab == 2) lcd.print("> ");
lcd.print("Go Back ");
}
// Control Menu
void refreshControlMenu(){
switch (subMenuEntered){
case 0: chooseProgramMenu(); return;
case 1: programVentilationMenu(); return;
case 2: programLightsMenu(); return;
}
lcd.setCursor(0, 0);
if (currentTab == 0) lcd.print("> ");
else lcd.print(" ");
lcd.print("Vent: ");
lcd.print(channelSelectMode == 0 ? "< " : " ");
lcd.print(VentMode == CONTROL_MODE_ALWAYS_ON ? " ON " : VentMode == CONTROL_MODE_ALWAYS_OFF ? " OFF" : "PROG");
lcd.print(" ");
lcd.print(channelSelectMode == 0 ? "> " : " ");
lcd.setCursor(0, 1);
if (currentTab == 1) lcd.print("> ");
else lcd.print(" ");
lcd.print("Lights: ");
lcd.print(channelSelectMode == 1 ? "< " : " ");
lcd.print(LightsMode == CONTROL_MODE_ALWAYS_ON ? " ON " : LightsMode == CONTROL_MODE_ALWAYS_OFF ? " OFF" : "PROG");
lcd.print(" ");
lcd.print(channelSelectMode == 1 ? "> " : " ");
lcd.setCursor(0, 2);
if (currentTab == 2) lcd.print("> ");
else lcd.print(" ");
lcd.print("Enter Programming");
lcd.setCursor(0, 3);
if (currentTab == 3) lcd.print("> ");
else lcd.print(" ");
lcd.print("Go Back ");
}
void chooseProgramMenu(){
lcd.setCursor(0, 0);
if (currentTab == 0) lcd.print("> ");
lcd.print("Program Vent");
lcd.print(" ");
lcd.setCursor(0, 1);
if (currentTab == 1) lcd.print("> ");
lcd.print("Program Lights");
lcd.print(" ");
}
void programVentilationMenu(){
if (channelSelectMode >= 0) canMegaFastForward = true;
int16_t timerSubtract = VentilationTimer / 60;
lcd.setCursor(0, 0);
lcd.print("Status: ");
lcd.print(ventilationState ? "ON" : "OFF");
lcd.print(" (");
if (timerSubtract < 1000) lcd.print("0");
if (timerSubtract < 100) lcd.print("0");
if (timerSubtract < 10) lcd.print("0");
lcd.print(timerSubtract % 10000);
lcd.print(") ");
lcd.setCursor(0, 1);
if (currentTab == 0) lcd.print("> ");
lcd.print("On: ");
if (channelSelectMode == 0) lcd.print("< ");
lcd.print(eepromVentilationActiveTime);
lcd.print(" min");
if (channelSelectMode == 0) lcd.print(" >");
else lcd.print(" ");
if (eepromVentilationActiveTime < 1000) lcd.print(" ");
if (eepromVentilationActiveTime < 100) lcd.print(" ");
if (eepromVentilationActiveTime < 10) lcd.print(" ");
lcd.setCursor(0, 2);
if (currentTab == 1) lcd.print("> ");
lcd.print("Off: ");
if (channelSelectMode == 1) lcd.print("< ");
lcd.print(eepromVentilationInactiveTime);
lcd.print(" min");
if (channelSelectMode == 1) lcd.print(" >");
else lcd.print(" ");
if (eepromVentilationInactiveTime < 1000) lcd.print(" ");
if (eepromVentilationInactiveTime < 100) lcd.print(" ");
if (eepromVentilationInactiveTime < 10) lcd.print(" ");
lcd.setCursor(0, 3);
if (currentTab == 2) lcd.print("> ");
lcd.print("Go Back ");
}
void programLightsMenu(){
if (channelSelectMode >= 0) canMegaFastForward = true;
lcd.setCursor(0, 0);
lcd.print("Active Between:");
lcd.setCursor(0, 1);
lcd.print(" ");
if (channelSelectMode == 0) lcd.print("<");
if (eepromLightsStartHour < 10) lcd.print("0");
lcd.print(eepromLightsStartHour);
lcd.print(":");
if (eepromLightsStartMin < 10) lcd.print("0");
lcd.print(eepromLightsStartMin);
if (channelSelectMode == 0) lcd.print(">");
lcd.print(" - ");
if (channelSelectMode == 1) lcd.print("<");
if (eepromLightsEndHour < 10) lcd.print("0");
lcd.print(eepromLightsEndHour);
lcd.print(":");
if (eepromLightsEndMin < 10) lcd.print("0");
lcd.print(eepromLightsEndMin);
if (channelSelectMode == 1) lcd.print(">");
lcd.print(" ");
lcd.setCursor(0, 2);
lcd.print(" ");
if (channelSelectMode == 0) lcd.print(" ");
if (currentTab == 0) lcd.print("^^^^^");
else lcd.print(" ");
lcd.print(" ");
if (channelSelectMode == 1) lcd.print(" ");
if (currentTab == 1) lcd.print("^^^^^");
else lcd.print(" ");
lcd.print(" ");
lcd.setCursor(0, 3);
if (currentTab == 2) lcd.print("> ");
lcd.print("Go Back ");
}
// Software Info Menu
void refreshSoftwareInfoMenu(){
lcd.setCursor(0, 0);
lcd.print("# Software Info #");
lcd.setCursor(0, 1);
lcd.print("# Version 0.");
lcd.print(__VERSION_INFO);
lcd.print(" #");
lcd.setCursor(0, 2);
if (currentTab == 0) lcd.print("> ");
lcd.print("Restart System");
if (currentTab != 0) lcd.print(" ");
lcd.setCursor(0, 3);
if (currentTab == 1) lcd.print("> ");
lcd.print("Go Back ");
}
void restartSystem(){
__JUMP_TO_ENTRY_POINT;
}
// ======= END REGION =======
// ESP8266 Code
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ArduinoJson.h>
#include <SoftwareSerial.h>
const uint32_t ESP8266Baud = 74880;
// WiFi Access Point Credentials
const char* ssid = "catcamus";
const char* password = "greenhouse123";
ESP8266WebServer server(80);
// Server properties
IPAddress staticIP(172, 17, 0, 1);
IPAddress subnet(255, 255, 0, 0);
IPAddress gateway(172, 17, 0, 1);
// Serial communication with Mega
SoftwareSerial listener(D3, D4);
String sensorData = "";
String tempSensorData = "";
String barometerData = "";
void setup(){
Serial.begin(ESP8266Baud);
Serial.println();
WiFi.softAPConfig(staticIP, gateway, subnet);
WiFi.softAP(ssid, password);
Serial.println("Configuring access point...");
Serial.print("IP Address: ");
Serial.println(WiFi.softAPIP());
server.on("/", HTTP_GET, handleRoot);
// Handlers for all data types
server.on("/sensorData", HTTP_GET, handleSensorData);
server.on("/tempSensorData", HTTP_GET, handleTempSensorData);
server.on("/barometerData", HTTP_GET, handleBarometerData);
server.begin();
listener.begin(CommunicationBaud);
}
void loop(){
server.handleClient();
if (listener.available() > 0){
String jsonData = listener.readStringUntil('\n');
DynamicJsonDocument doc(JSONBufferLen);
DeserializationError error = deserializeJson(doc, jsonData);
if (error){
Serial.print("JSON Deserialization failed: ");
Serial.println(error.f_str());
}
if (doc.containsKey("sensors")){
JsonArray sensors = doc["sensors"];
if (sensors.size() > 0){
sensorData = "";
for (int i = 0; i < sensors.size(); i++){
JsonObject theSensor = sensors[i];
uint8_t id = theSensor["id"];
int16_t percentage = theSensor["percentage"];
float voltage = theSensor["voltage"];
sensorData += "Sensor ID: ";
sensorData += id;
sensorData += ", Percentage: ";
sensorData += percentage;
sensorData += "%, Voltage: ";
sensorData += voltage;
sensorData += "mV\n";
}
}
}else if (doc.containsKey("tempsensors")){
JsonArray tempsensors = doc["tempsensors"];
if (tempsensors.size() > 0){
tempSensorData = "";
for (int i = 0; i < tempsensors.size(); i++){
JsonObject theTempSensor = tempsensors[i];
const char* channel = theTempSensor["channel"];
float temperature = theTempSensor["temperature"];
float humidity = theTempSensor["humidity"];
tempSensorData += "Channel: ";
tempSensorData += channel;
tempSensorData += ", Temperature: ";
tempSensorData += temperature;
tempSensorData += "C, Humidity: ";
tempSensorData += humidity;
tempSensorData += "%\n";
}
}
}else if (doc.containsKey("barometer")){
JsonArray barometer = doc["barometer"];
if (barometer.size() > 0){
barometerData = "";
for (int i = 0; i < barometer.size(); i++){
JsonObject theBarometer = barometer[i];
double temp = theBarometer["temp"];
double pressure = theBarometer["pressure"];
barometerData += "Temperature: ";
barometerData += temp;
barometerData += "C, Pressure: ";
barometerData += pressure;
barometerData += "hPa\n";
}
}
}
}
}
void handleRoot(){
String html = "<html><head><title>Arduino Greenhouse</title><style>";
html += "body { font-family: Arial, sans-serif; }";
html += "#sensor, #tempSensor, #barometer { font-size: 24px; font-weight: bold; color: #333; }";
html += "</style><script>";
html += "function updateData(){";
html += "fetch('/sensorData').then(response => response.text()).then(data => {";
html += " document.getElementById('sensor').innerHTML = '<pre>' + data + '</pre>';";
html += "});";
html += "fetch('/tempSensorData').then(response => response.text()).then(data => {";
html += " document.getElementById('tempSensor').innerHTML = '<pre>' + data + '</pre>';";
html += "});";
html += "fetch('/barometerData').then(response => response.text()).then(data => {";
html += " document.getElementById('barometer').innerHTML = '<pre>' + data + '</pre>';";
html += "});";
html += "}";
html += "setInterval(updateData, 1000);";
html += "</script></head><body>";
html += "<h1>Arduino Greenhouse v0.";
html += __VERSION_INFO;
html += "</h1>";
html += "<h2>Soil Sensors</h2>";
html += "<div id='sensor'><pre>n/A</pre></div><br>";
html += "<h2>Temperature Sensors</h2>";
html += "<div id='tempSensor'><pre>n/A</pre></div><br>";
html += "<h2>Barometer Data</h2>";
html += "<div id='barometer'><pre>n/A</pre></div><br>";
html += "</body></html>";
server.send(200, "text/html", html);
}
void handleSensorData(){
server.send(200, "text/plain", sensorData);
}
void handleTempSensorData(){
server.send(200, "text/plain", tempSensorData);
}
void handleBarometerData(){
server.send(200, "text/plain", barometerData);
}
#else
// Compile fallback Blink
void setup(){
pinMode(LED_BUILTIN, OUTPUT);
}
void loop(){
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
}
#endif