// Getting a State machine to control a heating controller
// https://forum.arduino.cc/t/getting-a-state-machine-to-control-a-heating-controller/1414368
// 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
Created : 25/01/2024 - 10/11/2025 V3.006B
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, @PerryBebbington, @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 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;
const byte rangeIndex = 0;
const byte clubIndex = 1;
const byte airgunIndex = 2;
float temperature_setpoint[3]; // define an array that goes from index 0 to index 2 which means the array has three elements. => [3]
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_prev_setpoint = 12.0; // °C
const float deadzone = 1.0; // °C
const uint8_t buttonPin[] = { 6, 7, 8, 9, 10 }; // Inputs for buttons.
const uint8_t buttonCount = 5; // Number of buttons.
int n = 0;
int EEPROM_address_1 = 0; // Address of the location of Range setpoint temperature setting
int EEPROM_address_2 = 4; // Address of the location of Club setpoint temperature setting
int EEPROM_address_3 = 8; // Address of the location of Airgun setpoint temperature setting
int EEPROM_address_4 = 12; // Address of the location of Range On time setting
int EEPROM_address_5 = 16; // Address of the location of Range Off time setting
int EEPROM_address_6 = 20; // Address of the location of Club On time setting
int EEPROM_address_7 = 24; // Address of the location of Club Off time setting
int EEPROM_address_8 = 28; // Address of the location of Airgun On time setting
int EEPROM_address_9 = 32; // Address of the location of Airgun Off time setting
int EEPROM_address_10 = 36; // Address of the location of Sunday On time setting
int EEPROM_address_11 = 40; // Address of the location of Sunday Off time setting
unsigned long startMillis = 0;
unsigned long MillisMenu = 0;
unsigned long overrideTimer1 = 0; // Heating override timer
unsigned long sensorDelayTimer = 0; // Sensor reading delay
unsigned long heatingDelayTimer = 0; // Heating delay
unsigned long EEPROMSaveDelay = 0; // EEPROM save delay
unsigned long EEPROMSaveDelay1 = 0; // EEPROM save delay
unsigned long backlightDisplayTimer = 0; // Backlight display delay
unsigned long temperatureDisplayTimer = 0; // Temperature display delay
unsigned long printPause = 0; // Used for print delay.
unsigned long printPause1 = 0; // Used for print delay
unsigned long printPause3 = 0; // Used for print delay.
unsigned long printPause4 = 0; // Used for print delay.
unsigned long generalPause = 0; // Used for general delay.
unsigned long currentMillis = 0;
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 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 isHeartBeatOn = false; // if = True shows current status of heart beat
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 buttonPressedFlag[buttonCount]; // if = True flags to indicate that a button has been detected as pressed and debounced
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
}
typedef void (*functionPtrs)(void);
functionPtrs functions[5] = { &leftButton, &upButton, &downButton, &rightButton, &enterButton };
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
};
enum setupModes { TEMPERATURE_SETUP_MODE,
TIME_SETUP_MODE
};
int myMode = NORMAL_MODE;
int On_Normal_Range; // 16:00 Normal ON time
int Off_Normal_Range; // 21:30 Normal OFF time
int On_Normal_Club; // 16:00 Normal ON time
int Off_Normal_Club; // 21:30 Normal OFF time
int On_Normal_Airgun; // 16:00 Normal ON time
int Off_Normal_Airgun; // 21:30 Normal OFF time
int On_Sunday; // 12:00 Sunday ON time
int Off_Sunday; // 17:30 Sunday OFF time
int old_data;
int new_data;
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
char *modeTags[] = { "normal", "override", "setup", "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
//---------------------------General control-------------------------//
myStateMachine();
backGroundItems();
//--------------------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) {
// Serial.println(F("Something time controlled is happening here!"));
if (theTime.dayOfTheWeek() != SUNDAY) {
// Monday to Saturday so use normal times
if (currentTime > On_Normal_Range && currentTime < Off_Normal_Range) {
// 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 > On_Normal_Club && currentTime < Off_Normal_Club) {
// 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 > On_Normal_Airgun && currentTime < Off_Normal_Airgun) {
// this is within normal Airgun heating time
isAirgunHeatingOn = true;
} else {
// this is outside normal Airgun heating time
isAirgunHeatingOn = false;
}
} else {
if (currentTime > On_Sunday && currentTime < Off_Sunday) {
// 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.setCursor(0, 0);
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 "375" to set the RTC to the current PC date & time or "376" to manually input the date & time.
Renmove the battery and upload sketch. Then re-comment line "375" or "376" Upload sketch again. Refiut the battery and then remove external power. Then re-instate power. */
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// rtc.adjust(DateTime(2025, 11, 3, 19, 46, 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() {
for (uint8_t i = 0; i < buttonCount; ++i) {
pinMode(buttonPin[i], 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 Setpoints--------------------------//
void initiateSetpoints() {
// EEPROM.put(EEPROM_address_1, temperature_setpoint_range); // write the float value to EEPROM ONLY used to initially set up setpoint
// EEPROM.put(EEPROM_address_2, temperature_setpoint_club); // write the float value to EEPROM ONLY used to initially set up setpoint
// EEPROM.put(EEPROM_address_3, temperature_setpoint_airgun); // write the float value to EEPROM ONLY used to initially set up setpoint
// EEPROM.put(EEPROM_address_4, 16 * 60 + 00); // write the value to EEPROM ONLY used to initially set up Range Normal On Time
// EEPROM.put(EEPROM_address_5, 21 * 60 + 30); // write the value to EEPROM ONLY used to initially set up Range Normal Off Time
// EEPROM.put(EEPROM_address_6, 16 * 60 + 00); // write the value to EEPROM ONLY used to initially set up Club Normal On Time
// EEPROM.put(EEPROM_address_7, 21 * 60 + 30); // write the value to EEPROM ONLY used to initially set up Club Normal Off Time
// EEPROM.put(EEPROM_address_8, 16 * 60 + 00); // write the value to EEPROM ONLY used to initially set up Airgun Normal On Time
// EEPROM.put(EEPROM_address_9, 21 * 60 + 30); // write the value to EEPROM ONLY used to initially set up Airgun Normal Off Time
// EEPROM.put(EEPROM_address_10, 12 * 60 + 00); // write the value to EEPROM ONLY used to initially set up Sunday On Time
// EEPROM.put(EEPROM_address_11, 17 * 60 + 30); // write the value to EEPROM ONLY used to initially set up Sunday Off Time
}
//------------------------Retrieve setpoint--------------------------//
void retrieveSetpoints() {
EEPROM.get(EEPROM_address_1, temperature_setpoint_range); //retrieve the range setpoint from EEPROM
EEPROM.get(EEPROM_address_2, temperature_setpoint_club); //retrieve the club setpoint from EEPROM
EEPROM.get(EEPROM_address_3, temperature_setpoint_airgun); //retrieve the airgun setpoint from EEPROM
EEPROM.get(EEPROM_address_4, On_Normal_Range); //retrieve the Normal Range On Time from EEPROM
EEPROM.get(EEPROM_address_5, Off_Normal_Range); //retrieve the Normal Range Off Time from EEPROM
EEPROM.get(EEPROM_address_6, On_Normal_Club); //retrieve the Normal Club On Time from EEPROM
EEPROM.get(EEPROM_address_7, Off_Normal_Club); //retrieve the Normal Club Off Time from EEPROM
EEPROM.get(EEPROM_address_8, On_Normal_Airgun); //retrieve the Normal Airgun On Time from EEPROM
EEPROM.get(EEPROM_address_9, Off_Normal_Airgun); //retrieve the Normal Airgun Off Time from EEPROM
EEPROM.get(EEPROM_address_10, On_Sunday); //retrieve the Sunday On Time from EEPROM
EEPROM.get(EEPROM_address_11, Off_Sunday); //retrieve the Sunday Off Time from EEPROM
Serial.print(F("Range Temperature Setpoint - "));
Serial.print(temperature_setpoint_range);
Serial.print(F(" Stored at addr "));
Serial.println(EEPROM_address_1);
Serial.print(F("Club Temperature Setpoint - "));
Serial.print(temperature_setpoint_club);
Serial.print(F(" Stored at addr "));
Serial.println(EEPROM_address_2);
Serial.print(F("Airgun Temperature Setpoint - "));
Serial.print(temperature_setpoint_airgun);
Serial.print(F(" Stored at addr "));
Serial.println(EEPROM_address_3);
Serial.print(F("Normal Range On Time - "));
Serial.print(On_Normal_Range);
Serial.print(F(" Stored at addr "));
Serial.println(EEPROM_address_4);
Serial.print(F("Normal Range Off Time - "));
Serial.print(Off_Normal_Range);
Serial.print(F(" Stored at addr "));
Serial.println(EEPROM_address_5);
Serial.print(F("Normal Club On Time - "));
Serial.print(On_Normal_Club);
Serial.print(F(" Stored at addr "));
Serial.println(EEPROM_address_6);
Serial.print(F("Normal Club Off Time - "));
Serial.print(Off_Normal_Club);
Serial.print(F(" Stored at addr "));
Serial.println(EEPROM_address_7);
Serial.print(F("Normal Airgun On Time - "));
Serial.print(On_Normal_Airgun);
Serial.print(F(" Stored at addr "));
Serial.println(EEPROM_address_8);
Serial.print(F("Normal Airgun Off Time - "));
Serial.print(Off_Normal_Airgun);
Serial.print(F(" Stored at addr "));
Serial.println(EEPROM_address_9);
Serial.print(F("Sunday On Time - "));
Serial.print(On_Sunday);
Serial.print(F(" Stored at addr "));
Serial.println(EEPROM_address_10);
Serial.print(F("Sunday Off Time - "));
Serial.print(Off_Sunday);
Serial.print(F(" Stored at addr "));
Serial.println(EEPROM_address_11);
}
//---------------------------Save Setpoints---------------------------//
void saveSetpoints() {
for (int n = 0; n < 1; n++) {
Serial.println(F("Changes saved"));
EEPROM.put(EEPROM_address_1, temperature_setpoint_range); // write the new range setpoint value to EEPROM, only if it has changed
EEPROM.put(EEPROM_address_2, temperature_setpoint_club); // write the new club setpoint value to EEPROM, only if it has changed
EEPROM.put(EEPROM_address_3, temperature_setpoint_airgun); // write the new airgun setpoint value to EEPROM, only if it has changed
EEPROM.put(EEPROM_address_4, On_Normal_Range); // write the new Range Normal On time to EEPROM, only if it has changed
EEPROM.put(EEPROM_address_5, Off_Normal_Range); // write the new Range Normal Off time to EEPROM, only if it has changed
EEPROM.put(EEPROM_address_6, On_Normal_Club); // write the new Club Normal On time to EEPROM, only if it has changed
EEPROM.put(EEPROM_address_7, Off_Normal_Club); // write the new Club Normal Off time to EEPROM, only if it has changed
EEPROM.put(EEPROM_address_8, On_Normal_Airgun); // write the new Airgun Normal On time to EEPROM, only if it has changed
EEPROM.put(EEPROM_address_9, Off_Normal_Airgun); // write the new Airgun Normal Off time to EEPROM, only if it has changed
EEPROM.put(EEPROM_address_10, On_Sunday); // write the new Sunday On time to EEPROM, only if it has changed
EEPROM.put(EEPROM_address_11, Off_Sunday); // write the new Sunday Off time to EEPROM, only if it has changed
if (TimePeriodIsOver(EEPROMSaveDelay, 2500)) {
retrieveSetpoints();
lcd.setCursor(3, 3);
lcd.print(F("SETTINGS SAVED"));
}
if (TimePeriodIsOver(EEPROMSaveDelay1, 2500)) {
lcd.clear();
isTimeSettingOn = false;
isSetupOn = false;
}
}
}
//--------------------------------LCD--------------------------------//
void initLCD() {
lcd.init();
lcd.backlight();
lcd.createChar(0, char_temp);
lcd.setCursor(2, 0);
lcd.print(F("G0RJM Three Zone"));
lcd.setCursor(1, 1);
lcd.print(F("Temperature Control"));
delay(1000);
lcd.setCursor(2, 2);
lcd.print(F("V3.006B 14/11/25"));
delay(1000);
lcd.setCursor(4, 3);
lcd.print(F("Working on it"));
Serial.println(F("G0RJM Three zone Temperature Control"));
delay(3000);
lcd.clear();
}
//---------------------------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
}
}
//--------------------------Background Items------------------------//
void backGroundItems() {
boilerInterlock();
serialPrintAreaTemperatures();
backlightDisplay();
buttonSettings();
buttonControl();
}
// -------------------------Backlight display-----------------------//
void backlightDisplay() {
if ((TimePeriodIsOver(backlightDisplayTimer, 60000)) && (isTimeDisplayOn)) {
lcd.noBacklight();
Serial.println(F("LCD Backlight OFF"));
}
}
//---------------------------Button Settings------------------------//
void buttonSettings() {
#define buttonPressed LOW // When the button is pressed the input will be low.
uint32_t currentMillis = millis(); // Millis times used to debounce the button
static uint32_t lastMillis[buttonCount]; // Start of the debounce timeout for each button
const uint32_t bounceTimeout = 50; // Debounce time in milliseconds
bool currentButtonState[buttonCount]; // Holds the current state of each button
static bool lastButtonState[buttonCount]; // Holds the previous debounced state of the button
uint8_t i;
for (i = 0; i < buttonCount; ++i) {
currentButtonState[i] = digitalRead(buttonPin[i]); // Reads the current state of each button and saves the result
if (lastButtonState[i] != currentButtonState[i]) { // °Checks to see if each button has been pressed or released, at this point each button has not been debounced
if (currentMillis - lastMillis[i] >= bounceTimeout) { // °Checks to see if the state of each button has been stable for at least bounceTimeout duration
lastButtonState[i] = currentButtonState[i]; // At this point the button has been debounced, so save the last state
if (currentButtonState[i] == buttonPressed) { // The button might have been pressed or released, this make sure only presses are acted on, not releases
buttonPressedFlag[i] = true; // Button press has been detected and debounced, set a flag to indicate to the next function that some action can be taken
}
}
} else {
lastMillis[i] = currentMillis; // Saves the current value of millis in last millis so the debounce timer for each button starts from current millis
}
}
}
//---------------------------Button Actions---------- --------------//
void buttonControl() {
uint8_t i;
for (i = 0; i < buttonCount; ++i) {
if (buttonPressedFlag[i]) {
buttonPressedFlag[i] = false; // °Clear the flag to ensure the action only happens once
functions[i](); // °Calls one of the 5 functions depending on which button was pressed
}
}
}
// 5 functions, which are called depending on which button has been pressed
void leftButton() {
Serial.println(F("Left Button has been pressed"));
isLeftButtonPressed = true;
Buzzer();
}
void upButton() {
Serial.println(F("Up Button has been pressed"));
isUpButtonPressed = true;
Buzzer();
}
void downButton() {
Serial.println(F("Down Button has been pressed"));
isDownButtonPressed = true;
Buzzer();
}
void rightButton() {
Serial.println(F("Right Button has been pressed"));
isRightButtonPressed = true;
Buzzer();
}
void enterButton() {
Serial.println(F("Enter Button has been pressed"));
isEnterButtonPressed = true;
Buzzer();
}
//---------------------------Buzzer----------------------------------//
void Buzzer() {
lcd.backlight();
tone(buzzer_pin, 3000, 100);
Serial.println(F("LCD Backlight ON"));
}
//----------------------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;
}
}
//------------------------My State machine---------------------------//
void myStateMachine() {
printStateIf(myMode);
switch (myMode) {
case NORMAL_MODE:
normalModeSelect();
if (isRightButtonPressed) {
myMode = OVERRIDE_MODE;
}
break; // skip cases below and jump down to END-OF-SWITCH
case OVERRIDE_MODE:
heatingOverride();
if (!isHeatingOverrideOn) {
myMode = NORMAL_MODE;
}
break;
default:
myMode = NORMAL_MODE;
break;
}
} // END-OF-SWITCH
//-------------------------Normal Mode Select----------------------//
void normalModeSelect() {
temperatureDisplay();
heatingIsAvailable();
isTimeDisplayOn = true;
isTimedSessionOn = true;
if (TimePeriodIsOver(printPause1, 15000)) {
Serial.println(F("Normal temperature control is available for all areas"));
}
}
//------------------------Heating Override Select---------------------//
void heatingOverride() {
lcd.setCursor(0, 0);
lcd.print(F(" OVERRIDE MODE "));
temperatureDisplay();
heatingIsAvailable();
isRangeHeatingOn = true;
isClubHeatingOn = true;
isAirgunHeatingOn = true;
isTimedSessionOn = false;
isHeatingOverrideOn = true;
if (TimePeriodIsOver(printPause3, 15000)) {
Serial.println(F(" Heating Override is On"));
}
if (TimePeriodIsOver(overrideTimer1, 60000)) { // Automatically cancels the override
Serial.println(F(" Auto termination of override"));
heatingOverrideIsCompleted();
} else if (isEnterButtonPressed) { // Manually cancels the override
Serial.println(F(" Manual termination of override"));
heatingOverrideIsCompleted();
}
}
//----------------------Heating Override Completed--------------------//
void heatingOverrideIsCompleted() {
isHeatingOverrideOn = false;
lcd.clear();
lcd.print(F(" OVERRIDE IS OFF "));
Buzzer();
}
//---------------------Temperature display - ------------------------//
void temperatureDisplay() {
if (TimePeriodIsOver(temperatureDisplayTimer, 15000)) {
lcd.setCursor(0, 1); // Range Temperature
lcd.print(F("Range: "));
lcd.print(range_sensor.getTempCByIndex(0), 1);
lcd.write((char)223);
lcd.print('C');
lcd.setCursor(0, 2); // °Club Temperature
lcd.print(F("Club: "));
lcd.print(club_sensor.getTempCByIndex(0), 1);
lcd.write((char)223);
lcd.print('C');
lcd.setCursor(0, 3); // Airgun Temperature
lcd.print(F("Airgun:"));
lcd.print(airgun_sensor.getTempCByIndex(0), 1);
lcd.write((char)223);
lcd.print('C');
}
}
//----------------Serial Print Area Temperatures----------------------//
void serialPrintAreaTemperatures() {
if (TimePeriodIsOver(sensorDelayTimer, 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_prev_setpoint - deadzone))) {
digitalWrite(range_relay, HIGH);
lcd.setCursor(13, 1);
lcd.print(F(" ON F"));
} else {
if ((range_sensor.getTempCByIndex(0) >= (frost_prev_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_prev_setpoint - deadzone))) {
digitalWrite(club_relay, HIGH);
lcd.setCursor(13, 2);
lcd.print(F(" ON F"));
} else {
if ((club_sensor.getTempCByIndex(0) >= (frost_prev_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_prev_setpoint - deadzone))) {
digitalWrite(airgun_relay, HIGH);
lcd.setCursor(13, 3);
lcd.print(F(" ON F"));
} else {
if ((airgun_sensor.getTempCByIndex(0) >= (frost_prev_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) <= (temperature_setpoint_range - deadzone))) {
digitalWrite(range_relay, HIGH);
lcd.setCursor(13, 1);
lcd.print(F(" ON T"));
} else {
if ((range_sensor.getTempCByIndex(0) >= (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) <= (temperature_setpoint_club - deadzone))) {
digitalWrite(club_relay, HIGH);
lcd.setCursor(13, 2);
lcd.print(F(" ON T"));
} else {
if ((club_sensor.getTempCByIndex(0) >= (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) <= (temperature_setpoint_airgun - deadzone))) {
digitalWrite(airgun_relay, HIGH);
lcd.setCursor(13, 3);
lcd.print(F(" ON T"));
} else {
if ((airgun_sensor.getTempCByIndex(0) >= (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));
}
}CLUB
AIRGUN
RANGE
BOILER
AIRGUN
CLUB
RANGE
DS3231 fitted
Heartbeat