// 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); // print it every time line of code is executed
#define dbgi(myFixedText, variableName, timeInterval) \
{ \
static unsigned long intervalStartTime; \
if (millis() - intervalStartTime >= timeInterval) { \
intervalStartTime = millis(); \
Serial.print(F(#myFixedText " " #variableName "=")); \
Serial.println(variableName); \
} \
} // print only after a specified interval of milliseconds has passed by
#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; \
} \
} // print only if the value of the variable has changed
#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 MyHeartbeatTimer = 0; // Timer-variables MUST be of type unsigned long
const byte OnBoard_LED = 13;
/* Author : John Marchant G0RJM Using IDE2.3.7
Created : 25/01/2024 - 19/1/2026 V3.007A1.3
Description : A long term ongoing project to display the current date, UK time and temperature on the 20x4 LCD screen and create a
programmable thermostatic heating control of three areas which are Range, Club and Airgun to include frost protection and override facilities.
This also incorporates the facility to change the temperature settings and On-Off time settings from the pushbuttons.
This has been very loosely based on many code parts borrowed from several other files/sketches and in collaboration with
the input and genorous help from several authors from the Arduino Forum with particular thanks to @alto777, @blh64, @cattledog,
@DaveX, @PaulRB, @StefanL38 and @UKHeliBob. */
#include "RTClib.h"
#include <OneWire.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.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 heart_beat_pin LED_BUILTIN // digital pin for heart beat LED
OneWire oneWire_range(ONE_WIRE_BUS_1);
OneWire oneWire_club(ONE_WIRE_BUS_2);
OneWire oneWire_airgun(ONE_WIRE_BUS_3);
const byte leftbutton = 6; // create object that is attached to pin 6;
const byte upbutton = 7; // create object that is attached to pin 7;
const byte downbutton = 8; // create object that is attached to pin 8;
const byte rightbutton = 9; // create object that is attached to pin 9;
const byte enterbutton = 10; // create object that is attached to pin 10;
const byte range_relay = 11; // Relay outputs
const byte club_relay = 12;
const byte buzzer_pin = A0;
const byte airgun_relay = A1;
const byte boiler_relay = A2;
byte lastUpButtonState = HIGH;
byte lastDownButtonState = HIGH;
byte lastLeftButtonState = HIGH;
byte lastRightButtonState = HIGH;
byte lastEnterButtonState = HIGH;
const unsigned long debounceTime = 50; // the last time the output pin was used
unsigned long leftButtonPressTime;
unsigned long upButtonPressTime;
unsigned long downButtonPressTime;
unsigned long rightButtonPressTime;
unsigned long enterButtonPressTime;
const byte hysteresis = 0.5; // for the changes to the displayed temperature on LCD
int temperatureValue;
int oldTemperatureValue;
float range_temperature;
float club_temperature;
float airgun_temperature;
float temperature_setpoint_range; // C
float temperature_setpoint_club; // C
float temperature_setpoint_airgun; // C
const int frost_prevention_setpoint = 12.0; // C
const float deadzone = 1.0; // C
int n = 0;
int time = 0;
int temperature = 0;
int oldTimeSet = 0;
int newTimeSet = 0;
int timer = 50;
struct Settings {
float temperature_setpoint_range = 25.0;
float temperature_setpoint_club = 25.0;
float temperature_setpoint_airgun = 25.0;
int On_Normal_Range = 16 * 60 + 00;
int Off_Normal_Range = 21 * 60 + 30;
int On_Normal_Club = 16 * 60 + 00;
int Off_Normal_Club = 21 * 60 + 30;
int On_Normal_Airgun = 16 * 60 + 00;
int Off_Normal_Airgun = 21 * 60 + 30;
int On_Sunday = 12 * 60 + 00;
int Off_Sunday = 17 * 60 + 30;
} settings;
unsigned long startMillis;
unsigned long overrideTimer; // Heating override timer
unsigned long overrideTimer1; // Heating override timer
unsigned long sensorPrintDelayTimer; // Sensor reading delay
unsigned long heatingDelayTimer; // Heating delay
unsigned long settingsSaveDelay; // Settings save delay
unsigned long LCDbacklightStartTime; // LCD backlight start
unsigned long EEPROMSaveDelay; // EEPROM save delay
unsigned long EEPROMSaveDelay1; // EEPROM save delay
unsigned long temperatureDisplayTimer; // Temperature display delay
unsigned long printPause; // Used for print delay.
unsigned long printPause1; // Used for print delay
unsigned long printPause3; // Used for print delay.
unsigned long printPause4; // Used for print delay.
unsigned long generalPause; // Used for general delay.
unsigned long generalPause1; // Used for general delay.
unsigned long generalPause2; // Used for general delay.
unsigned long currentMillis;
unsigned long last_heart_beat_time; // time in milliseconds of last heart beat status change
unsigned long heart_beat_freq = 2000; // time in milliseconds of heart beat frequency
unsigned long heart_beat_on_off_time; // the time the LED is on and off - 1/2 frequency
bool isSetupOn = false; // if = True allows the setup to be used
bool isHeartBeatOn = false; // if = True shows current status of heart beat
bool isBacklightOn = false; // if = True LCD backlight is on for timed period
bool isLCDlightOn = false; // if = True LCD backlight is on for timed period
bool isUpButtonPressed = false; // if = True up button action is being done
bool isDownButtonPressed = false; // if = True down button action is being done
bool isLeftButtonPressed = false; // if = True left button action is being done
bool isRightButtonPressed = false; // if = True right button action is being done
bool isEnterButtonPressed = false; // if = True enter button action is being done
bool isInfoDisplayOn = false; // if = True allows button display
bool isTimeDisplayOn = false; // if = True allows normal time control function to operate
bool isTimeSettingOn = false; // if = True allows the time to be adjusted
bool isTimedSessionOn = false; // if = True allows the timed period control function to operate
bool isClubHeatingOn = false; // if = True allows the Club heating
bool isRangeHeatingOn = false; // if = True allows the Range heating
bool isAirgunHeatingOn = false; // if = True allows the Airgun heating
bool isHeatingOverrideOn = false; // if = True allows the heating override facility
bool isTemperatureSettingOn = false; // if = True allows the temperature to be adjusted
bool TimePeriodIsOver(unsigned long &startOfPeriod, unsigned long TimePeriod) {
unsigned long currentMillis = millis();
if (currentMillis - startOfPeriod >= TimePeriod) {
// more time than TimePeriod has elapsed since last time if-condition was true
startOfPeriod = currentMillis; // a new period starts right here so set new starttime
return true;
} else return false; // actual TimePeriod is NOT yet over
}
DallasTemperature range_sensor(&oneWire_range);
DallasTemperature club_sensor(&oneWire_club);
DallasTemperature airgun_sensor(&oneWire_airgun);
enum { SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY };
enum operationModes { NORMAL_MODE,
OVERRIDE_MODE,
SETUP_MODE,
INFORMATION_MODE,
TEMPERATURE_SETUP_MODE,
TIME_SETUP_MODE };
int myMode = NORMAL_MODE;
int On_Normal_Range;
int Off_Normal_Range;
int On_Normal_Club;
int Off_Normal_Club;
int On_Normal_Airgun;
int Off_Normal_Airgun;
int On_Sunday;
int Off_Sunday;
LiquidCrystal_I2C lcd(0X27, 20, 4);
RTC_DS3231 rtc;
DST_RTC dst_rtc; // DST object
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
char *modeTags[] = { "normal", "override", "setup", "information", "temperature-setup", "time-setup" };
byte nModes = sizeof modeTags / sizeof *modeTags;
byte IndexNr; // global declaration of variable "IndexNr"
void setup() {
Serial.begin(115200);
Serial.println(F("Setup-Start"));
PrintFileNameDateTime(); // prints the real filename and compiletime
Wire.begin();
rtc.begin();
checkRTC();
startMillis = millis();
heart_beat_on_off_time = heart_beat_freq / 2; // LED is on and off at 1/2 frequency time
setPinModes();
setupOneWireSensors();
initiateSetpoints();
retrieveSetpoints();
initLCD();
} // End of setup
void loop() {
Heartbeat(); // first thing to do at top of loop heartbeat blinking
unsigned long currentMillis = millis(); // all time elements are data type unsigned long
operationModes();
buttonSettings();
serialPrintAreaTemperatures();
//--------------------Daylight Saving Setting---------------------//
DateTime standardTime = rtc.now();
DateTime theTime = dst_rtc.calculateTime(standardTime); // takes into account DST
printTheTime(theTime);
int currentTime = theTime.hour() * 60UL + theTime.minute();
//--------------------Timed heating setting-----------------------//
if (isTimedSessionOn) {
if (theTime.dayOfTheWeek() != SUNDAY) {
// Monday to Saturday so use normal times
if ((currentTime >= settings.On_Normal_Range && currentTime <= settings.Off_Normal_Range) || (isHeatingOverrideOn)) {
// this is within normal Range area heating time
isRangeHeatingOn = true;
} else {
// this is outside normal Range area heating time
isRangeHeatingOn = false;
}
}
if (theTime.dayOfTheWeek() != SUNDAY) {
// Monday to Saturday so use normal times
if ((currentTime >= settings.On_Normal_Club && currentTime <= settings.Off_Normal_Club) || (isHeatingOverrideOn)) {
// this is within normal Club area heating time
isClubHeatingOn = true;
} else {
// this is outside normal Club area heating time
isClubHeatingOn = false;
}
}
if (theTime.dayOfTheWeek() != SUNDAY) {
// Monday to Saturday so use normal times
if ((currentTime >= settings.On_Normal_Airgun && currentTime <= settings.Off_Normal_Airgun) || (isHeatingOverrideOn)) {
// this is within normal Airgun heating time
isAirgunHeatingOn = true;
} else {
// this is outside normal Airgun heating time
isAirgunHeatingOn = false;
}
} else {
if ((currentTime >= settings.On_Sunday && currentTime <= settings.Off_Sunday) || (isHeatingOverrideOn)) {
// this is within Sunday heating time
isRangeHeatingOn = true;
isClubHeatingOn = true;
isAirgunHeatingOn = true;
} else {
// this is outside Sunday heating time
isRangeHeatingOn = false;
isClubHeatingOn = false;
isAirgunHeatingOn = false;
}
}
}
//---------------------Date & Time display----------------------//
if ((isTimeDisplayOn) && (currentTime != 'theTimeP')) {
lcd.print(daysOfTheWeek[theTime.dayOfTheWeek()]);
lcd.setCursor(3, 0);
lcd.print(F(":"));
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(14, 0);
lcd.print(" ");
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);
currentTime = 'theTimeP';
}
} // End of loop
//----------------------------Check RTC------------------------------//
void checkRTC() {
if (rtc.lostPower()) {
Serial.println(F("RTC is NOT running!"));
/* Uncomment line "360" to set the RTC to the current PC date & time or "361" to manually input the date & time.
Remove the battery and upload sketch. Then re-comment line "360" or "361" Upload sketch again. Refit the battery and then remove external power. Then re-instate power. */
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// rtc.adjust(DateTime(2026, 1, 4, 21, 30, 0));
/* 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);
}
}
//----------------------------Pin Modes------------------------------//
void setPinModes() {
pinMode(leftbutton, INPUT_PULLUP);
pinMode(upbutton, INPUT_PULLUP);
pinMode(downbutton, INPUT_PULLUP);
pinMode(rightbutton, INPUT_PULLUP);
pinMode(enterbutton, INPUT_PULLUP);
pinMode(range_relay, OUTPUT);
pinMode(club_relay, OUTPUT);
pinMode(airgun_relay, OUTPUT);
pinMode(boiler_relay, OUTPUT);
pinMode(buzzer_pin, OUTPUT);
pinMode(heart_beat_pin, OUTPUT);
}
//------------------------OneWire Sensors----------------------------//
void setupOneWireSensors() {
range_sensor.begin();
club_sensor.begin();
airgun_sensor.begin();
range_sensor.setResolution(TEMPERATURE_PRECISION);
club_sensor.setResolution(TEMPERATURE_PRECISION);
airgun_sensor.setResolution(TEMPERATURE_PRECISION);
}
//------------------------Initiate setpoint--------------------------//
void initiateSetpoints() {
// EEPROM.put(0, settings);
}
//------------------------Retrieve setpoint--------------------------//
void retrieveSetpoints() {
EEPROM.get(0, settings);
Serial.println(F("Reading customised settings from EEPROM: Deg C "));
Serial.print(F("Range temperature setpoint is "));
Serial.println(settings.temperature_setpoint_range);
lcd.print(F("C"));
Serial.print(F("Club temperature setpoint is "));
Serial.println(settings.temperature_setpoint_club);
Serial.print(F("Airgun temperature setpoint is "));
Serial.println(settings.temperature_setpoint_airgun);
Serial.println(F("Time is shown as Hours * 60 + minutes and Hours:Mins"));
Serial.print(F("Normal Range On Time is "));
Serial.print(settings.On_Normal_Range);
Serial.print(F(" "));
Serial.print((settings.On_Normal_Range) / 60);
Serial.print(F(":"));
if ((settings.On_Normal_Range) % 60 < 10) Serial.print(F("0")); // Leading zero for minutes
Serial.print((settings.On_Normal_Range) % 60);
Serial.println(F(""));
Serial.print(F("Normal Range Off Time is "));
Serial.print(settings.Off_Normal_Range);
Serial.print(F(" "));
Serial.print((settings.Off_Normal_Range) / 60);
Serial.print(F(":"));
if ((settings.Off_Normal_Range) % 60 < 10) Serial.print(F("0")); // Leading zero for minutes
Serial.print((settings.Off_Normal_Range) % 60);
Serial.println(F(""));
Serial.print(F("Normal Club On Time is "));
Serial.print(settings.On_Normal_Club);
Serial.print(F(" "));
Serial.print((settings.On_Normal_Club) / 60);
Serial.print(F(":"));
if ((settings.On_Normal_Club) % 60 < 10) Serial.print(F("0")); // Leading zero for minutes
Serial.print((settings.On_Normal_Club) % 60);
Serial.println(F(""));
Serial.print(F("Normal Club Off Time is "));
Serial.print(settings.Off_Normal_Club);
Serial.print(F(" "));
Serial.print((settings.Off_Normal_Club) / 60);
Serial.print(F(":"));
if ((settings.Off_Normal_Club) % 60 < 10) Serial.print(F("0")); // Leading zero for minutes
Serial.print((settings.Off_Normal_Club) % 60);
Serial.println(F(""));
Serial.print(F("Normal Airgun On Time is "));
Serial.print(settings.On_Normal_Airgun);
Serial.print(F(" "));
Serial.print((settings.On_Normal_Airgun) / 60);
Serial.print(F(":"));
if ((settings.On_Normal_Airgun) % 60 < 10) Serial.print(F("0")); // Leading zero for minutes
Serial.print((settings.On_Normal_Airgun) % 60);
Serial.println(F(""));
Serial.print(F("Normal Airgun Off Time is "));
Serial.print(settings.Off_Normal_Airgun);
Serial.print(F(" "));
Serial.print((settings.Off_Normal_Airgun) / 60);
Serial.print(F(":"));
if ((settings.Off_Normal_Airgun) % 60 < 10) Serial.print(F("0")); // Leading zero for minutes
Serial.print((settings.Off_Normal_Range) % 60);
Serial.println(F(""));
Serial.print(F("Sunday On Time is "));
Serial.print(settings.On_Sunday);
Serial.print(F(" "));
Serial.print((settings.On_Sunday) / 60);
Serial.print(F(":"));
if ((settings.On_Sunday) % 60 < 10) Serial.print(F("0")); // Leading zero for minutes
Serial.print((settings.On_Sunday) % 60);
Serial.println(F(""));
Serial.print(F("Sunday Off Time is "));
Serial.print(settings.Off_Sunday);
Serial.print(F(" "));
Serial.print((settings.Off_Sunday) / 60);
Serial.print(F(":"));
if ((settings.Off_Sunday) % 60 < 10) Serial.print(F("0")); // Leading zero for minutes
Serial.print((settings.Off_Sunday) % 60);
Serial.println(F(""));
}
//---------------------------Save Setpoints---------------------------//
void saveSetpoints() {
for (int n = 0; n < 1; n++) {
EEPROM.put(0, settings); // write the new setpoint values to EEPROM, only if they have changed
lcd.clear();
lcd.print(F(" SETTINGS SAVED "));
Serial.println(F("Changes saved"));
if (TimePeriodIsOver(EEPROMSaveDelay, 1000)) {
retrieveSetpoints();
}
}
}
//--------------------------------LCD--------------------------------//
void initLCD() {
lcd.init();
lcd.backlight();
lcd.createChar(0, char_temp);
lcd.setCursor(0, 0);
lcd.print(F(" G0RJM Three Zone "));
lcd.setCursor(0, 1);
lcd.print(F("Temperature Control"));
delay(1000);
lcd.setCursor(0, 2);
lcd.print(F("V3.007A1.3 19/1/26"));
delay(1000);
lcd.setCursor(0, 3);
lcd.print(F(" Working on it "));
Serial.println(F("G0RJM Three zone Temperature Control"));
delay(3000);
lcd.clear();
isBacklightOn = true;
}
//---------------------------Heartbeat-------------------------------//
void Heartbeat() {
if (millis() - last_heart_beat_time >= heart_beat_on_off_time) { // time to swap status of the heart beat LED and update it
last_heart_beat_time = millis();
isHeartBeatOn = !isHeartBeatOn; // invert current heart beat status value
digitalWrite(heart_beat_pin, isHeartBeatOn); // update LED with new status
}
}
//---------------------------Button Settings------------------------//
void buttonSettings() {
byte leftButtonState = digitalRead(leftbutton);
byte upButtonState = digitalRead(upbutton);
byte downButtonState = digitalRead(downbutton);
byte rightButtonState = digitalRead(rightbutton);
byte enterButtonState = digitalRead(enterbutton);
if (leftButtonState != lastLeftButtonState) {
if (millis() - leftButtonPressTime >= debounceTime) {
leftButtonPressTime = millis();
lastLeftButtonState = leftButtonState;
if (leftButtonState == LOW) {
Serial.println(F("Left Button is Pressed"));
isLeftButtonPressed = true;
} else {
isLeftButtonPressed = false;
}
}
}
if (upButtonState != lastUpButtonState) {
if (millis() - upButtonPressTime >= debounceTime) {
upButtonPressTime = millis();
lastUpButtonState = upButtonState;
if (upButtonState == LOW) {
Serial.println(F("Up Button is Pressed"));
isUpButtonPressed = true;
} else {
isUpButtonPressed = false;
}
}
}
if (downButtonState != lastDownButtonState) {
if (millis() - downButtonPressTime >= debounceTime) {
downButtonPressTime = millis();
lastDownButtonState = downButtonState;
if (downButtonState == LOW) {
Serial.println(F("Down Button is Pressed"));
isDownButtonPressed = true;
} else {
isDownButtonPressed = false;
}
}
}
if (rightButtonState != lastRightButtonState) {
if (millis() - rightButtonPressTime >= debounceTime) {
rightButtonPressTime = millis();
lastRightButtonState = rightButtonState;
if (rightButtonState == LOW) {
Serial.println(F("Right Button is Pressed"));
isRightButtonPressed = true;
} else {
isRightButtonPressed = false;
}
}
}
if (enterButtonState != lastEnterButtonState) {
if (millis() - enterButtonPressTime >= debounceTime) {
enterButtonPressTime = millis();
lastEnterButtonState = enterButtonState;
if (enterButtonState == LOW) {
Serial.println(F("Enter Button is Pressed"));
isEnterButtonPressed = true;
} else {
isEnterButtonPressed = false;
}
}
}
if ((isLeftButtonPressed) || (isUpButtonPressed) || (isDownButtonPressed) || (isRightButtonPressed) || (isEnterButtonPressed)) {
tone(buzzer_pin, 3000, 100);
lcd.backlight();
LCDbacklightStartTime = millis(); // Restarts the time period after button is pressed
isBacklightOn = true;
}
if ((isBacklightOn) && (!isHeatingOverrideOn) && (!isSetupOn)) {
if (millis() - LCDbacklightStartTime >= 30000) { // Turns of LCD backlight after time period
lcd.noBacklight();
isBacklightOn = false;
}
}
}
//----------------------Print time to serial-------------------------//
void printTheTime(DateTime theTimeP) {
static float currentTime;
if (currentTime != 'theTimeP') {
Serial.print(theTimeP.day(), DEC);
Serial.print('/');
Serial.print(theTimeP.month(), DEC);
Serial.print('/');
Serial.print(theTimeP.year(), DEC);
Serial.print(' ');
currentTime = 'theTimeP';
}
if (TimePeriodIsOver(printPause, 1)) {
Serial.print(theTimeP.hour(), DEC);
Serial.print(':');
Serial.print(theTimeP.minute(), DEC);
Serial.print(':');
Serial.print(theTimeP.second(), DEC);
Serial.println();
}
}
//-----------------------Print The State----------------------------//
void printStateIf(int theState) {
static int lastPrinted = -1; // no, it does not!
if (lastPrinted != theState) {
if (theState >= 0 && theState < nModes) {
Serial.print(F("FSM state is mode "));
Serial.println(modeTags[theState]);
} else {
Serial.print(F("WHAT? state is "));
Serial.println(theState);
}
lastPrinted = theState;
}
}
//------------------------Operation Modes---------------------------//
void operationModes() {
printStateIf(myMode);
switch (myMode) {
case NORMAL_MODE:
normalModeSelect();
if (isRightButtonPressed) {
isRightButtonPressed = false;
myMode = OVERRIDE_MODE;
}
if (isLeftButtonPressed) {
myMode = SETUP_MODE;
}
if (isEnterButtonPressed) {
myMode = INFORMATION_MODE;
}
break;
case OVERRIDE_MODE:
heatingOverride();
if (!isHeatingOverrideOn) {
isRightButtonPressed = false;
myMode = NORMAL_MODE;
}
break;
case INFORMATION_MODE:
informationDisplay();
if (!isInfoDisplayOn) {
myMode = NORMAL_MODE;
}
break;
case SETUP_MODE:
settingMenu();
if (isLeftButtonPressed) {
isLeftButtonPressed = false;
myMode = TEMPERATURE_SETUP_MODE;
}
if (isRightButtonPressed) {
isRightButtonPressed = false;
myMode = TIME_SETUP_MODE;
}
if (!isSetupOn) {
myMode = NORMAL_MODE;
}
break;
case TEMPERATURE_SETUP_MODE:
temperatureSetting();
if (!isSetupOn) {
myMode = NORMAL_MODE;
}
break;
case TIME_SETUP_MODE:
timeSetting();
if (!isSetupOn) {
myMode = NORMAL_MODE;
}
break;
default:
myMode = NORMAL_MODE;
break;
} // END-OF-SWITCH
}
//-------------------------Normal Mode Select----------------------//
void normalModeSelect() {
temperatureDisplay();
heatingIsAvailable();
boilerInterlock();
isTimedSessionOn = true;
isTimeDisplayOn = true;
if (TimePeriodIsOver(printPause1, 15000)) {
Serial.println(F("Normal temperature control is available for all areas"));
}
}
//--------------------------Information Display------------------------//
void informationDisplay() {
isInfoDisplayOn = true;
isTimedSessionOn = false;
isTimeDisplayOn = false;
lcd.print(F(" INFORMATION:- "));
lcd.setCursor(0, 1);
lcd.print(F(" PRESS "));
lcd.setCursor(0, 2);
lcd.print(F(" LEFT FOR SETTINGS "));
lcd.setCursor(0, 3);
lcd.print(F(" RIGHT FOR OVERRIDE "));
if (TimePeriodIsOver(generalPause2, 10000)) {
isInfoDisplayOn = false;
}
}
//------------------------Heating Override Select---------------------//
void heatingOverride() {
lcd.print(F(" OVER-RIDE MODE "));
temperatureDisplay();
heatingIsAvailable();
boilerInterlock();
isTimedSessionOn = true;
isHeatingOverrideOn = true;
isTimeDisplayOn = false;
if (TimePeriodIsOver(printPause3, 15000)) {
Serial.println(F(" Heating Override is On"));
}
if (TimePeriodIsOver(overrideTimer, 40000)) { // Automatically cancels the override
Serial.println(F(" Auto termination of override"));
lcd.print(F(" OVER-RIDE IS OFF "));
tone(buzzer_pin, 3000, 100);
isHeatingOverrideOn = false;
} else if (isRightButtonPressed) { // Manually cancels the override
Serial.println(F(" Manual termination of override"));
lcd.print(F(" OVERRIDE IS OFF "));
isHeatingOverrideOn = false;
}
}
//------------------------------Setting Menu-------------------------//
void settingMenu() {
isSetupOn = true;
isTimeDisplayOn = false;
isTimedSessionOn = false;
lcd.setCursor(0, 0);
lcd.print(F("PRESS LEFT FOR TEMP "));
lcd.setCursor(0, 1);
lcd.print(F(" OR RIGHT FOR TIME "));
lcd.setCursor(0, 2);
lcd.print(F(" UP/DOWN TO CHANGE "));
lcd.setCursor(0, 3);
lcd.print(F(" ENTER TO SAVE "));
if (TimePeriodIsOver(printPause4, 15000)) {
Serial.println(F(" Setup Control is ON"));
}
if (TimePeriodIsOver(generalPause, 90000)) {
isSetupOn = false;
tone(buzzer_pin, 3000, 100);
Serial.println(F(" End of Setup Mode"));
isLeftButtonPressed = false;
}
}
//-------------------Temperature Menu screen------------------------//
void temperatureMenuScreen() {
EEPROM.get(0, settings);
lcd.print(F(" TEMP SETTING MENU: "));
lcd.setCursor(0, 1); // Range Temperature
lcd.print(F("Range: "));
lcd.print(settings.temperature_setpoint_range, 1);
lcd.write((char)223);
lcd.print(F("C"));
lcd.setCursor(0, 2); // Club Temperature
lcd.print(F("Club: "));
lcd.print(settings.temperature_setpoint_club, 1);
lcd.write((char)223);
lcd.print(F("C"));
lcd.setCursor(0, 3); // Airgun Temperature
lcd.print(F("Airgun: "));
lcd.print(settings.temperature_setpoint_airgun, 1);
lcd.write((char)223);
lcd.print(F("C"));
}
//------------------------Temperature Setting---------------------//
void temperatureSetting() {
switch (temperature) {
case 0:
temperatureMenuScreen(); // Temperature setting overview
if (isLeftButtonPressed) {
(temperature) = 1;
} else if (isEnterButtonPressed) {
settingSaving();
}
break;
case 1:
lcd.setCursor(0, 1);
lcd.print(F(" "));
lcd.setCursor(0, 2);
lcd.print(F("Range: "));
lcd.print(settings.temperature_setpoint_range, 1); // Range Temperature
lcd.write((char)223);
lcd.print(F("C"));
lcd.setCursor(0, 3);
lcd.print(F(" "));
if (isUpButtonPressed) {
settings.temperature_setpoint_range += 0.1; // increases the setpoint
saveSetpoints(); // automatic saving to EEPROM
} else if (isDownButtonPressed) {
settings.temperature_setpoint_range -= 0.1; // decreases the setpoint
saveSetpoints();
} else if (isEnterButtonPressed) {
(temperature) = 2;
}
break;
case 2:
lcd.setCursor(0, 1);
lcd.print(F(" "));
lcd.setCursor(0, 2);
lcd.print(F("Club: "));
lcd.print(settings.temperature_setpoint_club, 1); // Club Temperature
lcd.write((char)223);
lcd.print(F("C"));
lcd.setCursor(0, 3);
lcd.print(F(" "));
if (isUpButtonPressed) {
settings.temperature_setpoint_club += 0.1;
saveSetpoints();
} else if (isDownButtonPressed) {
settings.temperature_setpoint_club -= 0.1;
saveSetpoints();
} else if (isEnterButtonPressed) {
(temperature) = 3;
}
break;
case 3:
lcd.setCursor(0, 1);
lcd.print(F(" "));
lcd.setCursor(0, 2);
lcd.print(F("Airgun: "));
lcd.print(settings.temperature_setpoint_airgun, 1); // Airgun Temperature
lcd.write((char)223);
lcd.print(F("C"));
lcd.setCursor(0, 3);
lcd.print(F(" "));
if (isUpButtonPressed) {
settings.temperature_setpoint_airgun += 0.1;
saveSetpoints();
} else if (isDownButtonPressed) {
settings.temperature_setpoint_airgun -= 0.1;
saveSetpoints();
} else if (isEnterButtonPressed) {
(temperature) = 4;
isEnterButtonPressed = false;
}
break;
case 4:
lcd.setCursor(0, 0);
lcd.print(F(" "));
lcd.setCursor(0, 1);
lcd.print(F("PRESS UP TO RESTART "));
lcd.setCursor(0, 2);
lcd.print(F(" OR ENTER TO SAVE "));
if (isUpButtonPressed) {
(temperature) = 0;
} else if (isEnterButtonPressed) {
temperatureMenuScreen();
settingSaving();
}
break;
default:
settingSaving();
break;
}
}
//------------------------Setting Saving------------------------------//
void settingSaving() {
Serial.println(F("Saving soon"));
saveSetpoints();
if (TimePeriodIsOver(EEPROMSaveDelay1, 3000)) {
isSetupOn = false;
}
}
//-----------------------Time Menu Screen----------------------------//
void timeMenuScreen() {
EEPROM.get(0, settings);
if (TimePeriodIsOver(generalPause1, 1000)) {
lcd.setCursor(0, 0);
lcd.print(F("R-On:"));
lcd.print(settings.On_Normal_Range / 60);
lcd.print(F(":"));
if ((settings.On_Normal_Range) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.On_Normal_Range % 60);
lcd.setCursor(10, 0);
lcd.print(F(" Off:"));
lcd.print(settings.Off_Normal_Range / 60);
lcd.print(F(":"));
if ((settings.Off_Normal_Range) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.Off_Normal_Range % 60);
lcd.setCursor(0, 1);
lcd.print(F("C-On:"));
lcd.print(settings.On_Normal_Club / 60);
lcd.print(F(":"));
if ((settings.On_Normal_Club) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.On_Normal_Club % 60);
lcd.setCursor(10, 1);
lcd.print(F(" Off:"));
lcd.print(settings.Off_Normal_Club / 60);
lcd.print(F(":"));
if ((settings.Off_Normal_Club) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.Off_Normal_Club % 60);
lcd.setCursor(0, 2);
lcd.print(F("A-On:"));
lcd.print(settings.On_Normal_Airgun / 60);
lcd.print(F(":"));
if ((settings.On_Normal_Airgun) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.On_Normal_Airgun % 60);
lcd.setCursor(10, 2);
lcd.print(F(" Off:"));
lcd.print(settings.Off_Normal_Airgun / 60);
lcd.print(F(":"));
if ((settings.Off_Normal_Airgun) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.Off_Normal_Airgun % 60);
lcd.setCursor(0, 3);
lcd.print(F("S-On:"));
lcd.print(settings.On_Sunday / 60);
lcd.print(F(":"));
if ((settings.On_Sunday) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.On_Sunday % 60);
lcd.setCursor(10, 3);
lcd.print(F(" Off:"));
lcd.print(settings.Off_Sunday / 60);
lcd.print(F(":"));
if ((settings.Off_Sunday) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.Off_Sunday % 60);
}
}
//----------------------Time Setting Menu----------------------------//
void timeSetting() {
switch (time) {
case 0:
timeMenuScreen(); // Time settings overview
if (isRightButtonPressed) {
(time) = 1;
} else if (isEnterButtonPressed) {
settingSaving();
}
break;
case 1:
lcd.setCursor(0, 0);
lcd.print(" TIME SETTING MENU: ");
lcd.setCursor(0, 2);
lcd.print(F(" "));
lcd.setCursor(0, 3);
lcd.print(F(" "));
lcd.setCursor(0, 1);
lcd.print(F("Range ON: ")); //Range
lcd.print(settings.On_Normal_Range / 60);
lcd.print(F(":"));
if ((settings.On_Normal_Range) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.On_Normal_Range % 60);
if (isUpButtonPressed) {
settings.On_Normal_Range += 10; // increases the setpoint
saveSetpoints(); // Automatic saving to EEPROM
} else if (isDownButtonPressed) {
settings.On_Normal_Range -= 10; // decreses the set point
saveSetpoints();
timeMenuScreen();
} else if (isEnterButtonPressed) {
(time) = 2;
}
break;
case 2:
lcd.setCursor(0, 2);
lcd.print(F(" "));
lcd.setCursor(0, 3);
lcd.print(F(" "));
lcd.setCursor(0, 1);
lcd.print(F("Range OFF: "));
lcd.print(settings.Off_Normal_Range / 60);
lcd.print(F(":"));
if ((settings.Off_Normal_Range) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.Off_Normal_Range % 60);
if (isUpButtonPressed) {
settings.Off_Normal_Range += 10;
saveSetpoints();
} else if (isDownButtonPressed) {
settings.Off_Normal_Range -= 10;
saveSetpoints();
} else if (isEnterButtonPressed) {
(time) = 3;
}
break;
case 3:
lcd.setCursor(0, 2);
lcd.print(F(" "));
lcd.setCursor(0, 3);
lcd.print(F(" "));
lcd.setCursor(0, 1);
lcd.print(F("Club ON: ")); //Club
lcd.print(settings.On_Normal_Club / 60);
lcd.print(F(":"));
if ((settings.On_Normal_Club) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.On_Normal_Club % 60);
if (isUpButtonPressed) {
settings.On_Normal_Club += 10;
saveSetpoints();
} else if (isDownButtonPressed) {
settings.On_Normal_Club -= 10;
saveSetpoints();
} else if (isEnterButtonPressed) {
(time) = 4;
}
break;
case 4:
lcd.setCursor(0, 2);
lcd.print(F(" "));
lcd.setCursor(0, 3);
lcd.print(F(" "));
lcd.setCursor(0, 1);
lcd.print(F("Club OFF: "));
lcd.print(settings.Off_Normal_Club / 60);
lcd.print(F(":"));
if ((settings.Off_Normal_Club) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.Off_Normal_Club % 60);
if (isUpButtonPressed) {
settings.Off_Normal_Club += 10;
saveSetpoints();
} else if (isDownButtonPressed) {
settings.Off_Normal_Club -= 10;
saveSetpoints();
} else if (isEnterButtonPressed) {
(time) = 5;
}
break;
case 5:
lcd.setCursor(0, 2);
lcd.print(F(" "));
lcd.setCursor(0, 3);
lcd.print(F(" "));
lcd.setCursor(0, 1);
lcd.print(F("Airgun ON: "));
lcd.print(settings.On_Normal_Airgun / 60); // Airgun
lcd.print(F(":"));
if ((settings.On_Normal_Airgun) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.On_Normal_Airgun % 60);
if (isUpButtonPressed) {
settings.On_Normal_Airgun += 10;
saveSetpoints();
} else if (isDownButtonPressed) {
settings.On_Normal_Airgun -= 10;
saveSetpoints();
} else if (isEnterButtonPressed) {
(time) = 6;
}
break;
case 6:
lcd.setCursor(0, 2);
lcd.print(F(" "));
lcd.setCursor(0, 3);
lcd.print(F(" "));
lcd.setCursor(0, 1);
lcd.print(F("Airgun OFF: "));
lcd.print(settings.Off_Normal_Airgun / 60); // Airgun
lcd.print(F(":"));
if ((settings.Off_Normal_Airgun) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.Off_Normal_Airgun % 60);
if (isUpButtonPressed) {
settings.Off_Normal_Airgun += 10;
saveSetpoints();
} else if (isDownButtonPressed) {
settings.Off_Normal_Airgun -= 10;
saveSetpoints();
} else if (isEnterButtonPressed) {
(time) = 7;
}
break;
case 7:
lcd.setCursor(0, 2);
lcd.print(F(" "));
lcd.setCursor(0, 3);
lcd.print(F(" "));
lcd.setCursor(0, 1);
lcd.print(F("Sunday ON: ")); //Sunday
lcd.print(settings.On_Sunday / 60); // Airgun
lcd.print(F(":"));
if ((settings.On_Sunday) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.On_Sunday % 60);
if (isUpButtonPressed) {
settings.On_Sunday += 10;
saveSetpoints();
} else if (isDownButtonPressed) {
settings.On_Sunday -= 10;
saveSetpoints();
} else if (isEnterButtonPressed) {
(time) = 8;
}
break;
case 8:
lcd.setCursor(0, 2);
lcd.print(F(" "));
lcd.setCursor(0, 3);
lcd.print(F(" "));
lcd.setCursor(0, 1);
lcd.print(F("Sunday OFF: "));
lcd.print(settings.Off_Sunday / 60); // Airgun
lcd.print(F(":"));
if ((settings.Off_Sunday) % 60 < 10) lcd.print(F("0")); // Leading zero for minutes
lcd.print(settings.Off_Sunday % 60);
if (isUpButtonPressed) {
settings.Off_Sunday += 10;
saveSetpoints();
} else if (isDownButtonPressed) {
settings.Off_Sunday -= 10;
saveSetpoints();
} else if (isEnterButtonPressed) {
(time) = 9;
}
break;
case 9:
lcd.setCursor(0, 0);
lcd.print(F(" "));
lcd.setCursor(0, 1);
lcd.print(F("PRESS UP TO RESTART "));
lcd.setCursor(0, 2);
lcd.print(F(" OR ENTER TO SAVE "));
if (isUpButtonPressed) {
(time) = 0;
} else if (isEnterButtonPressed) {
timeMenuScreen();
settingSaving();
}
break;
default:
settingSaving();
break;
}
}
//-------------------Temperature LCD display ------------------------//
void temperatureDisplay() {
temperatureValue = ((range_sensor.getTempCByIndex(0), 1) + (club_sensor.getTempCByIndex(0), 1) + (airgun_sensor.getTempCByIndex(0), 1));
if (abs(temperatureValue - oldTemperatureValue) >= hysteresis) {
lcd.setCursor(0, 1); // Range Temperature
lcd.print(F("Range: "));
lcd.print(range_sensor.getTempCByIndex(0), 1);
lcd.write((char)223);
lcd.print(F("C"));
lcd.setCursor(0, 2); // Club Temperature
lcd.print(F("Club: "));
lcd.print(club_sensor.getTempCByIndex(0), 1);
lcd.write((char)223);
lcd.print(F("C"));
lcd.setCursor(0, 3); // Airgun Temperature
lcd.print(F("Airgun:"));
lcd.print(airgun_sensor.getTempCByIndex(0), 1);
lcd.write((char)223);
lcd.print(F("C"));
if (TimePeriodIsOver(temperatureDisplayTimer, 15000)) {
oldTemperatureValue = temperatureValue;
}
}
}
//---------------Serial Print Area Temperatures----------------------//
void serialPrintAreaTemperatures() {
if (TimePeriodIsOver(sensorPrintDelayTimer, 30000)) {
PrintFileNameDateTime();
Serial.print(F("Requesting temperatures (deg.C)..."));
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));
}
}
//-----------------------Heating Available---------------------------//
void heatingIsAvailable() {
if (isRangeHeatingOn) {
normalHeatingRangeArea();
} else {
frostPreventionRangeArea();
}
if (isClubHeatingOn) {
normalHeatingClubArea();
} else {
frostPreventionClubArea();
}
if (isAirgunHeatingOn) {
normalHeatingAirgunArea();
} else {
frostPreventionAirgunArea();
}
}
//------------------------Boiler Interlock--------------------------//
void boilerInterlock() {
if ((digitalRead(range_relay) == HIGH) || (digitalRead(club_relay) == HIGH) || (digitalRead(airgun_relay) == HIGH)) {
digitalWrite(boiler_relay, HIGH);
if (TimePeriodIsOver(heatingDelayTimer, 15000)) {
Serial.println(F("Heating should be happening, as temperature is below the set point!"));
}
} else {
digitalWrite(boiler_relay, LOW);
}
}
//-------------------Frost Prevention Range--------------------------//
void frostPreventionRangeArea() {
if ((range_sensor.getTempCByIndex(0) <= (frost_prevention_setpoint - deadzone))) {
digitalWrite(range_relay, HIGH);
lcd.setCursor(13, 1);
lcd.print(F(" ON F"));
} else {
if ((range_sensor.getTempCByIndex(0) >= (frost_prevention_setpoint + deadzone))) {
digitalWrite(range_relay, LOW);
lcd.setCursor(13, 1);
lcd.print(F(" OFF F"));
}
}
}
//--------------------Frost Prevention Club-------------------------//
void frostPreventionClubArea() {
if ((club_sensor.getTempCByIndex(0) <= (frost_prevention_setpoint - deadzone))) {
digitalWrite(club_relay, HIGH);
lcd.setCursor(13, 2);
lcd.print(F(" ON F"));
} else {
if ((club_sensor.getTempCByIndex(0) >= (frost_prevention_setpoint + deadzone))) {
digitalWrite(club_relay, LOW);
lcd.setCursor(13, 2);
lcd.print(F(" OFF F"));
}
}
}
//---------------------Frost Prevention Airgun-----------------------//
void frostPreventionAirgunArea() {
if ((airgun_sensor.getTempCByIndex(0) <= (frost_prevention_setpoint - deadzone))) {
digitalWrite(airgun_relay, HIGH);
lcd.setCursor(13, 3);
lcd.print(F(" ON F"));
} else {
if ((airgun_sensor.getTempCByIndex(0) >= (frost_prevention_setpoint + deadzone))) {
digitalWrite(airgun_relay, LOW);
lcd.setCursor(13, 3);
lcd.print(F(" OFF F"));
}
}
}
//-------------------Normal Heating Range----------------------------//
void normalHeatingRangeArea() {
if ((range_sensor.getTempCByIndex(0) <= (settings.temperature_setpoint_range - deadzone))) {
digitalWrite(range_relay, HIGH);
lcd.setCursor(13, 1);
lcd.print(F(" ON T"));
} else {
if ((range_sensor.getTempCByIndex(0) >= (settings.temperature_setpoint_range + deadzone))) {
digitalWrite(range_relay, LOW);
lcd.setCursor(13, 1);
lcd.print(F(" OFF T"));
}
}
}
//--------------------Normal Heating Club ---------------------------//
void normalHeatingClubArea() {
if ((club_sensor.getTempCByIndex(0) <= (settings.temperature_setpoint_club - deadzone))) {
digitalWrite(club_relay, HIGH);
lcd.setCursor(13, 2);
lcd.print(F(" ON T"));
} else {
if ((club_sensor.getTempCByIndex(0) >= (settings.temperature_setpoint_club + deadzone))) {
digitalWrite(club_relay, LOW);
lcd.setCursor(13, 2);
lcd.print(F(" OFF T"));
}
}
}
//---------------------Normal Heating Airgun-------------------------//
void normalHeatingAirgunArea() {
if ((airgun_sensor.getTempCByIndex(0) <= (settings.temperature_setpoint_airgun - deadzone))) {
digitalWrite(airgun_relay, HIGH);
lcd.setCursor(13, 3);
lcd.print(F(" ON T"));
} else {
if ((airgun_sensor.getTempCByIndex(0) >= (settings.temperature_setpoint_airgun + deadzone))) {
digitalWrite(airgun_relay, LOW);
lcd.setCursor(13, 3);
lcd.print(F(" OFF T"));
}
}
}
// helper-functions
void PrintFileNameDateTime() {
Serial.println(F("Code running comes from file "));
Serial.println(F(__FILE__));
Serial.print(F(" compiled "));
Serial.print(F(__DATE__));
Serial.print(F(" "));
Serial.println(F(__TIME__));
}
// easy to use helper-function for non-blocking timing
// explanation see here
// https://forum.arduino.cc/t/example-code-for-timing-based-on-millis-easier-to-understand-through-the-use-of-example-numbers-avoiding-delay/974017
/*boolean TimePeriodIsOver(unsigned long &startOfPeriod, unsigned long TimePeriod) {
unsigned long currentMillis = millis();
if (currentMillis - startOfPeriod >= TimePeriod) {
// more time than TimePeriod has elapsed since last time if-condition was true
startOfPeriod = currentMillis; // a new period starts right here so set new starttime
return true;
} else return false; // actual TimePeriod is NOT yet over
}
*/
void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
static unsigned long MyBlinkTimer;
pinMode(IO_Pin, OUTPUT);
if (TimePeriodIsOver(MyBlinkTimer, BlinkPeriod)) {
digitalWrite(IO_Pin, !digitalRead(IO_Pin));
}
}RESET
UP
AIRGUN
CLUB
RANGE
AIRGUN
RANGE
CLUB
LEFT
ENTER
RIGHT
DOWN
BOILER
DS3821
FITTED
HEARTBEAT
Uses the internal pullup resistors for the button inputs