Craig Gibson
Published © GPL3+

Garage MC (Monitor & Controller)

Garage Door IOT Monitor and Controller, linked to Home Assistant using MQTT. Also, OTA updates make it so I don't need a ladder for updates.

IntermediateShowcase (no instructions)1,496

Things used in this project

Hardware components

NodeMCU ESP8266 Breakout Board
NodeMCU ESP8266 Breakout Board
Any ESP8266 board will work, but I went with KeeYees Development Board WiFi WLAN Wireless Module for ESP8266 NodeMCU ESP-12E from Amazon
×1
Hall Effect Sensor - A3144
digital output, non-latching, normally HIGH
×2
Relay - 1-channel
5V 1 Channel Level Trigger Optocoupler Relay Module - Normally Open
×1
LED (generic)
LED (generic)
×2
Buzzer, Piezo
Buzzer, Piezo
×1
Resistor 221 ohm
Resistor 221 ohm
One for each LED
×2
Resistor 100 ohm
Resistor 100 ohm
For Piezo buzzer
×1
2-pin Screw Terminals
Optional
×2
Neodymium Magnet
Although any magnets will work, 2 neodymium magnets allow the Hall Sensors to be 3 cm or more from the magnet and still work.
×2
Breadboard (generic)
Breadboard (generic)
×1
Jumper wires (generic)
Jumper wires (generic)
×1
Perfboard (Prototyping Board)
×1

Software apps and online services

Arduino IDE
Arduino IDE
Fritzing
Blynk
Blynk
REMOVED
MQTT
MQTT

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

Fritzing breadboard diagram

Breadboard diagram used for testing

Fritzing Perf/Proto Board Diagram

Perf/Proto Board diagram for the final product.
Note: the NodeMCU goes on the header rails and the top pins were not used/connected

Code

Garage MC Code

Arduino
Updated 2023-01-15
/* ----------------------------------------------------------------------------
 * Garage MC (Monitor & Controller)
 * 
 * Monitors the position of the garage door using 2 x Hall Effect Sensors
 * Connects to Home Assistant to display the door status, send notifications, 
 * and a button to open/close the door
 * 
 * Board: NodeMCU - ESP8266-12E
 * Sensors: 
 *  2x A3144 Hall Effect Sensor
 *    - Uses a neodymium magnet on the garage door to trigger the sensors
 *    - digital output, non-latching
 *    - normally HIGH, magnet draws it LOW
 *  Relay - low trigger
 *        - Normally Open (N/O) connected to garage opener
 *        - The 1-channel 'TongLing' relay requires 5 volts from VIN pin
 *  Buzzer - passive (piezo) buzzer [100 ohm resistor from OUT to +]
 *  
 *  Program flow:
 *    SETUP - sets the following timers:
 *      Continuous timers
 *        CheckGarageState - Checks what state the garage door is in [not really needed as Interrupts trigger changes]
 *        CheckConnection - Simply checks to ensure we still have WiFi connections
 *      Run Once timer
 *        GarageStateChanged - Initialize the garage state
 *    LOOP - Only runs SimpleTimer, which runs the above timers
 *    INTERRUPTS - Occur with any change in the garage state (activated by the Hall Sensors)
 *      They set an immediate timer to run GarageStateChanged (minimal code in the Interrupts)
 *      GarageStateChanged also calls CheckGarageState
 *      
 *    The Garage Door can be opened/closed (ActivateDoor) from Home Assistant
 *    
 * ----------------------------------------------------------------------------
 */

/*******************************************************************************
/* Debugging Macros (comment out #define DEBUG to remove debugging code)
/*******************************************************************************/
//#define DEBUG
#ifdef DEBUG
 #define DEBUG_PRINT(x)     Serial.print (x)
 #define DEBUG_PRINTDEC(x)  Serial.print (x, DEC)
 #define DEBUG_PRINTLN(x)   Serial.println (x)
#else
 #define DEBUG_PRINT(x)
 #define DEBUG_PRINTDEC(x)
 #define DEBUG_PRINTLN(x)
#endif

