/*
Relay and Switch/Button MQTT Controller
Aaron Cake
http://www.aaroncake.net


Runs on Nano, Uno, ESP8266 etc.
Works with ENC28J60, W5100, W5500, ESP WiFi, etc. Any standard Arduino Ethernet library.

Requires PubSubClient found here: https://github.com/knolleary/pubsubclient
Requires MemoryFree found here: https://github.com/McNeight/MemoryFree/

Compile for Atmega1284 with MightyCore Bobduino pinout.


Ethernet: Pins D10, D11, D12, D13 used for Ethernet SPI
RDM6300 RFID Reader: Reader TX to Serial RX (pin D0)

Version 1: Intital version with basic functionality
Version 1.5: -change pin init to switch pins off right after pinMode to avoid sequential relay clicking
			-change digitalRead in switch state reading loop to only read pin at start of loop and assign to variable instead of multiple digitalReads
			-change switch/button/input state publish to publish state of input instead of just "t" for toggle
			-add uptime counter
			-add minute heartbeat signal published to /board/tele with uptime, IP, firmware version, free memory
Version 1.75: -pins A6 and A7 on Nano are analog input only! digitalread doesn't work. Create function bool ReadPinState(pin) to wrap
				digitalread/analogread and output boolean while handling A6 and A7
Version 1.80: -increase size of HeartBeatPublishString to 100 characters because previous 75 was causing overruns
				with long IP addresses leading to weird uptime values from overwritten memory
Version 1.85:	-move output pin init to first thing in setup() and write RELAY_OFF to pin before setting pinmode
				-this avoids active low relays from activating on boot
Version 1.90	-move strings to PROGMEM to save a crap load of memory
				-see http://www.gammon.com.au/progmem ...strcmp_P, strcpy_P, sprintf_p, PSTR
				-change JSON generation to sprintf_P (http://www.cplusplus.com/reference/cstdio/sprintf/ ) instead of all the concatinations
Version 2:
				-long/short press detection
				-rename "switch" variables to "input"
				-move more stuff to PROGMEM
				-clean up unneeded variables
Version 2.10:
				-add support for Atmega1284 board
				-add board type to telemetry
				-add board name selection via preprocessor DEFINEs
				-WS2812B LEDs on pin 6 if Atmega1284 board
				-Switch to ethernet.h library instead of ethernet2
Version 2.15:
				-add last will and testimate, online offline topics
					Info:
					boolean connect (clientID, willTopic, willQoS, willRetain, willMessage)

					Connects the client with a Will message specified.

					Parameters
					clientID : the client ID to use when connecting to the server.
					willTopic : the topic to be used by the will message (const char[])
					willQoS : the quality of service to be used by the will message (int : 0,1 or 2)
					willRetain : whether the will should be published with the retain flag (boolean)
					willMessage : the payload of the will message (const char[])
Version 2.5:
				-add OTA firmware update when on 1284
Version 2.75:
				-RFID reader support to Serial
Version 3.00:
				-add sensor support
				-add DHT sensor support
				-switch MemoryFree library for included function GetFreeMemory()
Version 3.25:
				-DHT SENSOR FAILURE DETECTION
					-use isnan() function
					-if NaN, send back out of range values for temp (99.9) and humidity (199.9)
					-change DHT routine to power DHT via spare I/O pin (optional via pre-compiler directive)
						-allow DHT to be rest if NaN
Version 3.50:
				-CODE CLEANUP and MQTT CONNECT BUG/ENDLESS LOOP FIX
				-enable watchdog via compiler directive...DONE
				-determine if reboot was from wathdog and add to telemetry...DONE
				-ethernet.maintain...DONE
				-rewrite MQTT connection logic becuase mqtt.connect() not reliable way to determine if connected. Now uses
				mqtt.isconnected() to determine state, loop until connection until MQTT_MAX_CONNECTION_ATTEMPTS then reboot
				if connection failed...DONE
				-rewrite build topic to be function using p_sprintf instead of all the strcats...DONE
				-publish states as retained...DONE
				-move RFID to sensors topic...DONE
				-fix LWT message...DONE
				

				
Too add:
			
			
			-move compiler directives on 1284 status LEDs to inside the function instead of everywhere
			-rewrite input scan as function which takes array of pins (in order to support non-MCU pins)
			-Add IR blaster support
			-Add MCP 23017 I/O module support
			-eventually-add web interface for status and on/off control
			-add periodic state publish (create a function to publish states)
			-pulse relay output by sending a mS value instead of just 1 or 0 to /r_x/ topic.
			-store pin states in EEPROM for super fast restore after reboot

HOW TO OTA UPDATE:
Must have ArduinoOTA library installed (https://github.com/JAndrassy/ArduinoOTA)
Must have most recent Optiboot or MightyCore after Jul 4 2021 (https://github.com/MCUdude/MightyCore) installed
The OTA crednetials are arduino : password

1.Place following content into file platform.local.txt in appropriate Arduino core directory.
Example: C:\Users\(user)AppData\Local\Arduino15\packages\MightyCore\hardware\avr\2.1.3\platform.local.txt

---FILE BEGIN---


# This configuration file supports the general ArduinoOTA library https://github.com/jandrassy/ArduinoOTA

## Create output (bin file)
recipe.objcopy.bin.pattern="{compiler.path}{compiler.elf2hex.cmd}" -O binary {compiler.elf2hex.extra_flags} "{build.path}/{build.project_name}.elf" "{build.path}/{build.project_name}.bin"

tools.avrdude.network_cmd={runtime.tools.arduinoOTA.path}/bin/arduinoOTA
tools.avrdude.upload.network_pattern="{network_cmd}" -address {serial.port} -port 65280 -username arduino -password password -sketch "{build.path}/{build.project_name}.bin" -upload /sketch -b

#IDE 2
tools.arduino_ota.cmd={runtime.tools.arduinoOTA.path}/bin/arduinoOTA
tools.arduino_ota.upload.field.password=Password
tools.arduino_ota.upload.field.password.secret=true
tools.arduino_ota.upload.pattern="{cmd}" -address {serial.port} -port 65280 -username arduino -password password -sketch "{build.path}/{build.project_name}.bin" -upload /sketch -b

## arduinoOTA as programmer. add entries with {ip} into programmers.txt
tools.arduinoOTA.cmd={runtime.tools.arduinoOTA.path}/bin/arduinoOTA
tools.arduinoOTA.program.params.verbose=
tools.arduinoOTA.program.params.quiet=
tools.arduinoOTA.program.pattern="{cmd}" -address {ip} -port 65280 -username arduino -password password -sketch "{build.path}/{build.project_name}.bin" -upload /sketch -b


---FILE END---

2.Add a "programmer" for (each) board with the IP address of the board into programmers.txt within appropriate Arduino core directory
Example: C:\Users\(user)\AppData\Local\Arduino15\packages\MightyCore\hardware\avr\2.1.3\programmers.txt
Name the programmer to match the IP. Remember to change the "arduinoOTAxxx." definiition
Add the lines to the end of the file:

---PROGRAMMER BEGIN---

arduinoOTA000.name=Arduino OTA (000.000.000.000)
arduinoOTA000.program.tool=arduinoOTA
arduinoOTA000.ip=000.000.000.000

---PROGRAMMER END---

3. Restart the Arduion IDE / Visual Studio after adding the lines if running

4. Program the firmware by selecting to use a hardware programmer, setting the programmer to the appropriate "Arduino OTA ()" port
	then building and uploading the firmware

*/



