OH MQTT Garage Controller (Dual doors, contact sensors, temperature & light)

Tags: #<Tag:0x00007f6171415440> #<Tag:0x00007f6171415008>

Hi All,
I have some largely working code for a dual garage controller, combined with logic for knowing the door state, and some sensor info (temperature, light), all connecting to OH via MQTT.

I am wondering what the equivalent structure would be for Homie (LeifHomie or regular type), and what the relevant merrits would be. I plan on using Arduino OTA updates at the very least, as the ceiling of my garage is difficult to get to.

I imagine I would have a node for each garage door (with properties for the state, and a command channel for open/closed), as well as a node for each of the light and temp (/humidity) sensors (currently a simple photoresistor and a DHT22).

Thanks for your feedback!

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include "DHT.h"

// Update these with values suitable for your network.

const char* Wifi_ssid = "wifinetworkname";
const char* Wifi_password = "wifinetworkpassword";
const char* mqtt_server = "192.168.x.xxx";
const char* mqtt_user = "mqttuser";
const char* mqtt_password = "mqttpassword";

//MQTT Topics
const char* out_topic = "Garage01/out";
const char* doorRecheckInterval_update = "Garage01/door/sensitivity";  // Command topic
const char* tempReportingInterval_update ="Garage01/temperature/updateInterval"; // Command topic
const char* photoChangeTolerance_update = "Garage01/photo/updateTolerance"; // Command topic
const char* DHT_temp_topic = "Garage01/temperature/DHT";
const char* DHT_humid_topic = "Garage01/humidity/DHT";
const char* DHT_HIC_topic = "Garage01/temperature/HIC";
const char* GARAGE01_command = "Garage01/command"; // Command topic
const char* GARAGE02_command = "Garage02/command"; // Command topic
const char* GARAGE01_door = "Garage01/door";
const char* GARAGE02_door = "Garage02/door";
const char* PHOTOTopic = "Garage01/photo";

float DHTTemp = 0.0;
float DHTHumid = 0.0;
float DHTHIC = 0.0;
int readFailures = 0;
int readSuccesses = 0;
int lightProgress =0;
int Temperature_Interval = 900000;  // get 1 temp every 5 minutes (300,000 ms)
int Sensors_Interval = 250; // sample the button ever 250 ms

const int RETRY_INTERVAL = 2000; // if getting a temperature failed, then retry after 2 seconds
long lastSensorsRead = 0;

unsigned long lastTemperatureSent = 0;
unsigned long lastRetryTemperature = 0;
bool lastTempSuccess = false;

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;

char msg[50];

#define SWITCH_PIN 0 // GPIO0 (D3)
#define PHOTOSENSOR_PIN A0
#define MIN_VALUE 25 // for photosensor
#define MAX_VALUE 1024 // for photosensor

#define GARAGE01_CONTACT_PIN 4 // GPIO4 (D2) 
#define GARAGE02_CONTACT_PIN 5 // GPIO5 (D1)
#define GARAGE01_OPENER_PIN 12 // GPIO12 (D6) // safe pins output pins should be GPIO04, GPIO05, GPIO12, GPIO13, GPIO14, GPIO15
#define GARAGE02_OPENER_PIN 13 // GPIO13 (D7) // safe pins output pins should be GPIO04, GPIO05, GPIO12, GPIO13, GPIO14, GPIO15
#define DHTPIN 2  // GPIO2 (D4) ***** what digital pin we're connected to
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
DHT dht(DHTPIN, DHTTYPE);

 int Photo_Tolerance = 7; // for photosensor reporting changes
 int photoSensor =0;
 int pvsGarage01Contact = 0;
 int pvsGarage02Contact = 0;
 int pvsPhotoSensor = 0;
 bool updateGarage01 = true;
 bool updateGarage02 = true;
 bool updateDHT = true;
 bool updatePhoto = true;


void readDHT(){
  // Reading temperature or humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  DHTHumid = dht.readHumidity();
  DHTTemp = dht.readTemperature();// Read temperature as Celsius (the default)
  // float f = dht.readTemperature(true);// Read temperature as Fahrenheit (isFahrenheit = true
  if (isnan(DHTHumid) || isnan(DHTTemp)) { // Check if any reads failed and exit early (to try again).
    Serial.println("Failed to read from DHT sensor!");
    DHTTemp=0.0;
  }
  DHTHIC = dht.computeHeatIndex(DHTTemp, DHTHumid, false); // Compute heat index in Celsius (isFahreheit = false)
  Serial.print("DHT22: ");
  Serial.print("Temperature: ");
  Serial.print(DHTTemp);
  Serial.print(" °C ");
  Serial.print(" %\t");
  Serial.print("Humidity: ");
  Serial.print(DHTHumid);
  Serial.print(" %\t");
  Serial.print("Heat index: ");
  Serial.print(DHTHIC);
  Serial.println(" °C ");
}

