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