//*******************************CONFIGURATION DEFINES / VARIABLES*********************************************
//*************************************************************************************************************

// ******************
// SET BOARD LOCATION/USE BY UN/COMMENTING APPROPRIATE DEFINE STATEMENTS

//#define SHOP
#define BATHROOM
//#define TST1284BRD
//#define TSTBRD
//#define BEDRMBRD

//************ADD BOARD SPECIFIC CONFIGURATIONS HERE
#ifdef SHOP
#define DHT22_PIN 24                        //DHT22 on Atmega 1284 pin 24 (PC2)
#endif

#ifdef TST1284BRD
  #define DHT22_PIN 24                        //DHT22 on Atmega 1284 pin 24 (PC2)
  #define DHT22_POWER_PIN 25					//DHT22 powered via pin 25 (PC3)
  #define RFID_ENABLE
#endif



//enable RDM6300 RFID reader support on Serial RX
//#define RFID_ENABLE

//enable DHT22 Sensor by specifying the pin number
//#define DHT22_PIN 24															//DHT22 on Atmega 1284 pin 24 (PC2)
//enable DH22 Sensor Power Pin by specifying pin number and uncommenting line. DHT22 sensors can be unstable. DHT22_POWER_PIN
//	specifies that DHT22 canbe powered by processor pin. Processor will bring pin high during init. If DHT22 read fails (returns NaN)
//	then pin will be toggled to reset sensor.
//#define DHT22_POWER_PIN 25													

//firmware version
const byte FirmwareMajorVersion = 5;
const byte FirmwareMinorVersion = 50;										//3.50

//Enable the watchdog timer
#define WATCHDOG_ENABLE


//*************************************************
// Make sure to assign a unique MAC to each board!
//*************************************************

//uint8_t mac[6] = { 0x03, 0xAA, 0x03, 0xFE, 0x01, 0xEE };

//set a predefined MAC based on preprocessor directives for various boards
#ifdef SHOP																		//shop board
uint8_t mac[6] = { 0x06, 0x6A, 0xCB, 0x7B, 0x27, 0xE3 };
#endif
#ifdef BATHROOM																	//bathroom board
uint8_t mac[6] = { 0x02, 0xBB, 0xCA, 0xBB, 0x01, 0xEE };
#endif
#ifdef BEDRMBRD																	//bedroom board
uint8_t mac[6] = { 0x56, 0x33, 0xCA, 0x1B, 0x01, 0xFF };
#endif
#ifdef TST1284BRD
uint8_t mac[6] = { 0x02, 0xCA, 0xAA, 0xBB, 0x01, 0xEE };						//test 1284 board
#endif
#ifdef TSTBRD
uint8_t mac[6] = { 0x02, 0xCA, 0xAA, 0xBB, 0x22, 0xEE };						//test nano board
#endif

//*************************************************
// BOARD MQTT TOPIC SETTINGS
//*************************************************


// MQTT topic this board should use. Will be used as prefix for communication. ie. BoardTopic + "/outlet1/"
//set by preprocessor directives below
#ifdef SHOP																//shop board
const char BoardTopic[] PROGMEM = "shop";
char const* MQTTClientName = "shop";
#endif
#ifdef BATHROOM															//bathroom board
const char BoardTopic[] PROGMEM = "bthrm";
char const* MQTTClientName = "bthrm";
#endif
#ifdef BEDRMBRD															//bedroom board
const char BoardTopic[] PROGMEM = "bedrm";
char const* MQTTClientName = "bedrm";
#endif
#ifdef TST1284BRD
#pragma message "BOARD: TEST1284BRD" 
const char BoardTopic[] PROGMEM = "tst1284brd";							//test 1284 board
char const* MQTTClientName = "tst1284brd";
#endif
#ifdef TSTBRD
#pragma message "BOARD: TSTBRD" 
const char BoardTopic[] PROGMEM = "tstbrd";							//nano based test board
char const* MQTTClientName = "tstbrd";
#endif



//****************************************************************************** END CONFIGURATION******************
//******************************************************************************************************************

//set BOARD_TYPE preprocessor directive based on CPU type (Arduino Nano, 1284 board, etc.)

//Atmega168 and Atmega328 (Arduino Nano, Uno)
#if defined(__AVR_ATmega168__) ||defined(__AVR_ATmega168P__) ||defined(__AVR_ATmega328P__)
#define BOARD_TYPE 168
#pragma message "CPU: 168/328" 

// Atmega1284 (My custom board)
#elif defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) 
#define BOARD_TYPE 1284 
#pragma message "CPU: 1284" 
#endif


//#include <MemoryFree.h>										//library with free memory function
//If Atmega1284 board, include library for watchdog
#ifdef WATCHDOG_ENABLE
#pragma message "Including avr/wdt.h"
	#include <avr/wdt.h>									   //watch dog timer
#endif

#include <PubSubClient.h>									  //MQTT client
#include <SPI.h>											//SPI library for Ethernet, etc. SPI bus

//Ethernet library for ENC28J60
//#include <UIPEthernet.h>

//library for W5500
//#include <Ethernet2.h>

//library for W5100/W5200/W5500
#include <Ethernet.h>


//if it is Atmega1284 board then include NeoPixel library (FASTLed has issues compiling for 1284) and set up to control WS2812B LEDs on pin 6
#if BOARD_TYPE == 1284
	#pragma message "Including Adafruit_NeoPixel.h" 
	#include <Adafruit_NeoPixel.h>
	#define WS2812BPIN 6															// WS2812B LEDs on pin 6
	#define WS2812BNUM 2															// 2 LEDs
#endif
//if it is Atmega1284 board then ArduinoOTA for OTA firmware updates
#if BOARD_TYPE == 1284
	#pragma message "Including ArduinoOTA.h" 
	#include <ArduinoOTA.h>
#endif

//if DHT22 is defined and thus enabled, include the library
#ifdef DHT22_PIN
	#pragma message "DHT sensor enabled. Including DHT.h"
	#include <DHT.h>
	#define DHTTYPE DHT22								// DHT 22  (AM2302), AM2321
#endif


//If any sensors have been enabled, enable the whole sensor framework. Otherwise don't waste memory


#ifdef DHT22_PIN
#define ENABLE_SENSORS
#endif


//function prototype for MQTT callback
void MQTTCallback(char* topic, byte* payload, unsigned int length);

// define these because some relay boards are active LOW, some active HIGH
#define RELAY_ON LOW 
#define RELAY_OFF HIGH

