Problem with millis countdown timer

Hi everyone,

I'm running into a problem I think has to do with the way I'm trying to store the current value of millis() in an array. It might also be something completely different I'm overlooking.

I have something with a lot of timers, working off stored EEPROM values. Some timers are simple on-off and some timers have a starting time and duration. I tried to write one function that does both (in hindsight maybe I should have just written 2). It get's complex because of the number of different timers I'm trying to execute. When I call the function to execute a timed event I'm trying to give it enough data to be unique so different timers won't interfere with each other. One of these things is when the timer is started it will store the current millis() into countdown[timerNumber]. Later it's supposed to reset the function after a certain delay using (millis() - countdown[timerNumber] > resetDelay)

This seems not to be working correctly. The reset seems to happen almost instantly even though int resetDelay = 90000;
Is this a problem caused by countdown[timerNumber] or somehow by resetDelay?

here is the function:


void switchTimer(int relayNumber, int timeOne, int timeTwo, int seconds, bool secondsOrNot, int timerNumber) { 
  //if secondsOrNot is true then timeOne is hours and timeTwo is minutes. If !secondsOrNot: timeOne is ON time and timeTwo is OFF time  
  //seconds, secondsOrNot and timerNumber have default values and don't have to be declared
  
  if (!secondsOrNot) {
    if (currentTime >= timeOne && currentTime < timeTwo) {
      if (digitalRead(relay[relayNumber]) == LOW) {   
        digitalWrite(relay[relayNumber], HIGH);
        outputDebug("Timer ");
        outputDebug(relayNumber); 
        outputDebugln(" on");
      } 
    }
    if (currentTime < timeOne || currentTime >= timeTwo) {
      if (digitalRead(relay[relayNumber]) == HIGH) {   
        digitalWrite(relay[relayNumber], LOW); 
        outputDebug("Timer ");
        outputDebug(relayNumber); 
        outputDebugln(" off");
      } 
    }     
  }
  
  if (secondsOrNot) {
    if (currentHour == timeOne && currentMinute == timeTwo && hasNotRunYet == true) {
      
      digitalWrite(relay[relayNumber], HIGH);
      countdown[timerNumber] = millis(); 
      hasNotRunYet = false; //flag prevents being activated more than once
      timerActive[timerNumber] = true;

      outputDebug("relay ");
      outputDebug(relayNumber); 
      outputDebug(" on with ");
      outputDebug(seconds); 
      outputDebugln(" seconds countdown");      
    }    

    if (timerActive[timerNumber] == true && digitalRead(relay[relayNumber]) == HIGH) {       
      if (millis() - countdown[timerNumber] > (seconds * 1000)) { //seconds to milliseconds 

        digitalWrite(relay[relayNumber], LOW);
        timerActive[timerNumber] = false;  

        outputDebug("relay ");
        outputDebug(relayNumber); 
        outputDebugln(" off");
      }
    }   

    if (hasNotRunYet == false) { 
      if (millis() - countdown[timerNumber] > resetDelay) { 
        hasNotRunYet = true;  
      
        outputDebug("hasNotYetRun reset");    
      }
    }

  } 

}

Does not compile.

Alright, if you want the complete code:

/*
  Sketch generated by the Arduino IoT Cloud Thing "Giga"
  https://create.arduino.cc/cloud/things/1170c289-7b0d-4273-8557-9c30106bd3cc

  Arduino IoT Cloud Variables description

  The following variables are automatically generated and updated when changes are made to the Thing

  float airTempAverage;
  float humidityAverage;
  float pressureAverage;

  Variables which are marked as READ/WRITE in the Cloud Thing will also have functions
  which are called when their values are changed from the Dashboard.
  These functions are generated with the Thing and added at the end of this sketch.
*/
#include "arduino_secrets.h"
#include "thingProperties.h"
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <SPI.h>
#include <Wire.h>
#include <SHT31.h>
#include <DHT.h>
#include <Bme280.h>
#include <WiFi.h>
#include <WiFiUdp.h>
#include <mbed_mktime.h>
#include <Timezone.h>
#include <NTPClient.h>
#include <I2C_eeprom.h>
#include <EMailSender.h>

#define DEBUG 1

#if DEBUG == 1
#define outputDebug(x); Serial.print(x);
#define outputDebugln(x); Serial.println(x);
#else
#define outputDebug(x); 
#define outputDebugln(x); 
#endif

//EEPROM
#define eepromLength 32768
I2C_eeprom EEPROM(0x50, eepromLength);
#define desiredTemp 70 //EEPROM address for settings configurable by user
#define desiredHumi 71
#define dayTemperature 64
#define nightTemperature 65

//TFT screen
#define TFT_CS        10
#define TFT_RST       9 // Or set to -1 and connect to Arduino RESET pin
#define TFT_DC        8
#define TFT_MOSI      7  // Data out --- SDA pin on TFT
#define TFT_SCLK      6  // Clock out --- SCL pin on TFT
Adafruit_ST7789 myScreen = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);

//sensors
#define DHT22_PIN 11
#define DHT22_PIN_2 12
#define DHTTYPE DHT22
#define ThermistorPin A11
#define Thermistor2Pin A10
DHT dht1(DHT22_PIN, DHTTYPE);
DHT dht2(DHT22_PIN_2, DHTTYPE);
SHT31 sht1(0x44);
SHT31 sht2(0x45);
Bme280TwoWire bme1;
Bme280TwoWire bme2;
int Vo, Vo2;
float R1 = 10000;
float logR2, logR3, R2, R3;
float c1 = 1.009249522e-03, c2 = 2.378405444e-04, c3 = 2.019202697e-07;
float temp1, temp2, temp3, temp4, temp5, temp6, hum1, hum2, hum3, hum4, hum5, hum6, pres1, pres2, waterTemp1, waterTemp2, waterTemp_1, waterTemp_2;
//averages for main menu/dashboard
float waterTempAverage;
bool updateDisplay;

//Variables for auto scroll
unsigned long previousMillis;
unsigned long previousMillis2;
unsigned long readTime;

//menu variables
#define ROOT_MENU_CNT 4
#define SUB_MENU1_CNT 1
#define SUB_MENU2_CNT 8
#define SUB_MENU3_CNT 5
#define SUB_MENU4_CNT 5

//setup the emum with all the menu pages options
enum pageType {ROOT_MENU, SUB_MENU1, SUB_MENU2, SUB_MENU3, SUB_MENU4, SUB_MENU3_ITEM1, SUB_MENU3_ITEM2, SUB_MENU3_ITEM3, SUB_MENU3_ITEM4, SUB_MENU3_ITEM5, SUB_MENU4_ITEM1, SUB_MENU4_ITEM2, SUB_MENU4_ITEM3, SUB_MENU4_ITEM4, SUB_MENU4_ITEM5, SCREEN_SAVER};
enum pageType currPage = ROOT_MENU;
enum pageType prevPage = SUB_MENU4_ITEM1;
//screen names for sub pages
char *subPage3Items[] = {"LIGHTS", "RAIN", "BACKWALL DRIP", "FOGGER", "AIR PUMP"};
char *subPage4Items[] = {"SET TEMPERATURE", "SET HUMIDITY", "SET LAND TEMPERATURE", "SET RAINSTORM", "RESET CRASH COUNTER"};
//selected item pointer for the root menu
uint8_t root_Pos = 1;
uint8_t rel_Pos = 1;  // selected item pointers for the relay menu
//selected item pointers for the sub menu's
uint8_t sub_Pos_3 = 1; //for menu 3
uint8_t sub_Pos_3_1 = 1; //for menu 3 sub item light timer
uint8_t sub_Pos_3_2 = 1; //for menu 3 sub item rain timers
uint8_t sub_Pos_3_3 = 1; //for menu 3 sub item drip timer
uint8_t sub_Pos_3_4 = 1; //for menu 3 sub item fogger
uint8_t sub_Pos_3_5 = 1; //for menu 3 sub item air ump
uint8_t numberOfTimers = 5; //for menu 3
uint8_t sub_Pos_4 = 1; //for menu 4
uint8_t sub_Pos_4_1 = 1; //set temperature
uint8_t sub_Pos_4_2 = 1; //set humidity
uint8_t sub_Pos_4_3 = 1; //set land temperature
uint8_t sub_Pos_4_4 = 1; //set rainstorm .... fuck it, seperate pointers for EVERYTHING!
uint8_t sub_Pos_4_5 = 1;

//joystick pins
#define xPin A0
#define yPin A1
#define kPin A2
//-----------
#define Neutral 0
#define Press 1
#define Up 2
#define Down 3
#define Right 4
#define Left 5
//-----------
#define highSwitchPoint 860 // mid position is around 700. top is 1023 and bottom is 0; //if its 1432 instead of 1023, like with the temperature calculation this changes.
#define lowSwitchPoint 350 // 860 about halfway between 700 and 1023 and 350 mid between 0 and 700

//relay block(s)
#define numberOfModules 1
//relay state array
#if numberOfModules == 2
#define numberOfRelays 16
int relay[numberOfRelays] = {23, 25, 27, 29, 31, 33, 35, 37, 46, 47, 48, 49, 50, 51, 52, 53};
#else 
#define numberOfRelays 8
int relay[numberOfRelays] = {23, 25, 27, 29, 31, 33, 35, 37};
#endif

//NTP and RTC 
WiFiUDP Udp; // A UDP instance to let us send and receive packets over UDP
NTPClient ntpClient(Udp, "nl.pool.ntp.org", 0, 1000);
unsigned long lastUpdate = 86400000; //24h
unsigned long printNow;
bool setRTC = false;
TimeChangeRule mySTD = {"EST", Last, Sun, Nov, 2, 60};     // Standard time = UTC +1 hours
TimeChangeRule myDST = {"EDT", Last, Sun, Mar, 2, 120};    // Daylight saving time = UTC +2 hours
Timezone myTZ(myDST, mySTD);
TimeChangeRule *tcr;

int status = WL_IDLE_STATUS;

//climate control variables
#define pwmPin_1 2
#define pwmPin_2 3
#define pwmPin_3 4
#define pwmPin_4 5
const uint16_t sequenceInterval[] {2000, 10000, 30000, 30000}; //credit noiasca  https://forum.arduino.cc/index.php?topic=666044
bool hold = false;
unsigned long startMillis = 0;    
unsigned long previousMillis_2;
unsigned long lightOn; 
const unsigned int pollInterval = 10000; // 10 seconds check time for climateControl
int pwmVal1, pwmVal2;
int pwm1Speed, pwm2Speed;
bool climateControlPause = false;
unsigned long pauseStart = 0;
int fanPauseTime = 120000; // 2 minutes pause after sprinklers have been used
int resetDelay = 90000;
bool noRain = false;
bool humidityMonitoring = true;
enum ccState {CASE0, CASE1, CASE2, CASE3, CASE4, CASE5, CASE6, CASE7, CASE8, CASE9, CASE10, CASE11, CASE12};
enum ccState climateState = CASE0;
enum ccState previousClimateState = CASE5;

//set time function
enum ChangeTimeState {CHANGING_HOUR, CHANGING_MINUTE, SAVING_COMPLETE};

//time switching variables
unsigned int currentHour = 0;
unsigned int currentMinute = 0;
unsigned int currentTime = 0;
unsigned long wifiTime = 0;  
unsigned long checkTime = 200; 
unsigned long adjustmentTime = 400; 
unsigned long screenTime = 600; 
unsigned int multipliedTime = 0;
bool dayTime = false;
bool hasNotRunYet = true;
#define numberOfTimers 5
unsigned long countdown[numberOfTimers];
bool timerActive[numberOfTimers] = {false};

//prototype for default values of optional half
void switchTimer(int relayNumber, int timeOne, int timeTwo, int seconds = 0, bool secondsOrNot = false, int timerNumber = 0); 

//updateStatistic variables
#define totalReadings 14
const int maxCount = 120;  
float airDeviation = 5.0; //degress C
float humidityDeviation = 20.0; //% RH
float reading[totalReadings];
float sensorValue[totalReadings];
unsigned int sensor[totalReadings];
bool sensorFault[totalReadings];
char msg[10];

//for alarm e-mails
EMailSender emailSend(SECRET_EMAIL, SECRET_GMAIL_PASS);
bool emailSent = false;

//Bluetooth = HC-05 ID and pass = APC / 3511
#define btState 13
bool BTconnected = false;
bool relayState[8];
const byte numChars = 10;
char receivedChars[numChars];
char tempChars[numChars];  // temporary array for use when parsing
unsigned int eepromAddress = 0;
unsigned int newSetting = 0;
bool newData = false;
bool dataToParse = false;
bool TimeToSet = false;
unsigned int relayNumber = 0;

//===========================================================================================================================
//                                                  SETUP 
//===========================================================================================================================
void setup() {
  Serial.begin(115200);
  Serial1.begin(38400);
  delay(1000);  

  //screen
  myScreen.init(240, 320);
  myScreen.fillScreen(ST77XX_BLACK);
  Serial.println("TFT Initialized");
  myScreen.setRotation(1);
  myScreen.setCursor(0, 0);
  myScreen.setTextSize(3);
  myScreen.setTextWrap(false);
  myScreen.setTextColor(ST77XX_GREEN);
  myScreen.println("Arne's");
  myScreen.println("Paludarium");
  myScreen.println("Controller");
  myScreen.println("0.7.6");    
  myScreen.println();
  myScreen.println("Connecting . . .");    
  myScreen.setTextSize(2);
  
  while (status != WL_CONNECTED) { // attempt to connect to WiFi network:
    Serial.print("Attempting to connect to: ");
    Serial.println(SSID);
    status = WiFi.begin(SSID, PASS);
    delay(10000);
  }

  myScreen.fillScreen(ST77XX_BLACK);
  
  initProperties(); // Defined in thingProperties.h  
  ArduinoCloud.begin(ArduinoIoTPreferredConnection); // Connect to Arduino IoT Cloud
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();  

  //joystick
  pinMode(xPin, INPUT);
  pinMode(yPin, INPUT);
  pinMode(kPin, INPUT_PULLUP);

  //Bluetooth state
  pinMode(btState, INPUT);

  //assign relay pins as output
  for (int i = 0; i < numberOfRelays; i++) {
  pinMode(relay[i], OUTPUT); }

  // Initialize countdown array
  for (int i = 0; i < numberOfTimers; i++) {
    countdown[i] = 0;
  }

  //variable fans
  pinMode(pwmPin_1, OUTPUT);
  pinMode(pwmPin_2, OUTPUT);
  pinMode(pwmPin_3, OUTPUT);
  pinMode(pwmPin_4, OUTPUT);

  //LED indicators
  pinMode(LEDR, OUTPUT);
  pinMode(LEDB, OUTPUT);
  digitalWrite(LEDR, LOW);
  digitalWrite(LEDB, HIGH);

  //sensors
  Wire.begin();
  dht1.begin();
  dht2.begin();
  sht1.begin();
  sht2.begin();
  bme1.begin(Bme280TwoWireAddress::Primary);
  bme2.begin(Bme280TwoWireAddress::Secondary);
  
  ntpClient.begin();  

  //crash counter
  int crashCounter = EEPROM.readByte(100);
  EEPROM.writeByte(100, crashCounter + 1);

}