/*******************************************************************************
/* Included Libraries
/*******************************************************************************/
#include "arduino_secrets.h"    // Passwords saved in this file
#include <ESP8266WiFi.h>        // WiFi Library
#include <SimpleTimer.h>        // Timer to start actions
//#include <ArduinoOTA.h>         // Over-The-Air Code Updates (firewall blocks)
#include <AsyncElegantOTA.h>    // Over-The-Air Code Updates
#include <PubSubClient.h>       // MQTT Client

/*******************************************************************************
/* Global Variables
/*******************************************************************************/

//----------------------------------------
// Pins
const int WIFI_OFF_PIN    = D2; // RED led - when Wifi is not connected
const int WIFI_ON_PIN     = D3; // GREEN led - when Wifi is connected
const int RELAY1_PIN      = D4; // D4 is HIGH at boot (relay triggers low) - Garage Door Control
const int BUZZER_PIN      = D5; // Passive (piezo) buzzer pin (PWM pin)
const int HALL_CLOSED_PIN = D6; // Hall sensor at garage door CLOSED point
const int HALL_OPEN_PIN   = D7; // Hall sensor at garage door OPEN point

//----------------------------------------
// Hall Sensors
//    Initialize to door AJAR (sensors are checked in Setup function anyway)
//    Global variables in Interrupts must be declared 'volatile'
volatile byte hallClosedState = HIGH; // sensor to check if the door is down (CLOSED)
volatile byte hallOpenState   = HIGH; // sensor to check if the door is up (OPEN)

//----------------------------------------
// Garage Door States
const int GARAGE_CLOSED         = 0;  // CLOSED - Door completely shut
const int GARAGE_AJAR           = 1;  // AJAR - between closed and open, could be stopped or moving
const int GARAGE_OPEN           = 2;  // OPEN - Door completely open
const char *garageStateString[] = {"CLOSED", "AJAR", "OPEN"}; // String for each state coinciding with numbers above
volatile int garageState        = GARAGE_AJAR; // default to the door AJAR

// Keep track of the time in the current state
// Checked in DoorOpenTooLong() function if:   
//     (millis()-stateStartMillis) > (MAX_OPEN_TIME+openNotifyRepeat)
unsigned long stateStartMillis      = 0;    // start time for the current garage door state
const unsigned int MAX_OPEN_TIME    = 900;  // If the door is open longer, send a notification (300 seconds=5min)
long openNotifyRepeat               = 0;    // time counter to re-send notifications to Blynk (seconds) about door open/ajar
SimpleTimer myTimer;

// Your WiFi credentials
const char ssid[] = SECRET_SSID;      // Wifi Network name
const char pass[] = SECRET_PASS;      // Wifi password
int cntWifiConnect = 0;               // Counter while waiting for Wifi to connect
bool wifiBlocking = false;            // Blocking for initial connection to wifi at startup

//----------------------------------------
// OTA variables
AsyncWebServer                  server(80); // OTA Updates - Web Server
#define CLIENT_NAME             "GarageMC"  // Host Name, MQTT topics, MQTT Client ID, and ArduinoOTA
const char ota_pass[]           = SECRET_OTA_PWD;
const unsigned long OTA_TIMEOUT = 300000;   // Turn off OTA after 5 minutes if no update
unsigned long otaStart          = 0;        // Start time for OTA = millis()
bool otaOn                      = false;    // set to "true" to turn on OTA updates
const int OTA_BLINK_DELAY       = 300;      // blink the leds while OTA is ON
unsigned long otaBlinkTimer     = 0;        // next led blink time

//----------------------------------------
// MQTT
WiFiClient espClient;
PubSubClient mqttClient(espClient);
const char mqtt_server[]        = "192.168.1.10"; // homeassistant.local
const int mqtt_port             = 1883;
const char mqtt_user[]          = "mqttuser";
const char mqtt_client_name[]   = CLIENT_NAME;
const char mqtt_pass[]          = SECRET_MQTT_PWD;
// topics
const char topicCheckIn[]       = CLIENT_NAME"/checkIn";
const char topicGarageControl[] = CLIENT_NAME"/control";
const char topicGarageOTA[]     = CLIENT_NAME"/ota";
const char topicGarageState[]   = CLIENT_NAME"/state";
unsigned long mqttReconnect     = 0;      // millis time for start of reconnect attempt
unsigned long mqttConnectPeriod = 12000;  // timeout for reconnect attempt
int mqttConnectTries            = 0;      // count of tries to reconnect to MQTT
void mqttCallback(char* topic, byte* payload, unsigned int length); // Callback function header