const int NumOfInputs = 7;											 //number of switches above. Starts at zero (0)
const int NumOfRelays = 7;												//number of relays above. Starts at zero (0)

//if compiling on Atmega1284
#if BOARD_TYPE == 1284
const byte InputPins[] = { 4,5,28,29,30,8,9,31 };						//my 1284 W5100 board
const byte RelayPins[] = { A0,A1,A2,A3,A4,A5,A6,A7 };					//my 1284 W5100 board
#endif
//if compiling on Atmega168/Atmega328
#if BOARD_TYPE == 168
const byte InputPins[] = { A0,A1,A2,A3,A4,A5,A6,A7 };					 //use analog inputs as switch/button inputs. Pins A0 - A7
const byte RelayPins[] = { 2,3,4,5,6,7,8,9 };							//use digital pins as relay outputs. Pins 0 - 9
#endif

byte LastInputState[] = { LOW,LOW,LOW,LOW,LOW,LOW,LOW,LOW };			//array of bytes to hold last switch state, init to zero
char SwitchStatePaylod[2];												//need a char to hold switch state payload for MQTT publish

//variables and constants for switch check timer
unsigned long PreviousMillis = 0;										 //the last time the switch check ran
const int InputCheckInterval = 100;									  //the interval to check switches, milliseconds
//const int SwitchIgnoreTime = 1500;										  //time period after a switch/button is toggled to ignore another toggle so buttons won't double toggle
//unsigned long SwitchPreviousToggleMillis[] = { 0,0,0,0,0,0,0,0 };		//array for last time switches were toggled. Num of elements must match NumOfSwitches
int InputHoldCounts[] = { 0,0,0,0,0,0,0,0 };							//array to keep track of number of SwitCheckInterval(s) switch is held for. 0 indicates first time through
int InputHoldTime = 0;													//integer to store number of mS switch held
const int MaxInputHoldTime = 4000;										//maximum amount of time switch is allowed to be held before it is considered a switch and hold ignored

//MQTT related variables
const char MQTTServer[] = "192.168.107.11";								//MQTT server
//const char MQTTServer[] = "1.1.1.13";
byte MQTTConnectionAttempts = 1;										//variable to track number of MQTT connection attempts. Start at 1
#define MQTT_MAX_CONNECTION_ATTEMPTS 10									//max amount of MQTT connections that will be attempted before giving up
																		
const char RelayTopicTemplate[] PROGMEM = "/%S/r%d/";					//sprintf_P template for MQTT relay topic
const char RelayStateTopicTemplate[] PROGMEM = "/%S/r%d_s/";			//sprintf_P template for MQTT relay state topic
const char TelemetryTopicTemplate[] PROGMEM = "/%S/tele/";				//sprintf_P template for MQTT telemetry topic
const char SwitchTopicTemplate[] PROGMEM = "/%S/s%d/";					//sprintf_P template for MQTT switch topic
const char SwitchHeldTopicTemplate[] PROGMEM = "/%S/s%d/held/";			//sprintf_P template for MQTT switch held topic
const char LWTTopicTemplate[] PROGMEM = "/%S/lwt/";						//sprintf_P template for MQTT last will and testiment topic

const char LWTMessageOnline[] = "online";					//LWT message to publish when board connects to broker and goes online
const char LWTMessageOffline[] = "offline";							//LWT message broker will publish when board connects to broker and goes offline

//variables for building topic string for subscription and comparison
char CountString[2];
char BuiltTopic[32];
char SwitchHeldTimeString[6];								//have to convert the integer SwitchHeldTime to string in order to publish

//uptime counter and heartbeat variables
int UptimeSeconds = 0;										//uptime counter seconds, 0-60
int UptimeMinutes = 0;										//uptime counter minutes, 0-60
int UptimeHours = 0;										//uptime counter hours, 0 - 24
int UptimeDays = 0;											//uptime counter days, 0 - maxint...32,000 day uptime? Unlikely
int WDTReset = 0;											//track whether reset by watchdog timer. Default 0 = no
unsigned long UptimePreviousMillis = 0;						//the last time the uptime counter updated
bool SendHeartbeat = false;									//flag to send the heartbeat. Set true in uptime loop to send a heartbeat

//if RFID is enabled, set up the variables
#ifdef RFID_ENABLE

#define ENABLE_SENSORS

#define RDM6300_PACKET_SIZE		14
#define RDM6300_PACKET_BEGIN	0x02
#define RDM6300_PACKET_END		0x03
#define RDM6300_NEXT_READ_MS	220
#define RDM6300_READ_TIMEOUT	20

uint32_t RDM3600_tag_id = 0;
uint32_t RDM3600_last_tag_id = 0;
uint32_t RDM3600_last_read_ms = 0;

const char RFIDJSON[] PROGMEM = "{\"RDM6300\":\"%s\"}";								//the JSON template to use with sprintf to generate the RFID JSON

char RFIDTagID[14];														//RFID tag from reader, as cstring for MQTT publish

#endif

//if sensors are enabled, define the necessary variables
#ifdef ENABLE_SENSORS
//const char SwitchHeldTopicTemplate[] PROGMEM = "/%S/s%d/held/";
const char SensorTopicTemplate[] PROGMEM = "/%S/sensor/";
unsigned long SensorPreviousMillis = 0;								//the last time the sensors were read
const long SensorInterval = 60000;									//interval at which the sensors are read in mS
#endif

//if DHT22 is enabled then define the varialbles needed
#ifdef DHT22_PIN
float DHT22TempRead;												//float raw sensor T and H read variables
float DHT22HumidRead;
#define DHT22H_FAILURE_VALUE 199.9												//value to send if DHT22 humidity read fails
#define DHT22T_FAILURE_VALUE 99.9												//value to send if DHT22 temp read fails
char DHT22TemperatureString[6];													//temp and humidity variables. Need to be char because will be converted to string for sprintf use
char DHT22HumidityString[6];													//6 characters for xxx.x + null
#ifdef DHT22_POWER_PIN					//if DHT22 power pin is defined, delcare variables to allow reset of DHT22
	unsigned long DHT22ResetPreviousMillis = 0;										//last time DHT22 reset procedure was run
	const int DHT22ResetInterval = 2000;											//how often DHT22 reset procedure should run if needed
	boolean DHT22ResetNeeded = false;												//true if DHT22 reset is needed
#endif
const char DHT22JSON[] PROGMEM = "{\"DHT22Temp\":\"%s\",\"DHT22Humidity\":\"%s\"}";								//the JSON template to use with sprintf to generate the DHT22 JSON
#endif


//char IP[16];
char HeartBeatPublishString[115];									//char array to hold heartbeat for MQTT publish, in JSON


//Function taken from https://learn.adafruit.com/memories-of-an-arduino/measuring-free-memory
//MemoryFree.h library stopped working, complaining freeMemory() was not declared in scope
//This does exactly the same thing without the need for the external library
extern char* __brkval;

int GetFreeMemory() {
	char top;
#if defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
	return &top - __brkval;
#else  // __arm__
	return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif  // __arm__
}