//===========================================================================================================================
//                                                  MAIN LOOP 
//===========================================================================================================================
void loop() {

  ArduinoCloud.update();

  if (millis() - wifiTime >= 1000) { //wifi and cloud status led and RTC

    if (status != WL_CONNECTED) {
      digitalWrite(LEDB, HIGH);
      digitalWrite(LEDR, LOW);
    }
    if (status = WL_CONNECTED) {

      if (!ArduinoCloud.connected()) {
        digitalWrite(LEDR, HIGH);
        digitalWrite(LEDB, HIGH);
      }
      if (ArduinoCloud.connected()) {
        digitalWrite(LEDR, HIGH);
        digitalWrite(LEDB, LOW);
      }
      if (setRTC == false) {  // Set RTC once on startup
        updateTime();
      }
      if (millis() - lastUpdate >= (60 * 60 * 24 * 1000)) {  // Update RTC every 24hrs
        setRTC = false;
        lastUpdate = millis();
        emailSent = false; //piggyback reset for e-mail notifications
      }
    }
    wifiTime = millis();
  }
    
  if (millis() - adjustmentTime >= 1000) {  //adjust fan speeds every second
    climateChange();
    adjustmentTime = millis();
  }
    
  if (millis() - screenTime >= 100) { //don't run continuously. Fuzz in checkJoystick() readings will never be coninuous 0
    autoGoBackToHomePage();
    screenSaver();
    activateScreen();
    screenTime = millis();
  }
    
  if (millis() - checkTime >= 900) {  //run automatedswitching every x seconds
    automatedSwitching();
    checkTime = millis();
  }

  if (millis() - readTime >= 2000) {  //poll all sensors every 2 seconds
    updateStatistics();
    updateBluetooth();
    if (currPage == ROOT_MENU) {
      printAverages();
      printRelays();
    }
    if (currPage == SUB_MENU1) {
      printStatistics();
    }
    readTime = millis();
  }

  if (millis() - printNow >= 60000 && currPage != SCREEN_SAVER) {  //print time on screen every minute
    printTime();
    printNow = millis();
  }

  if (millis() - startMillis >= pollInterval) {  //if a pre programmed event runs, pause climateControl
    if (climateControlPause == false) {
      climateControl();
    }
    startMillis = millis();
  }

  if (digitalRead(relay[0])) {  //adjust climateControl for day and night
    dayTime = true;
  } else if (!digitalRead(relay[0])) {
    dayTime = false;
  }

  checkJoystick();
  navigationFunctioner();
  changePage();
  fanPause();

  bluetooth();
  if (newData == true) { //receiving bluetooth data
    strcpy(tempChars, receivedChars); // this temporary copy is necessary to protect the original data because strtok() used in parseData() replaces the commas with \0       
    if (dataToParse) {
      parseData();
    } else {
      switchRelay();
    }
    newData = false;
  }

}

//===========================================================================================================================
//                                                  CHECK PAGE 
//===========================================================================================================================
void changePage() {
  if (currPage != prevPage){
    switch (currPage){
      case ROOT_MENU:     page_RootMenu(); break;
      case SUB_MENU1:     page_SubMenu1(); break;
      case SUB_MENU2:     page_SubMenu2(); break;
      case SUB_MENU3:     page_SubMenu3(); break;
      case SUB_MENU4:     page_SubMenu4(); break;
      //sub menu 3
      case SUB_MENU3_ITEM1:     page_SubMenu3_Item(0); break;
      case SUB_MENU3_ITEM2:     page_SubMenu3_Item(1); break;
      case SUB_MENU3_ITEM3:     page_SubMenu3_Item(2); break;
      case SUB_MENU3_ITEM4:     page_SubMenu3_Item(3); break;
      case SUB_MENU3_ITEM5:     page_SubMenu3_Item(4); break;
      //sub menu 4
      case SUB_MENU4_ITEM1:     page_SubMenu4_Item(0); break;
      case SUB_MENU4_ITEM2:     page_SubMenu4_Item(1); break;
      case SUB_MENU4_ITEM3:     page_SubMenu4_Item(2); break;
      case SUB_MENU4_ITEM4:     page_SubMenu4_Item(3); break;
      case SUB_MENU4_ITEM5:     page_SubMenu4_Item(4); break;
      case SCREEN_SAVER:        page_ScreenSaver();    break;
    }
    //update first time
    updateDisplay = true;
    prevPage = currPage;
  }
}

//===========================================================================================================================
//                                                  CHECK JOYSTICK POSITION 
//===========================================================================================================================
int checkJoystick() {
  int joystickXPin = analogRead(xPin);
  int joystickYPin = analogRead(yPin);
  int joystickSW = analogRead(kPin);

  /* inverted:  
  if (joystickXPin < lowSwitchPoint)  return Right;
  if (joystickXPin > highSwitchPoint) return Left;
  if (joystickYPin < lowSwitchPoint)  return Down;
  if (joystickYPin > highSwitchPoint) return Up;
  */
  if (joystickXPin > highSwitchPoint) return Right;
  if (joystickXPin < lowSwitchPoint)  return Left;
  if (joystickYPin > highSwitchPoint) return Down;
  if (joystickYPin < lowSwitchPoint)  return Up;
  
  if (!joystickSW) return Press;
  return Neutral;
}

//===========================================================================================================================
//                                                  CHECK SYSTEM STATS
//===========================================================================================================================
void updateStatistics() {
  
  sht1.read();
  sht2.read();

  reading[0] = dht1.readTemperature();
  reading[1] = dht2.readTemperature();
  reading[2] = sht1.getTemperature();
  reading[3] = sht2.getTemperature();
  reading[4] = bme1.getTemperature();
  reading[5] = bme2.getTemperature();

  reading[6] = dht1.readHumidity();
  reading[7] = dht2.readHumidity();
  reading[8] = sht1.getHumidity();
  reading[9] = sht2.getHumidity();
  reading[10] = bme1.getHumidity();
  reading[11] = bme2.getHumidity();

  reading[12] = bme1.getPressure() / 100;
  reading[13] = bme2.getPressure() / 100;

  // Check sensor readings and update sensor status
  for (int i = 0; i < totalReadings; i++) {
    if (!isnan(reading[i])) {
      sensorValue[i] = reading[i];
      sensor[i] = 0; //reset NAN counter
    } else {
      if (sensor [i] < maxCount) { //if NAN counter +
        sensor[i]++;        
      }
    }

    if (sensor[i] == maxCount) { //if x consecutive readings fail, trigger the alarm
      emailSent = false;
      alarm(i, " DEFECT.");
    }
  }
  
  temp1 = sensorValue[0];
  temp2 = sensorValue[1];
  temp3 = sensorValue[2];
  temp4 = sensorValue[3];
  temp5 = sensorValue[4];
  temp6 = sensorValue[5];
  hum1 = sensorValue[6];
  hum2 = sensorValue[7];
  hum3 = sensorValue[8];
  hum4 = sensorValue[9];
  hum5 = sensorValue[10];
  hum6 = sensorValue[11];
  pres1 = sensorValue[12];
  pres2 = sensorValue[13];

  airTempAverage = (temp1 + temp2 + temp3 + temp4 + temp5 + temp6) / 6;
  humidityAverage = (hum1 + hum2 + hum3 + hum4 + hum5 + hum6) / 6;  
  pressureAverage = (pres1 + pres2) / 2;

  // Check for significant deviations in sensor readings
  for (int i = 0; i < 5; i++) { //1-6 is temperature
    sensorFault[i] = false;
    if ((sensorValue[i] > airTempAverage + airDeviation) || (sensorValue[i] < airTempAverage - airDeviation)) {
      alarm(i, " Deviation.");
      sensorFault[i] = true;
    }
  }
  
  for (int i = 6; i < 12; i++) {  //7-12 is humidity
    sensorFault[i] = false;
    if ((sensorValue[i] > humidityAverage + humidityDeviation) || (sensorValue[i] < humidityAverage - humidityDeviation)) {
      alarm(i, " Deviation.");
      sensorFault[i] = true;
    }
  }

  //read water temperature1
  Vo = analogRead(ThermistorPin);
  R2 = R1 * (1432.0 / (float)Vo - 1.0); //1432 used to be 1023, representing maximum binary value of 10 bit. For some reason the Giga R1 is different. I don't understand why.
  logR2 = log(R2);
  waterTemp_1 = (1.0 / (c1 + c2 * logR2 + c3 * logR2 * logR2 * logR2));
  waterTemp1 = waterTemp_1 - 273.15; // to Celsius
  //read water temperature2
  Vo2 = analogRead(Thermistor2Pin);
  R3 = R1 * (1432.0 / (float)Vo2 - 1.0);
  logR3 = log(R3);
  waterTemp_2 = (1.0 / (c1 + c2 * logR3 + c3 * logR3 * logR3 * logR3));
  waterTemp2 = waterTemp_2 - 273.15;
  //WaterTemp Average
  waterTempAverage = (waterTemp1 + waterTemp2) / 2;

}

void alarm(int x, char *msg) {

  outputDebug("Sensor reading ");
  outputDebug(x);
  outputDebugln(msg);

  String sensorName;

  if (x = 0 || 6) { sensorName = "DHT22-1. "; }
  if (x = 1 || 7) { sensorName = "DHT22-2. "; }
  if (x = 2 || 8) { sensorName = "SHT31-1. "; }
  if (x = 3 || 9) { sensorName = "SHT31-2. "; }
  if (x = 4 || 10 || 12) { sensorName = "BME280-1. "; }
  if (x = 5 || 11 || 13) { sensorName = "BME280-2. "; }

  String emailMessage = "Malfunction in sensor ";
  emailMessage += sensorName; 
  emailMessage += String(msg);
  
  /*
  if (!emailSent) {
    EMailSender::EMailMessage message;
    message.subject = "APC alarm";
    message.message = emailMessage;

    EMailSender::Response resp = emailSend.send("arnekolman@gmail.com", message);
    
    outputDebugln("Sending status: ");
    outputDebugln(resp.status);
    outputDebugln(resp.code);
    outputDebugln(resp.desc);

    emailSent = true;
  }
  */
}

//===========================================================================================================================
//                                                     BLUETOOTH
//===========================================================================================================================
void bluetooth() {

  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '<';
  char endMarker = '>';
  char rc;

  if (Serial1.available() > 0 && newData == false) {
    rc = Serial1.read();

    if (recvInProgress == true) {
      if (rc != endMarker) {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx >= 4 && ndx < 9) {
          dataToParse = true;
        }
        if (ndx >= 9) {
          TimeToSet = true;
        }
        if (ndx >= numChars) {
          ndx = numChars - 1;
        }
      } else {
        receivedChars[ndx] = '\0';  // terminate the string
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }

    else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
}

void parseData() {  // split the data into its parts

  char* strtokIndx;  // this is used by strtok() as an index

  strtokIndx = strtok(tempChars, ",");  // get the first part
  eepromAddress = atoi(strtokIndx); // convert this part to an integer
  
  strtokIndx = strtok(NULL, ",");  // this continues where the previous call left off
  newSetting = atoi(strtokIndx);

  EEPROM.writeByte(eepromAddress, newSetting);
  dataToParse = false;

  outputDebug("EEPROM address ");
  outputDebugln(eepromAddress);
  outputDebug("New value ");
  outputDebugln(newSetting);
}

void switchRelay() {

  relayNumber = atoi(receivedChars);
  digitalWrite(relay[relayNumber], !digitalRead(relay[relayNumber]));
  
  outputDebug("Switching relay ");
  outputDebugln(relayNumber);

}

void updateBluetooth() {

  if (digitalRead(btState) == HIGH) {
    BTconnected = true;
  } else {
    BTconnected = false; 
  }

  for (int i = 0; i < numberOfRelays; i++) {
  relayState[i] = digitalRead(relay[i]); }
  
  if (BTconnected) {
    outputDebugln("Bluetooth Connected");
    Serial1.print("t1:"); Serial1.println(temp1, 1); 
    Serial1.print("t2:"); Serial1.println(temp2, 1); 
    Serial1.print("t3:"); Serial1.println(temp3, 1);
    Serial1.print("t4:"); Serial1.println(temp4, 1); 
    Serial1.print("t5:"); Serial1.println(temp5, 1); 
    Serial1.print("t6:"); Serial1.println(temp6, 1); 
    
    Serial1.print("h1:"); Serial1.println(hum1, 1); 
    Serial1.print("h2:"); Serial1.println(hum2, 1); 
    Serial1.print("h3:"); Serial1.println(hum3, 1); 
    Serial1.print("h4:"); Serial1.println(hum4, 1); 
    Serial1.print("h5:"); Serial1.println(hum5, 1); 
    Serial1.print("h6:"); Serial1.println(hum6, 1); 
    
    Serial1.print("ata:"); Serial1.println(airTempAverage, 1); 
    Serial1.print("ha:");  Serial1.println(humidityAverage, 1); 
    Serial1.print("pa:");  Serial1.println(pressureAverage, 1); 
    Serial1.print("wta:"); Serial1.println(waterTempAverage, 1); 
    
    Serial1.print("r0:"); Serial1.println(relayState[0]); 
    Serial1.print("r1:"); Serial1.println(relayState[1]); 
    Serial1.print("r2:"); Serial1.println(relayState[2]); 
    Serial1.print("r3:"); Serial1.println(relayState[3]); 
    Serial1.print("r4:"); Serial1.println(relayState[4]); 
    Serial1.print("r5:"); Serial1.println(relayState[5]); 
    Serial1.print("r6:"); Serial1.println(relayState[6]); 
    Serial1.print("r7:"); Serial1.println(relayState[7]); 

    Serial1.print("desT:"); Serial1.println(EEPROM.readByte(desiredTemp)); //value stored in EEPROM address
    Serial1.print("desH:"); Serial1.println(EEPROM.readByte(desiredHumi)); 
    Serial1.print("dT:");   Serial1.println(EEPROM.readByte(dayTemperature)); 
    Serial1.print("nT:");   Serial1.println(EEPROM.readByte(nightTemperature)); 
    Serial1.print("EEdesT:"); Serial1.println(desiredTemp); // EEPROM address to read or write
    Serial1.print("EEdesH:"); Serial1.println(desiredHumi); 
    Serial1.print("EEdT:");   Serial1.println(dayTemperature); 
    Serial1.print("EEnT:");   Serial1.println(nightTemperature); 
    
    Serial1.print("clock:");  Serial1.println(99); 
    Serial1.print("LTON:");   Serial1.println(0); 
    Serial1.print("LTOF:");   Serial1.println(2); 
  }

}

//===========================================================================================================================
//                                                  PAGE - ROOT MENU
//===========================================================================================================================
void page_RootMenu() {

  //clear screen
  myScreen.fillScreen(ST77XX_BLACK);
  //print title
  myScreen.setCursor(0, 0);
  myScreen.println(F("MAIN MENU"));
  myScreen.drawLine(0, 15, 320, 15, ST77XX_GREEN);
  myScreen.drawLine(0, 16, 320, 16, ST77XX_GREEN);
  myScreen.drawLine(0, 17, 320, 17, ST77XX_GREEN);
  //print framework
  myScreen.setTextSize(1);
  myScreen.setCursor(10, 30);
  myScreen.print(F("Average"));
  myScreen.setCursor(10, 40);
  myScreen.print(F("Temperature"));
  myScreen.setCursor(100, 30);
  myScreen.print(F("Average"));
  myScreen.setCursor(100, 40);
  myScreen.print(F("Humidity"));
  myScreen.setCursor(180, 30);
  myScreen.print(F("Average"));
  myScreen.setCursor(180, 40);
  myScreen.print(F("Pressure"));
  myScreen.setTextSize(2);

  myScreen.setCursor(200, 80);
  myScreen.print(F("PWM1"));
  
  myScreen.setTextSize(1);
  myScreen.setCursor(10, 200); myScreen.print(F("lights"));
  myScreen.setCursor(50, 220); myScreen.print(F("ventilation"));
  myScreen.setCursor(90, 200); myScreen.print(F("sprinklers"));
  myScreen.setCursor(130, 220); myScreen.print(F("irrigation"));
  myScreen.setCursor(170, 200); myScreen.print(F("fogger"));
  myScreen.setCursor(210, 220); myScreen.print(F("land heat"));
  myScreen.setCursor(250, 200); myScreen.print(F("window heat"));
  myScreen.setCursor(290, 220); myScreen.print(F("airpump"));
  myScreen.setTextSize(2);

  printTime();

  //rest in handled in navigationFunctioner();

}

void page_ScreenSaver() {

  //clear screen
  myScreen.fillScreen(ST77XX_BLACK);
  updateDisplay = false;
}
//=======================================================================================
//                                              PRINT AVERAGE STATS
//=======================================================================================
void printAverages() {

  myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);

  //framework is created in ROOT_MENU
  //update readings on timer from updateStatistics()
  myScreen.setCursor(10, 50);  
  myScreen.print(airTempAverage, 1);
  myScreen.print(F("\367")); 

  myScreen.setCursor(100, 50);  
  myScreen.print(humidityAverage, 1);
  myScreen.print(F("%")); 

  myScreen.setCursor(180, 50);  
  myScreen.print(pressureAverage, 1);
  myScreen.print(F(" hPa")); 

  //print PWM speed  
  myScreen.setCursor(255, 80);  
  myScreen.print(pwm1Speed);
  myScreen.print(F("%")); 

}