//----------------------------------------
// Interrupt Declarations
// ESP board needs ICACHE_RAM_ATTR included
void ICACHE_RAM_ATTR InterruptDoorClosed();
void ICACHE_RAM_ATTR InterruptDoorOpen();

/*******************************************************************************
/* SETUP - run once initialization code
/*******************************************************************************/
void setup() {
  #ifdef DEBUG
  Serial.begin(115200);
  delay(2000);             // give the Serial port time to initialize
  Serial.println("GarageMC Start");
  #endif
  
  // PINS setup first
  // Setup the relay. Ensure it is set HIGH, as it is active low (we don't want the door opening on reset)
  digitalWrite(RELAY1_PIN, HIGH); // Garage Door Relay
  pinMode(RELAY1_PIN, OUTPUT);    // Garage Door Relay
  pinMode(BUZZER_PIN, OUTPUT);    // Buzzer

  // Interrupts set for the two hall sensors
  // Both Interrupts are activated on a CHANGE of state
  pinMode(HALL_CLOSED_PIN,INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(HALL_CLOSED_PIN), InterruptDoorClosed, CHANGE);
  pinMode(HALL_OPEN_PIN,INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(HALL_OPEN_PIN), InterruptDoorOpen, CHANGE);

  // LED pins to show status of Wifi
  pinMode(WIFI_ON_PIN, OUTPUT);
  pinMode(WIFI_OFF_PIN, OUTPUT);
  SetWifiLeds(false);

  InitWiFi(); // Connect to WiFi
  InitMqtt(); // MQTT Setup

  // Setup Timers
  myTimer.setInterval(9000L, CheckGarageState);      // Check garage door state every 9 seconds
  myTimer.setInterval(300000L, CheckConnection);     // Check WiFi connection every 5 min (300,000)

  // Get the Intial state of the garage door based on hall sensors
  hallClosedState = digitalRead(HALL_CLOSED_PIN);
  hallOpenState = digitalRead(HALL_OPEN_PIN);
  DEBUG_PRINTLN((String)"@Setup@ OTA ClosedState: " + hallClosedState + " - OpenState: " + hallOpenState);
}

/*******************************************************************************
/* MAIN LOOP
 *  Run SimpleTimer which runs the code
 */
/*******************************************************************************/
void loop() {
  mqttLoop();   // Check for mqtt messages (and to stop the ota)
  
  // OTA - Only use if switched on from Home Assistant
  // don't check garage in OTA mode because it uses too many resources
  if (otaOn) {
    //ArduinoOTA.handle();
    if ((unsigned long)(millis() - otaStart) >  OTA_TIMEOUT) {
      otaReset();
    }
    // blink the red and green leds while OTA is on
    if ((unsigned long)(millis() - otaBlinkTimer) > OTA_BLINK_DELAY) {
      otaBlinkTimer = millis();
      SetWifiLeds(!digitalRead(WIFI_ON_PIN));
    }
  }
  // normal loop
  else {
    myTimer.run();  // Events Timer (run either way)
  }
}

/*******************************************************************************
/*******************************************************************************
/* Interrupts
// Sets a timer to immediately CheckGarageState
/*******************************************************************************/

// Interrupt for the Hall Sensor at the Door CLOSED point activated on CHANGE
void InterruptDoorClosed() {
  hallClosedState = digitalRead(HALL_CLOSED_PIN); // get the sensor state
  myTimer.setTimeout(10, GarageStateChanged);     // set short timer to run function immediately
}

// Interrupt for the Hall Sensor at the Door OPEN point activated on CHANGE
void InterruptDoorOpen() {
  hallOpenState = digitalRead(HALL_OPEN_PIN);   // get the sensor state
  myTimer.setTimeout(10, GarageStateChanged);   // set short timer to run function immediately
}