//Initialize ethernet client as ethClient
EthernetClient ethClient;

//initialize PubSubClient as "MQTTClient"
PubSubClient MQTTClient(MQTTServer, 1883, MQTTCallback, ethClient);



//if it is Atmega1284 board then set up NeoPixel library, add some colours, define a function to show status LEDs
#if BOARD_TYPE == 1284
	#pragma message "INIT NeoPixel" 
	Adafruit_NeoPixel StatusLEDs(WS2812BNUM, WS2812BPIN, NEO_GRB + NEO_KHZ800);					//init create NeoPixel class as StatusLEDs
	uint32_t StatusColourOrange = StatusLEDs.Color(14, 7, 0);								//create a colour, a nice orange
	uint32_t StatusColourGreen = StatusLEDs.Color(0, 10, 0);									//green
	uint32_t StatusColourRed = StatusLEDs.Color(10, 0, 0);										//red
	uint32_t StatusColourBlue = StatusLEDs.Color(0, 0, 10);										//blue

	//function to update status LED colour....LEDNum = 0 - 1 for two LEDs, LEDColour is 32 bit integer of colour
	void ShowStatusLED(byte LedNum, uint32_t LEDColour) {	
		StatusLEDs.setPixelColor(LedNum, LEDColour);
		StatusLEDs.show();
	}
#endif


//If DHT sensor is enabled, create the object
#ifdef DHT22_PIN
	DHT DHT22Sensor(DHT22_PIN, DHTTYPE);								//initialize DHT object

#endif


//if RFID is enabled, then include the functions
#ifdef RFID_ENABLE
	/* **************************************************RFID READER FUNCTIONS
	RFID processing code taken from RDM6300 libary by:
	Arad Eizen (https://github.com/arduino12)
	Rewritten to force use of hardware Serial instead of generic stream and/or SoftwareSerial
	*/

	//determine if time to allow next tag read
	bool RDM6300IsTagNear(void)
	{
		return millis() - RDM3600_last_read_ms < RDM6300_NEXT_READ_MS;
	}



	//function to check for serial stream from RFID tag reader, process stream and return tag ID
	bool RDM6300Update(void)
	{
		char buff[RDM6300_PACKET_SIZE];
		uint32_t tag_id;
		uint8_t checksum;

		if (!Serial.available()) {																	//if nothing available at serial port, exit function returning FALSE
			return false;
		}

		/* if a packet doesn't begin with the right byte, remove that byte */
		if (Serial.peek() != RDM6300_PACKET_BEGIN && Serial.read()) {
			return false;
		}

		/* if read a packet with the wrong size, drop it */
		if (RDM6300_PACKET_SIZE != Serial.readBytes(buff, RDM6300_PACKET_SIZE)) {
			return false;
		}

		/* if a packet doesn't end with the right byte, drop it */
		if (buff[13] != RDM6300_PACKET_END) {
			return false;
		}

		/* add null and parse checksum */
		buff[13] = 0;
		checksum = strtol(buff + 11, NULL, 16);
		/* add null and parse tag_id */
		buff[11] = 0;
		tag_id = strtol(buff + 3, NULL, 16);
		/* add null and parse version (needs to be xored with checksum) */
		buff[3] = 0;
		checksum ^= strtol(buff + 1, NULL, 16);

		/* xore the tag_id and validate checksum */
		for (uint8_t i = 0; i < 32; i += 8)
			checksum ^= ((tag_id >> i) & 0xFF);
		if (checksum)
			return false;

		/* if a new tag appears- return it */
		if (RDM3600_last_tag_id != tag_id) {
			RDM3600_last_tag_id = tag_id;
			RDM3600_last_read_ms = 0;
		}
		/* if the old tag is still here set tag_id to zero */
		if (RDM6300IsTagNear())
			tag_id = 0;
		RDM3600_last_read_ms = millis();

		RDM3600_tag_id = tag_id;
		return tag_id;
	}

	//get the RFID tag ID, then blank out the TagID to prepare for next read
	uint32_t RDM6300GetTagID(void)
	{
		uint32_t tag_id = RDM3600_tag_id;
		RDM3600_tag_id = 0;
		return tag_id;
	}

#endif





//function to generate heartbeat ping on topic /boardname/tele/
void Heartbeat() {

	//ArduinoJSON exists as a library to generate and serialise JSON. Not doing anything complicated so can do it in less memory manually
	//Bunch of sprintfs/strcats to build the heartbeat string.



															//create an uptime in ISO 8601 format:

															/*P is the duration designator(for period) placed at the start of the duration representation.
															Y is the year designator that follows the value for the number of years.
															M is the month designator that follows the value for the number of months.
															W is the week designator that follows the value for the number of weeks.
															D is the day designator that follows the value for the number of days.
															T is the time designator that precedes the time components of the representation.
															H is the hour designator that follows the value for the number of hours.
															M is the minute designator that follows the value for the number of minutes.
															S is the second designator that follows the value for the number of seconds.


															For example, "P3Y6M4DT12H30M5S" represents a duration
															of "three years, six months, four days, twelve hours, thirty minutes, and five seconds".*/




															//generate JSON string with sprintf_p . Format string stored in PROGMEM
	sprintf_P(HeartBeatPublishString, PSTR("{\"ip\":\"%d.%d.%d.%d\",\"uptime\":\"P%dDT%dH%dM%dS\",\"ver\":\"%d.%d\",\"freemem\":\"%d\",\"cpu\":\"%d\",\"wdtreset\":\"%d\"}"),
		Ethernet.localIP()[0], Ethernet.localIP()[1], Ethernet.localIP()[2], Ethernet.localIP()[3],
		UptimeDays, UptimeHours, UptimeMinutes, UptimeSeconds,
		(int)FirmwareMajorVersion, (int)FirmwareMinorVersion,
		(int)GetFreeMemory(), (int)BOARD_TYPE, (int)WDTReset);

	
	

	Serial.print(F("HEARTBEAT: "));					//print it out to the serial for debugging
	Serial.println(HeartBeatPublishString);


	#if BOARD_TYPE == 1284
		ShowStatusLED(1, StatusColourBlue);					//set 2nd status LED to blue as MQTT being published
	#endif



	//build the MQTT topic to publish
	sprintf_P(BuiltTopic, TelemetryTopicTemplate, BoardTopic);				//copy the telemetry topic string from progmem to BuiltTopic
	//strcpy_P(BuiltTopic, TelemetryTopicTemplate);
	
	Serial.print(F("MQTT: Heartbeat publish: "));
	Serial.println(BuiltTopic);
	MQTTClient.publish(BuiltTopic, HeartBeatPublishString);

	#if BOARD_TYPE == 1284
		ShowStatusLED(1, StatusColourGreen);					//set 2nd status LED back to green
	#endif

}