//=======================================================================================
//                                              PRINT RELAY STATES
//=======================================================================================
void printRelays() {   

  if (digitalRead(relay[0]) == LOW) 
  { myScreen.setTextColor(ST77XX_RED, ST77XX_BLACK); } 
  else { myScreen.setTextColor(ST77XX_GREEN, ST77XX_BLACK); }     
    myScreen.setCursor(10, 180); myScreen.print(F("R0"));
  
  if (digitalRead(relay[1]) == LOW) 
  { myScreen.setTextColor(ST77XX_RED, ST77XX_BLACK); } 
  else { myScreen.setTextColor(ST77XX_GREEN, ST77XX_BLACK); }  
    myScreen.setCursor(50, 180); myScreen.print(F("R1"));    
  
  if (digitalRead(relay[2]) == LOW) 
  { myScreen.setTextColor(ST77XX_RED, ST77XX_BLACK); } 
  else { myScreen.setTextColor(ST77XX_GREEN, ST77XX_BLACK); } 
    myScreen.setCursor(90, 180); myScreen.print(F("R2")); 
      
  if (digitalRead(relay[3]) == LOW) 
  { myScreen.setTextColor(ST77XX_RED, ST77XX_BLACK); } 
  else { myScreen.setTextColor(ST77XX_GREEN, ST77XX_BLACK); }
    myScreen.setCursor(130, 180); myScreen.print(F("R3")); 
      
  if (digitalRead(relay[4]) == LOW) 
  { myScreen.setTextColor(ST77XX_RED, ST77XX_BLACK); } 
    else { myScreen.setTextColor(ST77XX_GREEN, ST77XX_BLACK); } 
    myScreen.setCursor(170, 180); myScreen.print(F("R4")); 
    
  if (digitalRead(relay[5]) == LOW) 
  { myScreen.setTextColor(ST77XX_RED, ST77XX_BLACK); } 
  else { myScreen.setTextColor(ST77XX_GREEN, ST77XX_BLACK); } 
    myScreen.setCursor(210, 180); myScreen.print(F("R5"));
      
  if (digitalRead(relay[6]) == LOW) 
  { myScreen.setTextColor(ST77XX_RED, ST77XX_BLACK); } 
  else { myScreen.setTextColor(ST77XX_GREEN, ST77XX_BLACK); } 
    myScreen.setCursor(250, 180); myScreen.print(F("R6")); 
      
  if (digitalRead(relay[7]) == LOW) 
  { myScreen.setTextColor(ST77XX_RED, ST77XX_BLACK); } 
  else { myScreen.setTextColor(ST77XX_GREEN, ST77XX_BLACK); } 
    myScreen.setCursor(290, 180); myScreen.print(F("R7")); 
        
  myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
}

//=======================================================================================
//                                              PRINT TIME 
//=======================================================================================
void printTime() { 

  myScreen.setCursor(210, 0);;
  myScreen.setTextSize(2);  
  myScreen.print(getLocalTime()); 
}

void printDigits(int digits) {  
  //this void function is really useful; it adds a "0" to the beginning of the number, so that 5 minutes is displayed as "00:05:00", rather than "00:5 :00"
  if (digits < 10) {
    myScreen.print("0");
    myScreen.print(digits);
  } else {
    myScreen.print(digits);
  }
}

void updateTime() {

  ntpClient.update();   

  if (ntpClient.update()) {
    const unsigned long epoch = ntpClient.getEpochTime(); 
    time_t localTime = myTZ.toLocal(epoch, &tcr);   
    set_time(localTime);
    Serial.println("RTC is set");
    setRTC = true;
  }
}

String getLocalTime() {
  char buffer[16];
  tm t;
  _rtc_localtime(time(NULL), &t, RTC_4_YEAR_LEAP_YEAR_SUPPORT);
  strftime(buffer, 16, "%a %H:%M", &t);
  return String(buffer);
}

int getCurrentTime() {
  int multipliedTime;
  tm t;
  _rtc_localtime(time(NULL), &t, RTC_4_YEAR_LEAP_YEAR_SUPPORT);
  multipliedTime = (t.tm_hour * 100) + t.tm_min;
  return multipliedTime;
}
int getLocalHour() {
  int hour;
  tm t;
  _rtc_localtime(time(NULL), &t, RTC_FULL_LEAP_YEAR_SUPPORT);
  hour = t.tm_hour;
  return hour;
}
int getLocalMinutes() {
  int minutes;
  tm t;
  _rtc_localtime(time(NULL), &t, RTC_FULL_LEAP_YEAR_SUPPORT);
  minutes = t.tm_min;
  return minutes;
}

//===========================================================================================================================
//                                              PAGE - SUB MENU 1 STATISTICS                        
//===========================================================================================================================
void page_SubMenu1() {
  
  //clear screen
  myScreen.fillScreen(ST77XX_BLACK);
  //print title      
  myScreen.setCursor(0, 0);
  myScreen.println(F("STATISTICS"));
  myScreen.drawLine(0, 15, 320, 15, ST77XX_GREEN);
  myScreen.drawLine(0, 16, 320, 16, ST77XX_GREEN);
  myScreen.drawLine(0, 17, 320, 17, ST77XX_GREEN);  

  //draw framework once
  //horizontal framework 
  myScreen.setTextSize(1);
  myScreen.setCursor(70, 40);
  myScreen.print(F("Temperature"));    
  myScreen.setCursor(150, 40);
  myScreen.print(F("Humidity")); 
  myScreen.setCursor(220, 40);
  myScreen.print(F("Pressure"));   
  //vertical framework 
  myScreen.setCursor(10, 60);
  myScreen.print(F("NTC-1"));    
  myScreen.setCursor(10, 80);
  myScreen.print(F("DHT22-1")); 
  myScreen.setCursor(10, 100); 
  myScreen.print(F("SHT31-1"));   
  myScreen.setCursor(10, 120);
  myScreen.print(F("BME280-1")); 
  myScreen.setCursor(10, 140);  
  myScreen.print(F("NTC-2"));   
  myScreen.setCursor(10, 160); 
  myScreen.print(F("DHT22-2")); 
  myScreen.setCursor(10, 180);
  myScreen.print(F("SHT31-2"));   
  myScreen.setCursor(10, 200);
  myScreen.print(F("BME280-2"));

}

//=======================================================================================
//                                              PRINT SYSTEM STATS
//=======================================================================================
void printStatistics() {

  myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
  myScreen.setTextSize(1);
  //framework is created in SUB_MENU1
  //update readings on timer, called from main loop in updateStatistics

  //sensor set 1
  myScreen.setCursor(80, 60); //NTC 1
  myScreen.print(waterTemp1, 1); 
  myScreen.print(F("\367"));

  myScreen.setCursor(80, 80); //DHT22 1
  printRedOrWhite(temp1, sensorFault[0], 1);
  myScreen.setCursor(150, 80); 
  printRedOrWhite(hum1, sensorFault[6], 0); 
  myScreen.setCursor(80, 100); //SHT31 1  
  printRedOrWhite(temp3, sensorFault[2], 1);
  myScreen.setCursor(150, 100);  
  printRedOrWhite(hum3, sensorFault[8], 0);
  myScreen.setCursor(80, 120); //BME280 1  
  printRedOrWhite(temp5, sensorFault[4], 1);
  myScreen.setCursor(150, 120); 
  printRedOrWhite(hum5, sensorFault[10], 0);
  myScreen.setCursor(220, 120); 
  myScreen.print(pres1, 1);
  myScreen.print(F(" hPa"));
  
  //sensor set 2
  myScreen.setCursor(80, 140); //NTC 2
  myScreen.print(waterTemp2, 1); 
  myScreen.print(F("\367"));
  myScreen.setCursor(80, 160); // DHT22 2
  printRedOrWhite(temp2, sensorFault[1], 1);
  myScreen.setCursor(150, 160); 
  printRedOrWhite(hum2, sensorFault[7], 0);
  myScreen.setCursor(80, 180); //SHT31 2
  printRedOrWhite(temp4, sensorFault[3], 1);
  myScreen.setCursor(150, 180); 
  printRedOrWhite(hum4, sensorFault[9], 0);
  myScreen.setCursor(80, 200); //BME280 2
  printRedOrWhite(temp6, sensorFault[5], 1);
  myScreen.setCursor(150, 200); 
  printRedOrWhite(hum6, sensorFault[11], 0);
  myScreen.setCursor(220, 200); 
  myScreen.print(pres2, 1);
  myScreen.print(F(" hPa"));  

  myScreen.setTextSize(2);

}

void printRedOrWhite(float sensorData, bool fault, bool temperatureOrHumidity) {  
  //if the sensor returns NAN the variable will not be updated and will be displayed in red
  if (fault) {
    myScreen.setTextColor(ST77XX_RED, ST77XX_BLACK);
    myScreen.print(sensorData, 1);
    if (temperatureOrHumidity == true) {
      myScreen.print("\367");
    } else {      
      myScreen.print("%");
    }
    myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
  } else {
     myScreen.print(sensorData, 1);
    if (temperatureOrHumidity == true) {
      myScreen.print("\367");
    } else {      
      myScreen.print("%");
    }
  }
  
}

//===========================================================================================================================
//                                              PAGE - SUB MENU2 RELAYS                            
//===========================================================================================================================
void page_SubMenu2(void) {
  //clear screen
  myScreen.fillScreen(ST77XX_BLACK);
  //print title      
  myScreen.setCursor(0, 0);
  myScreen.println(F("RELAYS"));
  myScreen.drawLine(0, 15, 320, 15, ST77XX_GREEN);
  myScreen.drawLine(0, 16, 320, 16, ST77XX_GREEN);
  myScreen.drawLine(0, 17, 320, 17, ST77XX_GREEN);
  myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
  //print R-button positions
  myScreen.setCursor(0, 100); myScreen.print(F(" R0"));
  myScreen.setCursor(40, 100); myScreen.print(F(" R1")); 
  myScreen.setCursor(80, 100); myScreen.print(F(" R2")); 
  myScreen.setCursor(120, 100); myScreen.print(F(" R3")); 
  myScreen.setCursor(160, 100); myScreen.print(F(" R4")); 
  myScreen.setCursor(200, 100); myScreen.print(F(" R5")); 
  myScreen.setCursor(240, 100); myScreen.print(F(" R6")); 
  myScreen.setCursor(280, 100); myScreen.print(F(" R7"));       
}

void drawRelaysState(int number, bool state, int xPos, int yPos) {
   
  xPos = number*xPos;

  if (state == HIGH) {     
    myScreen.setCursor(xPos+10, yPos -20); //print relay state behind the selector icon(xPos+10), above or under the numbered relay    
    myScreen.setTextColor(ST77XX_BLACK, ST77XX_GREEN);
    myScreen.print(F("ON"));      
    myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);  
    myScreen.setCursor(xPos, yPos +20);       
    myScreen.print(F("   ")); 
    }
  else if (state == LOW) { 
    myScreen.setCursor(xPos, yPos +20);   // not shifting xPos to prevent the row from wrapping around to the front -> -1 even
    myScreen.setTextColor(ST77XX_BLACK, ST77XX_RED);
    myScreen.print(F("OFF"));      
    myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);  
    myScreen.setCursor(xPos+10, yPos -20);       
    myScreen.print(F("   ")); 
  }   
}

