 /*
-  Configuration for Home Assistant:
-    mqtt:
-      broker: <REDACTED>
-      port: 1883
-      client_id: 'ha'
-      username: 'ha'
-      password: '<REDACTED>'
-      
-    light:
-    # Interior: Living Room Display Case
-    - platform: mqtt
-      name: 'Living Room Display Case'
-      state_topic: 'firstfloor/lrdisplaycase1/status'
-      command_topic: 'firstfloor/lrdisplaycase1/switch'
-      optimistic: false
-
-  Sources:
-    - MQTT: File > Examples > PubSubClient > mqtt_esp8266
-    - TLS:  https://io.adafruit.com/blog/security/2016/07/05/adafruit-io-security-esp8266/
-    - OTA:  File > Examples > ArduinoOTA > BasicOTA
-*/
+ * Software Revision: 2.1
+ * 
+ * Configuration for Home Assistant:
+ *   light:
+ *   # Interior: Living Room Display Case
+ *   - platform: mqtt
+ *     name: 'Living Room Display Case'
+ *     state_topic: 'firstfloor/lrdisplaycase1/status'
+ *     command_topic: 'firstfloor/lrdisplaycase1/switch'
+ *     optimistic: false
+ */
 
 #include <ESP8266WiFi.h>    // https://github.com/esp8266/Arduino (GNUv2.1 licence)
 #include <PubSubClient.h>   // https://github.com/knolleary/pubsubclient (no licence)
+#include <ESP8266HTTPClient.h>
+#include <ESP8266httpUpdate.h>
 
 #define DEBUG
 #define MQTT_VERSION MQTT_VERSION_3_1_1
 
-// Wi-Fi: Access Point SSID and password
+// ESP8266: SSID and password of desired network
 const char*       AP_SSID           = "<REDACTED>";
 const char*       AP_PASSWORD       = "<REDACTED>";
 
-// MQTT: client ID, broker IP address, port, username & password
-const char*       MQTT_CLIENT_ID    = "<REDACTED>";
-const char*       MQTT_SERVER_IP    = "<REDACTED>";
-const uint16_t    MQTT_SERVER_PORT  = 1883;
-const char*       MQTT_USERNAME     = "<REDACTED>";
-const char*       MQTT_PASSWORD     = "<REDACTED>";
-
-// MQTT: topics
-
-// Living Room Side Lamp
-const char*       TOPIC_RELAY_STATUS             = "firstfloor/lrdisplaycase1/status";
-const char*       TOPIC_RELAY_COMMAND            = "firstfloor/lrdisplaycase1/switch";
-
-// MQTT: payloads
-// Lamps : "ON"/"OFF"
-const char*       PAYLOAD_ON            = "ON";
-const char*       PAYLOAD_OFF           = "OFF";
-
-boolean           g_relay_status       = false;
-
-// buffer used to send/receive data with MQTT
-const uint8_t     MSG_BUFFER_SIZE       = 20;
-char              g_msg_buffer[MSG_BUFFER_SIZE];
-
-const uint8_t     RELAY_PIN       = D1;
-
-// Fixed IP address: IP address, IP gateway, subnet, dns
+// Device network details: IP address, IP gateway, subnet, dns.
+// NOTE: WiFi does not support configurable MAC
 const IPAddress   IP                (<REDACTED>);
 const IPAddress   IP_GATEWAY        (<REDACTED>);
 const IPAddress   IP_SUBNET         (<REDACTED>);
 const IPAddress   IP_DNS            (<REDACTED>);
 
-// WiFi and MQTT client setup
-WiFiClient  g_wifiClient;
+// MQTT: client ID, broker IP, port, username & password
+const char*       MQTT_CLIENT_ID    = "<REDACTED>";
+const char*       MQTT_SERVER_IP    = "<REDACTED>";
+const uint8_t     MQTT_SERVER_PORT  = 1883;
+const char*       MQTT_USERNAME     = "<REDACTED>";
+const char*       MQTT_PASSWORD     = "<REDACTED>";
+
+// OTA Information
+const char*       OTA_SERVER_IP      = "<REDACTED>";
+const uint8_t     OTA_PORT           = 80;
+
+// MQTT: topics
+// Interior: Living Room Display Case
+const char*       TOPIC_RELAY_STATUS             = "firstfloor/lrdisplaycase1/status";
+const char*       TOPIC_RELAY_COMMAND            = "firstfloor/lrdisplaycase1/switch";
+
+// MQTT: Expected Payloads
+// Lamps : "ON"/"OFF"
+const char*       PAYLOAD_ON        = "ON";
+const char*       PAYLOAD_OFF       = "OFF";
+
+// Track current status of the devices
+boolean           g_relay_status                 = false;
+uint8_t           g_bypass_activated             = 0;
+boolean           g_bypass_hold                  = false;
+boolean           g_bypass_type                  = true; // True = switch; False = button
+
+// Buffer configuration used to send/receive data with MQTT
+const uint8_t     MSG_BUFFER_SIZE   = 20;
+char              g_msg_buffer[MSG_BUFFER_SIZE];
+
+// Pin configuration
+const uint8_t     RELAY_PIN         = D1;
+const uint8_t     BYPASS_PIN        = D2;
+
+// WiFi and MQTT client object initialization
+WiFiClient        g_wifiClient;
 PubSubClient      g_mqttClient(g_wifiClient);
 
-///////////////////////////////////////////////////////////////////////////
-//
-// RELAY
-//
-///////////////////////////////////////////////////////////////////////////
-
-/*
-  Function called to publish the status of the relay
-*/
+// Other event handling
+const uint8_t       DEBOUNCE_DELAY    = 200; // Delay in MS is .2 sec
+unsigned long       DEBOUNCE_TIME     = 0;
+const unsigned long UPDATE_DELAY      = 300000; // Delay in MS is 5 mins
+unsigned long       UPDATE_TIME       = 0;
+
+
+//############################################################################
+//
+//                          CONTROL AND STATUS 
+//
+//############################################################################
+
+/*
+ * Publishes the status of the relay
+ *   INPUT:  NA
+ *   RETURN: NA
+ */
 void publishRelayStatus() {
   if (g_relay_status) {
     if (g_mqttClient.publish(TOPIC_RELAY_STATUS, PAYLOAD_ON, true)) {
-#ifdef DEBUG
-      Serial.print(F("INFO: MQTT message publish succeeded. Topic: "));
-      Serial.print(TOPIC_RELAY_STATUS);
-      Serial.print(F(". Payload: "));
-      Serial.println(PAYLOAD_ON);
-#endif
+      #ifdef DEBUG
+        Serial.print(F("INFO:  MQTT message publish succeeded.\n       Topic: "));
+        Serial.println(TOPIC_RELAY_STATUS);
+        Serial.print(F("       Payload: "));
+        Serial.println(PAYLOAD_ON);
+      #endif
     } else {
-#ifdef DEBUG
-      Serial.println(F("ERROR: MQTT message publish failed, either connection lost, or message too large"));
-#endif
+      #ifdef DEBUG
+            Serial.println(F("ERROR: MQTT message publish failed, either connection lost, or message too large"));
+      #endif
     }
   } else {
     if (g_mqttClient.publish(TOPIC_RELAY_STATUS, PAYLOAD_OFF, true)) {
-#ifdef DEBUG
-      Serial.print(F("INFO: MQTT message publish succeeded. Topic: "));
-      Serial.print(TOPIC_RELAY_STATUS);
-      Serial.print(F(". Payload: "));
-      Serial.println(PAYLOAD_OFF);
-#endif
+      #ifdef DEBUG
+        Serial.print(F("INFO:  MQTT message publish succeeded.\n       Topic: "));
+        Serial.println(TOPIC_RELAY_STATUS);
+        Serial.print(F("       Payload: "));
+        Serial.println(PAYLOAD_OFF);
+      #endif
     } else {
-#ifdef DEBUG
-      Serial.println(F("ERROR: MQTT message publish failed, either connection lost, or message too large"));
-#endif
-    }
-  }
-}
-
-/*
-  Function called to switch the status of the relay
-*/
+      #ifdef DEBUG
+        Serial.println(F("ERROR: MQTT message publish failed, either connection lost, or message too large"));
+      #endif
+    }
+  }
+}
+
+/*
+ * Switches the state of the relay
+ *   INPUT:  NA
+ *   RETURN: NA
+ */
 void setRelayStatus() {
   if (g_relay_status) {
     digitalWrite(RELAY_PIN, HIGH);

      setRelayStatus();
      publishRelayStatus();
    } else {
#ifdef DEBUG
      Serial.println(F("ERROR: The payload of the MQTT message is not valid"));
#endif
    }
  } else {
    // do nothing.....
#ifdef DEBUG
    Serial.println(F("INFO: The received MQTT message was not used"));
#endif
  }
}

/*
   Function called to reconnect the client to the MQTT broker and publish/subscribe to/from some MQTT topics
*/
void reconnect() {
  while (!g_mqttClient.connected()) {
    if (g_mqttClient.connect(MQTT_CLIENT_ID, MQTT_USERNAME, MQTT_PASSWORD)) {
#ifdef DEBUG
      Serial.println(F("INFO: The client is successfully connected to the MQTT broker"));
#endif

      // subscribe to the relay command topic
      if (g_mqttClient.subscribe(TOPIC_RELAY_COMMAND)) {
#ifdef DEBUG
        Serial.print(F("INFO: Sending the MQTT subscribe succeeded. Topic: "));
        Serial.println(TOPIC_RELAY_COMMAND);
#endif
      } else {
#ifdef DEBUG
        Serial.print(F("ERROR: Sending the MQTT subscribe failed. Topic: "));
        Serial.println(TOPIC_RELAY_COMMAND);
#endif
      }

      // set the initial status of the relay
      setRelayStatus();

      // publish the initial status of the relay
      publishRelayStatus();
    } else {
#ifdef DEBUG
      Serial.println(F("ERROR: The connection failed with the MQTT broker"));
      Serial.print("ERROR: rc: ");
      Serial.println(g_mqttClient.state());
      // wait 5 seconds before retrying
      delay(5000);
#endif
    }
  }
}

///////////////////////////////////////////////////////////////////////////
//
// SETUP and LOOP
//
///////////////////////////////////////////////////////////////////////////

/*
   Function called once to initialize the board
*/
void setup() {
#ifdef DEBUG
  Serial.begin(115200);
  Serial.println(F("\nINFO: The Wi-Fi module is starting..."));
#endif

  // init the relay pin as output
  pinMode(RELAY_PIN,         OUTPUT);

  setupWifi();

  // set the MQTT broker IP address and port
  g_mqttClient.setServer(MQTT_SERVER_IP, MQTT_SERVER_PORT);
  // set the MQTT callback function
  g_mqttClient.setCallback(callback);
}

/*
   Function called infinitely after the setup function
*/
void loop() {
  // keep the MQTT client connected to the broker
  if (!g_mqttClient.connected()) {
    reconnect();
  }
  g_mqttClient.loop();
  
  // a delay is necessary if the debug is disabled
  // without a delay, there is some latency to switch on/off a relay
  yield();
  //delay(100);
}

  }
}


//############################################################################
//
//                          WIFI CONFIGURATION
//
//############################################################################

/*
 * Set up the connection to the Access Point
 *   INPUT:  NA
 *   RETURN: NA
*/
void setupWifi() {
  delay(10);
  WiFi.mode(WIFI_STA); // Station mode. This is a client.
  WiFi.begin(AP_SSID, AP_PASSWORD);
  WiFi.config(IP, IP_GATEWAY, IP_SUBNET, IP_DNS); // MAC address configuration only supported for ethernet devices.

  // Hold until wireless connection is up and running.
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  #ifdef DEBUG
    Serial.println(F("INFO:  Client is now connected to the wireless network!"));
    Serial.print(F("       IP address:  "));
    Serial.println(WiFi.localIP());
    Serial.print(F("       MAC Address: "));
    Serial.println(WiFi.macAddress());
  #endif
}


//############################################################################
//
//                                MQTT
//
//############################################################################

/*
 * Function called when a MQTT message arrives
 *   INPUT:  Charanter array 'p_topic' is the topic of the MQTT message
 *           Byte array 'p_payload' is the payload of the MQTT message
 *           Integer 'p_length' is the expectedlength of the payload
 *   RETURN: NA
 */
void callback(char* p_topic, byte* p_payload, unsigned int p_length) {
  #ifdef DEBUG
    Serial.println(F("INFO:  A new MQTT message has arrived"));
    Serial.print(F("       Topic:   "));
    Serial.println(p_topic);
    Serial.print(F("       Payload: "));
    for (int i = 0; i < p_length; i++) {
      Serial.print((char)p_payload[i]);
    }
    Serial.println();
    Serial.print(F("       Length:  "));
    Serial.println(p_length);
  #endif
  
  // Act on the contents of the payload IF manual bypass isn't triggered for the given event.
  if (String(TOPIC_RELAY_COMMAND).equals(p_topic) && !g_bypass_hold) {
    // Type juggle the payload from char array to string.
    String payload;
    for (uint8_t i = 0; i < p_length; i++) {
      payload.concat((char)p_payload[i]);
    }

    // Perform payload action.
    if (payload.equals(String(PAYLOAD_ON))) {
      g_relay_status = true;
      setRelayStatus();