//function to manage the uptime
void UptimeCounter() {
	UptimeSeconds++;									//increase uptime seconds by 1
	if (UptimeSeconds >= 60) {							//if seconds are greater than 60 that's a minute
		UptimeMinutes++;								//increase minutes by 1
		UptimeSeconds = 0;								//reset seconds to zero
		SendHeartbeat = true;							//set the flag to send the heartbeat publish
	}

	if (UptimeMinutes >= 60) {							//if minutes > 60 that's an hour
		UptimeHours++;									//increase hour count
		UptimeMinutes = 0;								//reset minutes to zero

	}

	if (UptimeHours >= 24) {
		UptimeDays++;
		UptimeHours = 0;
	}

	if (SendHeartbeat == true) {						//if the heartbeat flag is set (on minute changeover), send heartbeat
		Heartbeat();
		SendHeartbeat = false;							//set the flag false to it doesn't run until next minute
	}

	//Serial.print(F("Uptime (DD:HH:MM:SS): "));				//print out uptime every time function called
	//Serial.print(UptimeDays);
	//Serial.print(F(":"));
	//Serial.print(UptimeHours);
	//Serial.print(F(":"));
	//Serial.print(UptimeMinutes);
	//Serial.print(F(":"));
	//Serial.println(UptimeSeconds);

}



//WATCHDOG related functions. Only include function contents if WATCHDOG_ENABLED is defined. Otherwise functions contain nothing
// and will have no effect.

//function to use the watch dog timer to reboot the processor immediately
void Reboot() {
	#ifdef WATCHDOG_ENABLE
		wdt_disable();  //disable any existing watchdogs
		wdt_enable(WDTO_15MS); //enable with a 15MS timeout
		while (true) {					//loop forever so watchdog will reset processor
			delay(1);
		};   
	#endif
}


//function to reset the watchdog timer. Call often
void PetTheDog() {
	#ifdef WATCHDOG_ENABLE
		wdt_reset();										//reset the watchdog timer. 
	#endif
}



//on Nano, A6 and A7 are analog input only. digitalRead doesn't work. So this wrapper function is used to read the input pins
//and outputs a HIGH or LOW while handling the analog pins 
int ReadPinState(int PinToRead) {
	if (PinToRead == A6 || PinToRead == A7) {			//if we are reading from the special analog pins A6 and A7 (dedicated ADC/DAC)
		//Serial.println(analogRead(PinToRead));
		if (analogRead(PinToRead) < 500) {				//pin high reads about 1023, pin low reads about 10
			return LOW;									//so just return the correct  value based on analog read
		}
		else {
			return HIGH;
		}

	}
	else {
		return digitalRead(PinToRead);					//otherwise just digitalRead and pass the value to the function
	}
}

//function to connect to MQTT broker, subscribe to topics, maintain connection
void ConnectToMQTTBroker() {
	PetTheDog();										//pet the watchdog to reset the timer

	byte MQTTConnectResult = 0;							//variable to hold the result of MQTT.connect

	#if BOARD_TYPE == 1284
		if (MQTTClient.connected() == false) {
			ShowStatusLED(1, StatusColourOrange);
		}
	#endif



	while (MQTTClient.connected() == false) {
		
		PetTheDog();
	
	//build the LWT topic
	//strcpy_P(BuiltTopic, BoardTopic);
	//strcat_P(BuiltTopic, LWTTopic);								//add the LWT topic to board topic...ie. /board/lwt/


	Serial.print(F("MQTT: "));
	Serial.print(MQTTServer);
	Serial.print(F(": Attempt Connection ("));
	Serial.print(MQTTConnectionAttempts);						//print out the number of connection attempts
	Serial.println(F(")..."));

	//MQTTClient.connect((char*)MQTTClientName), BuiltTopic, 1, true, LWTMessageOffline;
	MQTTConnectionAttempts++;										//increment the connection attmpt
	
	sprintf_P(BuiltTopic, LWTTopicTemplate, BoardTopic);			//build the last will and testimate topic
	
	MQTTConnectResult = MQTTClient.connect((char*)MQTTClientName,BuiltTopic,1,1,LWTMessageOffline);

	//Serial.print(F("MQTTConnectResult: "));
	//Serial.println(MQTTConnectResult);

	if (MQTTConnectResult == 0) {								//the connection was UNSUCCESSFUL
#if BOARD_TYPE == 1284
		ShowStatusLED(1, StatusColourRed);						//set 2nd status LED to red for error
#endif
		Serial.print(F("MQTT: Connect: FAILED: "));
		Serial.print(MQTTConnectResult);
		Serial.print(F(", Code: "));
		Serial.println(MQTTClient.state());

		if (MQTTConnectionAttempts > MQTT_MAX_CONNECTION_ATTEMPTS) {			//if MQTT_MAX_CONNECTION_ATTEMPTS has been reached, reboot
			Serial.print(F("MQTT: Connect: Max attempts ("));
			Serial.print(MQTT_MAX_CONNECTION_ATTEMPTS);
			Serial.println(F(") reached. Rebooting."));
			Reboot();
		}


		Serial.println(F("MQTT: Connect: Retrying in 3 seconds..."));
		PetTheDog();												//pet the watchdog because about to wait 3 seconds
		delay(3000);												//evil delay for 3 seconds
	}

	if (MQTTConnectResult == 1) {									//if the connection was successful, can proceed
		MQTTConnectionAttempts = 1;									//reset the connection attempt count
		Serial.print(F("MQTT: Connected: "));						//print out the statius with connection result
		Serial.println(MQTTConnectResult);
		
		sprintf_P(BuiltTopic, LWTTopicTemplate, BoardTopic);			//build the last will and testimate topic
		MQTTClient.publish(BuiltTopic, LWTMessageOnline);				//publish the last will and testimate "online"
		Serial.print(F("MQTT: Publish: "));
		Serial.print(BuiltTopic);
		Serial.print(F(": "));
		Serial.println(LWTMessageOnline);


#if BOARD_TYPE == 1284
		ShowStatusLED(1, StatusColourGreen);					//set 2nd status LED to green
#endif

		//loop through and subscribe to relays topics
		for (int i = 0; i <= NumOfRelays; i++) {
			
			sprintf_P(BuiltTopic,RelayTopicTemplate,BoardTopic,i);					//use the RelayTopictemplate progmem variable to build topic...ie /test1284brd/r(i)/

#if BOARD_TYPE == 1284
			ShowStatusLED(1, StatusColourBlue);							//set 2nd status LED to blue for MQTT
#endif
			MQTTClient.subscribe(BuiltTopic);								 //subscribe to the topic.
			Serial.print(F("MQTT: Subscribe: "));
			Serial.println(BuiltTopic);
#if BOARD_TYPE == 1284
			ShowStatusLED(1, StatusColourGreen);						//set 2nd status LED to blue for MQTT
#endif

			PetTheDog();													//pet the watchdog to reset the timer
		}

		
		
	}
}


	
}





//*************************** SETUP **************************************************************************************
//************************************************************************************************************************