// ===========================================================================================================================
//                                              PAGE - SUB MENU 3 TIMERS                           
// ===========================================================================================================================
void page_SubMenu3() {

  //clear screen
  myScreen.fillScreen(ST77XX_BLACK);
  //print title      
  myScreen.setCursor(0, 0);
  myScreen.println(F("TIMERS"));
  myScreen.drawLine(0, 15, 320, 15, ST77XX_GREEN);
  myScreen.drawLine(0, 16, 320, 16, ST77XX_GREEN);
  myScreen.drawLine(0, 17, 320, 17, ST77XX_GREEN);

}
  
// =======================================================================================
//                                        GENERIC SUB MENU 3 ITEM PAGE                            
// =======================================================================================
void page_SubMenu3_Item(uint8_t item_number) {

  uint8_t item_Pos = 1; //for menu 3

  //clear screen
  myScreen.fillScreen(ST77XX_BLACK);
  //print title      
  myScreen.setCursor(0, 0);
  myScreen.println(subPage3Items[item_number]);
  myScreen.drawLine(0, 15, 320, 15, ST77XX_GREEN);
  myScreen.drawLine(0, 16, 320, 16, ST77XX_GREEN);
  myScreen.drawLine(0, 17, 320, 17, ST77XX_GREEN);  
  
}

// ===========================================================================================================================
//                                        PAGE - SUB MENU 4 SETTINGS                           
// ===========================================================================================================================
void page_SubMenu4() {
  
  //clear screen
  myScreen.fillScreen(ST77XX_BLACK);
  //print title      
  myScreen.setCursor(0, 0);
  myScreen.println(F("SETTINGS"));
  myScreen.drawLine(0, 15, 320, 15, ST77XX_GREEN);
  myScreen.drawLine(0, 16, 320, 16, ST77XX_GREEN);
  myScreen.drawLine(0, 17, 320, 17, ST77XX_GREEN);

}

// =======================================================================================
//                                      GENERIC SUB MENU 4 ITEM PAGE                            
// =======================================================================================
void page_SubMenu4_Item(uint8_t item_number) { 
  
  //clear screen
  myScreen.fillScreen(ST77XX_BLACK);
  //print title      
  myScreen.setCursor(0, 0);
  myScreen.println(subPage4Items[item_number]);
  myScreen.drawLine(0, 15, 320, 15, ST77XX_GREEN);
  myScreen.drawLine(0, 16, 320, 16, ST77XX_GREEN);
  myScreen.drawLine(0, 17, 320, 17, ST77XX_GREEN);
  
}

