Nearly SOLVED Using While function is only giving a partial answer

Evening @alto777, thank you for your help. I have done some tidying up of the millis functions and the lcd display code. That has solved these problems, well I think it has. However, that has brought another minor irritation to the fore.
When I activate the Heating Override at Line 211, using the 'Down Button' the system brings on the override with no problem, however it then reverts back to normal control after a couple of seconds. The millis delay function continues until it times out. I have proven this by using serial print at various points. I have not been able to put my finger on where I have made an error in the code. Would you perhaps have a chance to have a look and see if there is something else glaringly wrong.

/* Author : John Marchant G0RJM
 Created : 25/01/2024 - 16/10/2024
 Description : A project to display the current date, time and temperature on the 20x4 LCD screen
 and create a programmable thermostatic heating control of three zones to include frost protection and override facilities.
 This has been loosely based on the RTClib ds3231 example along with many code parts borrowed
 from several other files/sketches and in collaboration with several authors from the
 Arduino Forum in particular @alto777, @blh64, @cattledog, @StefanL38 and @UKHeliBob. */
#include "RTClib.h"
#include <OneWire.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ezButton.h>
#include <DallasTemperature.h>
#include <EEPROM.h>
#include <DST_RTC.h>

#define ONE_WIRE_BUS_1 3
#define ONE_WIRE_BUS_2 4
#define ONE_WIRE_BUS_3 5
#define TEMPERATURE_PRECISION 11  // 0.125deg resolution
#define buzzer_pin A0

OneWire oneWire_range(ONE_WIRE_BUS_1);
OneWire oneWire_club(ONE_WIRE_BUS_2);
OneWire oneWire_airgun(ONE_WIRE_BUS_3);

const byte range_relay = 10;  // Relay outputs
const byte club_relay = 11;
const byte airgun_relay = 12;
const byte boiler_relay = 13;

float range_temperature;
float club_temperature;
float airgun_temperature;

float range_temperature_setpoint;   // C
float club_temperature_setpoint;    // C
float airgun_temperature_setpoint;  // C
const int frost_setpoint = 16.0;    // C
const float deadzone = 1.0;         // C

ezButton leftbutton(6);   // create ezButton object that is attached to pin 6;
ezButton upbutton(7);     // create ezButton object that is attached to pin 7;
ezButton downbutton(8);   // create ezButton object that is attached to pin 8;
ezButton rightbutton(9);  // create ezButton object that is attached to pin 9;

const int ledPin = 13;

const unsigned menuOnTime = 20000;
const unsigned upButtonTime = 50;
const unsigned downButtonTime = 50;
const unsigned printInterval = 3000;
const unsigned overrideRunTime = 45000;
const unsigned backlightOnTime = 30000;

int value = 0;
int selector = 0;

int EEPROM_address_1 = 0;  // Address of the location of range setpoint temperature
int EEPROM_address_2 = 4;  // Address of the location of club setpoint temperature
int EEPROM_address_3 = 8;  // Address of the location of airgun setpoint temperature

unsigned long startMillis = 0;
unsigned long menuOffTime = 0;
unsigned long currentMillis = 0;
unsigned long previousMillis = 0;
unsigned long previousMillisMenu = 0;
unsigned long previousMillisBacklight = 0;
unsigned long previousMillisOverrideStart = 0;
unsigned long previousMillisAdjustSetpointUp = 0;
unsigned long previousMillisAdjustSetpointDown = 0;

bool isMenuOn = false;                // if = True initiiates the setup menu
bool isRangeHeatOn = false;           // if = True this allows the Range heating
bool isClubHeatOn = false;            // if = True this allows the Club heating
bool isAirgunHeatOn = false;          // if = True this allows the Airgun heating
bool isSetpointSave = false;          // if = True allows the new setpoints to be saved to EEPROM
bool isTimeControlOn = false;         // if = True allows normal time control function
bool isHeatingOverride = false;       // if = True allows the heating override facility
bool isAdjustSetpointUp = false;      // if = True allows the Setpoint increment
bool isAdjustSetpointDown = false;    // if = True allows the Setpoint decrement
bool isRangeSetpointAdjust = false;   // if = True allows the range setpoint to be adjusted
bool isClubSetpointAdjust = false;    // if = True allows the club setpoint to be adjusted
bool isAirgunSetpointAdjust = false;  // if = True allows the airgun setpoint to be adjusted

DallasTemperature range_sensor(&oneWire_range);
DallasTemperature club_sensor(&oneWire_club);
DallasTemperature airgun_sensor(&oneWire_airgun);

enum { SUNDAY,
       MONDAY,
       TUESDAY,
       WEDNESDAY,
       THURSDAY,
       FRIDAY,
       SATURDAY };

const int On_Normal_Range = 16 * 60UL + 00;    // 16:00 Normal ON time
const int Off_Normal_Range = 19 * 60UL + 30;   // 21:30 Normal OFF time
const int On_Normal_Club = 16 * 60UL + 00;     // 16:00 Normal ON time
const int Off_Normal_Club = 19 * 60UL + 30;    // 21:30 Normal OFF time
const int On_Normal_Airgun = 16 * 60UL + 00;   // 16:00 Normal ON time
const int Off_Normal_Airgun = 19 * 60UL + 30;  // 21:30 Normal OFF time
const int On_Sunday = 12 * 60UL + 00;          // 12:00 Sunday ON time
const int Off_Sunday = 17 * 60UL + 30;         // 17:30 Sunday OFF time

LiquidCrystal_I2C lcd(0X27, 20, 4);
RTC_DS3231 rtc;
DST_RTC dst_rtc;  // DST object

// Define US or EU rules for DST comment out as required. More countries could be added with different rules in DST_RTC.cpp
// const char rulesDST[] = "US"; // US DST rules
const char rulesDST[] = "EU";  // EU DST rules