void setup() {
	//if the watchdog is enabled, enable the watchdog timer for 8 seconds	
	#ifdef WATCHDOG_ENABLE
		wdt_enable(WDTO_8S);								//enable the watchdog timer for 8 seconds
	#endif
	
	PetTheDog();										//pet the watchdog to reset the timer

	//if Atmega1284 board, turn off the LEDs
	#if BOARD_TYPE == 1284
		StatusLEDs.begin();
		StatusLEDs.clear();											//turn the WS2812B LEDs off
	#endif

	//initialize all the pins used
	
	//loop through and set all the rely outputs as outputs, then turn them off
	for (int i = 0; i <= NumOfRelays; i++) {
		digitalWrite(RelayPins[i], RELAY_OFF);				//some relays are active low so before any pin setting, write low to the pin to make sure they don't activate
		pinMode(RelayPins[i], OUTPUT);						//set the pin modes for relay pins to output
		digitalWrite(RelayPins[i], RELAY_OFF);				//turn them all off
		
		PetTheDog();										//pet the watchdog to reset the timer
	}


	//open the serial port for debugging
	#if BOARD_TYPE == 1284
		ShowStatusLED(0, StatusColourGreen);					//momentarily blink 1st status LED green
	#endif
	Serial.begin(9600);
	delay(100);
	
	#if BOARD_TYPE == 1284
		ShowStatusLED(0, StatusColourOrange);					//set 1st status LED to orange
	#endif

	Serial.println(F("BOOT..."));
	Serial.print(F("Ver: "));
	Serial.print(FirmwareMajorVersion);
	Serial.print(F("."));
	Serial.println(FirmwareMinorVersion);
	
	// if MCUSR register bit 3 (WDRF) contains 1, system was reset by watchdog timer
	if (MCUSR & (1 << WDRF)) {
		Serial.println(F("WATCHDOG RESET"));
		WDTReset = 1;											//set watchdog reset flag
	}



	// loop through and set all the switch/button inputs as inputs, then read each one current status and fill the array
	for (int i = 0; i <= NumOfInputs; i++) {
		pinMode(InputPins[i], INPUT);
		Serial.print(F("PIN: Set input: "));
		Serial.println(InputPins[i]);
		LastInputState[i] = ReadPinState(InputPins[i]);   //now read current pin state and update element in LastSwitchState. Because it could have rebooted
		Serial.print(F("PIN: Read input: "));
		Serial.print(InputPins[i]);
		Serial.print(F(", State: "));
		Serial.println(LastInputState[i]);
		PetTheDog();										//pet the watchdog to reset the timer
	}
	

	
	// start the Ethernet and obtain an address via DHCP
	Serial.println(F("ETHERNET: Begin..."));
	PetTheDog();														//pet the watchdog
	if (Ethernet.begin(mac) == 0) {									//if Ethernet didn't obtain a link
		Serial.println(F("ETHERNET: DHCP: FAIL. No DHCP."));				//print a failure to serial
		#if BOARD_TYPE == 1284
				ShowStatusLED(0, StatusColourRed);					//set first status LED to red
		#endif
		
		delay(5000);												//no DHCP? reboot
		Reboot();
	}
	else {
		Serial.print(F("ETHERNET: DHCP: Success. IP Addresss: "));				//if Ethernet succeeded
		#if BOARD_TYPE == 1284
				ShowStatusLED(0, StatusColourGreen);						//and if 1284 board, set the 1st status LED to green indicating good network
		#endif
		Serial.println(Ethernet.localIP());									//print out the IP to serial

	}

	//if board is 1284, then start ArduinoOTA 
	#if BOARD_TYPE == 1284
	// start the OTEthernet library with internal (flash) based storage, username and password set to "ota"
	Serial.println(F("OTA: Begin"));
		ArduinoOTA.begin(Ethernet.localIP(), "Arduino" , "password", InternalStorage);
	#endif


	//if RFID is enabled, initialize the RFID library to listen
	#ifdef RFID_ENABLE
		Serial.println(F("RFID: Listening on Serial..."));
	#endif

	#ifdef ENABLE_SENSORS
		Serial.println(F("SENSOR: Sensors Enabled"));
	#endif // ENABLE_SENSORS

	
	//if DHT sensor is enabled then initialize the libary
	#ifdef DHT22_PIN
		#ifdef DHT22_POWER_PIN											//if DHT22 power pin is enabled, then need to toggle pin high to power sensor
			Serial.print(F("DHT22: Power Pin Option Enabled. Pin: "));
			Serial.println(DHT22_POWER_PIN);
			Serial.println(F("DHT22: Set Power Pin HIGH"));
			
			pinMode(DHT22_POWER_PIN, OUTPUT);								//set the pin to an output
			digitalWrite(DHT22_POWER_PIN, HIGH);							//and set it high
			delay(1000);													//wait one second (not sure if have to)
		#endif
		Serial.println(F("DHT22: DHT22 Sensor: Begin..."));
		DHT22Sensor.begin();											//initialize the DHT sensor object
		Serial.print(F("DHT22: Temp: "));
		Serial.println(DHT22Sensor.readTemperature());
		Serial.print(F("DHT22: Humidity: "));
		Serial.println(DHT22Sensor.readHumidity());
		Serial.println(F("DHT22: DHT22 Sensor: Complete"));
	#endif


	//attempt to connect to MQTT broker
	ConnectToMQTTBroker();

	//wait a bit before starting the main loop
	PetTheDog();										//pet the watchdog to reset the timer
	//delay(2000);
}



//*************************** LOOP ***************************************************************************************
//************************************************************************************************************************