//===========================================================================================================================
//                                       MOVING THROUGH ALL PAGES CONTENT
//===========================================================================================================================
void navigationFunctioner() {  

  bool down_was_down = false;
  bool up_was_down = false;
  bool right_was_down = false;
  bool left_was_down = false;

  if (checkJoystick() == Down) { down_was_down = true; delay(100); }
  if (checkJoystick() == Up) { up_was_down = true; delay(100); }
  if (checkJoystick() == Right) { right_was_down = true; delay(100); }
  if (checkJoystick() == Left) { left_was_down = true; delay(100); }

  if (currPage == ROOT_MENU) { //=====================================================================ROOT=================================================
    if (updateDisplay) {
      
      myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
      myScreen.setCursor(0, 80);    
      printSelected(1, root_Pos);    myScreen.println(F("STATISTICS"));
      printSelected(2, root_Pos);    myScreen.println(F("RELAYS"));
      printSelected(3, root_Pos);    myScreen.println(F("TIMERS"));
      printSelected(4, root_Pos);    myScreen.println(F("SETTINGS"));

      updateDisplay = false;
    }

    //move the pointer down
    if (checkJoystick() == Down && down_was_down == true) {
      if (root_Pos == ROOT_MENU_CNT) {      root_Pos = 1;    } 
      else {      root_Pos++;    }
      updateDisplay = true;
      down_was_down = false;
    }

    //move the pointer Up
    if (checkJoystick() == Up && up_was_down == true) {
      if (root_Pos == 1) {      root_Pos = ROOT_MENU_CNT;    } 
      else {      root_Pos--;    }
      updateDisplay = true;
      up_was_down = false;
    }

    //move to the selected page
    if (checkJoystick() == Right && right_was_down == true) {
      switch (root_Pos) {
        case 1: currPage = SUB_MENU1; return;
        case 2: currPage = SUB_MENU2; return;
        case 3: currPage = SUB_MENU3; return;
        case 4: currPage = SUB_MENU4; return;
      }
    }
  }

  if (currPage == SUB_MENU1) { //=====================================================================SUB1 STATS===========================================
    //exit - move back to the root menu
    if (checkJoystick() == Left && left_was_down == true) { 
      currPage = ROOT_MENU; return;     
    }
  }

  if (currPage == SUB_MENU2) { //=====================================================================SUB2 RELAYS===========================================    
    if (updateDisplay){  
      //draw selector
      myScreen.setCursor(0, 100); printSelectedSmall(1, rel_Pos);
      myScreen.setCursor(40, 100); printSelectedSmall(2, rel_Pos);
      myScreen.setCursor(80, 100); printSelectedSmall(3, rel_Pos);
      myScreen.setCursor(120, 100); printSelectedSmall(4, rel_Pos);
      myScreen.setCursor(160, 100); printSelectedSmall(5, rel_Pos);
      myScreen.setCursor(200, 100); printSelectedSmall(6, rel_Pos);
      myScreen.setCursor(240, 100); printSelectedSmall(7, rel_Pos);
      myScreen.setCursor(280, 100); printSelectedSmall(8, rel_Pos);

      //draw drawRelaysState(int number, bool state, int xPos, int yPos) 10 pixels to the right of the first rel_Pos
      for (int i = 0; i < numberOfRelays; i++) drawRelaysState(i, digitalRead(relay[i]), 40, 100);  
        
      if (rel_Pos == 1) {myScreen.setCursor(10, 150); myScreen.print(F("LIGHTS        "));}
      if (rel_Pos == 2) {myScreen.setCursor(10, 150); myScreen.print(F("FANS          "));}
      if (rel_Pos == 3) {myScreen.setCursor(10, 150); myScreen.print(F("RAIN          "));}
      if (rel_Pos == 4) {myScreen.setCursor(10, 150); myScreen.print(F("BACKWALL DRIP "));}
      if (rel_Pos == 5) {myScreen.setCursor(10, 150); myScreen.print(F("FOGGER        "));}
      if (rel_Pos == 6) {myScreen.setCursor(10, 150); myScreen.print(F("LAND HEATING  "));}
      if (rel_Pos == 7) {myScreen.setCursor(10, 150); myScreen.print(F("WINDOW HEATING"));}
      if (rel_Pos == 8) {myScreen.setCursor(10, 150); myScreen.print(F("AIRPUMP       "));}            
      // clear the update flag    
      updateDisplay = false;
    }
    //move the pointer right
    if (checkJoystick() == Right && right_was_down == true) {
      if (rel_Pos == SUB_MENU2_CNT) {rel_Pos = 1;} 
      else {rel_Pos++;}
      updateDisplay = true;
      right_was_down = false;
    }

    //move the pointer left / back to main menu
    if (checkJoystick() == Left && left_was_down == true) {
      if (rel_Pos == 1) { currPage = ROOT_MENU; return; } 
      else {rel_Pos--;}
      updateDisplay = true;
      left_was_down = false;      
    }
    
    //switch selected relay on
    if (checkJoystick() == Up && up_was_down == true) {
      if (digitalRead(relay[rel_Pos-1]) == LOW) { //first position in array in 0, so rel_Pos -1      
        digitalWrite(relay[rel_Pos-1], HIGH); }
      updateDisplay = true;
      up_was_down = false;
    }

    //switch selected relay off
    if (checkJoystick() == Down && down_was_down == true) {
      if (digitalRead(relay[rel_Pos-1]) == HIGH) {
        digitalWrite(relay[rel_Pos-1], LOW); }
      updateDisplay = true;
      down_was_down = false;
    }
  }

  if (currPage == SUB_MENU3) { //=====================================================================SUB3 TIMERS===========================================
    if (updateDisplay){      
      // print the items
      myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
      myScreen.setCursor(0, 30);
      printSelected(1, sub_Pos_3); myScreen.println(F("LIGHTS"));
      printSelected(2, sub_Pos_3); myScreen.println(F("RAIN"));
      printSelected(3, sub_Pos_3); myScreen.println(F("BACKWALL DRIP"));
      printSelected(4, sub_Pos_3); myScreen.println(F("FOGGER"));
      printSelected(5, sub_Pos_3); myScreen.println(F("AIR PUMP"));
      // clear the update flag
      updateDisplay = false;
    }

    //move the pointer down
    if (checkJoystick() == Down && down_was_down == true) {
      if (sub_Pos_3 == SUB_MENU3_CNT) {sub_Pos_3 = 1;} else {sub_Pos_3++;}
      updateDisplay = true;
      down_was_down = false;
    }

    //move the pointer Up
    if (checkJoystick() == Up && up_was_down == true) {
      if (sub_Pos_3 == 1) {sub_Pos_3 = SUB_MENU3_CNT;} else {sub_Pos_3--;}
      updateDisplay = true;
      up_was_down = false;
    }

    //move to the selected page
    if (checkJoystick() == Right && right_was_down == true) {
      switch (sub_Pos_3) {
        case 1: currPage = SUB_MENU3_ITEM1; return;
        case 2: currPage = SUB_MENU3_ITEM2; return;
        case 3: currPage = SUB_MENU3_ITEM3; return;
        case 4: currPage = SUB_MENU3_ITEM4; return;
        case 5: currPage = SUB_MENU3_ITEM5; return;
      }
    }
    //move back to the root menu
    if (checkJoystick() == Left && left_was_down == true) {
       currPage = ROOT_MENU; return;     
    }
  }
  
  if (currPage == SUB_MENU3_ITEM1) { //================================SUB3 ITEM 1 MAIN LIGHTS===============================================
    
    int xPos1 = 30, yPos1 = 40, yPos2 = 80, offset1 = 80;
    
    if (updateDisplay){      
      // print the items
      myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
      myScreen.setCursor(0, yPos1);
      printSelected(1, sub_Pos_3_1); printTimer(1, xPos1, yPos1, 0, false);  myScreen.print(" START");  //printTimer(int number, int xPos, int yPos, int eepromNumber, bool printDuration)
      myScreen.setCursor(0, yPos2);
      printSelected(2, sub_Pos_3_1); printTimer(2, xPos1, yPos2, 2, false);  myScreen.print(" STOP");
      // clear the update flag
      updateDisplay = false;
    }  
        
    if (checkJoystick() == Down && down_was_down == true) { //move the pointer down
      if (sub_Pos_3_1 == 1) {sub_Pos_3_1 = 2;}
      updateDisplay = true;
      down_was_down = false;
    }
   
    if (checkJoystick() == Up && up_was_down == true) {  //move the pointer Up
      if (sub_Pos_3_1 == 2) {sub_Pos_3_1 = 1;} 
      updateDisplay = true;
      up_was_down = false;
    }

    if (sub_Pos_3_1 == 1 && checkJoystick() == Right && right_was_down == true) { // change ON timer
      //outputDebugln("startChangeTime ON timer");
      startChangeTime(0, 1, xPos1 + offset1, yPos1); // startChangeTime(int eepromHour, int eepromMinutes, int xPos, int yPos)
      right_was_down = false;
    }
    
    if (sub_Pos_3_1 == 2 && checkJoystick() == Right && right_was_down == true) { // change OFF timer
      //outputDebugln("startChangeTime OFF timer");
      startChangeTime(2, 3, xPos1 + offset1, yPos2); 
      right_was_down = false;
    }
    
    //move back to the root menu
    if (checkJoystick() == Left && left_was_down == true) {
      currPage = SUB_MENU3;
      return;
    }

  }

  if (currPage == SUB_MENU3_ITEM2) { //=================================SUB3 ITEM 2 SPRINKLERS================================================
    
    int xPos1 = 30, yPos1 = 40, yPos2 = 70, yPos3 = 100, yPos4 = 130, yPos5 = 160, offset1 = 80, offset2 = 260; 

    if (updateDisplay){      
      // print the items
      myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
      myScreen.setCursor(0, yPos1);
      printSelected(1, sub_Pos_3_2); printTimer(1, xPos1, yPos1, 4, true); //printTimers(int number, int xPos, int yPos, int eepromNumber, bool printDuration)
      myScreen.setCursor(0, yPos2);
      printSelected(2, sub_Pos_3_2); printTimer(2, xPos1, yPos2, 8, true);  
      myScreen.setCursor(0, yPos3);
      printSelected(3, sub_Pos_3_2); printTimer(3, xPos1, yPos3, 12, true); 
      myScreen.setCursor(0, yPos4);
      printSelected(4, sub_Pos_3_2); printTimer(4, xPos1, yPos4, 16, true); 
      myScreen.setCursor(0, yPos5);
      printSelected(5, sub_Pos_3_2); printTimer(5, xPos1, yPos5, 20, true); 
      // clear the update flag
      updateDisplay = false;
    }  
            
    if (checkJoystick() == Down && down_was_down == true) { //move the pointer down
      if (sub_Pos_3_2 == numberOfTimers) {sub_Pos_3_2 = 1;} else {sub_Pos_3_2++;}
      updateDisplay = true;
      down_was_down = false;
    }
    
    if (checkJoystick() == Up && up_was_down == true) { //move the pointer Up
      if (sub_Pos_3_2 == 1) {sub_Pos_3_2 = numberOfTimers;} else {sub_Pos_3_2--;}
      updateDisplay = true;
      up_was_down = false;
    }

    if (sub_Pos_3_2 == 1 && checkJoystick() == Right && right_was_down == true) { // change timer 1 start and duration
      startChangeTime(4, 5, xPos1 + offset1, yPos1); // startChangeTime(int eepromHour, int eepromMinutes, int xPos, int yPos)
      startChangeSeconds(6, xPos1 + offset2, yPos1); // startChangeSeconds(int eepromSeconds, int xPos, int yPos)
      right_was_down = false;
    }    
    if (sub_Pos_3_2 == 2 && checkJoystick() == Right && right_was_down == true) {
      startChangeTime(8, 9, xPos1 + offset1, yPos2);
      startChangeSeconds(10, xPos1 + offset2, yPos2); 
      right_was_down = false;
    }
    if (sub_Pos_3_2 == 3 && checkJoystick() == Right && right_was_down == true) { 
      startChangeTime(12, 13, xPos1 + offset1, yPos3);
      startChangeSeconds(14, xPos1 + offset2, yPos3); 
      right_was_down = false;
    }
    if (sub_Pos_3_2 == 4 && checkJoystick() == Right && right_was_down == true) {
      startChangeTime(16, 17, xPos1 + offset1, yPos4);
      startChangeSeconds(18, xPos1 + offset2, yPos4); 
      right_was_down = false;
    }
    if (sub_Pos_3_2 == 5 && checkJoystick() == Right && right_was_down == true) { 
      startChangeTime(20, 21, xPos1 + offset1, yPos5);
      startChangeSeconds(22, xPos1 + offset2, yPos5); 
      right_was_down = false;
    }
    
    //move back to the root menu
    if (checkJoystick() == Left && left_was_down == true) {
      currPage = SUB_MENU3;
      return;
    }
  }

  if (currPage == SUB_MENU3_ITEM3) { //=================================SUB3 ITEM 3 BACKWALL DRIP=============================================

    int xPos1 = 30, yPos1 = 40, yPos2 = 80, offset1 = 80, offset2 = 260;

    if (updateDisplay){      
      // print the items
      myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
      myScreen.setCursor(0, yPos1);
      printSelected(1, sub_Pos_3_3); printTimer(1, xPos1, yPos1, 24, true); 
      myScreen.setCursor(0, yPos2);
      printSelected(2, sub_Pos_3_3); printTimer(2, xPos1, yPos2, 28, true); 
      // clear the update flag
      updateDisplay = false;
    }  
            
    if (checkJoystick() == Down && down_was_down == true) { 
      if (sub_Pos_3_3 == 2) {sub_Pos_3_3 = 1;} else {sub_Pos_3_3++;}
      updateDisplay = true;
      down_was_down = false;
    }
    
    if (checkJoystick() == Up && up_was_down == true) { 
      if (sub_Pos_3_3 == 1) {sub_Pos_3_3 = 2;} else {sub_Pos_3_3--;}
      updateDisplay = true;
      up_was_down = false;
    }

    if (sub_Pos_3_3 == 1 && checkJoystick() == Right && right_was_down == true) { 
      startChangeTime(24, 25, xPos1 + offset1 , yPos1); 
      startChangeSeconds(26, xPos1 + offset2, yPos1); 
      right_was_down = false;
    }    
    if (sub_Pos_3_3 == 2 && checkJoystick() == Right && right_was_down == true) {
      startChangeTime(28, 29, xPos1 + offset1 , yPos2);
      startChangeSeconds(30, xPos1 + offset2, yPos2); 
      right_was_down = false;
    }
    
    //move back to the root menu
    if (checkJoystick() == Left && left_was_down == true) {
      currPage = SUB_MENU3;
      return;
    }
  }

  if (currPage == SUB_MENU3_ITEM4) { //=================================SUB3 ITEM 4 FOGGER====================================================
    
    int xPos1 = 30, yPos1 = 40, yPos2 = 80, offset1 = 80;
    
    if (updateDisplay){      
      // print the items
      myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
      myScreen.setCursor(0, yPos1);
      printSelected(1, sub_Pos_3_4); printTimer(1, xPos1, yPos1, 32, false);  myScreen.print(" START");
      myScreen.setCursor(0, yPos2);
      printSelected(2, sub_Pos_3_4); printTimer(2, xPos1, yPos2, 34, false);  myScreen.print(" STOP");
      // clear the update flag
      updateDisplay = false;
    }  
        
    if (checkJoystick() == Down && down_was_down == true) {
      if (sub_Pos_3_4 == 1) {sub_Pos_3_4 = 2;}
      updateDisplay = true;
      down_was_down = false;
    }
   
    if (checkJoystick() == Up && up_was_down == true) {
      if (sub_Pos_3_4 == 2) {sub_Pos_3_4 = 1;} 
      updateDisplay = true;
      up_was_down = false;
    }

    if (sub_Pos_3_4 == 1 && checkJoystick() == Right && right_was_down == true) { 
      startChangeTime(32, 33, xPos1 + offset1, yPos1); 
      right_was_down = false;
    }
    
    if (sub_Pos_3_4 == 2 && checkJoystick() == Right && right_was_down == true) { 
      startChangeTime(34, 35, xPos1 + offset1, yPos2); 
      right_was_down = false;
    }
    
    //move back to the root menu
    if (checkJoystick() == Left && left_was_down == true) {
      currPage = SUB_MENU3;
      return;
    }
  }

  if (currPage == SUB_MENU3_ITEM5) { //=================================SUB3 ITEM 5 AIR PUMP==================================================
    
    int xPos1 = 30, yPos1 = 40, yPos2 = 70, yPos3 = 100, yPos4 = 130, yPos5 = 160, offset1 = 80, offset2 = 260; 

    if (updateDisplay){      
      // print the items
      myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
      myScreen.setCursor(0, yPos1);
      printSelected(1, sub_Pos_3_5); printTimer(1, xPos1, yPos1, 38, true); 
      myScreen.setCursor(0, yPos2);
      printSelected(2, sub_Pos_3_5); printTimer(2, xPos1, yPos2, 42, true);  
      myScreen.setCursor(0, yPos3);
      printSelected(3, sub_Pos_3_5); printTimer(3, xPos1, yPos3, 46, true); 
      myScreen.setCursor(0, yPos4);
      printSelected(4, sub_Pos_3_5); printTimer(4, xPos1, yPos4, 50, true); 
      myScreen.setCursor(0, yPos5);
      printSelected(5, sub_Pos_3_5); printTimer(5, xPos1, yPos5, 54, true); 
      // clear the update flag
      updateDisplay = false;
    }  
            
    if (checkJoystick() == Down && down_was_down == true) { 
      if (sub_Pos_3_5 == numberOfTimers) {sub_Pos_3_5 = 1;} else {sub_Pos_3_5++;}
      updateDisplay = true;
      down_was_down = false;
    }
    
    if (checkJoystick() == Up && up_was_down == true) { 
      if (sub_Pos_3_5 == 1) {sub_Pos_3_5 = numberOfTimers;} else {sub_Pos_3_5--;}
      updateDisplay = true;
      up_was_down = false;
    }

    if (sub_Pos_3_5 == 1 && checkJoystick() == Right && right_was_down == true) { 
      startChangeTime(38, 39, xPos1 + offset1 , yPos1);
      startChangeSeconds(40, xPos1 + offset2, yPos1); //duration of seconds can be turned into minutes in on/off function *60
      right_was_down = false;
    }    
    if (sub_Pos_3_5 == 2 && checkJoystick() == Right && right_was_down == true) {
      startChangeTime(42, 43, xPos1 + offset1 , yPos2);
      startChangeSeconds(44, xPos1 + offset2, yPos2); 
      right_was_down = false;
    }
    if (sub_Pos_3_5 == 3 && checkJoystick() == Right && right_was_down == true) { 
      startChangeTime(46, 47, xPos1 + offset1 , yPos3);
      startChangeSeconds(48, xPos1 + offset2, yPos3); 
      right_was_down = false;
    }
    if (sub_Pos_3_5 == 4 && checkJoystick() == Right && right_was_down == true) {
      startChangeTime(50, 51, xPos1 + offset1 , yPos4);
      startChangeSeconds(52, xPos1 + offset2, yPos4); 
      right_was_down = false;
    }
    if (sub_Pos_3_5 == 5 && checkJoystick() == Right && right_was_down == true) { 
      startChangeTime(54, 55, xPos1 + offset1 , yPos5);
      startChangeSeconds(56, xPos1 + offset2, yPos5); 
      right_was_down = false;
    }
    
    if (checkJoystick() == Left && left_was_down == true) {
      currPage = SUB_MENU3;
      return;
    }
  }

  if (currPage == SUB_MENU4) { //=====================================================================SUB4=================================================
    if (updateDisplay){      
      // print the items
      myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
      myScreen.setCursor(0, 30);
      printSelected(1, sub_Pos_4); myScreen.println(F("SET TEMPERATURE"));
      printSelected(2, sub_Pos_4); myScreen.println(F("SET HUMIDITY"));
      printSelected(3, sub_Pos_4); myScreen.println(F("SET LAND TEMPERATURE"));
      printSelected(4, sub_Pos_4); myScreen.println(F("SET RAINSTORM"));
      myScreen.println();
      printSelected(5, sub_Pos_4); myScreen.println(F("RESET CRASH COUNTER"));
      // clear the update flag
      updateDisplay = false;
    }

    //move the pointer down
    if (checkJoystick() == Down && down_was_down == true) {
      if (sub_Pos_4 == SUB_MENU4_CNT) {sub_Pos_4 = 1;} else {sub_Pos_4++;}
      updateDisplay = true;
      down_was_down = false;
    }

    //move the pointer Up
    if (checkJoystick() == Up && up_was_down == true) {
      if (sub_Pos_4 == 1) {sub_Pos_4 = SUB_MENU4_CNT;} else {sub_Pos_4--;}
      updateDisplay = true;
      up_was_down = false;
    }

    //move to the selected page
    if (checkJoystick() == Right && right_was_down == true) {
      switch (sub_Pos_4) {
        case 1: currPage = SUB_MENU4_ITEM1; return;
        case 2: currPage = SUB_MENU4_ITEM2; return;
        case 3: currPage = SUB_MENU4_ITEM3; return;
        case 4: currPage = SUB_MENU4_ITEM4; return;
        case 5: currPage = SUB_MENU4_ITEM5; return;
      }
    }
    //move back to the root menu
    if (checkJoystick() == Left && left_was_down == true) {
       currPage = ROOT_MENU; return;     
    }
  }
  
  if (currPage == SUB_MENU4_ITEM1) { //===================SUB4 ITEM 1 SET TEMPERATURE====================================
    
    int xPos1 = 30, yPos1 = 40, yPos2 = 80, yPos3 = 120;
    
    if (updateDisplay){      
      // print the items
      myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
      myScreen.setCursor(0, yPos1);
      printSelected(1, sub_Pos_4_1); printMinMax(xPos1, yPos1, 60, "MIN TEMPERATURE ", "\367");  //printMinMax(int xPos, int yPos, int eepromNumber, const char* txt, const char* txt2)
      myScreen.setCursor(0, yPos2); 
      printSelected(2, sub_Pos_4_1); printMinMax(xPos1, yPos2, 61, "MAX TEMPERATURE ", "\367");
      myScreen.setCursor(0, yPos3); 
      printSelected(3, sub_Pos_4_1); printMinMax(xPos1, yPos3, 70, "IDEAL ", "\367");
      // clear the update flag
      updateDisplay = false;
    }  
        
    if (checkJoystick() == Down && down_was_down == true) { 
      if (sub_Pos_4_1 == 3) {sub_Pos_4_1 = 1;} else {sub_Pos_4_1++;}
      updateDisplay = true;
      down_was_down = false;
    }
    
    if (checkJoystick() == Up && up_was_down == true) { 
      if (sub_Pos_4_1 == 1) {sub_Pos_4_1 = 3;} else {sub_Pos_4_1--;}
      updateDisplay = true;
      up_was_down = false;
    }

    if (sub_Pos_4_1 == 1 && checkJoystick() == Right && right_was_down == true) { 
      setValue(xPos1, yPos1, 10, 20, 60, 190); //setValue(int xPos, int yPos, int minValue, int maxValue, int eepromNumber, int shiftToRight)
      right_was_down = false;
    }
    
    if (sub_Pos_4_1 == 2 && checkJoystick() == Right && right_was_down == true) { 
      setValue(xPos1, yPos2, 20, 35, 61, 190); 
      right_was_down = false;
    }

    if (sub_Pos_4_1 == 3 && checkJoystick() == Right && right_was_down == true) { 
      setValue(xPos1, yPos3, 20, 30, 70, 70); 
      right_was_down = false;
    }
    
    if (checkJoystick() == Left && left_was_down == true) {
      currPage = SUB_MENU4;
      return;
    }
  }

  if (currPage == SUB_MENU4_ITEM2) { //===================SUB4 ITEM 2 SET HUMIDITY=======================================
    
    int xPos1 = 30, yPos1 = 40, yPos2 = 80, yPos3 = 120;
    
    if (updateDisplay){      
      // print the items
      myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
      myScreen.setCursor(0, yPos1);
      printSelected(1, sub_Pos_4_2); printMinMax(xPos1, yPos1, 62, "MIN HUMIDITY ", "%");  //printMinMax(int xPos, int yPos, int eepromNumber, const char* txt, const char* txt2)
      myScreen.setCursor(0, yPos2); 
      printSelected(2, sub_Pos_4_2); printMinMax(xPos1, yPos2, 63, "MAX HUMIDITY ", "%");
      myScreen.setCursor(0, yPos3); 
      printSelected(3, sub_Pos_4_2); printMinMax(xPos1, yPos3, 71, "IDEAL ", "%");
      // clear the update flag
      updateDisplay = false;
    }  
        
    if (checkJoystick() == Down && down_was_down == true) { 
      if (sub_Pos_4_2 == 3) {sub_Pos_4_2 = 1;} else {sub_Pos_4_2++;}
      updateDisplay = true;
      down_was_down = false;
    }
    
    if (checkJoystick() == Up && up_was_down == true) { 
      if (sub_Pos_4_2 == 1) {sub_Pos_4_2 = 3;} else {sub_Pos_4_2--;}
      updateDisplay = true;
      up_was_down = false;
    }

    if (sub_Pos_4_2 == 1 && checkJoystick() == Right && right_was_down == true) { 
      setValue(xPos1, yPos1, 40, 60, 62, 155); //setValue(int xPos, int yPos, int minValue, int maxValue, int eepromNumber, int shiftToRight)
      right_was_down = false;
    }
    
    if (sub_Pos_4_2 == 2 && checkJoystick() == Right && right_was_down == true) { 
      setValue(xPos1, yPos2, 60, 90, 63, 155); 
      right_was_down = false;
    }

    if (sub_Pos_4_2 == 3 && checkJoystick() == Right && right_was_down == true) { 
      setValue(xPos1, yPos3, 40, 80, 71, 70); 
      right_was_down = false;
    }
    
    if (checkJoystick() == Left && left_was_down == true) {
      currPage = SUB_MENU4;
      return;
    }
  }

  if (currPage == SUB_MENU4_ITEM3) { //===================SUB4 ITEM 3 SET LAND TEMPERATURE===============================
    
    int xPos1 = 30, yPos1 = 40, yPos2 = 80;
    
    if (updateDisplay){      
      // print the items
      myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
      myScreen.setCursor(0, yPos1);
      printSelected(1, sub_Pos_4_3); printMinMax(xPos1, yPos1, 64, "DAY TEMPERATURE ", "\367");  //printMinMax(int xPos, int yPos, int eepromNumber, const char* txt, const char* txt2)
      myScreen.setCursor(0, yPos2); 
      printSelected(2, sub_Pos_4_3); printMinMax(xPos1, yPos2, 65, "NIGHT TEMPERATURE ", "\367");
      // clear the update flag
      updateDisplay = false;
    }  

    if (checkJoystick() == Down && down_was_down == true) { 
      if (sub_Pos_4_3 == 1) {sub_Pos_4_3 = 2;} 
      updateDisplay = true;
      down_was_down = false;
    }
    
    if (checkJoystick() == Up && up_was_down == true) { 
      if (sub_Pos_4_3 == 2) {sub_Pos_4_3 = 1;} 
      updateDisplay = true;
      up_was_down = false;
    }       

    if (sub_Pos_4_3 == 1 && checkJoystick() == Right && right_was_down == true) { 
      setValue(xPos1, yPos1, 10, 25, 64, 190); //setValue(int xPos, int yPos, int minValue, int maxValue, int eepromNumber, int shiftToRight)
      right_was_down = false;
    }

    if (sub_Pos_4_3 == 2 && checkJoystick() == Right && right_was_down == true) { 
      setValue(xPos1, yPos2, 10, 20, 65, 220); //setValue(int xPos, int yPos, int minValue, int maxValue, int eepromNumber, int shiftToRight)
      right_was_down = false;
    }    
    
    if (checkJoystick() == Left && left_was_down == true) {
      currPage = SUB_MENU4;
      return;
    }
  }

  if (currPage == SUB_MENU4_ITEM4) { //===================SUB4 ITEM 4 SET RAINSTORM======================================
    
    int xPos1 = 30, yPos1 = 40, yPos2 = 80, yPos3 = 120;
    
    if (updateDisplay){      
      // print the items
      myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
      myScreen.setCursor(0, yPos1);
      printSelected(1, sub_Pos_4_4); printMinMax(xPos1, yPos1, 66, "MAX NUMBER ", "");  //printMinMax(int xPos, int yPos, int eepromNumber, const char* txt, const char* txt2)
      myScreen.setCursor(0, yPos2); 
      printSelected(2, sub_Pos_4_4); printMinMax(xPos1, yPos2, 67, "DURATION ", "");
      myScreen.setCursor(0, yPos3); 
      printSelected(3, sub_Pos_4_4); printMinMax(xPos1, yPos3, 68, "VENT PAUSE ", "");
      // clear the update flag
      updateDisplay = false;
    }  

    if (checkJoystick() == Down && down_was_down == true) { 
      if (sub_Pos_4_4 == 3) {sub_Pos_4_4 = 1;} else {sub_Pos_4_4++;}
      updateDisplay = true;
      down_was_down = false;
    }
    
    if (checkJoystick() == Up && up_was_down == true) { 
      if (sub_Pos_4_4 == 1) {sub_Pos_4_4 = 3;} else {sub_Pos_4_4--;}
      updateDisplay = true;
      up_was_down = false;
    }     

    if (sub_Pos_4_4 == 1 && checkJoystick() == Right && right_was_down == true) { 
      setValue(xPos1, yPos1, 1, 5, 66, 130); //setValue(int xPos, int yPos, int minValue, int maxValue, int eepromNumber, int shiftToRight)
      right_was_down = false;
    }
    
    if (sub_Pos_4_4 == 2 && checkJoystick() == Right && right_was_down == true) { 
      setValue(xPos1, yPos2, 10, 60, 67, 110); //setValue(int xPos, int yPos, int minValue, int maxValue, int eepromNumber, int shiftToRight)
      right_was_down = false;
    }    

    if (sub_Pos_4_4 == 3 && checkJoystick() == Right && right_was_down == true) { 
      setValue(xPos1, yPos3, 10, 120, 68, 130); //setValue(int xPos, int yPos, int minValue, int maxValue, int eepromNumber, int shiftToRight)
      right_was_down = false;
    }    
    
    if (checkJoystick() == Left && left_was_down == true) {
      currPage = SUB_MENU4;
      return;
    }
  }  

  if (currPage == SUB_MENU4_ITEM5) { //===================SUB4 ITEM 5 RESET CRASHCOUNTER=================================
    
    int xPos1 = 30, yPos1 = 40, yPos2 = 80;
    
    if (updateDisplay){      
      // print the items
      myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
      myScreen.setCursor(0, yPos1);
      printSelected(1, sub_Pos_4_5); myScreen.print("CRASH COUNTER = "); myScreen.print(EEPROM.readByte(100)); myScreen.print("  ");
      myScreen.setCursor(0, yPos2); 
      myScreen.setTextColor(ST77XX_RED);
      printSelected(2, sub_Pos_4_5); myScreen.print("CONFIRM RESET");
      myScreen.setTextColor(ST77XX_WHITE);
      // clear the update flag
      updateDisplay = false;
    }  

    if (checkJoystick() == Down && down_was_down == true) { 
      if (sub_Pos_4_5 == 1) {sub_Pos_4_5 = 2;} 
      updateDisplay = true;
      down_was_down = false;
    }
    
    if (checkJoystick() == Up && up_was_down == true) { 
      if (sub_Pos_4_5 == 2) {sub_Pos_4_5 = 1;} 
      updateDisplay = true;
      up_was_down = false;
    }     
    
    if (sub_Pos_4_5 == 2 && checkJoystick() == Right && right_was_down == true) { 
      EEPROM.writeByte(100, 0);
      updateDisplay = true;
      right_was_down = false;
    }    
    
    if (checkJoystick() == Left && left_was_down == true) {
      currPage = SUB_MENU4;
      return;
    }
  }    

}