/*******************************************************************************
/*******************************************************************************
/* Functions
/*******************************************************************************/
/*----------------------------------------------------
 * GarageStateChanged
 * Change in the state of the garage door (ie. door has moved)
 * Interrupts change the garageStateChange
*/
void GarageStateChanged() {
  stateStartMillis = millis();  // new state start time
  openNotifyRepeat = 0;         // Reset the Door Open/Ajar repeat time for new state

  // figure out the new state of the door
  if ((hallClosedState == LOW) && (hallOpenState == HIGH)) {          // Garage door is CLOSED
    garageState = GARAGE_CLOSED;
  } else if ((hallClosedState == HIGH) && (hallOpenState == LOW)) {   // Garage door is FULLY OPEN
    garageState = GARAGE_OPEN;
  } else if ((hallClosedState == HIGH) && (hallOpenState == HIGH)) {  // Garage door is AJAR or MOVING
    garageState = GARAGE_AJAR;
  } else {  // can't be in any other state, so we have an error
    DEBUG_PRINTLN("!ERROR! - GarageStateChanged()");
  }

  #ifdef DEBUG
  Serial.println((String)"@GarageStateChanged@ - State Start: " + stateStartMillis + 
                " - hallClosedState=" + hallClosedState + " hallOpenState=" + hallOpenState + 
                " - garageState=" + garageStateString[garageState]);
  #endif
  
  CheckGarageState();   // Update Garage State
  // publish the state change using MQTT
  mqttPublish(topicGarageState, garageStateString[garageState], true);
}

/*----------------------------------------------------
 * CheckGarageState
 * Main function to check the state of the garage door
 * Run by a SimpleTimer every 9 seconds
 * also called immediately from a timer after an Interrupt
*/
void CheckGarageState() {
  unsigned int secondsInState = 0; // used to notify time door open
  String strNotify = "Door " + (String)garageStateString[garageState] + " : ";
  
  // do things based on the current garage state
  switch (garageState) {
    case GARAGE_CLOSED:
      secondsInState = (millis() - stateStartMillis) / 1000;
      DEBUG_PRINTLN(strNotify + (String)secondsInState + " seconds.");
      break;
    case GARAGE_AJAR:
    case GARAGE_OPEN:
      if (secondsInState = DoorOpenTooLong()) {
        strNotify = strNotify + (String)(secondsInState/60) + " min " + (String)(secondsInState%60) + " sec!";
        DEBUG_PRINTLN(strNotify);
      }
      break;
  }
}

//----------------------------------------
// Determine if the garage door has been open for too long
// Return: Number of seconds open if longer than maxOpenTime, otherwise, return zero
unsigned int DoorOpenTooLong() {
  unsigned int secondsOpen = (millis() - stateStartMillis) / 1000;    // How long have we been in this State
  unsigned int maxOpenTime = MAX_OPEN_TIME + openNotifyRepeat;        // Get the Maximum Open Time
  DEBUG_PRINTLN((String)"@DoorOpenTooLong@ - MaxOpenTime: " + maxOpenTime + " - Time open: " + secondsOpen);

  if (secondsOpen > maxOpenTime) {
    openNotifyRepeat += MAX_OPEN_TIME; // ensure the Blynk notify is repeated every MAX_OPEN_TIME seconds
  } else {
    secondsOpen = 0;  // return 0 = Door hasn't been open too long
  }

  return secondsOpen;
}


/*******************************************************************************
/*******************************************************************************
/* WiFi & OTA Connections
/*******************************************************************************/

// Used for the Initial Connection from Setup
void InitWiFi() {
  DEBUG_PRINTLN("@InitWiFi@");
  WiFi.hostname(CLIENT_NAME);
  WiFi.mode(WIFI_STA);
  
  otaInit();                    // Set OTA variables
  
  wifiBlocking = true;
  CheckConnection();
}

// Check for a WiFi Connection
void CheckConnection() {
  DEBUG_PRINTLN("@CheckConnection@");
  
  if (WiFi.status() != WL_CONNECTED) {
    WiFi.begin(ssid, pass);                   // Start connection
    DEBUG_PRINT("Connecting to WiFi");
    cntWifiConnect = 0;
    if (wifiBlocking) {       // Initial Setup is Blocking
      while (wifiBlocking) {
        delay(500);
        ConnectionTimer();        
      }
    } else {
      myTimer.setTimeout(500, ConnectionTimer); // Run the Connection Timer
    }      
  } 
}