void loop() {
	PetTheDog();										//pet the watchdog to reset the timer
	unsigned long CurrentMillis = millis();				   //get the current time count
	byte SwitchStateRead = 0;							//hold the switch state read in the loop for processing, avoids multiple digitalreads

	//maintain the Ethernet connection (DHCP lease). Serial print any errors or status
	switch (Ethernet.maintain()) {
	case 1:
		Serial.println(F("ETHERNET: DHCP: Renew failed."));				//DHCP rewnew failed
		break;

	case 2:
		Serial.print(F("ETHERNET: DHCP: Renew success: "));			//DHCP renewal successful
		Serial.println(Ethernet.localIP());
		break;
	case 3:
		Serial.println(F("ETHERNET: DHCP: Rebind failed."));				//DHCP rebind failed
		break;

	case 4:
		Serial.print(F("ETHERNET: DHCP: Rebind success: "));				//DHCP rebind sucessful
		Serial.println(Ethernet.localIP());
		break;

	default:
		//nothing happened
		break;
	}




	//if board is 1284, then check for ArduinoOTA events
	#if BOARD_TYPE == 1284
		ArduinoOTA.poll();
	#endif


	//reconnect if connection is lost
	if (!MQTTClient.connected()) { 
		ConnectToMQTTBroker();
	}

	PetTheDog();										//watchdog timer reset

	//maintain MQTT connection
	MQTTClient.loop();

	if (CurrentMillis - UptimePreviousMillis >= 1000) {			//if 1 second (1000ms) has passed since last run, increase the uptime
		UptimePreviousMillis = CurrentMillis;					//update  the time uptime was last updated
		UptimeCounter();										//run the uptime function
	}

	//If sensors are enabled then check the sensors at SensorInverval
	#ifdef ENABLE_SENSORS

	//If the DHT22 sensor needs to be reset, do that before reading sensors. Can only do if DHT22_POWER_PIN defined
	#ifdef DHT22_POWER_PIN
	if (DHT22ResetNeeded == true) {													//if a DHT22 reset is needed
		if (digitalRead(DHT22_POWER_PIN) == HIGH) {									//if the pin is high, then set to low and take a time reading
			digitalWrite(DHT22_POWER_PIN, LOW);										//set the pin to LOW
			DHT22ResetPreviousMillis = CurrentMillis;								//set the time the sensor was turned off
			Serial.print(F("SENSOR: DHT22: FAIL: Turned sensor OFF pin: "));		//serial print the debug info
			Serial.print(DHT22_POWER_PIN);
			Serial.print(F(", timestamp: "));
			Serial.println(DHT22ResetPreviousMillis);
		}
		if (CurrentMillis - DHT22ResetPreviousMillis >= DHT22ResetInterval) {		//time to power back on the DHT22
			digitalWrite(DHT22_POWER_PIN, HIGH);									//set the pin to power DHT22 high
			Serial.print(F("SENSOR: DHT22: FAIL: Turned sensor ON pin: "));			//serial print the debug info
			Serial.print(DHT22_POWER_PIN);
			Serial.print(F(", timestamp: "));
			Serial.println(CurrentMillis);
			DHT22ResetNeeded = false;												//set the reset flag to false becuase reset is complete
		}
	}
	#endif


	if (CurrentMillis - SensorPreviousMillis >= SensorInterval) {
		SensorPreviousMillis = CurrentMillis;

		sprintf_P(BuiltTopic, SensorTopicTemplate, BoardTopic);					//build the sensor topic ie. /board/sensor/

		
		#ifdef DHT22_PIN													//if the DHT22 is enabled
			DHT22HumidRead = DHT22Sensor.readHumidity();						//read the temp and humidity from DHT22
			DHT22TempRead = DHT22Sensor.readTemperature();

			//DHT sensors suck, so now must check to make sure DHT read was successful and fix it if not

			if (isnan(DHT22HumidRead) == true) {															//check if humidity is NaN
				Serial.print(F("SENSOR: DHT22: FAIL: Humidity Read. Assigning fallback: "));				//send failure message to serial
				Serial.println(DHT22H_FAILURE_VALUE);
				DHT22HumidRead = DHT22H_FAILURE_VALUE;														//assign a fallback value to sensor
				#ifdef DHT22_POWER_PIN																		//if DHT22_POWER_PIN is defined, then can reset the sensor automatically
					DHT22ResetNeeded = true;																	//set the flag that DHT22 reset is needed
					Serial.println(F("SENSOR: DHT22: Set reset flag true"));
				#endif
			}

			if (isnan(DHT22TempRead) == true) {																//check if temp is NaN
				Serial.print(F("SENSOR: DHT22: FAIL: Temp Read. Assigning fallback: "));					//send failure message to serial
				Serial.println(DHT22T_FAILURE_VALUE);
				DHT22TempRead = DHT22T_FAILURE_VALUE;														//assign a fallback value to sensor
				#ifdef DHT22_POWER_PIN																		//if DHT22_POWER_PIN is defined, then can reset the sensor automatically
					DHT22ResetNeeded = true;																	//set the flag that DHT22 reset is needed
					Serial.println(F("SENSOR: DHT22: Set reset flag true"));
				#endif
			}

			dtostrf(DHT22HumidRead, 5, 1, DHT22HumidityString);			//read the DHT22 humidity and convert to a cstring with one decimal place for sprintf use
			dtostrf(DHT22TempRead, 5, 1, DHT22TemperatureString);		//read the DHT22 temp and convert to a cstring with one decimal place for sprintf use
		
			sprintf_P(HeartBeatPublishString, DHT22JSON, DHT22TemperatureString, DHT22HumidityString);			//add the readings to the JSON template
		

			#if BOARD_TYPE == 1284
				ShowStatusLED(1, StatusColourBlue);					//set 2nd status LED to blue as MQTT being published
			#endif

			Serial.print(F("MQTT: Sensor publish: "));
			Serial.println(BuiltTopic);

			MQTTClient.publish(BuiltTopic, HeartBeatPublishString);				//publish the sensor data

			Serial.print(F("SENSOR: DHT22: "));									//and print debug infor to serial port
			Serial.println(HeartBeatPublishString);

			#if BOARD_TYPE == 1284
				ShowStatusLED(1, StatusColourGreen);					//set 2nd status LED back to green
			#endif

		#endif
	}
	#endif

	//if the difference between the current time and last time is bigger than the interval, time to check the switches
	if (CurrentMillis - PreviousMillis >= InputCheckInterval) {
		PreviousMillis = CurrentMillis;									 //update the time the check last ran
		//Serial.print(F("Check switches: "));
		for (int i = 0; i <= NumOfInputs; i++) {							//loop through all the switch/button inputs
			PetTheDog();													//pet the watchdog to reset the timer
			//Serial.println(SwitchPins[i]);
			SwitchStateRead = ReadPinState(InputPins[i]);				//read from the input and assign to SwitchStateReadTemp
			if (SwitchStateRead != LastInputState[i]) {		   //if the input is different then it was last time, a switch/button has been toggled/pressed
				//if (CurrentMillis - SwitchPreviousToggleMillis[i] >= SwitchIgnoreTime) {     //if the switch hasn't togged within SwitchIgnoreTime
					//SwitchPreviousToggleMillis[i] = CurrentMillis;      //update the last time the switch toggled
					//LastSwitchState[i] = SwitchStateRead;			//update last switch state
				if (InputHoldCounts[i] == 0) {
					Serial.print(F("PIN: Switch changed: "));
					Serial.println(InputPins[i]);
				}
					
					InputHoldCounts[i]++;								//add 1 to hold count, counting number of cycles switch is held for
					InputHoldTime = (InputCheckInterval * InputHoldCounts[i]);		//multiply the interval at which switch is checked by number of checks to obtain mS switch was held for
					
					//If the hold count is lager than the max configured hold count, it is a switch or another input with long lasting states
					//Can zero out the counter and set the last state to the current state so hold count not sent and ready for next state change

					if (InputHoldTime >= MaxInputHoldTime) {
						InputHoldCounts[i] = 0;										//zero out held counts
						LastInputState[i] = SwitchStateRead;							//set the last state to current state to prepare for next state change
						Serial.print(F("PIN: Hold ingored. Max time exceeded: "));
						Serial.println(InputPins[i]);
					}


					//if HoldCount is 1, first time the loop has gone through so publish the state
					if (InputHoldCounts[i] == 1) {
						//pin state has changed, so publish state immediately
						//build the topic string to publish
						//sprintf_P(CountString, PSTR("%d"), i);			    //convert i to char
						//strcpy_P(BuiltTopic, BoardTopic);						 //do a strcopy to place BoardTopic in SubTopic
						//strcat_P(BuiltTopic, SwitchPrefix);				     //add the switch prefix (probably s)
						//strcat(BuiltTopic, CountString);					 // concatinate the i value after switch prefix. ex. /s1
						//strcat_P(BuiltTopic, BackSlash);						 //and add the backslash
						sprintf_P(BuiltTopic,SwitchTopicTemplate,BoardTopic,i);				//build the switch state topic.ie /board/s#/
						sprintf_P(SwitchStatePaylod, PSTR("%d"), SwitchStateRead);			//need to convert switch state byte reading to char for MQTT publish
						MQTTClient.publish(BuiltTopic, SwitchStatePaylod);				  //publish the switch/input state
						Serial.print(F("MQTT: Published: "));
						Serial.print(BuiltTopic);
						Serial.print(F(": "));
						Serial.println(SwitchStateRead);
					}
			}
			//the switch has been released if SwitchStateRead is equal to where it has started and some number of SwitchHoldCounts has happened
			if ((SwitchStateRead == LastInputState[i]) && (InputHoldCounts[i] >= 1)) {
				//publish the switch state
				//build the topic string to publish
				
				//strcpy_P(BuiltTopic, BoardTopic);							 //do a strcopy to place BoardTopic in SubTopic
				//strcat_P(BuiltTopic, SwitchPrefix);							  //add the switch prefix (probably s)
				//strcat(BuiltTopic, CountString);							 // concatinate the i value after switch prefix. ex. /s1
				//strcat_P(BuiltTopic, BackSlash);							 //and add the backslash

				sprintf_P(CountString, PSTR("%d"), i);									 //convert i to char
				sprintf_P(BuiltTopic, SwitchTopicTemplate, BoardTopic, i);				//build the switch state topic.ie /board/s#/
				sprintf_P(SwitchStatePaylod, PSTR("%d"), SwitchStateRead);				//need to convert switch state byte reading to char for MQTT publish
				MQTTClient.publish(BuiltTopic, SwitchStatePaylod);						 //publish the switch/input state
				Serial.print(F("MQTT: Published: "));
				Serial.print(BuiltTopic);
				Serial.print(F(": "));
				Serial.println(SwitchStateRead);
				
				//now publish the inverval for how long the switch was toggled
				
				sprintf_P(SwitchHeldTimeString, PSTR("%d"), InputHoldTime);		//convert it to cstring
				sprintf_P(BuiltTopic, SwitchHeldTopicTemplate, BoardTopic, i);				//build the held switch state topic.ie /board/s#/held/

				MQTTClient.publish(BuiltTopic, SwitchHeldTimeString);				//publish the held time via MQTT
				Serial.print(F("MQTT: Published: "));
				Serial.print(BuiltTopic);
				Serial.print(F(": "));
				Serial.println(SwitchHeldTimeString);

				//clean up variables
				InputHoldCounts[i] = 0;											//zero out switch hold counts

			}
		}


	}

	//if RFID is enabled, check for RFID tag and publish via MQTT if found
	#ifdef RFID_ENABLE
	
	if (RDM6300Update()) {													//RFID tag is near reader
		
		Serial.println(F("SENSOR: RFID: Tag Detected."));
		
		sprintf_P(RFIDTagID,PSTR("%lu"),RDM6300GetTagID());					//get the tag ID, converting it to cstring with sprintf
		
		Serial.print(F("SENSOR: RFID: Read Tag: "));
		Serial.println(RFIDTagID);											//write to serial for debug

		//build the MQTT topic to publish RFID tag
		sprintf_P(BuiltTopic, SensorTopicTemplate, BoardTopic);					//build the sensor topic ie. /board/sensor/
		
		sprintf_P(HeartBeatPublishString, RFIDJSON, RFIDTagID);					//add the readings to the JSON template

		MQTTClient.publish(BuiltTopic, HeartBeatPublishString);					//and publish the RFID JSON ID
		Serial.print(F("MQTT: Sensor Publish: "));									//print the published data to serial port
		Serial.print(BuiltTopic);
		Serial.print(F(" : "));
		Serial.print(HeartBeatPublishString);
	}
	#endif


	}