void printSelected(uint8_t p1, uint8_t p2) {
  if (p1 == p2) { myScreen.print(F(" > ")); } 
  else { myScreen.print(F("   ")); }
}
void printSelectedSmall(uint8_t p1, uint8_t p2) {
  if (p1 == p2) { myScreen.print(F(">")); } 
  else { myScreen.print(F(" ")); }
} 

//===========================================================================================================================
//                                             AUTO GO BACK TO HOMEPAGE
//===========================================================================================================================
void autoGoBackToHomePage() {
  unsigned long currentMillis = millis();  //call current millis

  if (checkJoystick() != 0) {  // Reset millis counter if joystick is moved
    previousMillis = currentMillis;
  }
  if (currPage != SCREEN_SAVER) {
    if (currPage != ROOT_MENU && currentMillis - previousMillis > 60 * 1000) {  //1 minute
      previousMillis = currentMillis;  //replace previous millis by current millis as new start point
      currPage = ROOT_MENU;
    }
  }
}

void screenSaver() {
  unsigned long currentMillis2 = millis();  

  if (checkJoystick() != 0) {  // Reset millis counter if joystick is moved
    previousMillis2 = currentMillis2;
  }
  if (currPage == ROOT_MENU && currentMillis2 - previousMillis2 > 300 * 1000) {  //after 5 minutes of no movement
    previousMillis2 = currentMillis2; 
    currPage = SCREEN_SAVER;  
    outputDebugln("Screensaver activated");
  }
}

void activateScreen() {

  if (currPage == SCREEN_SAVER && checkJoystick() != 0) {  //turn back on when joystick is moved
    currPage = ROOT_MENU;
    outputDebugln("Screen reactivated");
  }
}

//===========================================================================================================================
//                                                     CLIMATE CONTROL
//===========================================================================================================================
void climateControl() {

  //everything times 10 for better resolution
  unsigned int minAirTemp = EEPROM.readByte(60) * 10;
  unsigned int maxAirTemp = EEPROM.readByte(61) * 10;
  unsigned int minHumidity = EEPROM.readByte(62) * 10;
  unsigned int maxHumidity = EEPROM.readByte(63) * 10;
  unsigned int dayWaterTemp = EEPROM.readByte(dayTemperature) * 10;
  unsigned int nightWaterTemp = EEPROM.readByte(nightTemperature) * 10;
  unsigned int desiredAirTemp = EEPROM.readByte(desiredTemp) * 10;
  unsigned int desiredHumidity = EEPROM.readByte(desiredHumi) * 10;
  unsigned int airTemp = airTempAverage * 10; //all average readings have 1 decimal number
  unsigned int humidity = humidityAverage * 10;
  unsigned int waterTemp = waterTempAverage * 10;
  unsigned int hysteresis = 50;
  unsigned int airOn = desiredAirTemp + (hysteresis * .5);  //tweaking balance
  unsigned int airOff = desiredAirTemp - (hysteresis * .25); 
  unsigned int humidityOn = desiredHumidity + hysteresis;
  unsigned int humidityOff = desiredHumidity - (hysteresis * .5);

  if (dayTime) { //day schedule

    noRain = false;
    hold = false;

    if (waterTemp - 10 < dayWaterTemp) {
      digitalWrite(relay[5], HIGH);  //land heat on
    }
    if (waterTemp >= dayWaterTemp) {
      digitalWrite(relay[5], LOW);   //land heat off
    }
    
    if (airTemp <= airOn && airTemp >= airOff && humidity <= humidityOn && humidity >= humidityOff) {
      climateState = CASE0;
      outputDebugln("CASE 0"); 
    }
    
    if (humidityMonitoring) {
      if (airTemp > airOn && humidity > humidityOn) {
        hold = true;    // flag to disable singular conditions
        noRain = true;  //disable sprinklers
        outputDebugln("CASE 1 airTemp high & humidity high --- fans on max");
        climateState = CASE1;
      }

      else if (airTemp < airOff && humidity < humidityOff) {
        hold = true;
        outputDebugln("CASE 2 airTemp low & humidity low --- heating on & fogger on");
        climateState = CASE2;
      }

      else if (airTemp > airOn && humidity < humidityOff) {
        hold = true;  // prevent other singular trigger
        outputDebugln("CASE 3 airTemp high & humidity low --- fans off & sprinklers on");
        climateState = CASE3;
      }

      else if (airTemp < airOff && humidity > humidityOn) {
        hold = true;
        noRain = true;
        outputDebugln("CASE 4 airTemp low && humidity high --- heating on & fans low");
        climateState = CASE4;
      }
    }

    if (!hold) {
      if (humidity > humidityOn && humidityMonitoring) {
        noRain = true;
        outputDebugln("CASE 5 humidity high --- fans on, no rain");
        climateState = CASE5;
      }    

      else if (airTemp > airOn) {
        outputDebugln("CASE 6 airTemp high --- fans on");
        climateState = CASE6;
      }

      else if (humidity < humidityOff && humidityMonitoring) {
        outputDebugln("CASE 7 humidity low --- fogger on");
        climateState = CASE7;
      }

      else if (airTemp < airOff) {
        outputDebugln("CASE 8 airTemp low --- heat on");
        climateState = CASE8;
      }
    }
  }

  if (!dayTime) { //night schedule

    if (waterTemp - 10 < nightWaterTemp) {
      digitalWrite(relay[5], HIGH);  //land heat on
    }
    if (waterTemp >= nightWaterTemp) {
      digitalWrite(relay[5], LOW);   //land heat off
    }
    
    if (airTemp <= airOn && airTemp >= airOff && humidity <= humidityOn && humidity >= humidityOff) {
      climateState = CASE0;
      outputDebugln("CASE 0"); 
    }
    
    else if (airTemp > airOn) {
      outputDebugln("CASE 9 airTemp high --- fans on");
      climateState = CASE9;
    }

    else if (humidity < humidityOff) {
      outputDebugln("CASE 10 humidity low --- fogger on");
      climateState = CASE10;
    }

    else if (airTemp < minAirTemp) {
      outputDebugln("CASE 11 airTemp low --- heat on");
      climateState = CASE11;
    }

    else if (humidity > (990)) {
      outputDebugln("CASE 12 humidity max --- fans on");
      climateState = CASE12;
    }
  }
}