char daysOfTheWeek[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
byte char_temp[8] = { B00100, B01010, B01010, B01110, B01110, B11111, B11111, B01110 };  // for thermometer icon

void setup() {

  pinMode(range_relay, OUTPUT);
  pinMode(club_relay, OUTPUT);
  pinMode(airgun_relay, OUTPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(buzzer_pin, OUTPUT);

  leftbutton.setDebounceTime(50);   // set debounce time to 50 milliseconds
  upbutton.setDebounceTime(50);     // set debounce time to 50 milliseconds
  downbutton.setDebounceTime(50);   // set debounce time to 50 milliseconds
  rightbutton.setDebounceTime(50);  // set debounce time to 50 milliseconds

  Serial.begin(9600);

  // EEPROM.put(EEPROM_address_1, range_temperature_setpoint);   // write the float value to EEPROM ONLY used to initially set up setpoint
  // EEPROM.put(EEPROM_address_2, club_temperature_setpoint);    // write the float value to EEPROM ONLY used to initially set up setpoint
  // EEPROM.put(EEPROM_address_3, airgun_temperature_setpoint);  // write the float value to EEPROM ONLY used to initially set up setpoint

  RetrieveSetpoint();

  startMillis = millis();

  Wire.begin();
  rtc.begin();

  lcd.init();
  lcd.backlight();
  lcd.createChar(0, char_temp);
  lcd.setCursor(2, 0);
  lcd.print("G0RJM Three Zone");
  lcd.setCursor(1, 1);
  lcd.print("Temperature Control");
  lcd.setCursor(4, 2);
  lcd.print("Take it easy!");
  lcd.setCursor(4, 3);
  lcd.print("Working on it");

  delay(1000);
  lcd.clear();
  if (rtc.lostPower()) {
    Serial.println("RTC is NOT running!");
    // Uncomment the following line to set the RTC to the date & time this sketch was compiled
    // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // rtc.adjust(DateTime(2024,4,24,21,49,00));
    /* DST? If we're in it, let's subtract an hour from the RTC time to keep our DST calculation correct. This gives us
    Standard Time which our DST check will add an hour back to if we're in DST.
    */
    DateTime standardTime = rtc.now();
    if (dst_rtc.checkDST(standardTime) == true) {  // check whether we're in DST right now. If we are, subtract an hour.
      standardTime = standardTime.unixtime() - 3600;
    }
    rtc.adjust(standardTime);
  }

  Serial.println("G0RJM Three zone Temperature Control");

  range_sensor.begin();
  club_sensor.begin();
  airgun_sensor.begin();

  range_sensor.setResolution(TEMPERATURE_PRECISION);
  club_sensor.setResolution(TEMPERATURE_PRECISION);
  airgun_sensor.setResolution(TEMPERATURE_PRECISION);

}  // End of setup

void loop() {

  //       ("ABCDEFGHIJ1234567890")

  //-------------------------Button control---------------------------//

  leftbutton.loop();   // MUST call the loop() function first
  upbutton.loop();     // MUST call the loop() function first
  downbutton.loop();   // MUST call the loop() function first
  rightbutton.loop();  // MUST call the loop() function first

  // -----------------------Backlight display-------------------------//

  unsigned long currentMillis = millis();  // all time elements are data type unsigned long

  if ((isHeatingOverride) || (leftbutton.isPressed() || (rightbutton.isPressed() || (upbutton.isPressed())))) {
    lcd.backlight();
    previousMillisBacklight = currentMillis;
  }
  if ((!isHeatingOverride) && (currentMillis - previousMillisBacklight >= backlightOnTime)) {
    lcd.noBacklight();
    isMenuOn = false;
  }

  //---------------------------Heating Override-----------------------//

  if ((downbutton.isPressed() && (!isMenuOn) && (!isHeatingOverride))) {
    for (int n = 0; n < 1; n++) {
      isHeatingOverride = true;  // Establishes the heating override
      HeatingOverride();
      previousMillisOverrideStart = currentMillis;
    }
  }
  if (isHeatingOverride) {
    if (currentMillis - previousMillisOverrideStart >= overrideRunTime)  // Automatically cancels the override
    {
      HeatingOverrideComplete();
    }
  }
  if ((downbutton.isPressed() && (!isMenuOn) && (isHeatingOverride)))  // Manually cancels the override
  {
    if (currentMillis - previousMillisOverrideStart >= 10000) {
      HeatingOverrideComplete();
    }
  }

  //------------------------Boiler Interlock--------------------------//

  if ((digitalRead(range_relay) == HIGH) || (digitalRead(club_relay) == HIGH) || (digitalRead(airgun_relay) == HIGH)) {
    digitalWrite(boiler_relay, HIGH);
  } else {
    digitalWrite(boiler_relay, LOW);
  }

  if (!isMenuOn) {
    TemperatureDisplay();
  }

  //------------------------------Menu--------------------------------//

  if (leftbutton.isPressed()) {
    isMenuOn = true;  // Initiiates the setup menu
    SettingMenu();
    selector++;  // this is done after SettingMenu(), so case 0 will be executed on the first button press
    if (selector > 5) {
      selector = 0;
    }
    previousMillisMenu = currentMillis;
  }

  if (isMenuOn) {
    if (currentMillis - previousMillisMenu >= menuOnTime) {
      isMenuOn = false;
      selector = 6;
      delay(2000);
      lcd.noBacklight();
    }
  }

  //------------------------------------------------------------------//

  if (upbutton.isPressed()) {
    isAdjustSetpointUp = true;  // Setpoint adjustment upwards is available
    previousMillisAdjustSetpointUp = currentMillis;
  }

  if (isAdjustSetpointUp) {
    if (currentMillis - previousMillisAdjustSetpointUp >= upButtonTime) {
      isAdjustSetpointUp = false;
    }
  }

  //------------------------------------------------------------------//

  if (isAdjustSetpointUp && isRangeSetpointAdjust)  // If True, this allows the Range setpoint to be adjusted
  {
    range_temperature_setpoint += 0.1;  // Range Setpoint increment
    Serial.println("range_temperature_setpoint_up");
    RangeDisplay();
    Buzzer();
  }

  if (isAdjustSetpointUp && isClubSetpointAdjust)  // If True, this allows the Club setpoint to be adjusted
  {
    club_temperature_setpoint += 0.1;  // Club Setpoint increment
    Serial.println("club_temperature_setpoint_up");
    ClubDisplay();
    Buzzer();
  }

  if (isAdjustSetpointUp && isAirgunSetpointAdjust)  // If True, this allows the Airgun setpoint to be adjusted
  {
    airgun_temperature_setpoint += 0.1;  // Airgun Setpoint increment
    Serial.println("airgun_temperature_setpoint_up");
    AirgunDisplay();
    Buzzer();
  }

  //------------------------------------------------------------------//

  if (downbutton.isPressed()) {
    isAdjustSetpointDown = true;  // Setpoint adjustment Downwards is available
    previousMillisAdjustSetpointDown = currentMillis;
  }
  if (isAdjustSetpointDown) {
    if (currentMillis - previousMillisAdjustSetpointDown >= downButtonTime) {
      isAdjustSetpointDown = false;
    }
  }

  if (isAdjustSetpointDown && isRangeSetpointAdjust)  // If True, this allows the Range setpoint to be adjusted
  {
    range_temperature_setpoint -= 0.1;  // Range Setpoint decrement
    Serial.println("range_temperature_setpoint_down");
    RangeDisplay();
    Buzzer();
  }

  if (isAdjustSetpointDown && isClubSetpointAdjust)  // If True, this allows the Club setpoint to be adjusted
  {
    club_temperature_setpoint -= 0.1;  // Club Setpoint decrement
    Serial.println("club_temperature_setpoint_down");
    ClubDisplay();
    Buzzer();
  }

  if (isAdjustSetpointDown && isAirgunSetpointAdjust)  // If True, this allows the Airgun setpoint to be adjusted
  {
    airgun_temperature_setpoint -= 0.1;  // Airgun Setpoint decrement
    Serial.println("airgun_temperature_setpoint_down");
    AirgunDisplay();
    Buzzer();
  }

  //-----------------------Setpoint Save------------------------------//

  if (isSetpointSave && isAdjustSetpointUp && isAdjustSetpointDown)  // If True, the new temperature setpoints are saved to EEPROM
  {
    for (int n = 0; n < 1; n++) {
      Serial.println("Changes saved");
      EEPROM.put(EEPROM_address_1, range_temperature_setpoint);   // write the new range setpoint value to EEPROM, only if it has changed
      EEPROM.put(EEPROM_address_2, club_temperature_setpoint);    // write the new club setpoint value to EEPROM, only if it has changed
      EEPROM.put(EEPROM_address_3, airgun_temperature_setpoint);  // write the new airgun setpoint value to EEPROM, only if it has changed

      delay(2000);
      RetrieveSetpoint();
      lcd.setCursor(3, 3);
      lcd.print("Settings saved");
      delay(3000);
      lcd.clear();
      TemperatureDisplay();
      HeatingAvailable();
    }
  }
  //----------------------------Print delay---------------------------//

  if (currentMillis - previousMillis >= printInterval)  // has enough time passed yet?
  {
    previousMillis = previousMillis + printInterval;  // If yes, set the time del;ay for the next print event
                                                      // capture & print data, or print latest data, etc.
                                                      // the closing curly brace at end of loop.

    //--------------------Daylight Saving Setting---------------------//

    DateTime standardTime = rtc.now();

    Serial.println("Standard Time");
    printTheTime(standardTime);

    DateTime theTime = dst_rtc.calculateTime(standardTime);  // takes into account DST

    Serial.println("Time adjusted for Daylight Saving Time");
    printTheTime(theTime);


    //-----------------------Timeswitch Control-----------------------//

    int currentTime = theTime.hour() * 60UL + theTime.minute();

    //--------------------Timed heating setting-----------------------//

    if (theTime.dayOfTheWeek() != SUNDAY) {
      // Monday to Saturday so use normal times
      if (currentTime < On_Normal_Range || currentTime > Off_Normal_Range) {
        // this is outside normal range heating time
        isRangeHeatOn = false;
      } else {
        // this is within normal range heating time
        isRangeHeatOn = true;
      }
    } else {
      // Sunday times
      if (currentTime < On_Sunday || currentTime > Off_Sunday) {
        // this is outside Sunday range heating time
        isRangeHeatOn = false;
      } else {
        // this is within Sunday range heating time
        isRangeHeatOn = true;
      }
    }
    if (theTime.dayOfTheWeek() != SUNDAY) {
      // Monday to Saturday so use normal times
      if (currentTime < On_Normal_Club || currentTime > Off_Normal_Club) {
        // this is outside normal club heating time
        isClubHeatOn = false;
      } else {
        // this is within normal club heating time
        isClubHeatOn = true;
      }
    } else {
      // Sunday times
      if (currentTime < On_Sunday || currentTime > Off_Sunday) {
        // this is outside Sunday club heating time
        isClubHeatOn = false;
      } else {
        // this is within Sunday club heating time
        isClubHeatOn = true;
      }
    }
    if (theTime.dayOfTheWeek() != SUNDAY) {
      // Monday to Saturday so use normal times
      if (currentTime < On_Normal_Airgun || currentTime > Off_Normal_Airgun) {
        // this is outside normal airgun heating time
        isAirgunHeatOn = false;
      } else {
        // this is within normal airgun heating time
        isAirgunHeatOn = true;
      }
    } else {
      // Sunday times
      if (currentTime < On_Sunday || currentTime > Off_Sunday) {
        // this is outside Sunday airgun heating time
        isAirgunHeatOn = false;
      } else {
        // this is within Sunday airgun heating time
        isAirgunHeatOn = true;
      }
    }

    //---------------------Date & Time display----------------------//

    if ((isTimeControlOn) || (!isMenuOn)) {
      lcd.setCursor(0, 0);
      lcd.print(daysOfTheWeek[theTime.dayOfTheWeek()]);
      lcd.setCursor(3, 0);
      lcd.print(":");
      lcd.setCursor(4, 0);
      if (theTime.day() < 10) lcd.print('0');
      lcd.print(theTime.day(), DEC);
      lcd.print(':');
      if (theTime.month() < 10) lcd.print('0');
      lcd.print(theTime.month(), DEC);
      lcd.print(':');
      lcd.print(theTime.year(), DEC);
      lcd.setCursor(15, 0);
      if (theTime.hour() < 10) lcd.print('0');
      lcd.print(theTime.hour(), DEC);
      lcd.print(':');
      if (theTime.minute() < 10) lcd.print('0');
      lcd.print(theTime.minute(), DEC);
      Serial.println("Something else");
      HeatingAvailable();
    }

    //------------------------Serial print---------------------------//

    Serial.print("Requesting temperatures...");
    range_sensor.requestTemperatures();
    club_sensor.requestTemperatures();
    airgun_sensor.requestTemperatures();
    Serial.println(" done");
    Serial.print("Range: ");
    Serial.println(range_sensor.getTempCByIndex(0));
    Serial.print("Club: ");
    Serial.println(club_sensor.getTempCByIndex(0));
    Serial.print("Airgun: ");
    Serial.println(airgun_sensor.getTempCByIndex(0));
  }

}  // End of loop

//----------------------Print time to serial-------------------------//
void printTheTime(DateTime theTimeP) {

  Serial.print(theTimeP.year(), DEC);
  Serial.print('/');
  Serial.print(theTimeP.month(), DEC);
  Serial.print('/');
  Serial.print(theTimeP.day(), DEC);
  Serial.print(' ');
  Serial.print(theTimeP.hour(), DEC);
  Serial.print(':');
  Serial.print(theTimeP.minute(), DEC);
  Serial.print(':');
  Serial.print(theTimeP.second(), DEC);
  Serial.println();
}

//------------------------Retrieve setpoint--------------------------//
void RetrieveSetpoint() {

  EEPROM.get(EEPROM_address_1, range_temperature_setpoint);   //retrieve the range setpoint from EEPROM
  EEPROM.get(EEPROM_address_2, club_temperature_setpoint);    //retrieve the club setpoint from EEPROM
  EEPROM.get(EEPROM_address_3, airgun_temperature_setpoint);  //retrieve the airgun setpoint from EEPROM

  Serial.print("Range Temperature Setpoint  - ");
  Serial.print(range_temperature_setpoint);
  Serial.print(" Stored at addr ");
  Serial.println(EEPROM_address_1);
  Serial.print("Club Temperature Setpoint   - ");
  Serial.print(club_temperature_setpoint);
  Serial.print(" Stored at addr ");
  Serial.println(EEPROM_address_2);
  Serial.print("Airgun Temperature Setpoint - ");
  Serial.print(airgun_temperature_setpoint);
  Serial.print(" Stored at addr ");
  Serial.println(EEPROM_address_3);
}

//------------------------Setting Menu-------------------------------//
void SettingMenu() {

  switch (selector) {
    case 0:
      Serial.println("Case 0");  // General menu
      MenuScreen();
      break;
    case 1:
      Serial.println("Case 1");  // Displays the Range setpoint
      RangeDisplay();
      Buzzer();
      break;
    case 2:
      Serial.println("Case 2");  // Displays the Club setpoint
      ClubDisplay();
      Buzzer();
      break;
    case 3:
      Serial.println("Case 3");  // Displays the Airgun setpoint
      AirgunDisplay();
      Buzzer();
      break;
    case 4:
      Serial.println("Case 4");  // Instructs how to save the new settings
      SaveDisplay();
      break;
    case 5:
      Serial.println("Case 5");  // Reminds you to save
      lcd.clear();
      lcd.setCursor(3, 2);
      lcd.print("Did you save?");
      Buzzer();
  }
}

//---------------------------Buzzer----------------------------------//
void Buzzer() {

  {
    digitalWrite(buzzer_pin, HIGH);
    delay(100);
    digitalWrite(buzzer_pin, LOW);
  }
}

//-------------------------Menu screen-------------------------------//
void MenuScreen() {

  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 1);
  lcd.print("Range: ");
  lcd.print(range_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  lcd.setCursor(0, 2);
  lcd.print("Club:  ");
  lcd.print(club_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  lcd.setCursor(0, 3);
  lcd.print("Airgun:");
  lcd.print(airgun_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  isSetpointSave = false;
}

//-----------------------Setting Displays---------------------------//
void RangeDisplay() {

  isRangeSetpointAdjust = true;
  isClubSetpointAdjust = false;
  isAirgunSetpointAdjust = false;
  Serial.println("RangeChange");
  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 1);
  lcd.print("Range: ");
  lcd.print(range_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
}

//-------------------------------------------------------------------//
void ClubDisplay() {

  isClubSetpointAdjust = true;
  isRangeSetpointAdjust = false;
  isAirgunSetpointAdjust = false;
  Serial.println("ClubChange");
  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 2);
  lcd.print("Club:  ");
  lcd.print(club_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
}

//-------------------------------------------------------------------//
void AirgunDisplay() {

  isAirgunSetpointAdjust = true;
  isRangeSetpointAdjust = false;
  isClubSetpointAdjust = false;
  Serial.println("AirgunChange");
  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 3);
  lcd.print("Airgun:");
  lcd.print(airgun_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
}

//------------------------Save Display-------------------------------//
void SaveDisplay() {

  isRangeSetpointAdjust = false;
  isClubSetpointAdjust = false;
  isAirgunSetpointAdjust = false;
  Serial.println("SavingChanges");
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("To save the changes");
  lcd.setCursor(2, 1);
  lcd.print("Press Up & Down");
  lcd.setCursor(5, 2);
  lcd.print("Together");
  isSetpointSave = true;
}

//---------------------Temperature display - ------------------------//
void TemperatureDisplay() {

  lcd.setCursor(0, 1);  // Range Temperature
  lcd.print("Range: ");
  lcd.print(range_sensor.getTempCByIndex(0), 1);
  lcd.write((char)223);
  lcd.print('C');

  lcd.setCursor(0, 2);  // Club Temperature
  lcd.print("Club:  ");
  lcd.print(club_sensor.getTempCByIndex(0), 1);
  lcd.write((char)223);
  lcd.print('C');

  lcd.setCursor(0, 3);  // Airgun Temperature
  lcd.print("Airgun:");
  lcd.print(airgun_sensor.getTempCByIndex(0), 1);
  lcd.write((char)223);
  lcd.print('C');
}

//------------------------Heating Override---------------------------//
void HeatingOverride() {

  isHeatingOverride = true;
  isRangeHeatOn = true;
  isClubHeatOn = true;
  isAirgunHeatOn = true;
  isTimeControlOn = false;
  Serial.println("Heating Override On");
  HeatingAvailable();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("   OVERRIDE IS ON   ");
}

//--------------------Heating Override Complete----------------------//
void HeatingOverrideComplete() {

  isHeatingOverride = false;
  isRangeHeatOn = false;
  isClubHeatOn = false;
  isAirgunHeatOn = false;
  isTimeControlOn = true;
  lcd.setCursor(0, 0);
  lcd.print("   OVERRIDE IS OFF   ");
  delay(2000);
  lcd.noBacklight();
  Serial.println("Heating Override complete");
}

//-----------------------Heating Available---------------------------//
void HeatingAvailable() {

  RangeHeatingAvailable();
  ClubHeatingAvailable();
  AirgunHeatingAvailable();
  Serial.println("Wacky time");
}

//-------------------------------------------------------------------//
void RangeHeatingAvailable() {

  {
    if (isRangeHeatOn) {
      NormalHeatingRange();
      Serial.println("Heat R");
    } else {
      Serial.println("Frost Prev. R");
      FrostPreventionRange();
    }
  }
}

//-------------------------------------------------------------------//
void ClubHeatingAvailable() {

  {
    if (isClubHeatOn) {
      NormalHeatingClub();
      Serial.println("Heat C");
    } else {
      Serial.println("Frost Prev. C");
      FrostPreventionClub();
    }
  }
}

//-------------------------------------------------------------------//
void AirgunHeatingAvailable() {

  {
    if (isAirgunHeatOn) {
      NormalHeatingAirgun();
      Serial.println("Heat A");
    } else {
      Serial.println("Frost Prev. A");
      FrostPreventionAirgun();
    }
  }
}

//-------------------Frost Prevention Range--------------------------//
void FrostPreventionRange() {

  if ((range_sensor.getTempCByIndex(0) <= (frost_setpoint - deadzone))) {
    digitalWrite(range_relay, HIGH);
    Serial.println("Ra Heating");
    lcd.setCursor(13, 1);
    lcd.print("  ON  F");
  } else {
    if ((range_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone))) {
      digitalWrite(range_relay, LOW);
      lcd.setCursor(13, 1);
      lcd.print("  OFF F");
    }
  }
}

//--------------------Frost Prevention Club-------------------------//
void FrostPreventionClub() {

  if ((club_sensor.getTempCByIndex(0) <= (frost_setpoint - deadzone))) {
    digitalWrite(club_relay, HIGH);
    Serial.println("Cl Heating");
    lcd.setCursor(13, 2);
    lcd.print("  ON  F");
  } else {
    if ((club_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone))) {
      digitalWrite(club_relay, LOW);
      lcd.setCursor(13, 2);
      lcd.print("  OFF F");
    }
  }
}

//---------------------Frost Prevention Airgun-----------------------//
void FrostPreventionAirgun() {

  if ((airgun_sensor.getTempCByIndex(0) <= (frost_setpoint - deadzone))) {
    digitalWrite(airgun_relay, HIGH);
    Serial.println("Ai Heating");
    lcd.setCursor(13, 3);
    lcd.print("  ON  F");
  } else {
    if ((airgun_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone))) {
      digitalWrite(airgun_relay, LOW);
      lcd.setCursor(13, 3);
      lcd.print("  OFF F");
    }
  }
}

//-------------------Normal Heating Range----------------------------//
void NormalHeatingRange() {

  if ((range_sensor.getTempCByIndex(0) <= (range_temperature_setpoint - deadzone))) {
    digitalWrite(range_relay, HIGH);
    Serial.println("Heating R");
    lcd.setCursor(13, 1);
    lcd.print("  ON  T");
  } else {
    if ((range_sensor.getTempCByIndex(0) >= (range_temperature_setpoint + deadzone))) {
      digitalWrite(range_relay, LOW);
      lcd.setCursor(13, 1);
      lcd.print("  OFF T");
    }
  }
}

//--------------------Normal Heating Club ---------------------------//
void NormalHeatingClub() {

  if ((club_sensor.getTempCByIndex(0) <= (club_temperature_setpoint - deadzone))) {
    digitalWrite(club_relay, HIGH);
    Serial.println("Heating C");
    lcd.setCursor(13, 2);
    lcd.print("  ON  T");
  } else {
    if ((club_sensor.getTempCByIndex(0) >= (club_temperature_setpoint + deadzone))) {
      digitalWrite(club_relay, LOW);
      lcd.setCursor(13, 2);
      lcd.print("  OFF T");
    }
  }
}

//---------------------Normal Heating Airgun-------------------------//
void NormalHeatingAirgun() {

  if ((airgun_sensor.getTempCByIndex(0) <= (airgun_temperature_setpoint - deadzone))) {
    digitalWrite(airgun_relay, HIGH);
    Serial.println("Heating A");
    lcd.setCursor(13, 3);
    lcd.print("  ON  T");
  } else {
    if ((airgun_sensor.getTempCByIndex(0) >= (airgun_temperature_setpoint + deadzone))) {
      digitalWrite(airgun_relay, LOW);
      lcd.setCursor(13, 3);
      lcd.print("  OFF T");
    }
  }
}

Please verify that the down button on pin8 is correctly wired, and indeed reads HIGH when not pressed, and LOW when pressed.

The down button is only used twice in the code: in the Heating Override section which is not working correctly, and again in a Menu section for for setting isAdjustSetpointDown = true. But the true state in this section times out in 50 ms, and you might not notice a problem with the button wiring.

Nothing jumps. Is the time to the unprovoked early exit of heating override consistent?

I assume you see the messages

Heating Override On

and

Heating Override complete

This can only happen if those functions are called, and they are called from only a few places. I had a theory that didn't work out. Add printing before those function calls to see who where the call is being made.

  if ((downbutton.isPressed() && (!isMenuOn) && (isHeatingOverride)))  // Manually cancels the override
  {
    if (currentMillis - previousMillisOverrideStart >= 10000) {
Serial.println("                     manual termination of override");
      HeatingOverrideComplete();
    }
  }

and so forth, I think there are three or four calls all told to HeatingOverride() and HeatingOverrideComplete(), print a distinct message just before those.

I noticed this, a loop that only runs one iteration, so you don't need a loop. Not the problem, just sayin':

    for (int n = 0; n < 1; n++) {
      isHeatingOverride = true;  // Establishes the heating override
      HeatingOverride();
      previousMillisOverrideStart = currentMillis;
    }

a7

Afternoon @cattledog, thank you for your suggestion. I have checked the pushbutton connections and the four of them are connected to 'pull low' when pressed and they are all set via the ezButton library.

Afternoon @alto777 Thank you for your input. I have made a couple of changes to incorporate more serial prints. I will include screen shots of the Serial prints after the code.
It would appear to be less than one second after initiation that the LCD reverts to normal temperature control although the Override function continues until the auto time out at + 45seconds as shown by the Serial prints. I must have overlooked something that calls the normal control to be re-instated without cancelling the Override function.

/* Author : John Marchant G0RJM
 Created : 25/01/2024 - 16/10/2024
 Description : A project to display the current date, time and temperature on the 20x4 LCD screen
 and create a programmable thermostatic heating control of three zones to include frost protection and override facilities.
 This has been loosely based on the RTClib ds3231 example along with many code parts borrowed
 from several other files/sketches and in collaboration with several authors from the
 Arduino Forum in particular @alto777, @blh64, @cattledog, @StefanL38 and @UKHeliBob. */
#include "RTClib.h"
#include <OneWire.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ezButton.h>
#include <DallasTemperature.h>
#include <EEPROM.h>
#include <DST_RTC.h>

#define ONE_WIRE_BUS_1 3
#define ONE_WIRE_BUS_2 4
#define ONE_WIRE_BUS_3 5
#define TEMPERATURE_PRECISION 11  // 0.125deg resolution
#define buzzer_pin A0

OneWire oneWire_range(ONE_WIRE_BUS_1);
OneWire oneWire_club(ONE_WIRE_BUS_2);
OneWire oneWire_airgun(ONE_WIRE_BUS_3);

const byte range_relay = 10;  // Relay outputs
const byte club_relay = 11;
const byte airgun_relay = 12;
const byte boiler_relay = 13;

float range_temperature;
float club_temperature;
float airgun_temperature;

float range_temperature_setpoint;   // C
float club_temperature_setpoint;    // C
float airgun_temperature_setpoint;  // C
const int frost_setpoint = 12.0;    // C
const float deadzone = 1.0;         // C

ezButton leftbutton(6);   // create ezButton object that is attached to pin 6;
ezButton upbutton(7);     // create ezButton object that is attached to pin 7;
ezButton downbutton(8);   // create ezButton object that is attached to pin 8;
ezButton rightbutton(9);  // create ezButton object that is attached to pin 9;

const int ledPin = 13;

const unsigned menuOnTime = 20000;
const unsigned upButtonTime = 50;
const unsigned downButtonTime = 50;
const unsigned printInterval = 3000;
const unsigned overrideRunTime = 45000;
const unsigned backlightOnTime = 30000;

int value = 0;
int selector = 0;

int EEPROM_address_1 = 0;  // Address of the location of range setpoint temperature
int EEPROM_address_2 = 4;  // Address of the location of club setpoint temperature
int EEPROM_address_3 = 8;  // Address of the location of airgun setpoint temperature

unsigned long startMillis = 0;
unsigned long menuOffTime = 0;
unsigned long currentMillis = 0;
unsigned long previousMillis = 0;
unsigned long previousMillisMenu = 0;
unsigned long previousMillisBacklight = 0;
unsigned long previousMillisOverrideStart = 0;
unsigned long previousMillisAdjustSetpointUp = 0;
unsigned long previousMillisAdjustSetpointDown = 0;

bool isMenuOn = false;                // if = True initiiates the setup menu
bool isRangeHeatOn = false;           // if = True this allows the Range heating
bool isClubHeatOn = false;            // if = True this allows the Club heating
bool isAirgunHeatOn = false;          // if = True this allows the Airgun heating
bool isSetpointSave = false;          // if = True allows the new setpoints to be saved to EEPROM
bool isTimeControlOn = false;         // if = True allows normal time control function
bool isHeatingOverride = false;       // if = True allows the heating override facility
bool isAdjustSetpointUp = false;      // if = True allows the Setpoint increment
bool isAdjustSetpointDown = false;    // if = True allows the Setpoint decrement
bool isRangeSetpointAdjust = false;   // if = True allows the range setpoint to be adjusted
bool isClubSetpointAdjust = false;    // if = True allows the club setpoint to be adjusted
bool isAirgunSetpointAdjust = false;  // if = True allows the airgun setpoint to be adjusted

DallasTemperature range_sensor(&oneWire_range);
DallasTemperature club_sensor(&oneWire_club);
DallasTemperature airgun_sensor(&oneWire_airgun);

enum { SUNDAY,
       MONDAY,
       TUESDAY,
       WEDNESDAY,
       THURSDAY,
       FRIDAY,
       SATURDAY };

const int On_Normal_Range = 16 * 60UL + 00;    // 16:00 Normal ON time
const int Off_Normal_Range = 19 * 60UL + 30;   // 21:30 Normal OFF time
const int On_Normal_Club = 16 * 60UL + 00;     // 16:00 Normal ON time
const int Off_Normal_Club = 19 * 60UL + 30;    // 21:30 Normal OFF time
const int On_Normal_Airgun = 16 * 60UL + 00;   // 16:00 Normal ON time
const int Off_Normal_Airgun = 19 * 60UL + 30;  // 21:30 Normal OFF time
const int On_Sunday = 12 * 60UL + 00;          // 12:00 Sunday ON time
const int Off_Sunday = 17 * 60UL + 30;         // 17:30 Sunday OFF time

LiquidCrystal_I2C lcd(0X27, 20, 4);
RTC_DS3231 rtc;
DST_RTC dst_rtc;  // DST object

// Define US or EU rules for DST comment out as required. More countries could be added with different rules in DST_RTC.cpp
// const char rulesDST[] = "US"; // US DST rules
const char rulesDST[] = "EU";  // EU DST rules

char daysOfTheWeek[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
byte char_temp[8] = { B00100, B01010, B01010, B01110, B01110, B11111, B11111, B01110 };  // for thermometer icon

void setup() {

  pinMode(range_relay, OUTPUT);
  pinMode(club_relay, OUTPUT);
  pinMode(airgun_relay, OUTPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(buzzer_pin, OUTPUT);

  leftbutton.setDebounceTime(50);   // set debounce time to 50 milliseconds
  upbutton.setDebounceTime(50);     // set debounce time to 50 milliseconds
  downbutton.setDebounceTime(50);   // set debounce time to 50 milliseconds
  rightbutton.setDebounceTime(50);  // set debounce time to 50 milliseconds

  Serial.begin(9600);

  // EEPROM.put(EEPROM_address_1, range_temperature_setpoint);   // write the float value to EEPROM ONLY used to initially set up setpoint
  // EEPROM.put(EEPROM_address_2, club_temperature_setpoint);    // write the float value to EEPROM ONLY used to initially set up setpoint
  // EEPROM.put(EEPROM_address_3, airgun_temperature_setpoint);  // write the float value to EEPROM ONLY used to initially set up setpoint

  RetrieveSetpoint();

  startMillis = millis();

  Wire.begin();
  rtc.begin();

  lcd.init();
  lcd.backlight();
  lcd.createChar(0, char_temp);
  lcd.setCursor(2, 0);
  lcd.print("G0RJM Three Zone");
  lcd.setCursor(1, 1);
  lcd.print("Temperature Control");
  lcd.setCursor(4, 2);
  lcd.print("Take it easy!");
  lcd.setCursor(4, 3);
  lcd.print("Working on it");

  delay(1000);
  lcd.clear();
  if (rtc.lostPower()) {
    Serial.println("RTC is NOT running!");
    // Uncomment the following line to set the RTC to the date & time this sketch was compiled
    // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // rtc.adjust(DateTime(2024,4,24,21,49,00));
    /* DST? If we're in it, let's subtract an hour from the RTC time to keep our DST calculation correct. This gives us
    Standard Time which our DST check will add an hour back to if we're in DST.
    */
    DateTime standardTime = rtc.now();
    if (dst_rtc.checkDST(standardTime) == true) {  // check whether we're in DST right now. If we are, subtract an hour.
      standardTime = standardTime.unixtime() - 3600;
    }
    rtc.adjust(standardTime);
  }

  Serial.println("G0RJM Three zone Temperature Control");

  range_sensor.begin();
  club_sensor.begin();
  airgun_sensor.begin();

  range_sensor.setResolution(TEMPERATURE_PRECISION);
  club_sensor.setResolution(TEMPERATURE_PRECISION);
  airgun_sensor.setResolution(TEMPERATURE_PRECISION);

}  // End of setup

void loop() {

  //       ("ABCDEFGHIJ1234567890")

  //-------------------------Button control---------------------------//

  leftbutton.loop();   // MUST call the loop() function first
  upbutton.loop();     // MUST call the loop() function first
  downbutton.loop();   // MUST call the loop() function first
  rightbutton.loop();  // MUST call the loop() function first

  // -----------------------Backlight display-------------------------//

  unsigned long currentMillis = millis();  // all time elements are data type unsigned long

  if ((isHeatingOverride) || (leftbutton.isPressed() || (rightbutton.isPressed() || (upbutton.isPressed())))) {
    lcd.backlight();
    previousMillisBacklight = currentMillis;
  }
  if ((!isHeatingOverride) && (currentMillis - previousMillisBacklight >= backlightOnTime)) {
    lcd.noBacklight();
    isMenuOn = false;
  }

  //---------------------------Heating Override-----------------------//

  if ((downbutton.isPressed() && (!isMenuOn) && (!isHeatingOverride))) {

    isHeatingOverride = true;  // Establishes the heating override
    HeatingOverride();
    Serial.println("          manual start of override");
    previousMillisOverrideStart = currentMillis;
  }
  if (isHeatingOverride) {
    if (currentMillis - previousMillisOverrideStart >= overrideRunTime)  // Automatically cancels the override
    {
      Serial.println("       auto termination of override");
      HeatingOverrideComplete();
    }
  }
  if ((downbutton.isPressed() && (!isMenuOn) && (isHeatingOverride)))  // Manually cancels the override
  {
    if (currentMillis - previousMillisOverrideStart >= 10000) {
      Serial.println("       manual termination of override");
      HeatingOverrideComplete();
    }
  }

  //------------------------Boiler Interlock--------------------------//

  if ((digitalRead(range_relay) == HIGH) || (digitalRead(club_relay) == HIGH) || (digitalRead(airgun_relay) == HIGH)) {
    digitalWrite(boiler_relay, HIGH);
  } else {
    digitalWrite(boiler_relay, LOW);
  }

  if (!isMenuOn) {
    TemperatureDisplay();
  }

  //------------------------------Menu--------------------------------//

  if (leftbutton.isPressed()) {
    isMenuOn = true;  // Initiiates the setup menu
    SettingMenu();
    selector++;  // this is done after SettingMenu(), so case 0 will be executed on the first button press
    if (selector > 5) {
      selector = 0;
    }
    previousMillisMenu = currentMillis;
  }

  if (isMenuOn) {
    if (currentMillis - previousMillisMenu >= menuOnTime) {
      isMenuOn = false;
      selector = 6;
      delay(2000);
      lcd.noBacklight();
    }
  }

  //------------------------------------------------------------------//

  if (upbutton.isPressed()) {
    isAdjustSetpointUp = true;  // Setpoint adjustment upwards is available
    previousMillisAdjustSetpointUp = currentMillis;
  }

  if (isAdjustSetpointUp) {
    if (currentMillis - previousMillisAdjustSetpointUp >= upButtonTime) {
      isAdjustSetpointUp = false;
    }
  }

  //------------------------------------------------------------------//

  if (isAdjustSetpointUp && isRangeSetpointAdjust)  // If True, this allows the Range setpoint to be adjusted
  {
    range_temperature_setpoint += 0.1;  // Range Setpoint increment
    Serial.println("range_temperature_setpoint_up");
    RangeDisplay();
    Buzzer();
  }

  if (isAdjustSetpointUp && isClubSetpointAdjust)  // If True, this allows the Club setpoint to be adjusted
  {
    club_temperature_setpoint += 0.1;  // Club Setpoint increment
    Serial.println("club_temperature_setpoint_up");
    ClubDisplay();
    Buzzer();
  }

  if (isAdjustSetpointUp && isAirgunSetpointAdjust)  // If True, this allows the Airgun setpoint to be adjusted
  {
    airgun_temperature_setpoint += 0.1;  // Airgun Setpoint increment
    Serial.println("airgun_temperature_setpoint_up");
    AirgunDisplay();
    Buzzer();
  }

  //------------------------------------------------------------------//

  if (downbutton.isPressed()) {
    isAdjustSetpointDown = true;  // Setpoint adjustment Downwards is available
    previousMillisAdjustSetpointDown = currentMillis;
  }
  if (isAdjustSetpointDown) {
    if (currentMillis - previousMillisAdjustSetpointDown >= downButtonTime) {
      isAdjustSetpointDown = false;
    }
  }

  if (isAdjustSetpointDown && isRangeSetpointAdjust)  // If True, this allows the Range setpoint to be adjusted
  {
    range_temperature_setpoint -= 0.1;  // Range Setpoint decrement
    Serial.println("range_temperature_setpoint_down");
    RangeDisplay();
    Buzzer();
  }

  if (isAdjustSetpointDown && isClubSetpointAdjust)  // If True, this allows the Club setpoint to be adjusted
  {
    club_temperature_setpoint -= 0.1;  // Club Setpoint decrement
    Serial.println("club_temperature_setpoint_down");
    ClubDisplay();
    Buzzer();
  }

  if (isAdjustSetpointDown && isAirgunSetpointAdjust)  // If True, this allows the Airgun setpoint to be adjusted
  {
    airgun_temperature_setpoint -= 0.1;  // Airgun Setpoint decrement
    Serial.println("airgun_temperature_setpoint_down");
    AirgunDisplay();
    Buzzer();
  }

  //-----------------------Setpoint Save------------------------------//

  if (isSetpointSave && isAdjustSetpointUp && isAdjustSetpointDown)  // If True, the new temperature setpoints are saved to EEPROM
  {
    for (int n = 0; n < 1; n++) {
      Serial.println("Changes saved");
      EEPROM.put(EEPROM_address_1, range_temperature_setpoint);   // write the new range setpoint value to EEPROM, only if it has changed
      EEPROM.put(EEPROM_address_2, club_temperature_setpoint);    // write the new club setpoint value to EEPROM, only if it has changed
      EEPROM.put(EEPROM_address_3, airgun_temperature_setpoint);  // write the new airgun setpoint value to EEPROM, only if it has changed

      delay(2000);
      RetrieveSetpoint();
      lcd.setCursor(3, 3);
      lcd.print("Settings saved");
      delay(3000);
      lcd.clear();
      TemperatureDisplay();
      HeatingAvailable();
    }
  }
  //----------------------------Print delay---------------------------//

  if (currentMillis - previousMillis >= printInterval)  // has enough time passed yet?
  {
    previousMillis = previousMillis + printInterval;  // If yes, set the time del;ay for the next print event
                                                      // capture & print data, or print latest data, etc.
                                                      // the closing curly brace at end of loop.

    //--------------------Daylight Saving Setting---------------------//

    DateTime standardTime = rtc.now();

    Serial.println("Standard Time");
    printTheTime(standardTime);

    DateTime theTime = dst_rtc.calculateTime(standardTime);  // takes into account DST

    Serial.println("Time adjusted for Daylight Saving Time");
    printTheTime(theTime);


    //-----------------------Timeswitch Control-----------------------//

    int currentTime = theTime.hour() * 60UL + theTime.minute();

    //--------------------Timed heating setting-----------------------//

    if (theTime.dayOfTheWeek() != SUNDAY) {
      // Monday to Saturday so use normal times
      if (currentTime < On_Normal_Range || currentTime > Off_Normal_Range) {
        // this is outside normal range heating time
        isRangeHeatOn = false;
      } else {
        // this is within normal range heating time
        isRangeHeatOn = true;
      }
    } else {
      // Sunday times
      if (currentTime < On_Sunday || currentTime > Off_Sunday) {
        // this is outside Sunday range heating time
        isRangeHeatOn = false;
      } else {
        // this is within Sunday range heating time
        isRangeHeatOn = true;
      }
    }
    if (theTime.dayOfTheWeek() != SUNDAY) {
      // Monday to Saturday so use normal times
      if (currentTime < On_Normal_Club || currentTime > Off_Normal_Club) {
        // this is outside normal club heating time
        isClubHeatOn = false;
      } else {
        // this is within normal club heating time
        isClubHeatOn = true;
      }
    } else {
      // Sunday times
      if (currentTime < On_Sunday || currentTime > Off_Sunday) {
        // this is outside Sunday club heating time
        isClubHeatOn = false;
      } else {
        // this is within Sunday club heating time
        isClubHeatOn = true;
      }
    }
    if (theTime.dayOfTheWeek() != SUNDAY) {
      // Monday to Saturday so use normal times
      if (currentTime < On_Normal_Airgun || currentTime > Off_Normal_Airgun) {
        // this is outside normal airgun heating time
        isAirgunHeatOn = false;
      } else {
        // this is within normal airgun heating time
        isAirgunHeatOn = true;
      }
    } else {
      // Sunday times
      if (currentTime < On_Sunday || currentTime > Off_Sunday) {
        // this is outside Sunday airgun heating time
        isAirgunHeatOn = false;
      } else {
        // this is within Sunday airgun heating time
        isAirgunHeatOn = true;
      }
    }

    //---------------------Date & Time display----------------------//

    if ((isTimeControlOn) || (!isMenuOn)) {
      lcd.setCursor(0, 0);
      lcd.print(daysOfTheWeek[theTime.dayOfTheWeek()]);
      lcd.setCursor(3, 0);
      lcd.print(":");
      lcd.setCursor(4, 0);
      if (theTime.day() < 10) lcd.print('0');
      lcd.print(theTime.day(), DEC);
      lcd.print(':');
      if (theTime.month() < 10) lcd.print('0');
      lcd.print(theTime.month(), DEC);
      lcd.print(':');
      lcd.print(theTime.year(), DEC);
      lcd.setCursor(15, 0);
      if (theTime.hour() < 10) lcd.print('0');
      lcd.print(theTime.hour(), DEC);
      lcd.print(':');
      if (theTime.minute() < 10) lcd.print('0');
      lcd.print(theTime.minute(), DEC);
      Serial.println("Something else");
      HeatingAvailable();
    }

    //------------------------Serial print---------------------------//

    Serial.print("Requesting temperatures...");
    range_sensor.requestTemperatures();
    club_sensor.requestTemperatures();
    airgun_sensor.requestTemperatures();
    Serial.println(" done");
    Serial.print("Range: ");
    Serial.println(range_sensor.getTempCByIndex(0));
    Serial.print("Club: ");
    Serial.println(club_sensor.getTempCByIndex(0));
    Serial.print("Airgun: ");
    Serial.println(airgun_sensor.getTempCByIndex(0));
  }

}  // End of loop

//----------------------Print time to serial-------------------------//
void printTheTime(DateTime theTimeP) {

  Serial.print(theTimeP.year(), DEC);
  Serial.print('/');
  Serial.print(theTimeP.month(), DEC);
  Serial.print('/');
  Serial.print(theTimeP.day(), DEC);
  Serial.print(' ');
  Serial.print(theTimeP.hour(), DEC);
  Serial.print(':');
  Serial.print(theTimeP.minute(), DEC);
  Serial.print(':');
  Serial.print(theTimeP.second(), DEC);
  Serial.println();
}

//------------------------Retrieve setpoint--------------------------//
void RetrieveSetpoint() {

  EEPROM.get(EEPROM_address_1, range_temperature_setpoint);   //retrieve the range setpoint from EEPROM
  EEPROM.get(EEPROM_address_2, club_temperature_setpoint);    //retrieve the club setpoint from EEPROM
  EEPROM.get(EEPROM_address_3, airgun_temperature_setpoint);  //retrieve the airgun setpoint from EEPROM

  Serial.print("Range Temperature Setpoint  - ");
  Serial.print(range_temperature_setpoint);
  Serial.print(" Stored at addr ");
  Serial.println(EEPROM_address_1);
  Serial.print("Club Temperature Setpoint   - ");
  Serial.print(club_temperature_setpoint);
  Serial.print(" Stored at addr ");
  Serial.println(EEPROM_address_2);
  Serial.print("Airgun Temperature Setpoint - ");
  Serial.print(airgun_temperature_setpoint);
  Serial.print(" Stored at addr ");
  Serial.println(EEPROM_address_3);
}

//------------------------Setting Menu-------------------------------//
void SettingMenu() {

  switch (selector) {
    case 0:
      Serial.println("Case 0");  // General menu
      MenuScreen();
      break;
    case 1:
      Serial.println("Case 1");  // Displays the Range setpoint
      RangeDisplay();
      Buzzer();
      break;
    case 2:
      Serial.println("Case 2");  // Displays the Club setpoint
      ClubDisplay();
      Buzzer();
      break;
    case 3:
      Serial.println("Case 3");  // Displays the Airgun setpoint
      AirgunDisplay();
      Buzzer();
      break;
    case 4:
      Serial.println("Case 4");  // Instructs how to save the new settings
      SaveDisplay();
      break;
    case 5:
      Serial.println("Case 5");  // Reminds you to save
      lcd.clear();
      lcd.setCursor(3, 2);
      lcd.print("Did you save?");
      Buzzer();
  }
}

//---------------------------Buzzer----------------------------------//
void Buzzer() {

  {
    digitalWrite(buzzer_pin, HIGH);
    delay(100);
    digitalWrite(buzzer_pin, LOW);
  }
}

//-------------------------Menu screen-------------------------------//
void MenuScreen() {

  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 1);
  lcd.print("Range: ");
  lcd.print(range_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  lcd.setCursor(0, 2);
  lcd.print("Club:  ");
  lcd.print(club_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  lcd.setCursor(0, 3);
  lcd.print("Airgun:");
  lcd.print(airgun_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  isSetpointSave = false;
}

//-----------------------Setting Displays---------------------------//
void RangeDisplay() {

  isRangeSetpointAdjust = true;
  isClubSetpointAdjust = false;
  isAirgunSetpointAdjust = false;
  Serial.println("RangeChange");
  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 1);
  lcd.print("Range: ");
  lcd.print(range_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
}

//-------------------------------------------------------------------//
void ClubDisplay() {

  isClubSetpointAdjust = true;
  isRangeSetpointAdjust = false;
  isAirgunSetpointAdjust = false;
  Serial.println("ClubChange");
  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 2);
  lcd.print("Club:  ");
  lcd.print(club_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
}

//-------------------------------------------------------------------//
void AirgunDisplay() {

  isAirgunSetpointAdjust = true;
  isRangeSetpointAdjust = false;
  isClubSetpointAdjust = false;
  Serial.println("AirgunChange");
  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 3);
  lcd.print("Airgun:");
  lcd.print(airgun_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
}

//------------------------Save Display-------------------------------//
void SaveDisplay() {

  isRangeSetpointAdjust = false;
  isClubSetpointAdjust = false;
  isAirgunSetpointAdjust = false;
  Serial.println("SavingChanges");
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("To save the changes");
  lcd.setCursor(2, 1);
  lcd.print("Press Up & Down");
  lcd.setCursor(5, 2);
  lcd.print("Together");
  isSetpointSave = true;
}

//---------------------Temperature display - ------------------------//
void TemperatureDisplay() {

  lcd.setCursor(0, 1);  // Range Temperature
  lcd.print("Range: ");
  lcd.print(range_sensor.getTempCByIndex(0), 1);
  lcd.write((char)223);
  lcd.print('C');

  lcd.setCursor(0, 2);  // Club Temperature
  lcd.print("Club:  ");
  lcd.print(club_sensor.getTempCByIndex(0), 1);
  lcd.write((char)223);
  lcd.print('C');

  lcd.setCursor(0, 3);  // Airgun Temperature
  lcd.print("Airgun:");
  lcd.print(airgun_sensor.getTempCByIndex(0), 1);
  lcd.write((char)223);
  lcd.print('C');
}

//------------------------Heating Override---------------------------//
void HeatingOverride() {

  isRangeHeatOn = true;
  isClubHeatOn = true;
  isAirgunHeatOn = true;
  isTimeControlOn = false;
  Serial.println("Heating Override On");
  HeatingAvailable();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("   OVERRIDE IS ON   ");
}

//--------------------Heating Override Complete----------------------//
void HeatingOverrideComplete() {

  Serial.println("Heating Override complete");
  isHeatingOverride = false;
  isRangeHeatOn = false;
  isClubHeatOn = false;
  isAirgunHeatOn = false;
  isTimeControlOn = true;
  lcd.setCursor(0, 0);
  lcd.print("   OVERRIDE IS OFF   ");
  delay(2000);
  lcd.noBacklight();
}

//-----------------------Heating Available---------------------------//
void HeatingAvailable() {

  RangeHeatingAvailable();
  ClubHeatingAvailable();
  AirgunHeatingAvailable();
  Serial.println("Wacky time");
}

//-------------------------------------------------------------------//
void RangeHeatingAvailable() {

  {
    if (isRangeHeatOn) {
      NormalHeatingRange();
      Serial.println("Heat R");
    } else {
      Serial.println("Frost Prev. R");
      FrostPreventionRange();
    }
  }
}

//-------------------------------------------------------------------//
void ClubHeatingAvailable() {

  {
    if (isClubHeatOn) {
      NormalHeatingClub();
      Serial.println("Heat C");
    } else {
      Serial.println("Frost Prev. C");
      FrostPreventionClub();
    }
  }
}

//-------------------------------------------------------------------//
void AirgunHeatingAvailable() {

  {
    if (isAirgunHeatOn) {
      NormalHeatingAirgun();
      Serial.println("Heat A");
    } else {
      Serial.println("Frost Prev. A");
      FrostPreventionAirgun();
    }
  }
}

//-------------------Frost Prevention Range--------------------------//
void FrostPreventionRange() {

  if ((range_sensor.getTempCByIndex(0) <= (frost_setpoint - deadzone))) {
    digitalWrite(range_relay, HIGH);
    Serial.println("Ra Heating");
    lcd.setCursor(13, 1);
    lcd.print("  ON  F");
  } else {
    if ((range_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone))) {
      digitalWrite(range_relay, LOW);
      lcd.setCursor(13, 1);
      lcd.print("  OFF F");
    }
  }
}

//--------------------Frost Prevention Club-------------------------//
void FrostPreventionClub() {

  if ((club_sensor.getTempCByIndex(0) <= (frost_setpoint - deadzone))) {
    digitalWrite(club_relay, HIGH);
    Serial.println("Cl Heating");
    lcd.setCursor(13, 2);
    lcd.print("  ON  F");
  } else {
    if ((club_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone))) {
      digitalWrite(club_relay, LOW);
      lcd.setCursor(13, 2);
      lcd.print("  OFF F");
    }
  }
}

//---------------------Frost Prevention Airgun-----------------------//
void FrostPreventionAirgun() {

  if ((airgun_sensor.getTempCByIndex(0) <= (frost_setpoint - deadzone))) {
    digitalWrite(airgun_relay, HIGH);
    Serial.println("Ai Heating");
    lcd.setCursor(13, 3);
    lcd.print("  ON  F");
  } else {
    if ((airgun_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone))) {
      digitalWrite(airgun_relay, LOW);
      lcd.setCursor(13, 3);
      lcd.print("  OFF F");
    }
  }
}

//-------------------Normal Heating Range----------------------------//
void NormalHeatingRange() {

  if ((range_sensor.getTempCByIndex(0) <= (range_temperature_setpoint - deadzone))) {
    digitalWrite(range_relay, HIGH);
    Serial.println("Heating R");
    lcd.setCursor(13, 1);
    lcd.print("  ON  T");
  } else {
    if ((range_sensor.getTempCByIndex(0) >= (range_temperature_setpoint + deadzone))) {
      digitalWrite(range_relay, LOW);
      lcd.setCursor(13, 1);
      lcd.print("  OFF T");
    }
  }
}

//--------------------Normal Heating Club ---------------------------//
void NormalHeatingClub() {

  if ((club_sensor.getTempCByIndex(0) <= (club_temperature_setpoint - deadzone))) {
    digitalWrite(club_relay, HIGH);
    Serial.println("Heating C");
    lcd.setCursor(13, 2);
    lcd.print("  ON  T");
  } else {
    if ((club_sensor.getTempCByIndex(0) >= (club_temperature_setpoint + deadzone))) {
      digitalWrite(club_relay, LOW);
      lcd.setCursor(13, 2);
      lcd.print("  OFF T");
    }
  }
}

//---------------------Normal Heating Airgun-------------------------//
void NormalHeatingAirgun() {

  if ((airgun_sensor.getTempCByIndex(0) <= (airgun_temperature_setpoint - deadzone))) {
    digitalWrite(airgun_relay, HIGH);
    Serial.println("Heating A");
    lcd.setCursor(13, 3);
    lcd.print("  ON  T");
  } else {
    if ((airgun_sensor.getTempCByIndex(0) >= (airgun_temperature_setpoint + deadzone))) {
      digitalWrite(airgun_relay, LOW);
      lcd.setCursor(13, 3);
      lcd.print("  OFF T");
    }
  }
}


15:39:46.501 -> Frost Prev. C

15:39:46.577 -> Frost Prev. A

15:39:46.648 -> Wacky time

15:39:46.648 -> Requesting temperatures... done

15:39:47.558 -> Range: 24.25

15:39:47.558 -> Club: 24.25

15:39:47.590 -> Airgun: 24.25

15:39:48.619 -> Heating Override On

15:39:48.654 -> Heating R

15:39:48.688 -> Heat R

15:39:48.689 -> Heating C

15:39:48.726 -> Heat C

15:39:48.726 -> Heating A

15:39:48.761 -> Heat A

15:39:48.761 -> Wacky time

15:39:48.798 -> manual start of override

15:39:49.367 -> Standard Time

15:39:49.367 -> 2024/10/17 14:39:13

15:39:49.402 -> Time adjusted for Daylight Saving Time

And now the report at  the auto cancellation.
15:40:31.530 -> Frost Prev. R

15:40:31.567 -> Frost Prev. C

15:40:31.599 -> Frost Prev. A

15:40:31.669 -> Wacky time

15:40:31.702 -> Requesting temperatures... done

15:40:32.599 -> Range: 24.25

15:40:32.632 -> Club: 24.25

15:40:32.632 -> Airgun: 24.25

15:40:33.701 -> auto termination of override

15:40:33.733 -> Heating Override complete

15:40:36.007 -> Standard Time

15:40:36.007 -> 2024/10/17 14:39:59

15:40:36.041 -> Time adjusted for Daylight Saving Time

15:40:36.073 -> 2024/10/17 15:39:59

(you can put output in code tags too)

The more serial prints you add the better you can narrow down where the bug occurs

So far you have only

  if (isHeatingOverride) {
    if (currentMillis - previousMillisOverrideStart >= overrideRunTime)  // Automatically cancels the override
    {
      Serial.println("       auto termination of override");
      HeatingOverrideComplete();
    }
  }

5:39:48.798 -> manual start of override
15:40:33.701 -> auto termination of override
this 45 seconds which is

const unsigned overrideRunTime = 45000;

that is checked here

  if (isHeatingOverride) {
    if (currentMillis - previousMillisOverrideStart >= overrideRunTime)  // Automatically cancels the override
    {
      Serial.println("       auto termination of override");
      HeatingOverrideComplete();
    }
  }

Your code does what you have coded.
So where is the problem?

Ah! the LCD

Your LCD-Update function

void HeatingOverrideComplete() {

  Serial.println("Heating Override complete");
  isHeatingOverride = false;
  isRangeHeatOn = false;
  isClubHeatOn = false;
  isAirgunHeatOn = false;
  isTimeControlOn = true;
  lcd.setCursor(0, 0);
  lcd.print("   OVERRIDE IS OFF   ");
  delay(2000);
  lcd.noBacklight();
}

does a serial print

Heating Override complete

Which shows up after
15:40:33.701 -> auto termination of override
15:40:33.733 -> Heating Override complete

This means the extra switching off on the LCD is NOT in the serial printings that you have posted.
Without this information it is very hard to narrow down where the bug occurs

You should at least post character by character what does your LCD show:

  • before override is activated
  • directly after override activation
  • if the bug of changing the LCD-display has happened
  • after auto-termination of override

As a general advice
your code has a lot of flag-variables that must be checked a lot by using logical and-conditions

If you change your code to use state-machines,
the number of if-conditions is reduced to a minimum

because a state-machine based on the switch-case-break-statement executes only that parts of the code that are needed in each situation.
the rest of the code is "skipped" and this reduces the number of if-conditions.

Code that is skipped does not need additional if-conditions.

Thank you@DaveX
I did not know that. I have edited it.

Thank you @StefanL38. I will work my way through your suggestions and post my findings as soon as I can.

These two lines

untitled text 53:693:   lcd.print("   OVERRIDE IS ON   ");
untitled text 53:706:   lcd.print("   OVERRIDE IS OFF   ");

are the only messages concerning override publijhed to the LCD.

They find themselves one in HeatingOverride() and the other in HeatingOverrideComplete().

Those functions, in turn, appear to be called as follows:

  • HetaingOverride() because you hit a button.

  • HetaingOverrideComplete() either manually because override has been in effect for ten seconds and you hit a button, or automatically after 45 seconds.

There is no explanation for any other exit from override, so I am compelled to ask...

...what is the evidence you offer that shows that override has been exited off script?

All I can make of the serial output is that you pressed the button and 45 seconds later it correctly exited automatically.

I don't see where you demonstrate that, say, 13 seconds after pressing the button, pressing it again invokes manual exit.

And I don't see either where it ever exits which is not one of those two.

Run it again, enter override and let it play out to automatic exit, then enter override and 13 seconds later exit it manually.

And say or show where it ever exits "a few seconds" after being invoked.

a7

Evening @alto777 Thank you for more thoughtful input. I have inserted a couple more Serial print points and am now trying to capture a lot of Serial prints which may be of help.
Where the serial print shows that the "Time and date showing on LCD" is the time point that the override heating effect stops. I can visually confirm this as the three LEDs which are connected to the individual heating relays go out. the other serial prints that indicate that the system is in Frost prevention

20:53:51.281 -> Heating is available
20:53:51.316 -> Requesting temperatures... done
20:53:52.206 -> Range: 22.62
20:53:52.239 -> Club: 22.62
20:53:52.239 -> Airgun: 22.75
20:53:53.278 -> Heating Override is On
20:53:53.311 -> LCD showing Override is On
20:53:53.344 -> Normal Heating R
20:53:53.344 -> Heating R
20:53:53.376 -> Normal Heating C
20:53:53.376 -> Heating C
20:53:53.410 -> Normal Heating A
20:53:53.410 -> Heating A
20:53:53.442 -> Heating is available
20:53:53.442 ->           manual start of override
20:53:54.004 -> Standard Time
20:53:54.036 -> 2024/10/18 19:53:17
20:53:54.036 -> Time adjusted for Daylight Saving Time
20:53:54.103 -> 2024/10/18 20:53:17
20:53:54.103 -> Time & date showing on LCD // **This is where it has reverted to normal control**
20:53:54.135 -> Frost Prev. R
20:53:54.167 -> Frost Prev. C
20:53:54.237 -> Frost Prev. A
20:53:54.320 -> Heating is available
20:53:54.320 -> Requesting temperatures... done
20:53:55.237 -> Range: 22.62
20:53:55.237 -> Club: 22.75
20:53:55.269 -> Airgun: 22.75

20:54:36.005 -> Frost Prev. R
20:54:36.038 -> Frost Prev. C
20:54:36.112 -> Frost Prev. A
20:54:36.177 -> Heating is available
20:54:36.209 -> Requesting temperatures... done
20:54:37.092 -> Range: 22.62
20:54:37.128 -> Club: 22.62
20:54:37.160 -> Airgun: 22.75
20:54:38.332 ->        auto termination of override
20:54:38.364 -> Heating Override is complete 

 // **This is where the auto termination is called**

20:54:40.674 -> Standard Time
20:54:40.674 -> 2024/10/18 19:54:3
20:54:40.707 -> Time adjusted for Daylight Saving Time

This is showing next start of override
1:46:58.029 -> Heating Override is On
21:46:58.029 -> LCD showing Override is On
21:46:58.060 -> Normal Heating R
21:46:58.093 -> Heating R
21:46:58.093 -> Normal Heating C
21:46:58.126 -> Heating C
21:46:58.126 -> Normal Heating A
21:46:58.159 -> Heating A
21:46:58.159 -> Heating is available
21:46:58.193 ->           manual start of override
21:46:59.339 -> Standard Time
21:46:59.339 -> 2024/10/18 20:46:22
21:46:59.371 -> Time adjusted for Daylight Saving Time
21:46:59.404 -> 2024/10/18 21:46:22
21:46:59.436 -> Time & date showing on LCD

// **Followed by manual cancellation**

21:47:14.473 -> Frost Prev. C
21:47:14.506 -> Frost Prev. A
21:47:14.584 -> Heating is available
21:47:14.617 -> Requesting temperatures... done
21:47:15.513 -> Range: 23.00
21:47:15.513 -> Club: 23.00
21:47:15.546 -> Airgun: 23.00
21:47:15.997 ->        manual termination of override
21:47:16.062 -> Heating Override is complete

This is the latest version of the sketch. I think that somehow maybe I have got too much trying to go on at once.
I have had to comment out some of the background Serial prints as I had used up, too much of the memory!
As always, your thoughts are much appreciated.
I haven't tried to get my head around changing things to a state machine yet, but will do if you think it might simplify some of the code.

/* Author : John Marchant G0RJM
 Created : 25/01/2024 - 18/10/2024
 Description : A project to display the current date, time and temperature on the 20x4 LCD screen
 and create a programmable thermostatic heating control of three zones to include frost protection and override facilities.
 This has been loosely based on the RTClib ds3231 example along with many code parts borrowed
 from several other files/sketches and in collaboration with several authors from the
 Arduino Forum in particular @alto777, @blh64, @cattledog, @StefanL38 and @UKHeliBob. */
#include "RTClib.h"
#include <OneWire.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ezButton.h>
#include <DallasTemperature.h>
#include <EEPROM.h>
#include <DST_RTC.h>

#define ONE_WIRE_BUS_1 3
#define ONE_WIRE_BUS_2 4
#define ONE_WIRE_BUS_3 5
#define TEMPERATURE_PRECISION 11  // 0.125deg resolution
#define buzzer_pin A0

OneWire oneWire_range(ONE_WIRE_BUS_1);
OneWire oneWire_club(ONE_WIRE_BUS_2);
OneWire oneWire_airgun(ONE_WIRE_BUS_3);

const byte range_relay = 10;  // Relay outputs
const byte club_relay = 11;
const byte airgun_relay = 12;
const byte boiler_relay = 13;

float range_temperature;
float club_temperature;
float airgun_temperature;

float range_temperature_setpoint;   // C
float club_temperature_setpoint;    // C
float airgun_temperature_setpoint;  // C
const int frost_setpoint = 12.0;    // C
const float deadzone = 1.0;         // C

ezButton leftbutton(6);   // create ezButton object that is attached to pin 6;
ezButton upbutton(7);     // create ezButton object that is attached to pin 7;
ezButton downbutton(8);   // create ezButton object that is attached to pin 8;
ezButton rightbutton(9);  // create ezButton object that is attached to pin 9;

const int ledPin = 13;

const unsigned menuOnTime = 20000;
const unsigned upButtonTime = 50;
const unsigned downButtonTime = 50;
const unsigned printInterval = 3000;
const unsigned overrideRunTime = 45000;
const unsigned backlightOnTime = 30000;

int value = 0;
int selector = 0;

int EEPROM_address_1 = 0;  // Address of the location of range setpoint temperature
int EEPROM_address_2 = 4;  // Address of the location of club setpoint temperature
int EEPROM_address_3 = 8;  // Address of the location of airgun setpoint temperature

unsigned long startMillis = 0;
unsigned long menuOffTime = 0;
unsigned long currentMillis = 0;
unsigned long previousMillis = 0;
unsigned long previousMillisMenu = 0;
unsigned long previousMillisBacklight = 0;
unsigned long previousMillisOverrideStart = 0;
unsigned long previousMillisAdjustSetpointUp = 0;
unsigned long previousMillisAdjustSetpointDown = 0;

bool isMenuOn = false;                // if = True initiiates the setup menu
bool isRangeHeatOn = false;           // if = True this allows the Range heating
bool isClubHeatOn = false;            // if = True this allows the Club heating
bool isAirgunHeatOn = false;          // if = True this allows the Airgun heating
bool isSetpointSave = false;          // if = True allows the new setpoints to be saved to EEPROM
bool isTimeControlOn = false;         // if = True allows normal time control function
bool isHeatingOverride = false;       // if = True allows the heating override facility
bool isAdjustSetpointUp = false;      // if = True allows the Setpoint increment
bool isAdjustSetpointDown = false;    // if = True allows the Setpoint decrement
bool isRangeSetpointAdjust = false;   // if = True allows the range setpoint to be adjusted
bool isClubSetpointAdjust = false;    // if = True allows the club setpoint to be adjusted
bool isAirgunSetpointAdjust = false;  // if = True allows the airgun setpoint to be adjusted

DallasTemperature range_sensor(&oneWire_range);
DallasTemperature club_sensor(&oneWire_club);
DallasTemperature airgun_sensor(&oneWire_airgun);

enum { SUNDAY,
       MONDAY,
       TUESDAY,
       WEDNESDAY,
       THURSDAY,
       FRIDAY,
       SATURDAY };

const int On_Normal_Range = 16 * 60UL + 00;    // 16:00 Normal ON time
const int Off_Normal_Range = 19 * 60UL + 30;   // 21:30 Normal OFF time
const int On_Normal_Club = 16 * 60UL + 00;     // 16:00 Normal ON time
const int Off_Normal_Club = 19 * 60UL + 30;    // 21:30 Normal OFF time
const int On_Normal_Airgun = 16 * 60UL + 00;   // 16:00 Normal ON time
const int Off_Normal_Airgun = 19 * 60UL + 30;  // 21:30 Normal OFF time
const int On_Sunday = 12 * 60UL + 00;          // 12:00 Sunday ON time
const int Off_Sunday = 17 * 60UL + 30;         // 17:30 Sunday OFF time

LiquidCrystal_I2C lcd(0X27, 20, 4);
RTC_DS3231 rtc;
DST_RTC dst_rtc;  // DST object

// Define US or EU rules for DST comment out as required. More countries could be added with different rules in DST_RTC.cpp
// const char rulesDST[] = "US"; // US DST rules
const char rulesDST[] = "EU";  // EU DST rules

char daysOfTheWeek[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
byte char_temp[8] = { B00100, B01010, B01010, B01110, B01110, B11111, B11111, B01110 };  // for thermometer icon

void setup() {

  pinMode(range_relay, OUTPUT);
  pinMode(club_relay, OUTPUT);
  pinMode(airgun_relay, OUTPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(buzzer_pin, OUTPUT);

  leftbutton.setDebounceTime(50);   // set debounce time to 50 milliseconds
  upbutton.setDebounceTime(50);     // set debounce time to 50 milliseconds
  downbutton.setDebounceTime(50);   // set debounce time to 50 milliseconds
  rightbutton.setDebounceTime(50);  // set debounce time to 50 milliseconds

  Serial.begin(9600);

  // EEPROM.put(EEPROM_address_1, range_temperature_setpoint);   // write the float value to EEPROM ONLY used to initially set up setpoint
  // EEPROM.put(EEPROM_address_2, club_temperature_setpoint);    // write the float value to EEPROM ONLY used to initially set up setpoint
  // EEPROM.put(EEPROM_address_3, airgun_temperature_setpoint);  // write the float value to EEPROM ONLY used to initially set up setpoint

  RetrieveSetpoint();

  startMillis = millis();

  Wire.begin();
  rtc.begin();

  lcd.init();
  lcd.backlight();
  lcd.createChar(0, char_temp);
  lcd.setCursor(2, 0);
  lcd.print("G0RJM Three Zone");
  lcd.setCursor(1, 1);
  lcd.print("Temperature Control");
  lcd.setCursor(4, 2);
  lcd.print("Take it easy!");
  lcd.setCursor(4, 3);
  lcd.print("Working on it");

  delay(1000);
  lcd.clear();
  if (rtc.lostPower()) {
    Serial.println("RTC is NOT running!");
    // Uncomment the following line to set the RTC to the date & time this sketch was compiled
    // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // rtc.adjust(DateTime(2024,4,24,21,49,00));
    /* DST? If we're in it, let's subtract an hour from the RTC time to keep our DST calculation correct. This gives us
    Standard Time which our DST check will add an hour back to if we're in DST.
    */
    DateTime standardTime = rtc.now();
    if (dst_rtc.checkDST(standardTime) == true) {  // check whether we're in DST right now. If we are, subtract an hour.
      standardTime = standardTime.unixtime() - 3600;
    }
    rtc.adjust(standardTime);
  }

  // Serial.println("G0RJM Three zone Temperature Control");

  range_sensor.begin();
  club_sensor.begin();
  airgun_sensor.begin();

  range_sensor.setResolution(TEMPERATURE_PRECISION);
  club_sensor.setResolution(TEMPERATURE_PRECISION);
  airgun_sensor.setResolution(TEMPERATURE_PRECISION);

}  // End of setup

void loop() {

  //       ("ABCDEFGHIJ1234567890")

  //-------------------------Button control---------------------------//

  leftbutton.loop();   // MUST call the loop() function first
  upbutton.loop();     // MUST call the loop() function first
  downbutton.loop();   // MUST call the loop() function first
  rightbutton.loop();  // MUST call the loop() function first

  // -----------------------Backlight display-------------------------//

  unsigned long currentMillis = millis();  // all time elements are data type unsigned long

  if ((isHeatingOverride) || (leftbutton.isPressed() || (rightbutton.isPressed() || (upbutton.isPressed())))) {
    lcd.backlight();
    previousMillisBacklight = currentMillis;
  }
  if ((!isHeatingOverride) && (currentMillis - previousMillisBacklight >= backlightOnTime)) {
    lcd.noBacklight();
    isMenuOn = false;
  }

  //---------------------------Heating Override-----------------------//

  if ((downbutton.isPressed() && (!isMenuOn) && (!isHeatingOverride))) {

    isHeatingOverride = true;  // Establishes the heating override
    HeatingOverride();
    Serial.println("          manual start of override");
    previousMillisOverrideStart = currentMillis;
  }
  if (isHeatingOverride) {
    if (currentMillis - previousMillisOverrideStart >= overrideRunTime)  // Automatically cancels the override
    {
      Serial.println("       auto termination of override");
      HeatingOverrideComplete();
    }
  }
  if ((downbutton.isPressed() && (!isMenuOn) && (isHeatingOverride)))  // Manually cancels the override
  {
    if (currentMillis - previousMillisOverrideStart >= 10000) {
      Serial.println("       manual termination of override");
      HeatingOverrideComplete();
    }
  }

  //------------------------Boiler Interlock--------------------------//

  if ((digitalRead(range_relay) == HIGH) || (digitalRead(club_relay) == HIGH) || (digitalRead(airgun_relay) == HIGH)) {
    digitalWrite(boiler_relay, HIGH);
  } else {
    digitalWrite(boiler_relay, LOW);
  }

  if (!isMenuOn) {
    TemperatureDisplay();
  }

  //------------------------------Menu--------------------------------//

  if (leftbutton.isPressed()) {
    isMenuOn = true;  // Initiiates the setup menu
    SettingMenu();
    selector++;  // this is done after SettingMenu(), so case 0 will be executed on the first button press
    if (selector > 5) {
      selector = 0;
    }
    previousMillisMenu = currentMillis;
  }

  if (isMenuOn) {
    if (currentMillis - previousMillisMenu >= menuOnTime) {
      isMenuOn = false;
      selector = 6;
      delay(2000);
      lcd.noBacklight();
    }
  }

  //------------------------------------------------------------------//

  if (upbutton.isPressed()) {
    isAdjustSetpointUp = true;  // Setpoint adjustment upwards is available
    previousMillisAdjustSetpointUp = currentMillis;
  }

  if (isAdjustSetpointUp) {
    if (currentMillis - previousMillisAdjustSetpointUp >= upButtonTime) {
      isAdjustSetpointUp = false;
    }
  }

  //------------------------------------------------------------------//

  if (isAdjustSetpointUp && isRangeSetpointAdjust)  // If True, this allows the Range setpoint to be adjusted
  {
    range_temperature_setpoint += 0.1;  // Range Setpoint increment
                                        //  Serial.println("range_temperature_setpoint_up");
    RangeDisplay();
    Buzzer();
  }

  if (isAdjustSetpointUp && isClubSetpointAdjust)  // If True, this allows the Club setpoint to be adjusted
  {
    club_temperature_setpoint += 0.1;  // Club Setpoint increment
                                       //   Serial.println("club_temperature_setpoint_up");
    ClubDisplay();
    Buzzer();
  }

  if (isAdjustSetpointUp && isAirgunSetpointAdjust)  // If True, this allows the Airgun setpoint to be adjusted
  {
    airgun_temperature_setpoint += 0.1;  // Airgun Setpoint increment
                                         //   Serial.println("airgun_temperature_setpoint_up");
    AirgunDisplay();
    Buzzer();
  }

  //------------------------------------------------------------------//

  if (downbutton.isPressed()) {
    isAdjustSetpointDown = true;  // Setpoint adjustment Downwards is available
    previousMillisAdjustSetpointDown = currentMillis;
  }
  if (isAdjustSetpointDown) {
    if (currentMillis - previousMillisAdjustSetpointDown >= downButtonTime) {
      isAdjustSetpointDown = false;
    }
  }

  if (isAdjustSetpointDown && isRangeSetpointAdjust)  // If True, this allows the Range setpoint to be adjusted
  {
    range_temperature_setpoint -= 0.1;  // Range Setpoint decrement
                                        //  Serial.println("range_temperature_setpoint_down");
    RangeDisplay();
    Buzzer();
  }

  if (isAdjustSetpointDown && isClubSetpointAdjust)  // If True, this allows the Club setpoint to be adjusted
  {
    club_temperature_setpoint -= 0.1;  // Club Setpoint decrement
                                       //  Serial.println("club_temperature_setpoint_down");
    ClubDisplay();
    Buzzer();
  }

  if (isAdjustSetpointDown && isAirgunSetpointAdjust)  // If True, this allows the Airgun setpoint to be adjusted
  {
    airgun_temperature_setpoint -= 0.1;  // Airgun Setpoint decrement
                                         //  Serial.println("airgun_temperature_setpoint_down");
    AirgunDisplay();
    Buzzer();
  }

  //-----------------------Setpoint Save------------------------------//

  if (isSetpointSave && isAdjustSetpointUp && isAdjustSetpointDown)  // If True, the new temperature setpoints are saved to EEPROM
  {
    for (int n = 0; n < 1; n++) {
      Serial.println("Changes saved");
      EEPROM.put(EEPROM_address_1, range_temperature_setpoint);   // write the new range setpoint value to EEPROM, only if it has changed
      EEPROM.put(EEPROM_address_2, club_temperature_setpoint);    // write the new club setpoint value to EEPROM, only if it has changed
      EEPROM.put(EEPROM_address_3, airgun_temperature_setpoint);  // write the new airgun setpoint value to EEPROM, only if it has changed

      delay(2000);
      RetrieveSetpoint();
      lcd.setCursor(3, 3);
      lcd.print("Settings saved");
      delay(3000);
      lcd.clear();
      TemperatureDisplay();
      HeatingAvailable();
    }
  }
  //----------------------------Print delay---------------------------//

  if (currentMillis - previousMillis >= printInterval)  // has enough time passed yet?
  {
    previousMillis = previousMillis + printInterval;  // If yes, set the time del;ay for the next print event
                                                      // capture & print data, or print latest data, etc.
                                                      // the closing curly brace at end of loop.

    //--------------------Daylight Saving Setting---------------------//

    DateTime standardTime = rtc.now();

    Serial.println("Standard Time");
    printTheTime(standardTime);

    DateTime theTime = dst_rtc.calculateTime(standardTime);  // takes into account DST

    Serial.println("Time adjusted for Daylight Saving Time");
    printTheTime(theTime);


    //-----------------------Timeswitch Control-----------------------//

    int currentTime = theTime.hour() * 60UL + theTime.minute();

    //--------------------Timed heating setting-----------------------//

    if (theTime.dayOfTheWeek() != SUNDAY) {
      // Monday to Saturday so use normal times
      if (currentTime < On_Normal_Range || currentTime > Off_Normal_Range) {
        // this is outside normal range heating time
        isRangeHeatOn = false;
      } else {
        // this is within normal range heating time
        isRangeHeatOn = true;
      }
    } else {
      // Sunday times
      if (currentTime < On_Sunday || currentTime > Off_Sunday) {
        // this is outside Sunday range heating time
        isRangeHeatOn = false;
      } else {
        // this is within Sunday range heating time
        isRangeHeatOn = true;
      }
    }
    if (theTime.dayOfTheWeek() != SUNDAY) {
      // Monday to Saturday so use normal times
      if (currentTime < On_Normal_Club || currentTime > Off_Normal_Club) {
        // this is outside normal club heating time
        isClubHeatOn = false;
      } else {
        // this is within normal club heating time
        isClubHeatOn = true;
      }
    } else {
      // Sunday times
      if (currentTime < On_Sunday || currentTime > Off_Sunday) {
        // this is outside Sunday club heating time
        isClubHeatOn = false;
      } else {
        // this is within Sunday club heating time
        isClubHeatOn = true;
      }
    }
    if (theTime.dayOfTheWeek() != SUNDAY) {
      // Monday to Saturday so use normal times
      if (currentTime < On_Normal_Airgun || currentTime > Off_Normal_Airgun) {
        // this is outside normal airgun heating time
        isAirgunHeatOn = false;
      } else {
        // this is within normal airgun heating time
        isAirgunHeatOn = true;
      }
    } else {
      // Sunday times
      if (currentTime < On_Sunday || currentTime > Off_Sunday) {
        // this is outside Sunday airgun heating time
        isAirgunHeatOn = false;
      } else {
        // this is within Sunday airgun heating time
        isAirgunHeatOn = true;
      }
    }

    //---------------------Date & Time display----------------------//

    if ((isTimeControlOn) || (!isMenuOn)) {
      Serial.println("Time & date showing on LCD");
      lcd.setCursor(0, 0);
      lcd.print(daysOfTheWeek[theTime.dayOfTheWeek()]);
      lcd.setCursor(3, 0);
      lcd.print(":");
      lcd.setCursor(4, 0);
      if (theTime.day() < 10) lcd.print('0');
      lcd.print(theTime.day(), DEC);
      lcd.print(':');
      if (theTime.month() < 10) lcd.print('0');
      lcd.print(theTime.month(), DEC);
      lcd.print(':');
      lcd.print(theTime.year(), DEC);
      lcd.setCursor(15, 0);
      if (theTime.hour() < 10) lcd.print('0');
      lcd.print(theTime.hour(), DEC);
      lcd.print(':');
      if (theTime.minute() < 10) lcd.print('0');
      lcd.print(theTime.minute(), DEC);
      HeatingAvailable();
    }

    //------------------------Serial print---------------------------//

    Serial.print("Requesting temperatures...");
    range_sensor.requestTemperatures();
    club_sensor.requestTemperatures();
    airgun_sensor.requestTemperatures();
    Serial.println(" done");
    Serial.print("Range: ");
    Serial.println(range_sensor.getTempCByIndex(0));
    Serial.print("Club: ");
    Serial.println(club_sensor.getTempCByIndex(0));
    Serial.print("Airgun: ");
    Serial.println(airgun_sensor.getTempCByIndex(0));
  }

}  // End of loop

//----------------------Print time to serial-------------------------//
void printTheTime(DateTime theTimeP) {

  Serial.print(theTimeP.year(), DEC);
  Serial.print('/');
  Serial.print(theTimeP.month(), DEC);
  Serial.print('/');
  Serial.print(theTimeP.day(), DEC);
  Serial.print(' ');
  Serial.print(theTimeP.hour(), DEC);
  Serial.print(':');
  Serial.print(theTimeP.minute(), DEC);
  Serial.print(':');
  Serial.print(theTimeP.second(), DEC);
  Serial.println();
}

//------------------------Retrieve setpoint--------------------------//
void RetrieveSetpoint() {

  EEPROM.get(EEPROM_address_1, range_temperature_setpoint);   //retrieve the range setpoint from EEPROM
  EEPROM.get(EEPROM_address_2, club_temperature_setpoint);    //retrieve the club setpoint from EEPROM
  EEPROM.get(EEPROM_address_3, airgun_temperature_setpoint);  //retrieve the airgun setpoint from EEPROM

  Serial.print("Range Temperature Setpoint  - ");
  Serial.print(range_temperature_setpoint);
  Serial.print(" Stored at addr ");
  Serial.println(EEPROM_address_1);
  Serial.print("Club Temperature Setpoint   - ");
  Serial.print(club_temperature_setpoint);
  Serial.print(" Stored at addr ");
  Serial.println(EEPROM_address_2);
  Serial.print("Airgun Temperature Setpoint - ");
  Serial.print(airgun_temperature_setpoint);
  Serial.print(" Stored at addr ");
  Serial.println(EEPROM_address_3);
}

//------------------------Setting Menu-------------------------------//
void SettingMenu() {

  switch (selector) {
    case 0:
      Serial.println("Case 0");  // General menu
      MenuScreen();
      break;
    case 1:
      Serial.println("Case 1");  // Displays the Range setpoint
      RangeDisplay();
      Buzzer();
      break;
    case 2:
      Serial.println("Case 2");  // Displays the Club setpoint
      ClubDisplay();
      Buzzer();
      break;
    case 3:
      Serial.println("Case 3");  // Displays the Airgun setpoint
      AirgunDisplay();
      Buzzer();
      break;
    case 4:
      Serial.println("Case 4");  // Instructs how to save the new settings
      SaveDisplay();
      break;
    case 5:
      Serial.println("Case 5");  // Reminds you to save
      lcd.clear();
      lcd.setCursor(3, 2);
      lcd.print("Did you save?");
      Buzzer();
  }
}

//---------------------------Buzzer----------------------------------//
void Buzzer() {

  {
    digitalWrite(buzzer_pin, HIGH);
    delay(100);
    digitalWrite(buzzer_pin, LOW);
  }
}

//-------------------------Menu screen-------------------------------//
void MenuScreen() {

  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 1);
  lcd.print("Range: ");
  lcd.print(range_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  lcd.setCursor(0, 2);
  lcd.print("Club:  ");
  lcd.print(club_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  lcd.setCursor(0, 3);
  lcd.print("Airgun:");
  lcd.print(airgun_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  isSetpointSave = false;
}

//-----------------------Setting Displays---------------------------//
void RangeDisplay() {

  isRangeSetpointAdjust = true;
  isClubSetpointAdjust = false;
  isAirgunSetpointAdjust = false;
  Serial.println("RangeChange");
  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 1);
  lcd.print("Range: ");
  lcd.print(range_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
}

//-------------------------------------------------------------------//
void ClubDisplay() {

  isClubSetpointAdjust = true;
  isRangeSetpointAdjust = false;
  isAirgunSetpointAdjust = false;
  Serial.println("ClubChange");
  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 2);
  lcd.print("Club:  ");
  lcd.print(club_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
}

//-------------------------------------------------------------------//
void AirgunDisplay() {

  isAirgunSetpointAdjust = true;
  isRangeSetpointAdjust = false;
  isClubSetpointAdjust = false;
  Serial.println("AirgunChange");
  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 3);
  lcd.print("Airgun:");
  lcd.print(airgun_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
}

//------------------------Save Display-------------------------------//
void SaveDisplay() {

  isRangeSetpointAdjust = false;
  isClubSetpointAdjust = false;
  isAirgunSetpointAdjust = false;
  Serial.println("SavingChanges");
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("To save the changes");
  lcd.setCursor(2, 1);
  lcd.print("Press Up & Down");
  lcd.setCursor(5, 2);
  lcd.print("Together");
  isSetpointSave = true;
}

//---------------------Temperature display - ------------------------//
void TemperatureDisplay() {

  lcd.setCursor(0, 1);  // Range Temperature
  lcd.print("Range: ");
  lcd.print(range_sensor.getTempCByIndex(0), 1);
  lcd.write((char)223);
  lcd.print('C');

  lcd.setCursor(0, 2);  // Club Temperature
  lcd.print("Club:  ");
  lcd.print(club_sensor.getTempCByIndex(0), 1);
  lcd.write((char)223);
  lcd.print('C');

  lcd.setCursor(0, 3);  // Airgun Temperature
  lcd.print("Airgun:");
  lcd.print(airgun_sensor.getTempCByIndex(0), 1);
  lcd.write((char)223);
  lcd.print('C');
}

//------------------------Heating Override---------------------------//
void HeatingOverride() {
  Serial.println("Heating Override is On");
  Serial.println("LCD showing Override is On");
  isRangeHeatOn = true;
  isClubHeatOn = true;
  isAirgunHeatOn = true;
  isTimeControlOn = false;
  HeatingAvailable();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("   OVERRIDE IS ON   ");
}

//--------------------Heating Override Complete----------------------//
void HeatingOverrideComplete() {

  Serial.println("Heating Override is complete");
  isHeatingOverride = false;
  isRangeHeatOn = false;
  isClubHeatOn = false;
  isAirgunHeatOn = false;
  isTimeControlOn = true;
  lcd.setCursor(0, 0);
  lcd.print("   OVERRIDE IS OFF   ");
  delay(2000);
  lcd.noBacklight();
}

//-----------------------Heating Available---------------------------//
void HeatingAvailable() {

  RangeHeatingAvailable();
  ClubHeatingAvailable();
  AirgunHeatingAvailable();
  Serial.println("Heating is available");
}

//-------------------------------------------------------------------//
void RangeHeatingAvailable() {

  {
    if (isRangeHeatOn) {
      NormalHeatingRange();
      Serial.println("Heating R");
    } else {
      Serial.println("Frost Prev. R");
      FrostPreventionRange();
    }
  }
}

//-------------------------------------------------------------------//
void ClubHeatingAvailable() {

  {
    if (isClubHeatOn) {
      NormalHeatingClub();
      Serial.println("Heating C");
    } else {
      Serial.println("Frost Prev. C");
      FrostPreventionClub();
    }
  }
}

//-------------------------------------------------------------------//
void AirgunHeatingAvailable() {

  {
    if (isAirgunHeatOn) {
      NormalHeatingAirgun();
      Serial.println("Heating A");
    } else {
      Serial.println("Frost Prev. A");
      FrostPreventionAirgun();
    }
  }
}

//-------------------Frost Prevention Range--------------------------//
void FrostPreventionRange() {

  if ((range_sensor.getTempCByIndex(0) <= (frost_setpoint - deadzone))) {
    digitalWrite(range_relay, HIGH);
    Serial.println("Range frost Heating");
    lcd.setCursor(13, 1);
    lcd.print("  ON  F");
  } else {
    if ((range_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone))) {
      digitalWrite(range_relay, LOW);
      lcd.setCursor(13, 1);
      lcd.print("  OFF F");
    }
  }
}

//--------------------Frost Prevention Club-------------------------//
void FrostPreventionClub() {

  if ((club_sensor.getTempCByIndex(0) <= (frost_setpoint - deadzone))) {
    digitalWrite(club_relay, HIGH);
    Serial.println("Club frost Heating");
    lcd.setCursor(13, 2);
    lcd.print("  ON  F");
  } else {
    if ((club_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone))) {
      digitalWrite(club_relay, LOW);
      lcd.setCursor(13, 2);
      lcd.print("  OFF F");
    }
  }
}

//---------------------Frost Prevention Airgun-----------------------//
void FrostPreventionAirgun() {

  if ((airgun_sensor.getTempCByIndex(0) <= (frost_setpoint - deadzone))) {
    digitalWrite(airgun_relay, HIGH);
    Serial.println("Air frost Heating");
    lcd.setCursor(13, 3);
    lcd.print("  ON  F");
  } else {
    if ((airgun_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone))) {
      digitalWrite(airgun_relay, LOW);
      lcd.setCursor(13, 3);
      lcd.print("  OFF F");
    }
  }
}

//-------------------Normal Heating Range----------------------------//
void NormalHeatingRange() {

  if ((range_sensor.getTempCByIndex(0) <= (range_temperature_setpoint - deadzone))) {
    digitalWrite(range_relay, HIGH);
    Serial.println("Normal Heating R");
    lcd.setCursor(13, 1);
    lcd.print("  ON  T");
  } else {
    if ((range_sensor.getTempCByIndex(0) >= (range_temperature_setpoint + deadzone))) {
      digitalWrite(range_relay, LOW);
      lcd.setCursor(13, 1);
      lcd.print("  OFF T");
    }
  }
}

//--------------------Normal Heating Club ---------------------------//
void NormalHeatingClub() {

  if ((club_sensor.getTempCByIndex(0) <= (club_temperature_setpoint - deadzone))) {
    digitalWrite(club_relay, HIGH);
    Serial.println("Normal Heating C");
    lcd.setCursor(13, 2);
    lcd.print("  ON  T");
  } else {
    if ((club_sensor.getTempCByIndex(0) >= (club_temperature_setpoint + deadzone))) {
      digitalWrite(club_relay, LOW);
      lcd.setCursor(13, 2);
      lcd.print("  OFF T");
    }
  }
}

//---------------------Normal Heating Airgun-------------------------//
void NormalHeatingAirgun() {

  if ((airgun_sensor.getTempCByIndex(0) <= (airgun_temperature_setpoint - deadzone))) {
    digitalWrite(airgun_relay, HIGH);
    Serial.println("Normal Heating A");
    lcd.setCursor(13, 3);
    lcd.print("  ON  T");
  } else {
    if ((airgun_sensor.getTempCByIndex(0) >= (airgun_temperature_setpoint + deadzone))) {
      digitalWrite(airgun_relay, LOW);
      lcd.setCursor(13, 3);
      lcd.print("  OFF T");
    }
  }
}

What does it take to make you post

THIS information?

What does this mean that you

try

to capture serial prints

Are you using Arduino IDE 2.X??
Throw it away ! I am serious !
This IDE 2.X has many bells and whistles but is missing the most useful debugging-tools

simply print
but also
copy & paste hundreds of lines of serial printing
Install IDE 1.8.19 to have this comfort.
IDE 1.8.19 has this comfort of copy & paste hundreds of lines of serial printing

Evening @StefanL38, thank you for your reply. Is it OK to attach photos of the LCD on the forum? If so I will take some tomorrow.

I'll try, but it looks like time is going backwards. Your printing may make sense to you, but I still don't see what you are getting at, or if I do how it is at all possible.

What part of this does not work?

  • Press the override button, override begins. 45 seconds later override is exited.

  • Press the override button, override begins. 13 seconds later, press the button, override is exited.

And/or is there anything other than pressing the button that makes override happen? How and where in the code is that? And if so, how in those additional cases is override exited?

a7

Evening @alto777
I will have another look and ponder at this scenario tomorrow and let you know what my thoughts are. I have obviously overlooked something in the sketch that is cancelling the effect of the override within one second of it starting, whilst still allowing the override time out to continue. The sketch in general appears to function correctly apart from this override function.

It appears that you are focusing on the lcd routine and function calls right after the start of override.

I think that somehow maybe I have got too much trying to go on at once.

Trying to simplify and reduce the code to the minimal amount which still exhibits the problem would certainly help you focus on the problem.

Sure ! You seem to have not looked into many other threads than your own.

this is still way too UNprecise for analysing.
One way to find a bug is extra-ordinary excessive serial debug printing.

You observe switching relays. Which should not happen. So making visible
WHEN and WHERE in the code the switching happends will guide to the place in the code where it happends.

I have taken the effort to add a lot of serial debug-printing into your code.
For this I moved all fixed-string printing from RAM to flash-memory by using the
F-Macro which frees up 1 kB of RAM

I added my debug-macros at the top of the code.

I added this function

void printRelayStateChanges() {
  dbgc("01", digitalRead(range_relay)  );
  dbgc("02", digitalRead(club_relay)   );
  dbgc("03", digitalRead(airgun_relay) );
  dbgc("04", digitalRead(boiler_relay) );
}

which works this way:
example for digitalRead(range_relay)
only in case the expression digitalRead(range_relay) has changed its value this change will be printed.
If the value stayed the same it will not print.

dbgc
dbg for "debug"
"c" for Change

I added serial printing to most of the functions when entering an when leaving the function.

This will make visible the code-execution in much more detail.
Analysing this amout of serial printing requires IDE 1.8.19

I am really sorry to continiue with big red bolded capital letters. They are meant as
emphasizing.
They are not meant as aggressive shouting.
Read it ? They are not meant as aggressive shouting.
They are meant as
the 25th time complaining about Arduino IDE 2.X not beeing able to use serial printing in a comfortable way

Analysing this amout of serial printing

CAN'T BE DONE WITH ARDUINO IDE 2.X !!

(as a sidemark: @ptillisch if you know the person who is repsonsible for deciding
to add this functionality to IDE 2.X Ask this person what alternatives does IDE 2.X offer for debugging in a case like this. Can it be done by hardware-debugging on an Arduino Uno R3?"
)

For analysing this, Arduino IDE 1.8.19 must be used
Here is a tutorial that explains how tp install Arduino IE Version 1.8.19 as a portable version
that works completely independant of any other Arduino IDE-installation

You can flash your code with IDE 2.X and then run IDE 1.8.19 adjust serial monitor to the correct COM-Port and open serial monitor

// MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START *
// a detailed explanation how these macros work is given in this tutorial
// https://forum.arduino.cc/t/comfortable-serial-debug-output-short-to-write-fixed-text-name-and-content-of-any-variable-code-example/888298

#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);

#define dbgi(myFixedText, variableName,timeInterval) \
  { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  }

#define dbgc(myFixedText, variableName) \
  { \
    static long lastState; \
    if ( lastState != variableName ){ \
      Serial.print( F(#myFixedText " "  #variableName" changed from ") ); \
      Serial.print(lastState); \
      Serial.print( F(" to ") ); \
      Serial.println(variableName); \
      lastState = variableName; \
    } \
  }

#define dbgcf(myFixedText, variableName) \
  { \
    static float lastState; \
    if ( lastState != variableName ){ \
      Serial.print( F(#myFixedText " "  #variableName" changed from ") ); \
      Serial.print(lastState); \
      Serial.print( F(" to ") ); \
      Serial.println(variableName); \
      lastState = variableName; \
    } \
  }
// MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END *

unsigned long MyTestTimer = 0;                   // Timer-variables MUST be of type unsigned long
const byte    OnBoard_LED = 2;     // onboard-LEDESP32 / ESP8266

/* Author : John Marchant G0RJM
  Created : 25/01/2024 - 18/10/2024
  Description : A project to display the current date, time and temperature on the 20x4 LCD screen
  and create a programmable thermostatic heating control of three zones to include frost protection and override facilities.
  This has been loosely based on the RTClib ds3231 example along with many code parts borrowed
  from several other files/sketches and in collaboration with several authors from the
  Arduino Forum in particular @alto777, @blh64, @cattledog, @StefanL38 and @UKHeliBob. */
#include "RTClib.h"
#include <OneWire.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ezButton.h>
#include <DallasTemperature.h>
#include <EEPROM.h>
#include <DST_RTC.h>

#define ONE_WIRE_BUS_1 3
#define ONE_WIRE_BUS_2 4
#define ONE_WIRE_BUS_3 5
#define TEMPERATURE_PRECISION 11  // 0.125deg resolution
#define buzzer_pin A0

OneWire oneWire_range(ONE_WIRE_BUS_1);
OneWire oneWire_club(ONE_WIRE_BUS_2);
OneWire oneWire_airgun(ONE_WIRE_BUS_3);

const byte range_relay = 10;  // Relay outputs
const byte club_relay = 11;
const byte airgun_relay = 12;
const byte boiler_relay = 13;

float range_temperature;
float club_temperature;
float airgun_temperature;

float range_temperature_setpoint;   // C
float club_temperature_setpoint;    // C
float airgun_temperature_setpoint;  // C
const int frost_setpoint = 12.0;    // C
const float deadzone = 1.0;         // C

ezButton leftbutton(6);   // create ezButton object that is attached to pin 6;
ezButton upbutton(7);     // create ezButton object that is attached to pin 7;
ezButton downbutton(8);   // create ezButton object that is attached to pin 8;
ezButton rightbutton(9);  // create ezButton object that is attached to pin 9;

const int ledPin = 13;

const unsigned menuOnTime = 20000;
const unsigned upButtonTime = 50;
const unsigned downButtonTime = 50;
const unsigned printInterval = 3000;
const unsigned overrideRunTime = 45000;
const unsigned backlightOnTime = 30000;

int value = 0;
int selector = 0;

int EEPROM_address_1 = 0;  // Address of the location of range setpoint temperature
int EEPROM_address_2 = 4;  // Address of the location of club setpoint temperature
int EEPROM_address_3 = 8;  // Address of the location of airgun setpoint temperature

unsigned long startMillis = 0;
unsigned long menuOffTime = 0;
unsigned long currentMillis = 0;
unsigned long previousMillis = 0;
unsigned long previousMillisMenu = 0;
unsigned long previousMillisBacklight = 0;
unsigned long previousMillisOverrideStart = 0;
unsigned long previousMillisAdjustSetpointUp = 0;
unsigned long previousMillisAdjustSetpointDown = 0;

bool isMenuOn = false;                // if = True initiiates the setup menu
bool isRangeHeatOn = false;           // if = True this allows the Range heating
bool isClubHeatOn = false;            // if = True this allows the Club heating
bool isAirgunHeatOn = false;          // if = True this allows the Airgun heating
bool isSetpointSave = false;          // if = True allows the new setpoints to be saved to EEPROM
bool isTimeControlOn = false;         // if = True allows normal time control function
bool isHeatingOverride = false;       // if = True allows the heating override facility
bool isAdjustSetpointUp = false;      // if = True allows the Setpoint increment
bool isAdjustSetpointDown = false;    // if = True allows the Setpoint decrement
bool isRangeSetpointAdjust = false;   // if = True allows the range setpoint to be adjusted
bool isClubSetpointAdjust = false;    // if = True allows the club setpoint to be adjusted
bool isAirgunSetpointAdjust = false;  // if = True allows the airgun setpoint to be adjusted

DallasTemperature range_sensor(&oneWire_range);
DallasTemperature club_sensor(&oneWire_club);
DallasTemperature airgun_sensor(&oneWire_airgun);

enum { SUNDAY,
       MONDAY,
       TUESDAY,
       WEDNESDAY,
       THURSDAY,
       FRIDAY,
       SATURDAY
     };

const int On_Normal_Range = 16 * 60UL + 00;    // 16:00 Normal ON time
const int Off_Normal_Range = 19 * 60UL + 30;   // 21:30 Normal OFF time
const int On_Normal_Club = 16 * 60UL + 00;     // 16:00 Normal ON time
const int Off_Normal_Club = 19 * 60UL + 30;    // 21:30 Normal OFF time
const int On_Normal_Airgun = 16 * 60UL + 00;   // 16:00 Normal ON time
const int Off_Normal_Airgun = 19 * 60UL + 30;  // 21:30 Normal OFF time
const int On_Sunday = 12 * 60UL + 00;          // 12:00 Sunday ON time
const int Off_Sunday = 17 * 60UL + 30;         // 17:30 Sunday OFF time

LiquidCrystal_I2C lcd(0X27, 20, 4);
RTC_DS3231 rtc;
DST_RTC dst_rtc;  // DST object

// Define US or EU rules for DST comment out as required. More countries could be added with different rules in DST_RTC.cpp
// const char rulesDST[] = "US"; // US DST rules
const char rulesDST[] = "EU";  // EU DST rules

char daysOfTheWeek[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
byte char_temp[8] = { B00100, B01010, B01010, B01110, B01110, B11111, B11111, B01110 };  // for thermometer icon

void setup() {

  pinMode(range_relay, OUTPUT);
  pinMode(club_relay, OUTPUT);
  pinMode(airgun_relay, OUTPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(buzzer_pin, OUTPUT);

  leftbutton.setDebounceTime(50);   // set debounce time to 50 milliseconds
  upbutton.setDebounceTime(50);     // set debounce time to 50 milliseconds
  downbutton.setDebounceTime(50);   // set debounce time to 50 milliseconds
  rightbutton.setDebounceTime(50);  // set debounce time to 50 milliseconds

  Serial.begin(9600);

  // EEPROM.put(EEPROM_address_1, range_temperature_setpoint);   // write the float value to EEPROM ONLY used to initially set up setpoint
  // EEPROM.put(EEPROM_address_2, club_temperature_setpoint);    // write the float value to EEPROM ONLY used to initially set up setpoint
  // EEPROM.put(EEPROM_address_3, airgun_temperature_setpoint);  // write the float value to EEPROM ONLY used to initially set up setpoint

  RetrieveSetpoint();

  startMillis = millis();

  Wire.begin();
  rtc.begin();

  lcd.init();
  lcd.backlight();
  lcd.createChar(0, char_temp);
  lcd.setCursor(2, 0);
  lcd.print("G0RJM Three Zone");
  lcd.setCursor(1, 1);
  lcd.print("Temperature Control");
  lcd.setCursor(4, 2);
  lcd.print("Take it easy!");
  lcd.setCursor(4, 3);
  lcd.print("Working on it");

  delay(1000);
  lcd.clear();
  if (rtc.lostPower()) {
    Serial.println( F("RTC is NOT running!") ) ;
    // Uncomment the following line to set the RTC to the date & time this sketch was compiled
    // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // rtc.adjust(DateTime(2024,4,24,21,49,00));
    /* DST? If we're in it, let's subtract an hour from the RTC time to keep our DST calculation correct. This gives us
      Standard Time which our DST check will add an hour back to if we're in DST.
    */
    DateTime standardTime = rtc.now();
    if (dst_rtc.checkDST(standardTime) == true) {  // check whether we're in DST right now. If we are, subtract an hour.
      standardTime = standardTime.unixtime() - 3600;
    }
    rtc.adjust(standardTime);
  }

  // Serial.println( F("G0RJM Three zone Temperature Control") );

  range_sensor.begin();
  club_sensor.begin();
  airgun_sensor.begin();

  range_sensor.setResolution(TEMPERATURE_PRECISION);
  club_sensor.setResolution(TEMPERATURE_PRECISION);
  airgun_sensor.setResolution(TEMPERATURE_PRECISION);

}  // End of setup


void printRelayStateChanges() {
  dbgc("01", digitalRead(range_relay)  );
  dbgc("02", digitalRead(club_relay)   );
  dbgc("03", digitalRead(airgun_relay) );
  dbgc("04", digitalRead(boiler_relay) );
}

void loop() {
  printRelayStateChanges();
  //       ("ABCDEFGHIJ1234567890")
  //-------------------------Button control---------------------------//

  leftbutton.loop();   // MUST call the loop() function first
  upbutton.loop();     // MUST call the loop() function first
  downbutton.loop();   // MUST call the loop() function first
  rightbutton.loop();  // MUST call the loop() function first

  // -----------------------Backlight display-------------------------//

  unsigned long currentMillis = millis();  // all time elements are data type unsigned long

  if ((isHeatingOverride) || (leftbutton.isPressed() || (rightbutton.isPressed() || (upbutton.isPressed())))) {
    lcd.backlight();
    previousMillisBacklight = currentMillis;
  }
  printRelayStateChanges();

  if ((!isHeatingOverride) && (currentMillis - previousMillisBacklight >= backlightOnTime)) {
    lcd.noBacklight();
    isMenuOn = false;
  }
  printRelayStateChanges();
  //---------------------------Heating Override-----------------------//

  if ((downbutton.isPressed() && (!isMenuOn) && (!isHeatingOverride))) {

    isHeatingOverride = true;  // Establishes the heating override
    HeatingOverride();
    Serial.println( F("          manual start of override" ) );
    previousMillisOverrideStart = currentMillis;
  }
  printRelayStateChanges();

  if (isHeatingOverride) {
    if (currentMillis - previousMillisOverrideStart >= overrideRunTime)  // Automatically cancels the override
    {
      Serial.println( F("       auto termination of override") );
      HeatingOverrideComplete();
    }
  }
  printRelayStateChanges();

  if ((downbutton.isPressed() && (!isMenuOn) && (isHeatingOverride)))  // Manually cancels the override
  {
    if (currentMillis - previousMillisOverrideStart >= 10000) {
      Serial.println( F("       manual termination of override") );
      HeatingOverrideComplete();
    }
  }
  printRelayStateChanges();


  //------------------------Boiler Interlock--------------------------//

  if ((digitalRead(range_relay) == HIGH) || (digitalRead(club_relay) == HIGH) || (digitalRead(airgun_relay) == HIGH)) {
    digitalWrite(boiler_relay, HIGH);
  }
  else {
    digitalWrite(boiler_relay, LOW);
    Serial.println( F("08 else to digitalRead(range_relay) == HIGH) || (digitalRead(club_relay) == HIGH) || (digitalRead(airgun_relay) == HIGH ") );
  }
  printRelayStateChanges();


  if (!isMenuOn) {
    TemperatureDisplay();
  }
  printRelayStateChanges();


  //------------------------------Menu--------------------------------//

  if (leftbutton.isPressed()) {
    isMenuOn = true;  // Initiiates the setup menu
    SettingMenu();
    selector++;  // this is done after SettingMenu(), so case 0 will be executed on the first button press
    if (selector > 5) {
      selector = 0;
    }
    previousMillisMenu = currentMillis;
  }
  printRelayStateChanges();


  if (isMenuOn) {
    if (currentMillis - previousMillisMenu >= menuOnTime) {
      isMenuOn = false;
      selector = 6;
      delay(2000);
      lcd.noBacklight();
    }
    printRelayStateChanges();

  }

  //------------------------------------------------------------------//

  if (upbutton.isPressed()) {
    isAdjustSetpointUp = true;  // Setpoint adjustment upwards is available
    previousMillisAdjustSetpointUp = currentMillis;
  }
  printRelayStateChanges();


  if (isAdjustSetpointUp) {
    if (currentMillis - previousMillisAdjustSetpointUp >= upButtonTime) {
      isAdjustSetpointUp = false;
    }
    printRelayStateChanges();

  }

  //------------------------------------------------------------------//

  if (isAdjustSetpointUp && isRangeSetpointAdjust)  // If True, this allows the Range setpoint to be adjusted
  {
    range_temperature_setpoint += 0.1;  // Range Setpoint increment
    //  Serial.println( F("range_temperature_setpoint_up") ) ;
    RangeDisplay();
    Buzzer();
  }
  printRelayStateChanges();


  if (isAdjustSetpointUp && isClubSetpointAdjust)  // If True, this allows the Club setpoint to be adjusted
  {
    club_temperature_setpoint += 0.1;  // Club Setpoint increment
    //   Serial.println( F("club_temperature_setpoint_up") );
    ClubDisplay();
    Buzzer();
  }
  printRelayStateChanges();


  if (isAdjustSetpointUp && isAirgunSetpointAdjust)  // If True, this allows the Airgun setpoint to be adjusted
  {
    airgun_temperature_setpoint += 0.1;  // Airgun Setpoint increment
    //   Serial.println( F("airgun_temperature_setpoint_up") );
    AirgunDisplay();
    Buzzer();
  }
  printRelayStateChanges();


  //------------------------------------------------------------------//

  if (downbutton.isPressed()) {
    isAdjustSetpointDown = true;  // Setpoint adjustment Downwards is available
    previousMillisAdjustSetpointDown = currentMillis;
  }
  printRelayStateChanges();

  if (isAdjustSetpointDown) {
    if (currentMillis - previousMillisAdjustSetpointDown >= downButtonTime) {
      isAdjustSetpointDown = false;
    }
  }
  printRelayStateChanges();


  if (isAdjustSetpointDown && isRangeSetpointAdjust)  // If True, this allows the Range setpoint to be adjusted
  {
    range_temperature_setpoint -= 0.1;  // Range Setpoint decrement
    //  Serial.println( F("range_temperature_setpoint_down") );
    RangeDisplay();
    Buzzer();
  }
  printRelayStateChanges();


  if (isAdjustSetpointDown && isClubSetpointAdjust)  // If True, this allows the Club setpoint to be adjusted
  {
    club_temperature_setpoint -= 0.1;  // Club Setpoint decrement
    //  Serial.println( F("club_temperature_setpoint_down") );
    ClubDisplay();
    Buzzer();
  }
  printRelayStateChanges();


  if (isAdjustSetpointDown && isAirgunSetpointAdjust)  // If True, this allows the Airgun setpoint to be adjusted
  {
    airgun_temperature_setpoint -= 0.1;  // Airgun Setpoint decrement
    //  Serial.println( F("airgun_temperature_setpoint_down") );
    AirgunDisplay();
    Buzzer();
  }
  printRelayStateChanges();


  //-----------------------Setpoint Save------------------------------//

  if (isSetpointSave && isAdjustSetpointUp && isAdjustSetpointDown)  // If True, the new temperature setpoints are saved to EEPROM
  {
    for (int n = 0; n < 1; n++) {
      printRelayStateChanges();

      Serial.println( F("Changes saved") );
      EEPROM.put(EEPROM_address_1, range_temperature_setpoint);   // write the new range setpoint value to EEPROM, only if it has changed
      EEPROM.put(EEPROM_address_2, club_temperature_setpoint);    // write the new club setpoint value to EEPROM, only if it has changed
      EEPROM.put(EEPROM_address_3, airgun_temperature_setpoint);  // write the new airgun setpoint value to EEPROM, only if it has changed

      delay(2000);
      RetrieveSetpoint();
      printRelayStateChanges();

      lcd.setCursor(3, 3);
      lcd.print("Settings saved");
      delay(3000);
      lcd.clear();
      TemperatureDisplay();
      printRelayStateChanges();

      HeatingAvailable();
      printRelayStateChanges();

    }
  }
  //----------------------------Print delay---------------------------//

  if (currentMillis - previousMillis >= printInterval)  // has enough time passed yet?
  {
    previousMillis = previousMillis + printInterval;  // If yes, set the time del;ay for the next print event
    // capture & print data, or print latest data, etc.
    // the closing curly brace at end of loop.

    //--------------------Daylight Saving Setting---------------------//

    DateTime standardTime = rtc.now();

    Serial.println( F("Standard Time") );
    printTheTime(standardTime);

    DateTime theTime = dst_rtc.calculateTime(standardTime);  // takes into account DST

    Serial.println( F("Time adjusted for Daylight Saving Time") );
    printTheTime(theTime);


    //-----------------------Timeswitch Control-----------------------//

    int currentTime = theTime.hour() * 60UL + theTime.minute();

    //--------------------Timed heating setting-----------------------//

    if (theTime.dayOfTheWeek() != SUNDAY) {
      // Monday to Saturday so use normal times
      if (currentTime < On_Normal_Range || currentTime > Off_Normal_Range) {
        // this is outside normal range heating time
        isRangeHeatOn = false;
      }
      else {
        // this is within normal range heating time
        isRangeHeatOn = true;
      }
    }
    else {
      // Sunday times
      if (currentTime < On_Sunday || currentTime > Off_Sunday) {
        // this is outside Sunday range heating time
        isRangeHeatOn = false;
      }
      else {
        // this is within Sunday range heating time
        isRangeHeatOn = true;
      }
    }
    printRelayStateChanges();

    if (theTime.dayOfTheWeek() != SUNDAY) {
      // Monday to Saturday so use normal times
      if (currentTime < On_Normal_Club || currentTime > Off_Normal_Club) {
        // this is outside normal club heating time
        isClubHeatOn = false;
      }
      else {
        // this is within normal club heating time
        isClubHeatOn = true;
      }
    }
    else {
      // Sunday times
      if (currentTime < On_Sunday || currentTime > Off_Sunday) {
        // this is outside Sunday club heating time
        isClubHeatOn = false;
      } else {
        // this is within Sunday club heating time
        isClubHeatOn = true;
      }
    }
    if (theTime.dayOfTheWeek() != SUNDAY) {
      // Monday to Saturday so use normal times
      if (currentTime < On_Normal_Airgun || currentTime > Off_Normal_Airgun) {
        // this is outside normal airgun heating time
        isAirgunHeatOn = false;
      }
      else {
        // this is within normal airgun heating time
        isAirgunHeatOn = true;
      }
    }
    else {
      // Sunday times
      if (currentTime < On_Sunday || currentTime > Off_Sunday) {
        // this is outside Sunday airgun heating time
        isAirgunHeatOn = false;
      }
      else {
        // this is within Sunday airgun heating time
        isAirgunHeatOn = true;
      }
    }
    printRelayStateChanges();


    //---------------------Date & Time display----------------------//

    if ((isTimeControlOn) || (!isMenuOn)) {
      Serial.println( F("Time & date showing on LCD") );
      lcd.setCursor(0, 0);
      lcd.print(daysOfTheWeek[theTime.dayOfTheWeek()]);
      lcd.setCursor(3, 0);
      lcd.print(":");
      lcd.setCursor(4, 0);
      if (theTime.day() < 10) lcd.print('0');
      lcd.print(theTime.day(), DEC);
      lcd.print(':');
      if (theTime.month() < 10) lcd.print('0');
      lcd.print(theTime.month(), DEC);
      lcd.print(':');
      lcd.print(theTime.year(), DEC);
      lcd.setCursor(15, 0);
      if (theTime.hour() < 10) lcd.print('0');
      lcd.print(theTime.hour(), DEC);
      lcd.print(':');
      if (theTime.minute() < 10) lcd.print('0');
      lcd.print(theTime.minute(), DEC);
      HeatingAvailable();
    }
    printRelayStateChanges();


    //------------------------Serial print---------------------------//

    Serial.print( F("Requesting temperatures...") );
    range_sensor.requestTemperatures();
    club_sensor.requestTemperatures();
    airgun_sensor.requestTemperatures();
    Serial.println( F(" done") );
    Serial.print( F("Range: ") );
    Serial.println(range_sensor.getTempCByIndex(0));
    Serial.print( F("Club: ") );
    Serial.println(club_sensor.getTempCByIndex(0));
    Serial.print( F("Airgun: ") );
    Serial.println(airgun_sensor.getTempCByIndex(0));
  }

}  // End of loop

//----------------------Print time to serial-------------------------//
void printTheTime(DateTime theTimeP) {

  Serial.print(theTimeP.year(), DEC);
  Serial.print('/');
  Serial.print(theTimeP.month(), DEC);
  Serial.print('/');
  Serial.print(theTimeP.day(), DEC);
  Serial.print(' ');
  Serial.print(theTimeP.hour(), DEC);
  Serial.print(':');
  Serial.print(theTimeP.minute(), DEC);
  Serial.print(':');
  Serial.print(theTimeP.second(), DEC);
  Serial.println();
}

//------------------------Retrieve setpoint--------------------------//
void RetrieveSetpoint() {

  EEPROM.get(EEPROM_address_1, range_temperature_setpoint);   //retrieve the range setpoint from EEPROM
  EEPROM.get(EEPROM_address_2, club_temperature_setpoint);    //retrieve the club setpoint from EEPROM
  EEPROM.get(EEPROM_address_3, airgun_temperature_setpoint);  //retrieve the airgun setpoint from EEPROM

  Serial.print( F("Range Temperature Setpoint  - ") );
  Serial.print(range_temperature_setpoint);
  Serial.print( F(" Stored at addr ") );
  Serial.println(EEPROM_address_1);
  Serial.print( F("Club Temperature Setpoint   - ") );
  Serial.print(club_temperature_setpoint);
  Serial.print( F(" Stored at addr ") );
  Serial.println(EEPROM_address_2);
  Serial.print( F("Airgun Temperature Setpoint - ") );
  Serial.print(airgun_temperature_setpoint);
  Serial.print( F(" Stored at addr ") );
  Serial.println(EEPROM_address_3);
}

//------------------------Setting Menu-------------------------------//
void SettingMenu() {

  switch (selector) {
    case 0:
      Serial.println( F("Case 0") );  // General menu
      MenuScreen();
      break;
    case 1:
      Serial.println( F("Case 1") );  // Displays the Range setpoint
      RangeDisplay();
      Buzzer();
      break;
    case 2:
      Serial.println( F("Case 2") );  // Displays the Club setpoint
      ClubDisplay();
      Buzzer();
      break;
    case 3:
      Serial.println( F("Case 3") );  // Displays the Airgun setpoint
      AirgunDisplay();
      Buzzer();
      break;
    case 4:
      Serial.println( F("Case 4") );  // Instructs how to save the new settings
      SaveDisplay();
      break;
    case 5:
      Serial.println( F("Case 5") );  // Reminds you to save
      lcd.clear();
      lcd.setCursor(3, 2);
      lcd.print( F("Did you save?") );
      Buzzer();
  }
}

//---------------------------Buzzer----------------------------------//
void Buzzer() {

  digitalWrite(buzzer_pin, HIGH);
  delay(100);
  digitalWrite(buzzer_pin, LOW);
}

//-------------------------Menu screen-------------------------------//
void MenuScreen() {

  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 1);
  lcd.print("Range: ");
  lcd.print(range_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  lcd.setCursor(0, 2);
  lcd.print("Club:  ");
  lcd.print(club_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  lcd.setCursor(0, 3);
  lcd.print("Airgun:");
  lcd.print(airgun_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  isSetpointSave = false;
}

//-----------------------Setting Displays---------------------------//
void RangeDisplay() {
  Serial.println( F("enter RangeDisplay") );
  isRangeSetpointAdjust = true;
  isClubSetpointAdjust = false;
  isAirgunSetpointAdjust = false;
  Serial.println( F("RangeChange") );
  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 1);
  lcd.print("Range: ");
  lcd.print(range_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  printRelayStateChanges();
  
  Serial.println( F("exit RangeDisplay") );
}

//-------------------------------------------------------------------//
void ClubDisplay() {
  Serial.println( F("enter ClubDisplay") );

  isClubSetpointAdjust = true;
  isRangeSetpointAdjust = false;
  isAirgunSetpointAdjust = false;
  Serial.println( F("ClubChange") );
  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 2);
  lcd.print("Club:  ");
  lcd.print(club_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  printRelayStateChanges();
  Serial.println( F("exit ClubDisplay") );
}

//-------------------------------------------------------------------//
void AirgunDisplay() {
  Serial.println( F("enter AirgunDisplay") );

  isAirgunSetpointAdjust = true;
  isRangeSetpointAdjust = false;
  isClubSetpointAdjust = false;
  Serial.println( F("AirgunChange") );
  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 3);
  lcd.print("Airgun:");
  lcd.print(airgun_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  printRelayStateChanges();
  Serial.println( F("exit AirgunDisplay") );
}

//------------------------Save Display-------------------------------//
void SaveDisplay() {
  Serial.println( F("enter SaveDisplay") );

  isRangeSetpointAdjust = false;
  isClubSetpointAdjust = false;
  isAirgunSetpointAdjust = false;
  Serial.println( F("SavingChanges") );
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("To save the changes");
  lcd.setCursor(2, 1);
  lcd.print("Press Up & Down");
  lcd.setCursor(5, 2);
  lcd.print("Together");
  isSetpointSave = true;
  printRelayStateChanges();
  Serial.println( F("exit SaveDisplay") );
}

//---------------------Temperature display - ------------------------//
void TemperatureDisplay() {
  Serial.println( F("enter TemperatureDisplay") );

  lcd.setCursor(0, 1);  // Range Temperature
  lcd.print("Range: ");
  lcd.print(range_sensor.getTempCByIndex(0), 1);
  lcd.write((char)223);
  lcd.print('C');

  lcd.setCursor(0, 2);  // Club Temperature
  lcd.print("Club:  ");
  lcd.print(club_sensor.getTempCByIndex(0), 1);
  lcd.write((char)223);
  lcd.print('C');

  lcd.setCursor(0, 3);  // Airgun Temperature
  lcd.print("Airgun:");
  lcd.print(airgun_sensor.getTempCByIndex(0), 1);
  lcd.write((char)223);
  lcd.print('C');
  printRelayStateChanges();
  Serial.println( F("exit TemperatureDisplay") );
}

//------------------------Heating Override---------------------------//
void HeatingOverride() {
  Serial.println( F("enter HeatingOverride") );

  Serial.println( F("Heating Override is On") );
  Serial.println( F("LCD showing Override is On") );
  isRangeHeatOn = true;
  isClubHeatOn = true;
  isAirgunHeatOn = true;
  isTimeControlOn = false;
  HeatingAvailable();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print( F("   OVERRIDE IS ON   ") );
  printRelayStateChanges();
  Serial.println( F("exit HeatingOverride") );
}

//--------------------Heating Override Complete----------------------//
void HeatingOverrideComplete() {
  Serial.println( F("enter HeatingOverrideComplete") );

  Serial.println( F("Heating Override is complete") );
  isHeatingOverride = false;
  isRangeHeatOn = false;
  isClubHeatOn = false;
  isAirgunHeatOn = false;
  isTimeControlOn = true;
  lcd.setCursor(0, 0);
  lcd.print( F("   OVERRIDE IS OFF   ") );
  delay(2000);
  lcd.noBacklight();
  Serial.println( F("exit HeatingOverrideComplete") );
}

//-----------------------Heating Available---------------------------//
void HeatingAvailable() {
  Serial.println( F("enter HeatingAvailable") );

  RangeHeatingAvailable();
  ClubHeatingAvailable();
  AirgunHeatingAvailable();
  Serial.println( F("Heating is available") );
  printRelayStateChanges();
  Serial.println( F("exit HeatingAvailable") );
}

//-------------------------------------------------------------------//
void RangeHeatingAvailable() {
  Serial.println( F("enter RangeHeatingAvailable") );

  if (isRangeHeatOn) {
    NormalHeatingRange();
    Serial.println( F("Heating R") );
  } else {
    Serial.println( F("Frost Prev. R") );
    FrostPreventionRange();
  }
  printRelayStateChanges();
  Serial.println( F("exit RangeHeatingAvailable") );
}

//-------------------------------------------------------------------//
void ClubHeatingAvailable() {
  Serial.println( F("enter ClubHeatingAvailable") );

  if (isClubHeatOn) {
    NormalHeatingClub();
    Serial.println( F("Heating C") );
  }
  else {
    Serial.println(F("Frost Prev. C") );
    FrostPreventionClub();
  }
  printRelayStateChanges();
  Serial.println( F("exit ClubHeatingAvailable") );
}

//-------------------------------------------------------------------//
void AirgunHeatingAvailable() {
  Serial.println( F("enter AirgunHeatingAvailable") );

  if (isAirgunHeatOn) {
    NormalHeatingAirgun();
    Serial.println( F("Heating A") );
  }
  else {
    Serial.println( F("Frost Prev. A") );
    FrostPreventionAirgun();
  }
  printRelayStateChanges();
  Serial.println( F("exit AirgunHeatingAvailable") );
}

//-------------------Frost Prevention Range--------------------------//
void FrostPreventionRange() {
  Serial.println( F("enter FrostPreventionRange") );

  if ((range_sensor.getTempCByIndex(0) <= (frost_setpoint - deadzone))) {
    digitalWrite(range_relay, HIGH);
    Serial.println( F("Range frost Heating") );
    lcd.setCursor(13, 1);
    lcd.print( F("  ON  F") );
  }
  else {
    if ( (range_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone))  ) {
      digitalWrite(range_relay, LOW);
      Serial.println( F("01 range_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone") );
      lcd.setCursor(13, 1);
      lcd.print( F("  OFF F") );
    }
  }
  printRelayStateChanges();
  Serial.println( F("exit FrostPreventionRange") );
}

//--------------------Frost Prevention Club-------------------------//
void FrostPreventionClub() {
  Serial.println( F("enter FrostPreventionClub") );

  if ((club_sensor.getTempCByIndex(0) <= (frost_setpoint - deadzone))) {
    digitalWrite(club_relay, HIGH);
    Serial.println( F("02 Club frost Heating") );
    lcd.setCursor(13, 2);
    lcd.print( F("  ON  F") );
  }
  else {
    if ((club_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone))) {
      digitalWrite(club_relay, LOW);
      Serial.println( F("03 club_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone") );
      lcd.setCursor(13, 2);
      lcd.print("  OFF F");
    }
  }
  printRelayStateChanges();
  Serial.println( F("exit FrostPreventionClub") );
}

//---------------------Frost Prevention Airgun-----------------------//
void FrostPreventionAirgun() {
  Serial.println( F("enter FrostPreventionAirgun") );

  if ((airgun_sensor.getTempCByIndex(0) <= (frost_setpoint - deadzone))) {
    digitalWrite(airgun_relay, HIGH);
    Serial.println( F("Air frost Heating") );
    lcd.setCursor(13, 3);
    lcd.print("  ON  F");
  }
  else {
    if ((airgun_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone))) {
      digitalWrite(airgun_relay, LOW);
      Serial.println( F("04 airgun_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone") );
      lcd.setCursor(13, 3);
      lcd.print("  OFF F");
    }
  }
  printRelayStateChanges();
  Serial.println( F("exit FrostPreventionAirgun") );
}

//-------------------Normal Heating Range----------------------------//
void NormalHeatingRange() {
  Serial.println( F("enter NormalHeatingRange") );

  if ((range_sensor.getTempCByIndex(0) <= (range_temperature_setpoint - deadzone))) {
    digitalWrite(range_relay, HIGH);
    Serial.println( F("Normal Heating R") );
    lcd.setCursor(13, 1);
    lcd.print(F("  ON  T") );
  }
  else {
    if ((range_sensor.getTempCByIndex(0) >= (range_temperature_setpoint + deadzone))) {
      digitalWrite(range_relay, LOW);
      Serial.println( F("05 range_sensor.getTempCByIndex(0) >= (range_temperature_setpoint + deadzone") );
      lcd.setCursor(13, 1);
      lcd.print( F("  OFF T") );
    }
  }
  printRelayStateChanges();
  Serial.println( F("exit NormalHeatingRange") );
}

//--------------------Normal Heating Club ---------------------------//
void NormalHeatingClub() {
  Serial.println( F("enter NormalHeatingClub") );

  if ((club_sensor.getTempCByIndex(0) <= (club_temperature_setpoint - deadzone))) {
    digitalWrite(club_relay, HIGH);
    Serial.println( F("Normal Heating C") );
    lcd.setCursor(13, 2);
    lcd.print( F("  ON  T") );
  }
  else {
    if ((club_sensor.getTempCByIndex(0) >= (club_temperature_setpoint + deadzone))) {
      digitalWrite(club_relay, LOW);
      Serial.println( F("06 club_sensor.getTempCByIndex(0) >= (club_temperature_setpoint + deadzone") );
      lcd.setCursor(13, 2);
      lcd.print( F("  OFF T") );
    }
  }
  printRelayStateChanges();
  Serial.println( F("exit NormalHeatingClub") );
}

//---------------------Normal Heating Airgun-------------------------//
void NormalHeatingAirgun() {
  Serial.println( F("enter NormalHeatingAirgun") );

  if ((airgun_sensor.getTempCByIndex(0) <= (airgun_temperature_setpoint - deadzone))) {
    digitalWrite(airgun_relay, HIGH);
    Serial.println( F("Normal Heating A") );
    lcd.setCursor(13, 3);
    lcd.print( F("  ON  T") );
  }
  else {
    if ((airgun_sensor.getTempCByIndex(0) >= (airgun_temperature_setpoint + deadzone))) {
      digitalWrite(airgun_relay, LOW);
      Serial.println( F("07 airgun_sensor.getTempCByIndex(0) >= (airgun_temperature_setpoint + deadzone") );
      lcd.setCursor(13, 3);
      lcd.print( F("  OFF T") );
    }
  }
  printRelayStateChanges();
  Serial.println( F("exit NormalHeatingAirgun") );
}

Thank you @StefanL38,
Wow, that is an incredible amount of time and effort that you have put into my project.
That is much appreciated and so kind of you.
Thank you for confirming that photo attachments are allowed.
I will work my way through all of this and set up the earlier IDE.
I will be away from the project for a few days and will report back once I have progressed further.

Evening @StefanL38,
I have made use of the sketch that you kindly modified along with the IDE 1.8.19. When you suggested using the debugging macro previously, I was not completely sure how to impliment it.
The combined effort of your input helped to identify what was or not working.
Following some semi-logical thinking, lots of temporary Serial.prints and some trial and error, this project has progressed very well, in that all of the currently planned functions now appear to work correctly.
I have also carried out some further tidying of the sketch and added more descriptive text.
Once again, thank so much for the continued time and effort you have generously provided.
Just to bore you, here is the latest version of the sketch which anyone else is welcome to use in part or whole.
This project is still not complete, as once I finish one upgrade, another bright idea arrives to further extend my knowledge/frustration and the usability of the project.

/* Author : John Marchant G0RJM
 Created : 25/01/2024 - 30/10/2024
 Description : A project to display the current date, time and temperature on the 20x4 LCD screen
 and create a programmable thermostatic heating control of three zones to include frost protection and override facilities.
 This has been loosely based on the RTClib ds3231 example along with many code parts borrowed
 from several other files/sketches and in collaboration with several authors from the
 Arduino Forum in particular @alto777, @blh64, @cattledog, @StefanL38 and @UKHeliBob. */
#include "RTClib.h"
#include <OneWire.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ezButton.h>
#include <DallasTemperature.h>
#include <EEPROM.h>
#include <DST_RTC.h>

#define ONE_WIRE_BUS_1 3
#define ONE_WIRE_BUS_2 4
#define ONE_WIRE_BUS_3 5
#define TEMPERATURE_PRECISION 11  // 0.125deg resolution
#define buzzer_pin A0

OneWire oneWire_range(ONE_WIRE_BUS_1);
OneWire oneWire_club(ONE_WIRE_BUS_2);
OneWire oneWire_airgun(ONE_WIRE_BUS_3);

const byte range_relay = 10;  // Relay outputs
const byte club_relay = 11;
const byte airgun_relay = 12;
const byte boiler_relay = 13;

float range_temperature;
float club_temperature;
float airgun_temperature;

float range_temperature_setpoint;   // C
float club_temperature_setpoint;    // C
float airgun_temperature_setpoint;  // C
const int frost_setpoint = 12.0;    // C
const float deadzone = 1.0;         // C

ezButton leftbutton(6);   // create ezButton object that is attached to pin 6;
ezButton upbutton(7);     // create ezButton object that is attached to pin 7;
ezButton downbutton(8);   // create ezButton object that is attached to pin 8;
ezButton rightbutton(9);  // create ezButton object that is attached to pin 9;

const int ledPin = 13;

const unsigned menuOnTime = 20000;
const unsigned upButtonTime = 50;
const unsigned downButtonTime = 50;
const unsigned printInterval = 2000;
const unsigned overrideRunTime = 60000;
const unsigned backlightOnTime = 30000;

int value = 0;
int selector = 0;

int EEPROM_address_1 = 0;  // Address of the location of range setpoint temperature
int EEPROM_address_2 = 4;  // Address of the location of club setpoint temperature
int EEPROM_address_3 = 8;  // Address of the location of airgun setpoint temperature

unsigned long startMillis = 0;
unsigned long menuOffTime = 0;
unsigned long currentMillis = 0;
unsigned long previousMillis = 0;
unsigned long previousMillisMenu = 0;
unsigned long previousMillisBacklight = 0;
unsigned long previousMillisOverrideStart = 0;
unsigned long previousMillisOverrideFinish = 0;
unsigned long previousMillisAdjustSetpointUp = 0;
unsigned long previousMillisAdjustSetpointDown = 0;

bool isMenuOn = false;                // if = True initiiates the setup menu
bool isRangeHeatOn = false;           // if = True this allows the Range heating
bool isClubHeatOn = false;            // if = True this allows the Club heating
bool isAirgunHeatOn = false;          // if = True this allows the Airgun heating
bool isSetpointSave = false;          // if = True allows the new setpoints to be saved to EEPROM
bool isTimeControlOn = false;         // if = True allows normal time control function
bool isHeatingOverride = false;       // if = True allows the heating override facility
bool isAdjustSetpointUp = false;      // if = True allows the Setpoint increment
bool isAdjustSetpointDown = false;    // if = True allows the Setpoint decrement
bool isRangeSetpointAdjust = false;   // if = True allows the range setpoint to be adjusted
bool isClubSetpointAdjust = false;    // if = True allows the club setpoint to be adjusted
bool isAirgunSetpointAdjust = false;  // if = True allows the airgun setpoint to be adjusted

DallasTemperature range_sensor(&oneWire_range);
DallasTemperature club_sensor(&oneWire_club);
DallasTemperature airgun_sensor(&oneWire_airgun);

enum { SUNDAY,
       MONDAY,
       TUESDAY,
       WEDNESDAY,
       THURSDAY,
       FRIDAY,
       SATURDAY };

const int On_Normal_Range = 16 * 60UL + 00;    // 16:00 Normal ON time
const int Off_Normal_Range = 21 * 60UL + 30;   // 21:30 Normal OFF time
const int On_Normal_Club = 16 * 60UL + 00;     // 16:00 Normal ON time
const int Off_Normal_Club = 21 * 60UL + 30;    // 21:30 Normal OFF time
const int On_Normal_Airgun = 16 * 60UL + 00;   // 16:00 Normal ON time
const int Off_Normal_Airgun = 21 * 60UL + 30;  // 21:30 Normal OFF time
const int On_Sunday = 12 * 60UL + 00;          // 12:00 Sunday ON time
const int Off_Sunday = 17 * 60UL + 30;         // 17:30 Sunday OFF time

LiquidCrystal_I2C lcd(0X27, 20, 4);
RTC_DS3231 rtc;
DST_RTC dst_rtc;  // DST object

// Define US or EU rules for DST comment out as required. More countries could be added with different rules in DST_RTC.cpp
// const char rulesDST[] = "US"; // US DST rules
const char rulesDST[] = "EU";  // EU DST rules

char daysOfTheWeek[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
byte char_temp[8] = { B00100, B01010, B01010, B01110, B01110, B11111, B11111, B01110 };  // for thermometer icon

void setup() {

  pinMode(range_relay, OUTPUT);
  pinMode(club_relay, OUTPUT);
  pinMode(airgun_relay, OUTPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(buzzer_pin, OUTPUT);

  leftbutton.setDebounceTime(50);   // set debounce time to 50 milliseconds
  upbutton.setDebounceTime(50);     // set debounce time to 50 milliseconds
  downbutton.setDebounceTime(50);   // set debounce time to 50 milliseconds
  rightbutton.setDebounceTime(50);  // set debounce time to 50 milliseconds

  Serial.begin(9600);

  // EEPROM.put(EEPROM_address_1, range_temperature_setpoint);   // write the float value to EEPROM ONLY used to initially set up setpoint
  // EEPROM.put(EEPROM_address_2, club_temperature_setpoint);    // write the float value to EEPROM ONLY used to initially set up setpoint
  // EEPROM.put(EEPROM_address_3, airgun_temperature_setpoint);  // write the float value to EEPROM ONLY used to initially set up setpoint

  RetrieveSetpoint();

  startMillis = millis();

  Wire.begin();
  rtc.begin();

  lcd.init();
  lcd.backlight();
  lcd.createChar(0, char_temp);
  lcd.setCursor(2, 0);
  lcd.print("G0RJM Three Zone");
  lcd.setCursor(1, 1);
  lcd.print("Temperature Control");
  lcd.setCursor(4, 2);
  lcd.print("Take it easy!");
  lcd.setCursor(4, 3);
  lcd.print("Working on it");

  delay(1000);
  lcd.clear();
  if (rtc.lostPower()) {
    Serial.println(F("RTC is NOT running!"));
    // Uncomment the following line to set the RTC to the date & time this sketch was compiled
    // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // rtc.adjust(DateTime(2024,4,24,21,49,00));
    /* DST? If we're in it, let's subtract an hour from the RTC time to keep our DST calculation correct. This gives us
    Standard Time which our DST check will add an hour back to if we're in DST.
    */
    DateTime standardTime = rtc.now();
    if (dst_rtc.checkDST(standardTime) == true) {  // check whether we're in DST right now. If we are, subtract an hour.
      standardTime = standardTime.unixtime() - 3600;
    }
    rtc.adjust(standardTime);
  }

  Serial.println(F("G0RJM Three zone Temperature Control"));

  range_sensor.begin();
  club_sensor.begin();
  airgun_sensor.begin();

  range_sensor.setResolution(TEMPERATURE_PRECISION);
  club_sensor.setResolution(TEMPERATURE_PRECISION);
  airgun_sensor.setResolution(TEMPERATURE_PRECISION);

}  // End of setup

void loop() {

  //       ("ABCDEFGHIJ1234567890")

  //---------------------------Button control-------------------------//

  leftbutton.loop();   // MUST call the loop() function first
  upbutton.loop();     // MUST call the loop() function first
  downbutton.loop();   // MUST call the loop() function first
  rightbutton.loop();  // MUST call the loop() function first

  // -------------------------Backlight display-----------------------//

  unsigned long currentMillis = millis();  // all time elements are data type unsigned long

  if ((isHeatingOverride) || (leftbutton.isPressed() || (rightbutton.isPressed() || (upbutton.isPressed())))) {
    lcd.backlight();
    previousMillisBacklight = currentMillis;
  }
  if ((!isHeatingOverride) && (currentMillis - previousMillisBacklight >= backlightOnTime)) {
    lcd.noBacklight();
    isMenuOn = false;
  }

  //---------------------------Heating Override-----------------------//

  if ((downbutton.isPressed() && (!isMenuOn) && (!isHeatingOverride))) {
    if (currentMillis - previousMillisOverrideFinish >= 30000) {
      isHeatingOverride = true;  // Establishes the heating override
      isTimeControlOn = false;
      HeatingOverride();
      Serial.println(F("          manual start of override"));
      previousMillisOverrideStart = currentMillis;
    }
  }
  if (isHeatingOverride) {
    if (currentMillis - previousMillisOverrideStart >= overrideRunTime)  // Automatically cancels the override
    {
      Serial.println(F("       auto termination of override"));
      HeatingOverrideComplete();
      isTimeControlOn = true;
      previousMillisOverrideFinish = currentMillis;
    }
  }
  if ((downbutton.isPressed() && (!isMenuOn) && (isHeatingOverride)))  // Manually cancels the override
  {
    if (currentMillis - previousMillisOverrideStart >= 10000) {
      Serial.println(F("       manual termination of override"));
      HeatingOverrideComplete();
      //  isTimeControlOn = true;
      previousMillisOverrideFinish = currentMillis;
    }
  }

  //------------------------Timed Control Interlock--------------------//

  if (downbutton.isPressed()) {
    isTimeControlOn = false;
    Serial.println(F("Timed Control is OFF"));
  } else {
    if (!isHeatingOverride) {
      isTimeControlOn = true;
      Serial.println(F("Timed Control is ON"));
    }
  }

  //------------------------Boiler Interlock--------------------------//

  if ((digitalRead(range_relay) == HIGH) || (digitalRead(club_relay) == HIGH) || (digitalRead(airgun_relay) == HIGH)) {
    digitalWrite(boiler_relay, HIGH);
  } else {
    digitalWrite(boiler_relay, LOW);
  }

  if (!isMenuOn) {
    TemperatureDisplay();
    HeatingAvailable();
  }

  //------------------------------Menu-------------------------------//

  if (leftbutton.isPressed()) {
    isMenuOn = true;  // Initiiates the setup menu
    SettingMenu();
    selector++;  // this is done after SettingMenu(), so case 0 will be executed on the first button press
    if (selector > 5) {
      selector = 0;
    }
    previousMillisMenu = currentMillis;
  }

  if (isMenuOn) {
    if (currentMillis - previousMillisMenu >= menuOnTime) {
      isMenuOn = false;
      selector = 6;
      delay(2000);
      lcd.noBacklight();
    }
  }

  //----------------------------Up Button-----------------------------//

  if (upbutton.isPressed()) {
    isAdjustSetpointUp = true;  // Setpoint adjustment upwards is available
    previousMillisAdjustSetpointUp = currentMillis;
  }

  if (isAdjustSetpointUp) {
    if (currentMillis - previousMillisAdjustSetpointUp >= upButtonTime) {
      isAdjustSetpointUp = false;
    }
  }

  if (isAdjustSetpointUp && isRangeSetpointAdjust)  // If True, this allows the Range setpoint to be adjusted
  {
    range_temperature_setpoint += 0.1;  // Range Setpoint increment
    Serial.println(F("range_temperature_setpoint_up"));
    RangeDisplay();
    Buzzer();
  }

  if (isAdjustSetpointUp && isClubSetpointAdjust)  // If True, this allows the Club setpoint to be adjusted
  {
    club_temperature_setpoint += 0.1;  // Club Setpoint increment
    Serial.println(F("club_temperature_setpoint_up"));
    ClubDisplay();
    Buzzer();
  }

  if (isAdjustSetpointUp && isAirgunSetpointAdjust)  // If True, this allows the Airgun setpoint to be adjusted
  {
    airgun_temperature_setpoint += 0.1;  // Airgun Setpoint increment
    Serial.println(F("airgun_temperature_setpoint_up"));
    AirgunDisplay();
    Buzzer();
  }

  //----------------------------Down Button---------------------------//

  if (downbutton.isPressed()) {
    isAdjustSetpointDown = true;  // Setpoint adjustment Downwards is available
    previousMillisAdjustSetpointDown = currentMillis;
  }
  if (isAdjustSetpointDown) {
    if (currentMillis - previousMillisAdjustSetpointDown >= downButtonTime) {
      isAdjustSetpointDown = false;
    }
  }

  if (isAdjustSetpointDown && isRangeSetpointAdjust)  // If True, this allows the Range setpoint to be adjusted
  {
    range_temperature_setpoint -= 0.1;  // Range Setpoint decrement
    Serial.println(F("range_temperature_setpoint_down"));
    RangeDisplay();
    Buzzer();
  }

  if (isAdjustSetpointDown && isClubSetpointAdjust)  // If True, this allows the Club setpoint to be adjusted
  {
    club_temperature_setpoint -= 0.1;  // Club Setpoint decrement
    Serial.println(F("club_temperature_setpoint_down"));
    ClubDisplay();
    Buzzer();
  }

  if (isAdjustSetpointDown && isAirgunSetpointAdjust)  // If True, this allows the Airgun setpoint to be adjusted
  {
    airgun_temperature_setpoint -= 0.1;  // Airgun Setpoint decrement
    Serial.println(F("airgun_temperature_setpoint_down"));
    AirgunDisplay();
    Buzzer();
  }

  //---------------------------Setpoint Save--------------------------//

  if (isSetpointSave && isAdjustSetpointUp && isAdjustSetpointDown)  // If True, the new temperature setpoints are saved to EEPROM
  {
    for (int n = 0; n < 1; n++) {
      Serial.println("Changes saved");
      EEPROM.put(EEPROM_address_1, range_temperature_setpoint);   // write the new range setpoint value to EEPROM, only if it has changed
      EEPROM.put(EEPROM_address_2, club_temperature_setpoint);    // write the new club setpoint value to EEPROM, only if it has changed
      EEPROM.put(EEPROM_address_3, airgun_temperature_setpoint);  // write the new airgun setpoint value to EEPROM, only if it has changed

      delay(2000);
      RetrieveSetpoint();
      lcd.setCursor(3, 3);
      lcd.print("Settings saved");
      delay(3000);
      lcd.clear();
    }
  }
  //----------------------------Print delay---------------------------//

  if (currentMillis - previousMillis >= printInterval)  // has enough time passed yet?
  {
    previousMillis = previousMillis + printInterval;  // If yes, set the time del;ay for the next print event
                                                      // capture & print data, or print latest data, etc.
                                                      // the closing curly brace at end of loop.

    //--------------------Daylight Saving Setting---------------------//

    DateTime standardTime = rtc.now();

    Serial.println("Standard Time");
    printTheTime(standardTime);

    DateTime theTime = dst_rtc.calculateTime(standardTime);  // takes into account DST

    Serial.println(F("Time adjusted for Daylight Saving Time"));
    printTheTime(theTime);

    int currentTime = theTime.hour() * 60UL + theTime.minute();

    //--------------------Timed heating setting-----------------------//

    if (isTimeControlOn) {
      if (theTime.dayOfTheWeek() != SUNDAY) {
        // Monday to Saturday so use normal times
        if (currentTime < On_Normal_Range || currentTime > Off_Normal_Range) {
          // this is outside normal range heating time
          isRangeHeatOn = false;
        } else {
          // this is within normal range heating time
          isRangeHeatOn = true;
        }
      } else {
        // Sunday times
        if (currentTime < On_Sunday || currentTime > Off_Sunday) {
          // this is outside Sunday range heating time
          isRangeHeatOn = false;
        } else {
          // this is within Sunday range heating time
          isRangeHeatOn = true;
        }
      }
      if (theTime.dayOfTheWeek() != SUNDAY) {
        // Monday to Saturday so use normal times
        if (currentTime < On_Normal_Club || currentTime > Off_Normal_Club) {
          // this is outside normal club heating time
          isClubHeatOn = false;
        } else {
          // this is within normal club heating time
          isClubHeatOn = true;
        }
      } else {
        // Sunday times
        if (currentTime < On_Sunday || currentTime > Off_Sunday) {
          // this is outside Sunday club heating time
          isClubHeatOn = false;
        } else {
          // this is within Sunday club heating time
          isClubHeatOn = true;
        }
      }
      if (theTime.dayOfTheWeek() != SUNDAY) {
        // Monday to Saturday so use normal times
        if (currentTime < On_Normal_Airgun || currentTime > Off_Normal_Airgun) {
          // this is outside normal airgun heating time
          isAirgunHeatOn = false;
        } else {
          // this is within normal airgun heating time
          isAirgunHeatOn = true;
        }
      } else {
        // Sunday times
        if (currentTime < On_Sunday || currentTime > Off_Sunday) {
          // this is outside Sunday airgun heating time
          isAirgunHeatOn = false;
        } else {
          // this is within Sunday airgun heating time
          isAirgunHeatOn = true;
        }
      }
    }

    //---------------------Date & Time display----------------------//

    if ((!isMenuOn) && (!isHeatingOverride)) {
      Serial.println(F("Time & date showing on LCD"));
      lcd.setCursor(0, 0);
      lcd.print(daysOfTheWeek[theTime.dayOfTheWeek()]);
      lcd.setCursor(3, 0);
      lcd.print(":");
      lcd.setCursor(4, 0);
      if (theTime.day() < 10) lcd.print('0');
      lcd.print(theTime.day(), DEC);
      lcd.print(':');
      if (theTime.month() < 10) lcd.print('0');
      lcd.print(theTime.month(), DEC);
      lcd.print(':');
      lcd.print(theTime.year(), DEC);
      lcd.setCursor(15, 0);
      if (theTime.hour() < 10) lcd.print('0');
      lcd.print(theTime.hour(), DEC);
      lcd.print(':');
      if (theTime.minute() < 10) lcd.print('0');
      lcd.print(theTime.minute(), DEC);
    }

    //------------------------Serial print---------------------------//

    Serial.print(F("Requesting temperatures..."));
    range_sensor.requestTemperatures();
    club_sensor.requestTemperatures();
    airgun_sensor.requestTemperatures();
    Serial.println(F(" done"));
    Serial.print(F("Range: "));
    Serial.println(range_sensor.getTempCByIndex(0));
    Serial.print(F("Club: "));
    Serial.println(club_sensor.getTempCByIndex(0));
    Serial.print(F("Airgun: "));
    Serial.println(airgun_sensor.getTempCByIndex(0));
  }

}  // End of loop

//----------------------Print time to serial-------------------------//
void printTheTime(DateTime theTimeP) {

  Serial.print(theTimeP.year(), DEC);
  Serial.print('/');
  Serial.print(theTimeP.month(), DEC);
  Serial.print('/');
  Serial.print(theTimeP.day(), DEC);
  Serial.print(' ');
  Serial.print(theTimeP.hour(), DEC);
  Serial.print(':');
  Serial.print(theTimeP.minute(), DEC);
  Serial.print(':');
  Serial.print(theTimeP.second(), DEC);
  Serial.println();
}

//------------------------Retrieve setpoint--------------------------//
void RetrieveSetpoint() {

  EEPROM.get(EEPROM_address_1, range_temperature_setpoint);   //retrieve the range setpoint from EEPROM
  EEPROM.get(EEPROM_address_2, club_temperature_setpoint);    //retrieve the club setpoint from EEPROM
  EEPROM.get(EEPROM_address_3, airgun_temperature_setpoint);  //retrieve the airgun setpoint from EEPROM

  Serial.print(F("Range Temperature Setpoint  - "));
  Serial.print(range_temperature_setpoint);
  Serial.print(F(" Stored at addr "));
  Serial.println(EEPROM_address_1);
  Serial.print(F("Club Temperature Setpoint   - "));
  Serial.print(club_temperature_setpoint);
  Serial.print(F(" Stored at addr "));
  Serial.println(EEPROM_address_2);
  Serial.print(F("Airgun Temperature Setpoint - "));
  Serial.print(airgun_temperature_setpoint);
  Serial.print(F(" Stored at addr "));
  Serial.println(EEPROM_address_3);
}

//--------------------------Setting Menu-----------------------------//
void SettingMenu() {

  switch (selector) {
    case 0:
      Serial.println(F("Case 0"));  // General menu
      MenuScreen();
      break;
    case 1:
      Serial.println(F("Case 1"));  // Displays the Range setpoint
      RangeDisplay();
      Buzzer();
      break;
    case 2:
      Serial.println(F("Case 2"));  // Displays the Club setpoint
      ClubDisplay();
      Buzzer();
      break;
    case 3:
      Serial.println(F("Case 3"));  // Displays the Airgun setpoint
      AirgunDisplay();
      Buzzer();
      break;
    case 4:
      Serial.println(F("Case 4"));  // Instructs how to save the new settings
      SaveDisplay();
      break;
    case 5:
      Serial.println(F("Case 5"));  // Reminds you to save
      lcd.clear();
      lcd.setCursor(3, 2);
      lcd.print("Did you save?");
      Buzzer();
  }
}

//---------------------------Buzzer----------------------------------//
void Buzzer() {

  {
    digitalWrite(buzzer_pin, HIGH);
    delay(100);
    digitalWrite(buzzer_pin, LOW);
  }
}

//-------------------------Menu screen-------------------------------//
void MenuScreen() {

  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 1);
  lcd.print("Range: ");
  lcd.print(range_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  lcd.setCursor(0, 2);
  lcd.print("Club:  ");
  lcd.print(club_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  lcd.setCursor(0, 3);
  lcd.print("Airgun:");
  lcd.print(airgun_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
  isSetpointSave = false;
}

//-----------------------Setting Displays---------------------------//
void RangeDisplay() {

  isRangeSetpointAdjust = true;
  isClubSetpointAdjust = false;
  isAirgunSetpointAdjust = false;
  Serial.println(F("RangeChange"));
  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 1);
  lcd.print("Range: ");
  lcd.print(range_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
}

//-------------------------------------------------------------------//
void ClubDisplay() {

  isClubSetpointAdjust = true;
  isRangeSetpointAdjust = false;
  isAirgunSetpointAdjust = false;
  Serial.println(F("ClubChange"));
  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 2);
  lcd.print("Club:  ");
  lcd.print(club_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
}

//-------------------------------------------------------------------//
void AirgunDisplay() {

  isAirgunSetpointAdjust = true;
  isRangeSetpointAdjust = false;
  isClubSetpointAdjust = false;
  Serial.println(F("AirgunChange"));
  lcd.clear();
  lcd.print(" Setting Menu:      ");
  lcd.setCursor(0, 3);
  lcd.print("Airgun:");
  lcd.print(airgun_temperature_setpoint, 1);
  lcd.write((char)223);
  lcd.print('C');
}

//------------------------Save Display-------------------------------//
void SaveDisplay() {

  isRangeSetpointAdjust = false;
  isClubSetpointAdjust = false;
  isAirgunSetpointAdjust = false;
  Serial.println(F("SavingChanges"));
  lcd.clear();
  lcd.print("To save the changes");
  lcd.setCursor(2, 1);
  lcd.print("Press Up & Down");
  lcd.setCursor(5, 2);
  lcd.print("Together");
  isSetpointSave = true;
}

//---------------------Temperature display - ------------------------//
void TemperatureDisplay() {

  lcd.setCursor(0, 1);  // Range Temperature
  lcd.print("Range: ");
  lcd.print(range_sensor.getTempCByIndex(0), 1);
  lcd.write((char)223);
  lcd.print('C');

  lcd.setCursor(0, 2);  // Club Temperature
  lcd.print("Club:  ");
  lcd.print(club_sensor.getTempCByIndex(0), 1);
  lcd.write((char)223);
  lcd.print('C');

  lcd.setCursor(0, 3);  // Airgun Temperature
  lcd.print("Airgun:");
  lcd.print(airgun_sensor.getTempCByIndex(0), 1);
  lcd.write((char)223);
  lcd.print('C');
}

//------------------------Heating Override---------------------------//
void HeatingOverride() {

  Serial.println(F("Heating Override is On"));
  isRangeHeatOn = true;
  isClubHeatOn = true;
  isAirgunHeatOn = true;
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("   OVERRIDE IS ON   ");
}

//--------------------Heating Override Complete----------------------//
void HeatingOverrideComplete() {

  Serial.println(F("Heating Override is complete"));
  isHeatingOverride = false;
  isRangeHeatOn = false;
  isClubHeatOn = false;
  isAirgunHeatOn = false;
  lcd.setCursor(0, 0);
  lcd.print("   OVERRIDE IS OFF   ");
  delay(2000);
  lcd.noBacklight();
}

//-----------------------Heating Available---------------------------//
void HeatingAvailable() {

  RangeHeatingAvailable();
  ClubHeatingAvailable();
  AirgunHeatingAvailable();
  Serial.println(F("Heating is available"));
}

//-------------------------------------------------------------------//
void RangeHeatingAvailable() {

  {
    if (isRangeHeatOn) {
      NormalHeatingRange();
      Serial.println(F("Heating R"));
    } else {
      FrostPreventionRange();
      Serial.println(F("Frost Prev. R"));
    }
  }
}

//-------------------------------------------------------------------//
void ClubHeatingAvailable() {

  {
    if (isClubHeatOn) {
      NormalHeatingClub();
      Serial.println(F("Heating C"));
    } else {
      FrostPreventionClub();
      Serial.println(F("Frost Prev. C"));
    }
  }
}

//-------------------------------------------------------------------//
void AirgunHeatingAvailable() {

  {
    if (isAirgunHeatOn) {
      NormalHeatingAirgun();
      Serial.println(F("Heating A"));
    } else {
      FrostPreventionAirgun();
      Serial.println(F("Frost Prev. A"));
    }
  }
}

//-------------------Frost Prevention Range--------------------------//
void FrostPreventionRange() {

  if ((range_sensor.getTempCByIndex(0) <= (frost_setpoint - deadzone))) {
    digitalWrite(range_relay, HIGH);
    Serial.println(F("Range frost Heating"));
    lcd.setCursor(13, 1);
    lcd.print("  ON  F");
  } else {
    if ((range_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone))) {
      digitalWrite(range_relay, LOW);
      lcd.setCursor(13, 1);
      lcd.print("  OFF F");
    }
  }
}

//--------------------Frost Prevention Club-------------------------//
void FrostPreventionClub() {

  if ((club_sensor.getTempCByIndex(0) <= (frost_setpoint - deadzone))) {
    digitalWrite(club_relay, HIGH);
    Serial.println(F("Club frost Heating"));
    lcd.setCursor(13, 2);
    lcd.print("  ON  F");
  } else {
    if ((club_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone))) {
      digitalWrite(club_relay, LOW);
      lcd.setCursor(13, 2);
      lcd.print("  OFF F");
    }
  }
}

//---------------------Frost Prevention Airgun-----------------------//
void FrostPreventionAirgun() {

  if ((airgun_sensor.getTempCByIndex(0) <= (frost_setpoint - deadzone))) {
    digitalWrite(airgun_relay, HIGH);
    Serial.println(F("Air frost Heating"));
    lcd.setCursor(13, 3);
    lcd.print("  ON  F");
  } else {
    if ((airgun_sensor.getTempCByIndex(0) >= (frost_setpoint + deadzone))) {
      digitalWrite(airgun_relay, LOW);
      lcd.setCursor(13, 3);
      lcd.print("  OFF F");
    }
  }
}

//-------------------Normal Heating Range----------------------------//
void NormalHeatingRange() {

  if ((range_sensor.getTempCByIndex(0) <= (range_temperature_setpoint - deadzone))) {
    digitalWrite(range_relay, HIGH);
    Serial.println(F("Normal Heating R"));
    lcd.setCursor(13, 1);
    lcd.print("  ON  T");
  } else {
    if ((range_sensor.getTempCByIndex(0) >= (range_temperature_setpoint + deadzone))) {
      digitalWrite(range_relay, LOW);
      lcd.setCursor(13, 1);
      lcd.print("  OFF T");
    }
  }
}

//--------------------Normal Heating Club ---------------------------//
void NormalHeatingClub() {

  if ((club_sensor.getTempCByIndex(0) <= (club_temperature_setpoint - deadzone))) {
    digitalWrite(club_relay, HIGH);
    Serial.println(F("Normal Heating C"));
    lcd.setCursor(13, 2);
    lcd.print("  ON  T");
  } else {
    if ((club_sensor.getTempCByIndex(0) >= (club_temperature_setpoint + deadzone))) {
      digitalWrite(club_relay, LOW);
      lcd.setCursor(13, 2);
      lcd.print("  OFF T");
    }
  }
}

//---------------------Normal Heating Airgun-------------------------//
void NormalHeatingAirgun() {

  if ((airgun_sensor.getTempCByIndex(0) <= (airgun_temperature_setpoint - deadzone))) {
    digitalWrite(airgun_relay, HIGH);
    Serial.println(F("Normal Heating A"));
    lcd.setCursor(13, 3);
    lcd.print("  ON  T");
  } else {
    if ((airgun_sensor.getTempCByIndex(0) >= (airgun_temperature_setpoint + deadzone))) {
      digitalWrite(airgun_relay, LOW);
      lcd.setCursor(13, 3);
      lcd.print("  OFF T");
    }
  }
}