int percentageChange(int n1, int n2){
  int pChange = 0;
  if (n1 >n2) {
    pChange = round(100.0* (((float)n1-(float)n2)/(float)n1));
  }
  else if (n2>n1) {
    pChange = round(100.0* (((float)n2-(float)n1)/(float)n1));
  }
  return pChange;
}

int absChange (int n1, int n2) {
  int aChange =0;
  if (n1 >n2) {
    aChange = n1-n2;
  }
  else if (n2>n1) {
    aChange = n2-n1;
  }
  return aChange;
}

void readSensors() { // to read door contact sensors and lux sensor
  // in the end, should report lux sensor q15 min or if change, and report door if change
  // temp reading is managed separately because of the delay in getting a reading on a DHT22

  // read and manage photo sensor
  int pChange = 0;
  int aChange = 0;
  int rawPhoto = 0;
  rawPhoto = analogRead(PHOTOSENSOR_PIN);
  float level = ((rawPhoto - MIN_VALUE) * 100 / (MAX_VALUE - MIN_VALUE));
  photoSensor = level;
  
  pChange=percentageChange(pvsPhotoSensor, photoSensor);
  aChange=absChange(pvsPhotoSensor, photoSensor);

  if (aChange > Photo_Tolerance) {
    Serial.print("Raw light value ");
    Serial.print(rawPhoto);
    Serial.print("; Lux value has changed singificantly from ");
    Serial.print(pvsPhotoSensor);
    Serial.print("to ");
    Serial.print(photoSensor);
    Serial.print(". Percentage change: ");
    Serial.print(pChange);
    Serial.print(", Absolute change: ");
    Serial.println (aChange);
    updatePhoto=true;
  }
  pvsPhotoSensor=photoSensor; // update previous value

// read and manage Garage01
  if (digitalRead(GARAGE01_CONTACT_PIN)==HIGH) {
    if (pvsGarage01Contact!=HIGH) { // if this is new
      Serial.println("Garage01 Change: Switch went high. Garage01 Door is OPEN.");
      updateGarage01=true;
    }  
    pvsGarage01Contact=HIGH; // set previous flag to HIGH  
  }
  else {
    if (pvsGarage01Contact!=LOW) { // if this is new
      Serial.println("Garage01 Change: Switch went low. Garage01 Door is CLOSED.");
      updateGarage01=true;
    }  
    pvsGarage01Contact=LOW; // set previous flag to LOW 
  }

// read and manage Garage02
  if (digitalRead(GARAGE02_CONTACT_PIN)==HIGH) {
    if (pvsGarage02Contact!=HIGH) { // if this is new
      Serial.println("Garage02 Change: Switch went high. Garage02 Door is OPEN");
      updateGarage02=true;
    }  
    pvsGarage02Contact=HIGH; // set previous flag to HIGH  
  }
  else {
    if (pvsGarage02Contact!=LOW) { // if this is new
      Serial.println("Garage02 Change: Switch went low. Garage02 Door is CLOSED.");
      updateGarage02=true;
    }  
    pvsGarage02Contact=LOW; // set previous flag to LOW
  }
}


void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(Wifi_ssid);

  WiFi.begin(Wifi_ssid, Wifi_password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
  int inInt = 0;
  payload[length] = '\0'; // Null terminator used to terminate the char array
  String message = (char*)payload;
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  // Switch on the LED if an 1 was received as first character
  if ((char)payload[0] == '1') {
    digitalWrite(BUILTIN_LED, LOW);   // Turn the LED on (Note that LOW is the voltage level
    // but actually the LED is on; this is because
    // it is active low on the ESP-01)
  } else {
    digitalWrite(BUILTIN_LED, HIGH);  // Turn the LED off by making the voltage HIGH
  }

  
  if(message == "Boo"){
    client.publish(out_topic, "Eek!");
    lastMsg = millis(); // reset the count to keep it displayed longer
  }

// GARAGE 01 Commands
  if (strcmp(topic, GARAGE01_command)==0) {
    if (message == "TOGGLE"){
      digitalWrite(GARAGE01_OPENER_PIN, 1);
      delay (500);
      digitalWrite(GARAGE01_OPENER_PIN, 0);
      client.publish(out_topic, "Garage 01 - Toggling Garage Door");
    }
    else if (message == "OPEN"){
      if (pvsGarage01Contact == LOW) {
        digitalWrite(GARAGE01_OPENER_PIN, 1);
        delay (500);
        digitalWrite(GARAGE01_OPENER_PIN, 0);
        client.publish(out_topic, "Garage 01 - Opening Garage Door");
      }
      else {
        client.publish(out_topic, "Garage 01 Door already open");
      }
    }
    else if (message == "CLOSE") {
      if (pvsGarage01Contact == HIGH) {
        digitalWrite(GARAGE01_OPENER_PIN, 1);
        delay (500);
        digitalWrite(GARAGE01_OPENER_PIN, 0);
        client.publish(out_topic, "Garage 01 - Closing Garage Door");
      }
      else {
        client.publish(out_topic, "Garage 01 Door already closed");
      }
    }
  }


// GARAGE 02 COMMANDS
 // if (topic == GARAGE02_command) {
  if (strcmp(topic, GARAGE02_command)==0) {
    if (message == "TOGGLE"){
      digitalWrite(GARAGE02_OPENER_PIN, 1);
      delay (500);
      digitalWrite(GARAGE02_OPENER_PIN, 0);
      client.publish(out_topic, "Garage 02 - Toggling Garage Door");
    }
    else if (message == "OPEN"){
      if (pvsGarage02Contact == LOW) {
        digitalWrite(GARAGE02_OPENER_PIN, 1);
        delay (500);
        digitalWrite(GARAGE02_OPENER_PIN, 0);
        client.publish(out_topic, "Garage 02 - Opening Garage Door");
      }
      else {
        client.publish(out_topic, "Garage 02 Door already open");
      }
    }
    else if (message == "CLOSE") {
      if (pvsGarage02Contact == HIGH) {
        digitalWrite(GARAGE02_OPENER_PIN, 1);
        delay (500);
        digitalWrite(GARAGE02_OPENER_PIN, 0);
        client.publish(out_topic, "Garage 02 - Closing Garage Door");
      }
      else {
        client.publish(out_topic, "Garage 02 Door already closed");
      }
    }
  }

  //if (topic == tempReportingInterval_update) {
  if (strcmp(topic, tempReportingInterval_update)==0) {
     inInt = message.toInt();
     if (inInt > 30000){  //minimum is 30 sec)
       Serial.print("Updating Temperature reporting interval from ");
       Serial.print (Temperature_Interval);
       Serial.print(" to ");
       Serial.print(inInt);
       Serial.println (" miliseconds");
       Temperature_Interval = inInt;
       client.publish(out_topic, "Garage 01 - Updated temperature reporting interval");
     }
     else {
       Serial.println ("Failed update.  Did NOT update temperature reporting interval.  Attempt from ");
       Serial.print (Temperature_Interval);
       Serial.print(" to ");
       Serial.print(inInt);
       Serial.println (" miliseconds");       
     }
  }
  //if (topic == doorRecheckInterval_update) {
  if (strcmp(topic, doorRecheckInterval_update)==0) {
     inInt = message.toInt();
     if (inInt > 50){  //minimum is 50 msec)
       Serial.print("Updating garage door contact check interval from ");
       Serial.print (Sensors_Interval);
       Serial.print(" to ");
       Serial.print(inInt);
       Serial.println (" miliseconds");
       Sensors_Interval = inInt;
       client.publish(out_topic, "Garage 01 - Updated garage door contact check interval.");
     }
     else {
       Serial.println ("Failed update.  Did *NOT* update garage door contact check interval.  Attempt from ");
       Serial.print (Sensors_Interval);
       Serial.print(" to ");
       Serial.print(inInt);
       Serial.println (" miliseconds");       
     }
  }
  //if (topic == photoChangeTolerance_update) {
  if (strcmp(topic, photoChangeTolerance_update)==0) {
     inInt = message.toInt();
     if (inInt > 1){  //minimum is 2 Lux)
       Serial.print("Updating light change tolerance from ");
       Serial.print (Photo_Tolerance);
       Serial.print(" to ");
       Serial.print(inInt);
       Serial.println (" Lux");
       Photo_Tolerance = inInt;
       client.publish(out_topic, "Garage 01 - Updated light change tolerance.");
     }
     else {
       Serial.println ("Failed update.  Did *NOT* update light change tolerance.  Attempt from ");
       Serial.print (Photo_Tolerance);
       Serial.print(" to ");
       Serial.print(inInt);
       Serial.println (" Lux");       
     }
  }
}