void climateChange() {  //it's real

  //everything times 10 for better resolution
  unsigned int minAirTemp = EEPROM.readByte(60) * 10;
  unsigned int maxAirTemp = EEPROM.readByte(61) * 10;
  unsigned int minHumidity = EEPROM.readByte(62) * 10;
  unsigned int maxHumidity = EEPROM.readByte(63) * 10;
  unsigned int dayWaterTemp = EEPROM.readByte(dayTemperature) * 10;
  unsigned int nightWaterTemp = EEPROM.readByte(nightTemperature) * 10;
  unsigned int desiredAirTemp = EEPROM.readByte(desiredTemp) * 10;
  unsigned int desiredHumidity = EEPROM.readByte(desiredHumi) * 10;
  unsigned int airTemp = airTempAverage * 10;
  unsigned int humidity = humidityAverage * 10;
  unsigned int waterTemp = waterTempAverage * 10;
  unsigned int hysteresis = 50;
  unsigned int airOn = desiredAirTemp + (hysteresis * .5);
  unsigned int airOff = desiredAirTemp - (hysteresis * .25);
  unsigned int heatOn = desiredAirTemp - (hysteresis * .5);
  unsigned int heatOff = desiredAirTemp - (hysteresis * .25);
  unsigned int humidityOn = desiredHumidity + hysteresis;
  unsigned int humidityOff = desiredHumidity - (hysteresis * .5);

  if (climateState != previousClimateState) {
    switch (climateState) {

      case CASE0:                      //default do nothing
        digitalWrite(relay[1], LOW);  //fans off
        pwmVal1 = 0;
        digitalWrite(pwmPin_1, pwmVal1); 
        digitalWrite(relay[4], LOW);  //fogger off
        digitalWrite(relay[6], LOW);  //window heating off --- ??? maybe not?
        break;

      case CASE1:                      //decrease temperature and decrease humidity
        digitalWrite(relay[1], HIGH);  //fans on
        digitalWrite(relay[6], LOW);   //window heating off
        digitalWrite(relay[4], LOW);   //fogger off
        break;

      case CASE2:                      //increase temperature and increase humidity
        digitalWrite(relay[1], LOW);   //fans off
        pwmVal1 = 0;
        digitalWrite(pwmPin_1, pwmVal1);
        digitalWrite(relay[4], HIGH);  //fogger on
        digitalWrite(relay[6], HIGH);  //window heating on
        break;

      case CASE3:                      //decrease temperature and increase humidity
        digitalWrite(relay[6], LOW);  //window heating off
        //running sequence below
        break;

      case CASE4:                      //increase temperature and decrease humidity
        digitalWrite(relay[6], HIGH);  //heating on
        digitalWrite(relay[1], HIGH);  //fans low
        pwmVal1 = 100;
        digitalWrite(pwmPin_1, pwmVal1);   //low state --- needs testing
        break;

      //single states
      case CASE5:                      //decrease humidity
        digitalWrite(relay[4], LOW);   //fogger off
        digitalWrite(relay[1], HIGH);  //fans on
        break;

      case CASE6:                      //decrease temperature
        digitalWrite(relay[6], LOW);   //window heating off
        digitalWrite(relay[1], HIGH);  //fans on
        break;

      case CASE7:                      //increase humidity
        digitalWrite(relay[1], LOW);   //fans off
        pwmVal1 = 0;
        digitalWrite(pwmPin_1, pwmVal1);
        digitalWrite(relay[4], HIGH);  //fogger on
        break;

      case CASE8:                      //increase temperature
        digitalWrite(relay[1], LOW);   //fans off
        pwmVal1 = 0;
        digitalWrite(pwmPin_1, pwmVal1);
        digitalWrite(relay[6], HIGH);  //window heat on
        break;

      //night
      case CASE9:                      //decrease temperature
        digitalWrite(relay[6], LOW);   //window heating off
        digitalWrite(relay[1], HIGH);  //fans on
        break;

      case CASE10:                     //increase humidity
        digitalWrite(relay[1], LOW);   //fans off
        pwmVal1 = 0;
        digitalWrite(pwmPin_1, pwmVal1);
        digitalWrite(relay[4], HIGH);  //fogger on
        break;

      case CASE11:                     //increase temperature
        digitalWrite(relay[1], LOW);   //fans off
        pwmVal1 = 0;
        digitalWrite(pwmPin_1, pwmVal1);
        digitalWrite(relay[6], HIGH);  //window heat on
        break;

      case CASE12:                     //decrease humidity
        digitalWrite(relay[4], LOW);   //fogger off
        digitalWrite(relay[1], HIGH);  //fans on
        break;
    }
    previousClimateState = climateState;
  }

  if (climateState == CASE1) {  //decrease temperature and decrease humidity
    
    if (airTemp > maxAirTemp) { airTemp = maxAirTemp; }
    pwmVal1 = map(airTemp, desiredAirTemp, maxAirTemp, 0, 255);
    digitalWrite(pwmPin_1, pwmVal1);
    digitalWrite(pwmPin_2, pwmVal1);  //to be adjusted for different fans i.e. pwmVal2
  }

  else if (climateState == CASE3) {  //decrease temperature and increase humidity
    //turn on sprinklers and wait a bit for endothermic evaporation, then turn on fans again. This event is unlikely to happen.
    static byte state = 4;  //at start go to state 0 with ++
    if (millis() - previousMillis_2 >= sequenceInterval[state]) {
      // go to the next state
      state++;
      state = state % 4;
      outputDebug(F("state = "));
      outputDebugln(state);

      // act according to state
      switch (state) {
        case 0:  //fans off and wait 2 seconds
          digitalWrite(relay[1], LOW);
          pwmVal1 = 0;
          digitalWrite(pwmPin_1, pwmVal1);
          break;
        case 1:  //sprinklers for 10 seconds
          digitalWrite(relay[2], HIGH);
          break;
        case 2:  //sprinklers off and wait another 30 seconds
          digitalWrite(relay[2], LOW);
          break;
        case 3:  //fans back on for (at least) 30 seconds
          digitalWrite(relay[1], HIGH);
          if (airTemp > maxAirTemp) { airTemp = maxAirTemp; }
          pwmVal1 = map(airTemp, desiredAirTemp, maxAirTemp, 0, 255);
          digitalWrite(pwmPin_1, pwmVal1);
          break;
      }
      previousMillis_2 = millis();
    }
  }

  else if (climateState == CASE5) {  //decrease humidity

    if (humidity > maxHumidity) { humidity = maxHumidity; }
    pwmVal1 = map(humidity, desiredHumidity, maxHumidity, 0, 255);
    digitalWrite(pwmPin_1, pwmVal1);
    //pwmVal2 = map(humidity, humidityOff, maxHumidity, 0, 255);
    //digitalWrite(pwmPin_2, pwmVal2);
  }

  else if (climateState == CASE6) {  //decrease temperature

    if (airTemp > maxAirTemp) { airTemp = maxAirTemp; }
    pwmVal1 = map(airTemp, desiredAirTemp, maxAirTemp, 0, 255);
    digitalWrite(pwmPin_1, pwmVal1);
    //pwmVal2 = map(airTemp, airOff, maxAirTemp, 0, 255);
    //digitalWrite(pwmPin_2, pwmVal2);
  }

  else if (climateState == CASE8) {  //increase temperature
    //digitalWrite(relay[1], LOW); //fans_2 to be made icm with front window ventilation
    //pwmVal2 = map(airTemp, minAirTemp, maxAirTemp, 0, 180);
  }

  else if (climateState == CASE9) {  //decrease temperature

    if (airTemp > maxAirTemp) { airTemp = maxAirTemp; }
    pwmVal1 = map(airTemp, desiredAirTemp, maxAirTemp, 0, 255);
    digitalWrite(pwmPin_1, pwmVal1);
  }

  else if (climateState == CASE12) {  //decrease humidity

    if (humidity > maxHumidity) { humidity = maxHumidity; }
    pwmVal1 = map(humidity, maxHumidity, 100, 0, 100);  //value between set max and absolute? 100-speed needs to be tweaked
    digitalWrite(pwmPin_1, pwmVal1);
  }

  outputDebugln(pwmVal1);
  pwm1Speed = map(pwmVal1, 0, 255, 0, 100);
  
}

//===========================================================================================================================
//                                               ENTER AND SAVE VALUES TO EEPROM
//===========================================================================================================================
void startChangeTime(int eepromHour, int eepromMinutes, int xPos, int yPos) { // xPos is starting location for Hour digits. xPos for minutes is shifted right

  int hourTime = EEPROM.readByte(eepromHour);
  int minuteTime = EEPROM.readByte(eepromMinutes);
  bool right_was_down = false;
  bool hourHasBeenSaved = false;  
  bool minuteHasBeenSaved = false;  
  bool everythingHasBeenSaved = false;  
  ChangeTimeState state = CHANGING_HOUR;

  //if (!everythingHasBeenSaved) {
  while (!everythingHasBeenSaved) {
    switch (state) {
      case CHANGING_HOUR: 
        if (checkJoystick() == Right) { right_was_down = true; delay(100); }

        hourTime = changeTime(hourTime, 0, 23, xPos, yPos);

        if (checkJoystick() == Right && right_was_down == true) {
          EEPROM.writeByte(eepromHour, hourTime);
          myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK); //revert color to normal
          myScreen.setCursor(xPos, yPos);          
          printDigits(hourTime);
          right_was_down = false;
          hourHasBeenSaved = true;  
        }   
        if (hourHasBeenSaved && !minuteHasBeenSaved) {
          state = CHANGING_MINUTE;
        }   
      break;

      case CHANGING_MINUTE: 
        if (checkJoystick() == Right) { right_was_down = true; delay(100); }

        minuteTime = changeTime(minuteTime, 0, 59, xPos +35, yPos); //+35 to shift from hour digits to minute digits   
        
        if (checkJoystick() == Right && right_was_down == true) {
          EEPROM.writeByte(eepromMinutes, minuteTime);
          myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK); //revert color to normal
          myScreen.setCursor(xPos +35, yPos);          
          printDigits(minuteTime);
          right_was_down = false;
          minuteHasBeenSaved = true; 
        }
        if (minuteHasBeenSaved && hourHasBeenSaved) {
          state = SAVING_COMPLETE;
          delay(200);
        }   
      break;      
      
      case SAVING_COMPLETE:      
        everythingHasBeenSaved = true;
        //outputDebugln("saving completed");
      break;

    }
  }
}

int changeTime(int &timeValue, int minValue, int maxValue, int xPos, int yPos) { 
  
  bool down_was_down = false;
  bool up_was_down = false;

  if (checkJoystick() == Down) { down_was_down = true; delay(100); }
  if (checkJoystick() == Up) { up_was_down = true; delay(100); } 
  
  myScreen.setTextColor(ST77XX_BLACK, ST77XX_WHITE); //invert the selected block  
  myScreen.setCursor(xPos, yPos);
  printDigits(timeValue);
  
  if (checkJoystick() == Up && up_was_down == true) {
    timeValue++; 
    myScreen.setCursor(xPos, yPos);
    printDigits(timeValue);
    if (timeValue > maxValue) {
      timeValue = minValue;
    }
    up_was_down = false;
  }
  if (checkJoystick() == Down && down_was_down == true) {
    timeValue--; 
    myScreen.setCursor(xPos, yPos);
    printDigits(timeValue);
    if (timeValue < minValue) {
      timeValue = maxValue;
    }
    down_was_down = false;
  }  
  return timeValue;  
}

void startChangeSeconds(int eepromSeconds, int xPos, int yPos) { 

  int secondsTime = EEPROM.readByte(eepromSeconds);
  bool right_was_down = false;
  bool timeHasBeenSaved = false;
  bool valueStored = false; //extra delay flag to prevent instant exit and restart

  while(!timeHasBeenSaved) {

    if (checkJoystick() == Right) { right_was_down = true; delay(100); }
    secondsTime = changeTime(secondsTime, 0, 59, xPos, yPos);

    if (checkJoystick() == Right && right_was_down == true) {
      EEPROM.writeByte(eepromSeconds, secondsTime);
      myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK); //revert color to normal
      myScreen.setCursor(xPos, yPos);          
      printDigits(secondsTime);
      valueStored = true;
      //outputDebugln("duration stored");
      right_was_down = false;
    }
    if (valueStored){
      delay(200);
      timeHasBeenSaved = true;
    }
  }
}

void printTimer(int number, int xPos, int yPos, int eepromNumber, bool printDuration) {

  myScreen.setCursor(xPos, yPos);
  myScreen.print("TIMER");
  myScreen.print(number);
  myScreen.setCursor(xPos + 80, yPos); // 80 only works for the time + duration notation
  printDigits(EEPROM.readByte(eepromNumber));
  myScreen.print(":");
  printDigits(EEPROM.readByte(eepromNumber + 1)); 
  if (printDuration){
    myScreen.print(" DURATION");
    myScreen.setCursor(xPos + 260, yPos);
    printDigits(EEPROM.readByte(eepromNumber + 2));
  }
}

void printMinMax(int xPos, int yPos, int eepromNumber, const char* txt, const char* txt2) {

  myScreen.setCursor(xPos, yPos);
  myScreen.print(txt);
  printDigits(EEPROM.readByte(eepromNumber));  
  myScreen.print(txt2);
}

void setValue(int xPos, int yPos, int minValue, int maxValue, int eepromNumber, int shiftToRight) { 

  int xPos2 = xPos + shiftToRight; //adjusting location after text
  int valueToSet = EEPROM.readByte(eepromNumber);
  bool right_was_down = false;  
  bool valueHasBeenStored = false;
  bool setValueStored = false; //extra delay flag to prevent instant exit and restart

  while (!valueHasBeenStored) { 

    if (checkJoystick() == Right) { right_was_down = true; delay(100); }
    valueToSet = changeTime(valueToSet, minValue, maxValue, xPos2, yPos);

    if (checkJoystick() == Right && right_was_down == true) {
      EEPROM.writeByte(eepromNumber, valueToSet);
      myScreen.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
      myScreen.setCursor(xPos2, yPos);          
      printDigits(valueToSet);
      setValueStored = true;  
      right_was_down = false;
    }   
    if (setValueStored) {
      delay(200);
      valueHasBeenStored = true;
    }   
  }
}

//===========================================================================================================================
//                                                     AUTOMATED TIMER FUNCTIONS
//===========================================================================================================================
void automatedSwitching() {
  
  currentHour = getLocalHour();
  currentMinute = getLocalMinutes();
  currentTime = getCurrentTime();

//====================================LIGHTS=====================================

  int lightOnHour = EEPROM.readByte(0), lightOnMinute = EEPROM.readByte(1), lightOffHour = EEPROM.readByte(2), lightOffMinute = EEPROM.readByte(3);

  int onTime = (lightOnHour * 100) + lightOnMinute;
  int offTime = (lightOffHour * 100) + lightOffMinute;

  switchTimer(0, onTime, offTime); //switchTimer(relay, ON, OFF, countdown, secondsOrNot, timerNumber)    

//====================================VENTILATION================================
  
  int delayedOn = onTime + 30; //30 minutes after light on
  int delayedOff = offTime - 30; //30 minutes before light off
  //switchTimer (1, delayedOn, delayedOff, 0, false, 0); // fans should not be on by default

//====================================SPRINKLERS=================================

  int rain1Hour = EEPROM.readByte(4),   rain1Minute = EEPROM.readByte(5),  rain1Duration = EEPROM.readByte(6);
  int rain2Hour = EEPROM.readByte(8),   rain2Minute = EEPROM.readByte(9),  rain2Duration = EEPROM.readByte(10);
  int rain3Hour = EEPROM.readByte(12),  rain3Minute = EEPROM.readByte(13), rain3Duration = EEPROM.readByte(14);
  int rain4Hour = EEPROM.readByte(16),  rain4Minute = EEPROM.readByte(17), rain4Duration = EEPROM.readByte(18);
  int rain5Hour = EEPROM.readByte(20),  rain5Minute = EEPROM.readByte(21), rain5Duration = EEPROM.readByte(22);

  if (!noRain) {
    switchTimer(2, rain1Hour, rain1Minute, rain1Duration, true, 0);
    switchTimer(2, rain2Hour, rain2Minute, rain2Duration, true, 1); 
    switchTimer(2, rain3Hour, rain3Minute, rain3Duration, true, 2); 
    switchTimer(2, rain4Hour, rain4Minute, rain4Duration, true, 3); 
    switchTimer(2, rain5Hour, rain5Minute, rain5Duration, true, 4); 
  }
//====================================BACKWALL DRIP==============================

  int drip1Hour = EEPROM.readByte(24),   drip1Minute = EEPROM.readByte(25),  drip1Duration = EEPROM.readByte(26);
  int drip2Hour = EEPROM.readByte(28),   drip2Minute = EEPROM.readByte(29),  drip2Duration = EEPROM.readByte(30);

  switchTimer(3, drip1Hour, drip1Minute, drip1Duration, true, 0); 
  switchTimer(3, drip2Hour, drip2Minute, drip2Duration, true, 1); 

//====================================FOGGER=====================================

  int fogOnHour = EEPROM.readByte(32), fogOnMinute = EEPROM.readByte(33), fogOffHour = EEPROM.readByte(34), fogOffMinute = EEPROM.readByte(35);

  int fogOn = (fogOnHour * 100) + fogOnMinute;
  int fogOff = (fogOffHour * 100) + fogOffMinute;
  
  switchTimer(4, fogOn, fogOff); 
  
  if (currentTime >= delayedOn && currentTime <= fogOn) {
    humidityMonitoring = true;
  } else if (currentTime >= fogOn && currentTime <= delayedOn) {
    humidityMonitoring = false;
  }

//====================================WINDOW HEAT================================

  //switchTimer(6, onTime, offTime, 0, false, 0); //for now linked to temperature controll. Will eventuelly be on permanent and fans over the heat to controll the temp

//====================================AIRPUMP====================================
  
  int airpump1Hour = EEPROM.readByte(38),  airpump1Minute = EEPROM.readByte(39), airpump1Duration = EEPROM.readByte(40);
  int airpump2Hour = EEPROM.readByte(42),  airpump2Minute = EEPROM.readByte(43), airpump2Duration = EEPROM.readByte(44);
  int airpump3Hour = EEPROM.readByte(46),  airpump3Minute = EEPROM.readByte(47), airpump3Duration = EEPROM.readByte(48);
  int airpump4Hour = EEPROM.readByte(50),  airpump4Minute = EEPROM.readByte(51), airpump4Duration = EEPROM.readByte(52);
  int airpump5Hour = EEPROM.readByte(54),  airpump5Minute = EEPROM.readByte(55), airpump5Duration = EEPROM.readByte(56);

  switchTimer(7, airpump1Hour, airpump1Minute, airpump1Duration, true, 0); 
  switchTimer(7, airpump2Hour, airpump2Minute, airpump2Duration, true, 1); 
  switchTimer(7, airpump3Hour, airpump3Minute, airpump3Duration, true, 2); 
  switchTimer(7, airpump4Hour, airpump4Minute, airpump4Duration, true, 3); 
  switchTimer(7, airpump5Hour, airpump5Minute, airpump5Duration, true, 4); 

}