void MQTTCallback(char* topic, byte* payload, unsigned int length) {

	PetTheDog();										//pet the watchdog to reset the timer
	//MQTTCallback is fired whenever PubSubClient (MQTTClient) receives an MQTT message from MQTT_SERVER	
	//Note:  the "topic" value gets overwritten everytime it receives confirmation (callback) message from MQTT

	//set the status LED to blue to indicate a callback is being processed
	#if BOARD_TYPE == 1284
		ShowStatusLED(1, StatusColourBlue);					//set 2nd status LED to red
	#endif



	//Print out some debugging info
	Serial.print(F("MQTT: MQTT callback. Topic: "));
	Serial.println(topic);

	//loop through the array of relays and build the topic to look for
	for (int i = 0; i <= NumOfRelays; i++) {
		PetTheDog();												//pet the watchdog to reset the timer
		sprintf_P(BuiltTopic, RelayTopicTemplate, BoardTopic, i);				//build the topic to check
		Serial.print(F("Checking topic: "));
		Serial.println(BuiltTopic);

		

		// if the topic is found, strcmp returns 0
		if (strcmp(topic, BuiltTopic) == 0) {    //found the topic so position in RelayPins array is known
			Serial.print(F("PIN: Changing output for topic: "));
			Serial.println(BuiltTopic);
			if (payload[0] == '1') {      //payload is 1 so need to turn the output ON
				digitalWrite(RelayPins[i], RELAY_ON);   //turn on the output
				Serial.print(F("PIN: Set Output: "));
				Serial.print(RelayPins[i]);
				Serial.print(F(", State: "));
				Serial.println(RELAY_ON);
				sprintf_P(BuiltTopic, RelayStateTopicTemplate, BoardTopic, i);			//prepare the state topic
				MQTTClient.publish(BuiltTopic, "1",true);     //publish payload 1 indicating RelayPin[i] is on
				Serial.print(F("MQTT: Published state: "));
				Serial.println(BuiltTopic);
			}
			if (payload[0] == '0') {     //payload is 0 so need to turn the output OFF
				digitalWrite(RelayPins[i], RELAY_OFF);   //turn off the output
				Serial.print(F("PIN: Set Output: "));
				Serial.print(RelayPins[i]);
				Serial.print(F(", State: "));
				Serial.println(RELAY_OFF);
				sprintf_P(BuiltTopic, RelayStateTopicTemplate, BoardTopic, i);			//prepare the state topic
				MQTTClient.publish(BuiltTopic, "0",true);     //publish payload 0 indicating RelayPin[i] is on
				Serial.print(F("MQTT: Published state: "));
				Serial.println(BuiltTopic);
			}
		}
	}

	//set the 2nd status LED back to green since callback is done
	#if BOARD_TYPE == 1284
		ShowStatusLED(1, StatusColourGreen);					//set 2nd status LED to green
	#endif
}