// LIBRARIES INCLUDES ---------------------------------------------------------------------------------------------
#include <Arduino.h>
// DEFINES -------------------------------------------------------------------------------------------------------
// DEFINE DEBUG MODE ACTIVE/DEACTIVE
#define DEBUG 1
#if DEBUG == 1
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#else
#define debug(x)
#define debugln(x)
#endif
// DEFINE WHETHER THE LOOPS SHOULD BE SHOWN WHEN DEBUG MODE IS ON
#define DEBUG_LOOPS 0
// DEFINES GENERAL
#define HOSTNAME "FirstFloorSwitchBoard"
#define ONBOARD_LED 2
#define DIGITAL_OUTPUTS 2
#define DIGITAL_INPUTS 5
#define TIMERS_ON_OFF 0
// DEFINES RELAY OUTPUTS PINS
#define RELAY0 12
#define RELAY1 14
// DEFINES INPUTS PINS
#define INPUT0 21 //Feedback
#define INPUT1 22 //Feedback
#define INPUT2 25 //Switch Taster
#define INPUT3 26 //Switch Relay 0
#define INPUT4 27 //Switch Relay 1
// FEEDBACK SIGNAL FOR RELAYS WITH ASSIGNED FEEDBACK INPUT (only when relay is set to type 1 (pulse relay))
const byte feedbackInputFailCountsMax = 5;
const unsigned long feedbackSignalMonitoringTimeMs = 1500;
// VARIOUS --------------------------------------------------------------------------------------------------------
long lastMillis = 0;
long loops = 0;
const int READ_INPUTS_CYCLIC = 300; // Read inputs cyclic all xxxMilliseconds
const int CHECK_AUTO_OFF_CYCLIC = 10000; // Check if any relay must be switched off automatically by time xxxMilliseconds
const int CHECK_FEEDBACK_SIGNAL = 100; // When a relay has an assinged feedback input check every set xxxMilliseconds wheterh a signal check is required (must be less than the set 'expectedFeedbackTimeMs'!)
const int SET_RELAYS_IMPULSE_CYCLIC = 50; // Check if Relay need to be set by impulse mode all xxxMilliseconds
int offsetTimeSyncCyclic = 0; // Offset to sync call to clock
bool firstCycleDone = false;
unsigned long setRelaysImpulse;
unsigned long checkAutoOffRelays;
unsigned long checkFeedbackSignal;
unsigned long readInputsCalled; // Time set when function to read inputs has been called
unsigned long currentMillis;
// RELAY OUPUTS DEFINITON AND MQTT TOPICS STRUCTS ----------------------------------------------------------------
//Info for feedbackInput:
//Number of input which is used as feedback (used when a relay works as a pulse relay), 128 = no feedback signal defined
//for example relay 0 expects feedback from input 1, we assign input 1 to relay 0, relay 1 has no feedback signal, we assing 128
struct outputDefinition {
int outputPins[DIGITAL_OUTPUTS] = {
RELAY0,
RELAY1
};
int feedbackInput[DIGITAL_OUTPUTS] = {
0,
1
};
const char * outputBaseTopic[DIGITAL_OUTPUTS] = {
"basement/switchBoard/relay0",
"basement/switchBoard/relay1"
};
const char * outputStateTopic[DIGITAL_OUTPUTS] = {
"basement/switchBoard/relay0/state",
"basement/switchBoard/relay1/state"
};
const char * outputTimerTopic[DIGITAL_OUTPUTS] = {
"basement/switchBoard/relay0/timer",
"basement/switchBoard/relay1/timer"
};
};
struct outputDefinition outputDefine;
// FEEDBACK SIGNAL STATE STRUCT ---------------------------------------------------------------------------------
//Info for setTime:
//When the relay is set to type 1 (pulse relay) and a state change is requested by autoOff or clock then set the current time into this var to compare later on after a defined time whether the state is correct
//Info for expectedFeedbackTimeMs:
//expected time till the correct feedback state should be given
//Info for compareType:
//when is set to 1 it checks desired state to current state for equlity else for unequality
//Info for desiredState:
//is the expected state for the feedbacksignal
//Info for failureCount:
//When feedback state is not correct add 1 to the counter
//Info for failureCountMax:
//When defined max counter, when it is reached se
struct feedbackSignal {
unsigned long setTime[DIGITAL_OUTPUTS] = {
0,
0
};
unsigned long expectedFeedbackTimeMs[DIGITAL_OUTPUTS] = {
500,
500
};
bool compareType[DIGITAL_OUTPUTS] = {
0,
0
};
bool desiredState[DIGITAL_OUTPUTS] = {
0,
0
};
int failureCount[DIGITAL_OUTPUTS] = {
0,
0
};
int failureCountMax[DIGITAL_OUTPUTS] = {
3,
3
};
};
struct feedbackSignal feedbackSignalCheck;
// TIMER STRUCT --------------------------------------------------------------------------------------------------
struct relayTimerStruct {
byte relay;
byte relayType; // 0 = standard relay / 1 = impulse relay (stairway automatic, needs feedback signal of an input whether lamp is on or off)
byte setMode; // 0 = manual / 1 = clock timer / 2 = impulse / 3 = autoOff
unsigned long autoOffMilliSec;
unsigned long impulseMilliSec = 0;
unsigned long startImpulseMilliSec;
byte day;
int switchOn[TIMERS_ON_OFF];
int switchOff[TIMERS_ON_OFF];
bool setByRemoteControl = 0;
};
struct relayTimerStruct relayTimers[DIGITAL_OUTPUTS];
// INPUT STATE STRUCT --------------------------------------------------------------------------------------------
struct inputDefinition {
int inputPin[DIGITAL_INPUTS] = {
INPUT0,
INPUT1,
INPUT2,
INPUT3,
INPUT4
};
const char * inputBaseTopic[DIGITAL_INPUTS] = {
"basement/switchBoard/input0",
"basement/switchBoard/input1",
"basement/switchBoard/input2",
"basement/switchBoard/input3",
"basement/switchBoard/input4"
};
const char * inputStateTopic[DIGITAL_INPUTS] = {
"basement/switchBoard/input0/state",
"basement/switchBoard/input1/state",
"basement/switchBoard/input2/state",
"basement/switchBoard/input3/state",
"basement/switchBoard/input4/state"
};
};
struct inputDefinition inputDefine;
// INPUT STATE STRUCT --------------------------------------------------------------------------------------------
struct inputStates {
bool current = 0;
bool old = 0;
unsigned long switchOnTimeMs;
};
struct inputStates inputState[DIGITAL_INPUTS];
// COMPARE FEEDBACK SIGNAL (only for relays with defined feedback input) --------------------------------------------
void compareFeedbackSignal(int inputNo, bool desiredState, int relayNo, bool equal){
//compare states of desired state and feedback input if set to equal both must have the same state
debug("Compare mode is (1=equal / 0=unequal): ");
debugln(equal);
debug("Feedback input no: ");
debug(inputNo);
if(inputState[inputNo].current == desiredState && equal){
//reset counter
feedbackSignalCheck.failureCount[relayNo] = 0;
debug(" is in correct state, equal to desired state. ");
}else if(inputState[inputNo].current != desiredState && !equal){
//reset counter
feedbackSignalCheck.failureCount[relayNo] = 0;
debug(" is in correct state, not equal to desired state. ");
}else{
feedbackSignalCheck.failureCount[relayNo] += 1;
if(feedbackSignalCheck.failureCount[relayNo] >= feedbackSignalCheck.failureCountMax[relayNo]){
//client.publish(outputDefine.outputStateTopic[relayNo], "ERROR");
debug("Failure Count ERROR");
}else{
//client.publish(outputDefine.outputStateTopic[relayNo], "WARNING");
debug("Failure Count WARNING");
}
debug(" is not in correct state to desired state. ");
}
debug("Input state is: ");
debug(inputState[inputNo].current);
debug(" / Desired state is: ");
debugln(desiredState);
debug("Fail counter is now: ");
debugln(feedbackSignalCheck.failureCount[relayNo]);
}
// IOs ------------------------------------------------------------------------------------------------------------
void setup_ios() {
// Outputs
if(DIGITAL_OUTPUTS != 0){
pinMode(RELAY0, OUTPUT);
pinMode(RELAY1, OUTPUT);
// set outputs by default off
digitalWrite(RELAY0, LOW);
digitalWrite(RELAY1, LOW);
}
// Inputs
if(DIGITAL_INPUTS != 0){
//INPUT_PULLUP
pinMode(INPUT0, INPUT_PULLUP);
pinMode(INPUT1, INPUT_PULLUP);
pinMode(INPUT2, INPUT_PULLUP);
pinMode(INPUT3, INPUT_PULLUP);
pinMode(INPUT4, INPUT_PULLUP);
}
}
// SET DIGITAL OUTPUTS (RELAYS) -----------------------------------------------------------------------------------
void setOutputs(bool OnOff, int relayNumber) {
if (!OnOff) { // Switch off
debug("RELAY: ");
debug(relayNumber);
debugln(" OFF");
digitalWrite(outputDefine.outputPins[relayNumber], HIGH);
//reset current switch on time
relayTimers[relayNumber].startImpulseMilliSec = 0;
//client.publish(outputDefine.outputStateTopic[relayNumber], "OFF");
} else if (OnOff) { // Switch on
debug("RELAY: ");
debug(relayNumber);
debugln(" ON");
digitalWrite(outputDefine.outputPins[relayNumber], LOW);
//for relays defined as type 1 (impulse relay) set desired state for input to check whether the feedback signal is in correct position
if(relayTimers[relayNumber].relayType == 1 && outputDefine.feedbackInput[relayNumber] != 128){
feedbackSignalCheck.setTime[relayNumber] = currentMillis;
int inputNo = outputDefine.feedbackInput[relayNumber];
if(inputState[inputNo].current == LOW){
debugln("Impulse relay feedback check low");
feedbackSignalCheck.desiredState[relayNumber] = 0;
}else{
debugln("Impulse relay feedback check high");
feedbackSignalCheck.desiredState[relayNumber] = 1;
}
debug("Feedback-Input state: ");
debugln(inputState[inputNo].current);
debug("Desired feedback state: ");
debugln(feedbackSignalCheck.desiredState[relayNumber]);
}
//set current switch on time
relayTimers[relayNumber].startImpulseMilliSec = currentMillis;
//client.publish(outputDefine.outputStateTopic[relayNumber], "ON");
} else {
debug("RELAY: ");
debug(relayNumber);
debugln("OUTPUT STATE UNDEFINDED!");
}
}
// READ DIGITAL OUTPUT STATES (RELAYS) ----------------------------------------------------------------------------
void readoutputStates() {
if(DIGITAL_OUTPUTS != 0){
for (int i = 0; i < DIGITAL_OUTPUTS; i++) {
if (digitalRead(outputDefine.outputPins[i]) == 0) {
//client.publish(outputDefine.outputStateTopic[i], "ON");
} else {
//client.publish(outputDefine.outputStateTopic[i], "OFF");
}
}
}
}
// READ INPUTS AND SEND MQTT IF THE STATE HAS CHANGED -------------------------------------------------------------
void readInputs(bool force) {
//parameter force is used when websocket connects and request a HeartBeat from this ESP32 device
if(DIGITAL_INPUTS != 0){
/*
inputDefine[0].current = digitalRead(PinNumber1);
inputDefine[1].current = pcfIn.digitalRead(PinNumber2);
*/
// check if input state has been changed to the previos cycyle state
for (int i = 0; i < DIGITAL_INPUTS; i++) {
// get current state
inputState[i].current = digitalRead(inputDefine.inputPin[i]);
// compare old and current state
if (inputState[i].old != inputState[i].current || force) {
inputState[i].old = inputState[i].current;
// send mqtt signal on/off for input state
if (inputState[i].current == LOW) {
debug("Key: ");
debug(i);
debugln(" pressed");
//client.publish(inputDefine.inputStateTopic[i], "ON");
//set switch on time to current time
inputState[i].switchOnTimeMs = millis();
if(i==3){
if(digitalRead(outputDefine.outputPins[i]) == 0){
setOutputs(1, 0);
debugln("set ON relay 0");
}else{
setOutputs(0, 0);
debugln("set OFF relay 0");
}
}
else if(i==4){
if(digitalRead(outputDefine.outputPins[i]) == 0){
setOutputs(1, 1);
debugln("set ON relay 1");
}else{
setOutputs(0, 1);
debugln("set OFF relay 1");
}
}
} else {
debug("Key: ");
debug(i);
debugln(" not pressed");
//client.publish(inputDefine.inputStateTopic[i], "OFF");
//set swtich on time to 0
inputState[i].switchOnTimeMs = 0;
}
}
}
}
}
// ARDUINO SETUP --------------------------------------------------------------------------------------------------
void setup() {
Serial.begin(115200);
if (DEBUG) {
Serial.println("DEBUG MODE IS ON");
} else {
Serial.println("DEBUG MODE IS OFF");
}
Serial.println("setup in progress...");
// if onboard LED is activated
if (ONBOARD_LED != 0){
pinMode(ONBOARD_LED,OUTPUT);
}
// init inputs + outputs
setup_ios();
Serial.println("setup is done!");
}
// ----------------------------------------------------------------------------------------------------------------
// PROGRAM LOOP ---------------------------------------------------------------------------------------------------
void loop() {
// LOOP COUNTER
currentMillis = millis();
loops++;
if (currentMillis - lastMillis > 1000 && DEBUG_LOOPS) {
debug("Loops last second:");
debugln(loops);
lastMillis = currentMillis;
loops = 0;
}
// READ INUPTS CYCLIC
if (currentMillis - readInputsCalled >= READ_INPUTS_CYCLIC && DIGITAL_INPUTS != 0) {
readInputs(0);
readInputsCalled = currentMillis;
}
// CHECK FEEDBACK SIGNAL (only when relay type 1 (impulse relay) )
if (currentMillis - checkFeedbackSignal >= CHECK_FEEDBACK_SIGNAL && DIGITAL_INPUTS != 0 && DIGITAL_OUTPUTS != 0) {
for (int i = 0; i < DIGITAL_OUTPUTS; i++) {
if(relayTimers[i].relayType == 1){
if( feedbackSignalCheck.setTime[i] != 0 && (currentMillis - feedbackSignalCheck.setTime[i] >= feedbackSignalCheck.expectedFeedbackTimeMs[i]) ){
debug("CurrentMillis: ");
debugln(currentMillis);
debug("Set Time: ");
debugln(feedbackSignalCheck.setTime[i]);
debug("Expected Feedback Time: ");
debugln(feedbackSignalCheck.expectedFeedbackTimeMs[i]);
//call function to compare state, parameter 1=inputNo, 2=desiredState 3=relayNo 4=equal
compareFeedbackSignal(outputDefine.feedbackInput[i], feedbackSignalCheck.desiredState[i], i, feedbackSignalCheck.compareType[i]);
feedbackSignalCheck.setTime[i] = 0;
}
}
}
checkFeedbackSignal = currentMillis;
}
// RELAY IMPULSE MODE CYCLIC
if (currentMillis - setRelaysImpulse >= SET_RELAYS_IMPULSE_CYCLIC && DIGITAL_OUTPUTS != 0) {
for (int i = 0; i < DIGITAL_OUTPUTS; i++) {
//check if mode is impulse or relay type is pulse relay
if (relayTimers[i].setMode == 2 || relayTimers[i].relayType == 1) {
// If time matches then switch relay off
if ((currentMillis - relayTimers[i].startImpulseMilliSec >= relayTimers[i].impulseMilliSec) && relayTimers[i].startImpulseMilliSec > 0) {
debug("Result: ");
debugln(currentMillis - relayTimers[i].startImpulseMilliSec);
debug("currentMillisec: ");
debugln(currentMillis);
debug("impulseMilliSec: ");
debugln(relayTimers[i].impulseMilliSec);
debug("startImpulseMilliSec: ");
debugln(relayTimers[i].startImpulseMilliSec);
relayTimers[i].startImpulseMilliSec = 0;
// Switch off relay
setOutputs(0, i);
}
}
}
setRelaysImpulse = currentMillis;
}
// CHECK FOR AUTO OFF CYCLIC
if (currentMillis - checkAutoOffRelays >= CHECK_AUTO_OFF_CYCLIC && DIGITAL_INPUTS != 0 && DIGITAL_OUTPUTS != 0) {
for (int i = 0; i < DIGITAL_OUTPUTS; i++) {
//get index for the relay which is assigned to the input
int assignedInput = outputDefine.feedbackInput[i];
debug("Index: ");
debugln(assignedInput);
debug("Input ");
debug(i);
debug(" switch on Time: ");
debugln(inputState[assignedInput].switchOnTimeMs);
debug("Relay Type: ");
debugln(relayTimers[i].relayType);
debug("Relay Mode: ");
debugln(relayTimers[i].setMode);
debug("Auto Off Time Milliseconds: ");
debugln(relayTimers[i].autoOffMilliSec);
//check if a relay is assinged to an input, if 128 then it is not and if the max failure counter has not reached yet
if(assignedInput != 128 && feedbackSignalCheck.failureCount[i] < feedbackSignalCheck.failureCountMax[i]){
//check if this relay is set to 1 (impulse relay type), whether mode 3 (autoOff) is set and if the autoOff time bigger 0
//in case the feedback signal check reaches the max count, then stop trying to switch off, this in order to avoid endless switch off tries when the feedback signal does not work
if(relayTimers[i].relayType == 1 && relayTimers[i].setMode == 3 && relayTimers[i].autoOffMilliSec > 0 ){
//check if the duration since the input was set expires or equal the set autoOff time
if ( ( (relayTimers[i].autoOffMilliSec + inputState[assignedInput].switchOnTimeMs) <= currentMillis ) && inputState[assignedInput].switchOnTimeMs != 0) {
// Since the relay works as an impulse relay it must switch on first and off after the defined time it will switch off by the above condition "RELAY IMPULSE MODE CYCLIC"
debug("Relay ");
debug(i);
debug(" Switched Off by AutoOff after ");
debug(relayTimers[i].autoOffMilliSec);
debugln(" Milliseconds");
setOutputs(1, i);
}
}
}
}
checkAutoOffRelays = currentMillis;
}
// SET FIRST CYCLE DONE TO TRUE AND SEND FIRST HEARTBEAT
if (firstCycleDone == false) {
firstCycleDone = true;
//client.publish(mqttTopicHeartBeat, "online");
// read outputStateTopics
readoutputStates();
}
}