void switchTimer(int relayNumber, int timeOne, int timeTwo, int seconds, bool secondsOrNot, int timerNumber) { 
  //if secondsOrNot is true then timeOne is hours and timeTwo is minutes. If !secondsOrNot: timeOne is ON time and timeTwo is OFF time  
  //seconds, secondsOrNot and timerNumber have default values and don't have to be declared
  
  if (!secondsOrNot) {
    if (currentTime >= timeOne && currentTime < timeTwo) {
      if (digitalRead(relay[relayNumber]) == LOW) {   
        digitalWrite(relay[relayNumber], HIGH);
        outputDebug("Timer ");
        outputDebug(relayNumber); 
        outputDebugln(" on");
      } 
    }
    if (currentTime < timeOne || currentTime >= timeTwo) {
      if (digitalRead(relay[relayNumber]) == HIGH) {   
        digitalWrite(relay[relayNumber], LOW); 
        outputDebug("Timer ");
        outputDebug(relayNumber); 
        outputDebugln(" off");
      } 
    }     
  }
  
  if (secondsOrNot) {
    if (currentHour == timeOne && currentMinute == timeTwo && hasNotRunYet == true) {
      
      digitalWrite(relay[relayNumber], HIGH);
      countdown[timerNumber] = millis(); 
      hasNotRunYet = false; //flag prevents being activated more than once
      timerActive[timerNumber] = true;

      outputDebug("relay ");
      outputDebug(relayNumber); 
      outputDebug(" on with ");
      outputDebug(seconds); 
      outputDebugln(" seconds countdown");      
    }    

    if (timerActive[timerNumber] == true && digitalRead(relay[relayNumber]) == HIGH) {       
      if (millis() - countdown[timerNumber] > (seconds * 1000)) { //seconds to milliseconds 

        digitalWrite(relay[relayNumber], LOW);
        timerActive[timerNumber] = false;  

        outputDebug("relay ");
        outputDebug(relayNumber); 
        outputDebugln(" off");
      }
    }   

    if (hasNotRunYet == false) { 
      if (millis() - countdown[timerNumber] > resetDelay) { 
        hasNotRunYet = true;  
      
        outputDebug("hasNotYetRun reset");    
      }
    }

  } 

}

void fanPause() {

  if (digitalRead(relay[2]) == HIGH && !climateControlPause) { 
    pauseStart = millis();
    digitalWrite(pwmPin_1, 0); //fans off
    outputDebugln("ClimateControl paused");    
    climateControlPause = true;
  }
 
  if (climateControlPause) { 
    if (millis() - pauseStart > fanPauseTime) {  
      climateControlPause = false;  
      outputDebugln("Pause ended");  
    }
  }
}

1 Like

What's the largest number that an int variable can hold?

1 Like

a little over 2.1 billion?

From the arduino reference:
"an int stores a 16-bit (2-byte) value. This yields a range of -32,768 to 32,767"

Always use "unsigned long" for any variables dealing with millis().

1 Like

What type of Arduino do you have?

alright sure, but that is what I am doing, aren't I?

countdown[] is an unsigned long

Giga R1

ah but this isnt related to millis I now see. I've put int resetDelay = 90000

I'll change resetDelay to a long then

edit: this did not fix it

You did mean to say you changes it to unsigned long, right?

Perhaps it makes no difference, but it would be good to take the advices given thoroughly.

A simple test.


I'm not in the lab, but I can read a bit and wonder if you have inadvertently assigned one of the timer slots for multiple uses.

    switchTimer(2, rain1Hour, rain1Minute, rain1Duration, true, 0);
    switchTimer(2, rain2Hour, rain2Minute, rain2Duration, true, 1); 

// and not much later 
   
//====================================BACKWALL DRIP==============================

 

    switchTimer(3, drip1Hour, drip1Minute, drip1Duration, true, 0); 
    switchTimer(3, drip2Hour, drip2Minute, drip2Duration, true, 1); 

If I could now, I would grab away that function and build a test sketch for it. I may not be the only one who reads, but then likes to run and often add copious serial printing to confirm what reading has led me to think the code is, or should be doing.


Word.

a7

Unless told otherwise, the compiler will perform this calculation using integer arithmetic.

Try

Serial.print(60 * 60 * 24 * 1000);

to see what I mean.

Use (60UL * 60 * 24 * 1000) to force long integer arithmetic at the beginning

Check all your integer expressions and make sure that they will not be out of bounds, such this and several others

1 Like

Question:

Does any of your timed intervals go across midnight?

I don’t think this syntax will fly

EDIT:
I don’t think this will do what you want

2 Likes

...it does if you want behavior exactly like:

x=0;
sensorName = "DHT22-1. ";
2 Likes

"zero short-circuit-boolean-OR 6" is bool true

@alto777 I've just woken up (kwarter to 5 in the morning) with the thought of the timerNumbers starting at 0 for rain, drip and airpump. I've changed it so timerNumber[] and thus countdown[] will number 0-11 so each one is unique. I'll test it in a few hours but hopefully now I can get a few more hours sleep instead turning around over and over.

@mancera1979
None go over midnight, and all other code including the sensorName bit has run stable for the past 3 months

turns out: nope.

I've split the function in two. switchTimer now only does simple on/off functions and switchCountdown now only does countdowns. Changed all function calls so they have a unique timerNumber and changed the hasNotRunYet boolean into array hasNotRunYet[timerNumber] so it, too, is unique.

code:


#define numberOfTimers 12
unsigned long countdown[numberOfTimers];
bool timerActive[numberOfTimers] = {false};
bool hasNotRunYet[numberOfTimers] = {true};

void automatedSwitching() {
  
  currentHour = getLocalHour();
  currentMinute = getLocalMinutes();
  currentTime = getCurrentTime();

//====================================LIGHTS=====================================

  int lightOnHour = EEPROM.readByte(0), lightOnMinute = EEPROM.readByte(1), lightOffHour = EEPROM.readByte(2), lightOffMinute = EEPROM.readByte(3);

  int onTime = (lightOnHour * 100) + lightOnMinute;
  int offTime = (lightOffHour * 100) + lightOffMinute;

  switchTimer(0, onTime, offTime); //switchTimer(relay, ON, OFF, countdown, secondsOrNot, timerNumber)    

//====================================VENTILATION================================
  
  int delayedOn = onTime + 30; //30 minutes after light on
  int delayedOff = offTime - 30; //30 minutes before light off
  //switchTimer (1, delayedOn, delayedOff); // fans should not be on by default

//====================================SPRINKLERS=================================

  int rain1Hour = EEPROM.readByte(4),   rain1Minute = EEPROM.readByte(5),  rain1Duration = EEPROM.readByte(6);
  int rain2Hour = EEPROM.readByte(8),   rain2Minute = EEPROM.readByte(9),  rain2Duration = EEPROM.readByte(10);
  int rain3Hour = EEPROM.readByte(12),  rain3Minute = EEPROM.readByte(13), rain3Duration = EEPROM.readByte(14);
  int rain4Hour = EEPROM.readByte(16),  rain4Minute = EEPROM.readByte(17), rain4Duration = EEPROM.readByte(18);
  int rain5Hour = EEPROM.readByte(20),  rain5Minute = EEPROM.readByte(21), rain5Duration = EEPROM.readByte(22);

  if (!noRain) {
    switchCountdown(2, rain1Hour, rain1Minute, rain1Duration, 0);
    switchCountdown(2, rain2Hour, rain2Minute, rain2Duration, 1); 
    switchCountdown(2, rain3Hour, rain3Minute, rain3Duration, 2); 
    switchCountdown(2, rain4Hour, rain4Minute, rain4Duration, 3); 
    switchCountdown(2, rain5Hour, rain5Minute, rain5Duration, 4); 
  }
//====================================BACKWALL DRIP==============================

  int drip1Hour = EEPROM.readByte(24),   drip1Minute = EEPROM.readByte(25),  drip1Duration = EEPROM.readByte(26);
  int drip2Hour = EEPROM.readByte(28),   drip2Minute = EEPROM.readByte(29),  drip2Duration = EEPROM.readByte(30);

  switchCountdown(3, drip1Hour, drip1Minute, drip1Duration, 5); 
  switchCountdown(3, drip2Hour, drip2Minute, drip2Duration, 6); 

//====================================FOGGER=====================================

  int fogOnHour = EEPROM.readByte(32), fogOnMinute = EEPROM.readByte(33), fogOffHour = EEPROM.readByte(34), fogOffMinute = EEPROM.readByte(35);

  int fogOn = (fogOnHour * 100) + fogOnMinute;
  int fogOff = (fogOffHour * 100) + fogOffMinute;
  
  switchTimer(4, fogOn, fogOff); 
  
  if (currentTime >= delayedOn && currentTime <= fogOn) {
    humidityMonitoring = true;
  } else if (currentTime >= fogOn && currentTime <= delayedOn) {
    humidityMonitoring = false;
  }

//====================================WINDOW HEAT================================

  //switchTimer(6, onTime, offTime); //for now linked to temperature controll. Will eventuelly be on permanent and fans over the heat to controll the temp

//====================================AIRPUMP====================================
  
  int airpump1Hour = EEPROM.readByte(38),  airpump1Minute = EEPROM.readByte(39), airpump1Duration = EEPROM.readByte(40);
  int airpump2Hour = EEPROM.readByte(42),  airpump2Minute = EEPROM.readByte(43), airpump2Duration = EEPROM.readByte(44);
  int airpump3Hour = EEPROM.readByte(46),  airpump3Minute = EEPROM.readByte(47), airpump3Duration = EEPROM.readByte(48);
  int airpump4Hour = EEPROM.readByte(50),  airpump4Minute = EEPROM.readByte(51), airpump4Duration = EEPROM.readByte(52);
  int airpump5Hour = EEPROM.readByte(54),  airpump5Minute = EEPROM.readByte(55), airpump5Duration = EEPROM.readByte(56);

  switchCountdown(7, airpump1Hour, airpump1Minute, airpump1Duration, 7); 
  switchCountdown(7, airpump2Hour, airpump2Minute, airpump2Duration, 8); 
  switchCountdown(7, airpump3Hour, airpump3Minute, airpump3Duration, 9); 
  switchCountdown(7, airpump4Hour, airpump4Minute, airpump4Duration, 10); 
  switchCountdown(7, airpump5Hour, airpump5Minute, airpump5Duration, 11); 

}

void switchTimer(int relayNumber, int startTime, int stopTime) {   

  if (currentTime >= startTime && currentTime < stopTime) {
    if (digitalRead(relay[relayNumber]) == LOW) {   
      digitalWrite(relay[relayNumber], HIGH);
      outputDebug("Timer ");
      outputDebug(relayNumber); 
      outputDebugln(" on");
    } 
  }
  if (currentTime < startTime || currentTime >= stopTime) {
    if (digitalRead(relay[relayNumber]) == HIGH) {   
      digitalWrite(relay[relayNumber], LOW); 
      outputDebug("Timer ");
      outputDebug(relayNumber); 
      outputDebugln(" off");
    } 
  }   

}

void switchCountdown(int relayNumber, int startHour, int startMinute, int duration, int timerNumber) { 
  
  if (currentHour == startHour && currentMinute == startMinute && hasNotRunYet[timerNumber] == true) {
    
    digitalWrite(relay[relayNumber], HIGH);
    countdown[timerNumber] = millis(); 
    hasNotRunYet[timerNumber] = false; //flag prevents being activated more than once
    timerActive[timerNumber] = true;

    outputDebug("relay ");
    outputDebug(relayNumber); 
    outputDebug(" on with ");
    outputDebug(duration); 
    outputDebugln(" seconds countdown");      
  }    

  if (timerActive[timerNumber] == true && digitalRead(relay[relayNumber]) == HIGH) {       
    if (millis() - countdown[timerNumber] > (duration * 1000)) { //seconds to milliseconds 

      digitalWrite(relay[relayNumber], LOW);
      timerActive[timerNumber] = false;  

      outputDebug("relay ");
      outputDebug(relayNumber); 
      outputDebugln(" off");
    }
  }   

  if (hasNotRunYet[timerNumber] == false) { 
    if (millis() - countdown[timerNumber] > resetDelay) { 

      hasNotRunYet[timerNumber] = true;  
    
      outputDebug("hasNotYetRun reset");    
    }
  }  

}

So far it's looking alright. the 2 events I put in ran without causing trouble. Now maybe one more hour of sleep :sob: