/*
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
}