// ConnectionTimer
// None blocking timer which sets up the WiFi connection
void ConnectionTimer() {
  if (WiFi.status() != WL_CONNECTED) {  // NOT connected to Wifi
    DEBUG_PRINT(".");
    if (cntWifiConnect < 30) {  // Only try for so long
      cntWifiConnect++;
      if (!wifiBlocking) {
        myTimer.setTimeout(500, ConnectionTimer);
      }
    } else {
      DEBUG_PRINTLN("@ConnectionTimer@ WiFi NOT connected");
      SetWifiLeds(false); // Turn on the Red Not connect LED
      WiFi.disconnect();  // Stop trying to connect (for now)
      wifiBlocking = false;
    }
  } else {                              // CONNECTED to Wifi
    DEBUG_PRINT("IP address: ");
    DEBUG_PRINTLN(WiFi.localIP());
    SetWifiLeds(true);                // Set LED to show WiFi connected
    myTimer.setTimeout(400, GarageStateChanged);
    wifiBlocking = false;
  }
}

// Turn on the appropriate LED based on WiFi Status
void SetWifiLeds(bool wifiOn) {
  digitalWrite(WIFI_ON_PIN, wifiOn);
  digitalWrite(WIFI_OFF_PIN, !wifiOn);
}

// Setup OTA (but not listening)
void otaInit() {
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", "Welcome to the GarageMC!");
  });
  AsyncElegantOTA.begin(&server, "GarageMC", ota_pass);    // Start AsyncElegantOTA  
/*  ArduinoOTA.setHostname(CLIENT_NAME);
  ArduinoOTA.setPasswordHash(ota_pass);

  ArduinoOTA.onStart([]() {
    DEBUG_PRINTLN("OTA Starting update");
    digitalWrite(WIFI_ON_PIN, true);
    digitalWrite(WIFI_OFF_PIN, true);
  });
  ArduinoOTA.onEnd([]() {
    DEBUG_PRINTLN("OTA Finished");
  }); */
}

void otaUpdate() {
  otaOn = !otaOn;         // If the button is pressed a second time it will turn if off
  if (otaOn) {
    otaStart = millis();  // Timeout end time
    server.begin();       // Start the web server
    //ArduinoOTA.begin();     // Start the OTA service
    DEBUG_PRINTLN("MQTT OTA Update");
  } else {
    otaReset();
  }
}

void otaReset() {
  //ESP.restart();
  server.end();       // Stop the web server
  SetWifiLeds(true);
}

/*******************************************************************************
/*******************************************************************************
/* Activate the Garage Door
/*******************************************************************************/

// The door plays a song prior to activating (opening/closing) the door
// So, someone could Activate the door from Blynk, then someone else use the garage controller
// To prevent two activations, we check that the door state hasn't changed from button press to actual activation
int activateState = 0;

//-------------------------------------------
// send signal to relay (garage door) to open or close
void ActivateDoor() {
  DEBUG_PRINTLN("Activate Door pressed");
  activateState = garageState;                        // get the garage state when the button was pressed
  long songLength = PlaySong();                       // Play a song to notify anyone in the garage that the door is about to move
  myTimer.setTimeout(songLength, ActivateDoorRelay);  // Wait for the song to finish, then activate the relay
}

//-------------------------------------------
void ActivateDoorRelay() {
  // only trigger the relay if the state hasn't changed since the button was pressed
  if (garageState == activateState) {
    digitalWrite(RELAY1_PIN, LOW);                    // trigger the relay to open/close the door
    myTimer.setTimeout(300, ActivateDoorRelayReset);  // Timer to wait 300ms
  }
}

//-------------------------------------------
void ActivateDoorRelayReset() {
  digitalWrite(RELAY1_PIN, HIGH);                   // Deactivate the door relay

  #ifdef DEBUG
  TestButton(); // for testing, sets hall sensor states
  #endif
}

//-------------------------------------------
// Test Function - manually set the states of the Hall Sensors for testing purposes
void TestButton() {
  switch (garageState) {
    case 0: // CLOSED so change to AJAR
      hallClosedState = HIGH;
      hallOpenState = HIGH;
      break;
    case 1: // AJAR so change to OPEN
      hallClosedState = HIGH;
      hallOpenState = LOW;
      break;
    case 2: // OPEN so change to CLOSED
      hallClosedState = LOW;
      hallOpenState = HIGH;
      break;
  }
  myTimer.setTimeout(10, GarageStateChanged);   // set short timer to run function immediately
}

/*******************************************************************************
/*******************************************************************************
/* PlaySound
/*******************************************************************************/

//----------------------------------------
// Melody variables (played by piezo buzzer)
// Play a short song to let anyone in the garage know that the door is about to move
const int numNotes      = 8;                                          // number of notes
const char noteNames[]  = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' }; // musical notes used
const int frequencies[] = { 262, 294, 330, 349, 392, 440, 494, 523 }; // note frequencies
const int songLength    = 17;                                  // Length equals the total notes & spaces
const char notes[]      = "cdfda ag cdfdg gf";                 // Notes to play, a space represents a rest
const int beats[]       = {1,1,1,1,1,1,4,4,2,1,1,1,1,1,1,4,4}; // Notes & rests. 1=quarter-note, 2=half-note, etc.
const int tempo         = 113;                                 // Tempo is how fast to play the song. Lower value is faster.
int iSong               = 0;                                   // Counter for current note to play

//-------------------------------------------
// - Play a sound so that people know that the garage door is about to move
// - None blocking, therefore...
// - Returns: long of the duration of the song in milliseconds
long PlaySong() {
  iSong = 0;                              // reset to first note of song
  myTimer.setTimeout(10, PlaySongTimer);  // start playing the song (none blocking)

  // Calculate the length of the song in milliseconds to return
  long duration = 0;
  for (int i = 0; i < songLength; i++) {         // step through the song arrays
    duration += (beats[i] * tempo) + (tempo/10); // length of note/rest in ms + brief pause between notes
  }
  return duration;
}

//-------------------------------------------
// Only called by timer set in PlaySong
void PlaySongTimer() {
  int duration = 0;
  if (iSong < songLength) {           // step through the song array
    duration = beats[iSong] * tempo;  // length of note/rest in ms
    if (notes[iSong] != ' ') {        // if this is not a rest, play the note
      tone(BUZZER_PIN, Frequency(notes[iSong]), duration);
    }
    iSong++;                          // increment the counter to go to the next note
    // timer to wait for tone/rest to finish + brief pause between notes, then return to function
    myTimer.setTimeout(duration+(tempo/10), PlaySongTimer);
  }
}

//-------------------------------------------
// This function takes a note character (a-g), and returns the
// corresponding frequency in Hz for the tone() function.
int Frequency(char note) {
  // Now we'll search through the letters in the array, and if
  // we find it, we'll return the frequency for that note.
  for (int i = 0; i < numNotes; i++) {    // Step through the notes
    if (noteNames[i] == note) {           // Is this the one?
      return(frequencies[i]);             // Yes! Return the frequency
    }
  }
  return(0);
}

/*******************************************************************************
/*******************************************************************************
/* MQTT Functions
 *  boolean PubSubClient::subscribe(const char* topic)
 *  boolean PubSubClient::subscribe(const char* topic, uint8_t qos)
 *  boolean PubSubClient::publish(const char* topic, const char* payload)
 *  boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained)
 *  
 */
/*******************************************************************************/

void InitMqtt() {
  mqttClient.setServer(mqtt_server, mqtt_port);
  mqttClient.setCallback(mqttCallback);
  mqttConnect();
}

// MQTT Loop
// If connected, the MQTT client checks for connections
// If Not connected, reconnect to MQTT
void mqttLoop() {
  if (!mqttClient.connected()) {                  // MQTT non-blocking reconnect
    if ((unsigned long)(millis() - mqttReconnect) > mqttConnectPeriod) {
      if (!mqttConnect()) {                       // Unable to Reconnect to MQTT broker
        mqttReconnect = millis();
        mqttConnectPeriod = 12000;
        mqttConnectTries++;
        if (mqttConnectTries >= 10) {               // try for 2 minutes
          mqttConnectPeriod = 3600000;
          mqttConnectTries = 0;                     // reset attempts
        }
      }
      else {  // Successfull reconnect
        mqttConnectTries = 0;
        mqttConnectPeriod = 12000;
      }
    }
  }
  else mqttClient.loop(); // Connected so do client loop
}

// Reconnect - Non-blocking
boolean mqttConnect() {
  if (mqttClient.connect(mqtt_client_name, mqtt_user, mqtt_pass)) {
    DEBUG_PRINTLN(CLIENT_NAME" Connected to MQTT");
    mqttClient.publish(topicCheckIn, "Online");   // Once connected, publish an announcement...
    mqttClient.subscribe(topicGarageControl);     // Subscribe to Control topic to Activate
    mqttClient.subscribe(topicGarageOTA);         // Subscribe to OTA topic
  } else {
    DEBUG_PRINT("MQTT Connection Failed=");
    DEBUG_PRINTLN(mqttClient.state());
  }
  return mqttClient.connected();
}

// callback - handle messages received from the broker
void mqttCallback(char* topic, byte* payload, unsigned int length) {
  DEBUG_PRINTLN((String)"Message arrived [" + topic + "] ");

  payload[length] = '\0';
  char *cstring = (char *) payload;
  DEBUG_PRINTLN((String)"Payload: " + cstring);

  // do actions based on the topic & payload
  String newTopic = topic;
  String newPayload = String((char *)payload);
  if (newTopic == topicGarageControl) {
    if (newPayload == "ACTIVATE") {
      // activate the garage door
      ActivateDoor();
      DEBUG_PRINTLN("MQTT activated the garage door");
    }
  } else if (newTopic == topicGarageOTA) {
    if (newPayload == "OTA_UPDATE") {
      otaUpdate();   
    }
  } else DEBUG_PRINTLN("Unknown Topic");
}

bool mqttPublish(String topic, String payload, bool retain) {
  bool bPublish;
  if (mqttClient.connected()) {
    DEBUG_PRINTLN("MQTT Publish: " + topic + " - " + payload);
    mqttClient.publish(topic.c_str(), payload.c_str(), retain);
    bPublish = true;
  } else {
    DEBUG_PRINTLN("MQTT Publish - NO CONNECTION");
    bPublish = false;
  }
  return bPublish;
}


/*******************************************************************************
/*******************************************************************************
 *    Home Assistant Configuaration:
      # GarageMC Entry
      button:
        - name: "Garage OTA Update"
          command_topic: "GarageMC/ota"
          payload_press: "OTA_UPDATE"
      cover:
        - platform: mqtt
          name: "Garage"
          command_topic: "GarageMC/control"
          payload_open: "ACTIVATE"
          payload_close: "ACTIVATE"
          payload_stop: 
          state_topic: "GarageMC/state"
          state_open: "OPEN"
          state_opening: "AJAR"
          state_closed: "CLOSED"

       # GarageMC Entry
        - platform: mqtt
          name: "Garage Door"
          state_topic: "GarageMC/state"
 *   
*
* Home Assistant Automation
  alias: Garage Open - Repeat Notification
  description: >-
    If the Garage is not CLOSED, send repeated telegram notifications (unless
    leave_garage_open is ON) until the Garage is CLOSED. And if it's Night Time,
    automatically close the garage door.
  trigger:
    - platform: state
      entity_id:
        - sensor.garage_door
      from: CLOSED
      for:
        hours: 0
        minutes: 10
        seconds: 0
  condition: []
  action:
    - repeat:
        sequence:
          - if:
              - condition: state
                entity_id: input_boolean.garage_keepopen
                state: "off"
            then:
              - service: telegram_bot.send_message
                data:
                  message: Close the Garage Door!
              - service: script.garage_auto_close_at_night
                data: {}
          - delay:
              hours: 0
              minutes: 10
              seconds: 0
              milliseconds: 0
        until:
          - condition: state
            entity_id: sensor.garage_door
            state: CLOSED
    - service: input_boolean.turn_off
      data: {}
      target:
        entity_id: input_boolean.garage_keepopen
  mode: single
*/

Credits

Craig Gibson

Craig Gibson

2 projects • 1 follower

Comments

Add projectSign up / Login