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;
}
}
}