void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str(), mqtt_user, mqtt_password)) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish(out_topic, "Online");

      //Subscribe to topics
      client.subscribe(doorRecheckInterval_update);
      client.subscribe(tempReportingInterval_update);
      client.subscribe(photoChangeTolerance_update);
      client.subscribe(GARAGE01_command);
      client.subscribe(GARAGE02_command);
      
      digitalWrite(BUILTIN_LED, HIGH);  // Turn the LED off by making the voltage HIGH
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
      digitalWrite(BUILTIN_LED, LOW);  // Turn the LED on by making the voltage LOW
    }
  }
}

void setup() {
  pinMode(BUILTIN_LED, OUTPUT);     // Initialize the BUILTIN_LED pin as an output
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
  pinMode(SWITCH_PIN, INPUT_PULLUP);
  pinMode(GARAGE01_CONTACT_PIN, INPUT_PULLUP);
  pinMode(GARAGE02_CONTACT_PIN, INPUT_PULLUP);
  pinMode(GARAGE01_OPENER_PIN, OUTPUT);
  pinMode(GARAGE02_OPENER_PIN, OUTPUT);
  // no analog setup for PHOTOSENSOR_PIN ... uses analogRead
  digitalWrite(GARAGE01_OPENER_PIN, 0);
  digitalWrite(GARAGE02_OPENER_PIN, 0);
  dht.begin();
}

void loop() {

  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  long now = millis();
  char buffer[10];
  char longbuffer[20];

 if (now - lastSensorsRead > Sensors_Interval) {
   lastSensorsRead = now;
   readSensors();
   // insert managing changed sensor code here, sending MQTT updates, etc.
   if (updateGarage01 == true) {
      //stuff
      Serial.println (" Garage 01 Sending MQTT Update.");
      if (pvsGarage01Contact == 0){
        client.publish(GARAGE01_door,"CLOSED");
        // digitalWrite(GARAGE01_OPENER_PIN, 0);  // for debug purposes, toggle relay
      }
      else {
        client.publish(GARAGE01_door,"OPEN");
        //digitalWrite(GARAGE01_OPENER_PIN, 1);// for debug purposes, toggle relay
      }
      updateGarage01 = false;
   }
   if (updateGarage02 == true) {
      //stuff
      Serial.println (" Garage 02 Sending MQTT Update.");
      if (pvsGarage02Contact == 0){
        client.publish(GARAGE02_door,"CLOSED");
        //digitalWrite(GARAGE02_OPENER_PIN, 0); // for debug purposes, toggle relay
      }
      else {
        client.publish(GARAGE02_door,"OPEN");
        // digitalWrite(GARAGE02_OPENER_PIN, 1); // for debug purposes, toggle relay
      }
      updateGarage02 = false;
   }
   if (updatePhoto == true) {
      //stuff
      Serial.println (" Photosensor Sending MQTT Update.");
      dtostrf(pvsPhotoSensor,0, 1, buffer);
      client.publish(PHOTOTopic, buffer);
      updatePhoto =false;    
   }
 }

  // MANAGE TEMPERATURE READINGS
  if ((now - lastMsg > Temperature_Interval)||((lastTempSuccess==false)&&(now-lastRetryTemperature > RETRY_INTERVAL))) {  // if it's been long enough for a fresh read, OR if the last read was bad and it's been long enough for a retry
    if (((lastTempSuccess==false)&&(now-lastRetryTemperature > RETRY_INTERVAL))) {Serial.println("Retrying DHT Sensor");}
    else {Serial.println("Routine DHT Sensor Measurement");}
    lastMsg = now;
    lastRetryTemperature = now;
    readDHT();

    if ((DHTTemp!=0) && (DHTHumid != 0)) {
      ++readSuccesses;
      lastTempSuccess = true;
      Serial.print("Successes: ");
      Serial.print(readSuccesses);
      Serial.print(" (/");
      Serial.print(readSuccesses+readFailures);
      Serial.println(")");
      dtostrf(DHTTemp,0, 1, buffer);
      client.publish(DHT_temp_topic, buffer);
      dtostrf(DHTHumid,0, 1, buffer);
      client.publish(DHT_humid_topic, buffer);
      dtostrf(DHTHIC,0, 0, buffer);
      client.publish(DHT_HIC_topic, buffer);
    }
    else {
      Serial.println("Error reading temperatures, not sending result");
      ++readFailures;
      
      strcpy(longbuffer, "Failures: ");
      sprintf(buffer, "%d", readFailures);
      strcat(longbuffer, buffer);
      strcat(longbuffer, "(/");
      sprintf(buffer, "%d", readFailures+readSuccesses);
      strcat(longbuffer, buffer);
      strcat(longbuffer, ")");
      Serial.println(longbuffer);
      client.publish(out_topic, longbuffer);
      lastTempSuccess = false;
    }
  }
}