Here are few bits of code for a four-channel temperature sensor using a WEMOS D1 R2 and a device driver for Hubitat
Works with DHT11 or DHT22 sensors. There is an as-yet unused analogue input could be handy.
The 999's are me testing the sensor disconnected failure mode.
So the Driver for Hubitat
metadata {
definition(name: "Greenway MQTT ERV DAS Driver", namespace: "Greenway", author: "Nick Goodey") {
capability "Initialize"
capability "Sensor"
capability "Polling"
capability "Battery"
//ERV DAS
attribute "StaleTile", "String"
attribute "StaleTemp", "Number"
attribute "StaleHumidity", "Number"
attribute "FreshTile", "String"
attribute "FreshTemp", "Number"
attribute "FreshHumidity", "Number"
attribute "InletTile", "String"
attribute "InletTemp", "Number"
attribute "InletHumidity", "Number"
attribute "ExhaustTile", "String"
attribute "ExhaustTemp", "Number"
attribute "ExhaustHumidity", "Number"
attribute "LoftTile", "String"
attribute "LoftTemp", "Number"
attribute "LoftHumidity", "Number"
attribute "Packet", "Number"
attribute "RSSI", "Number"
attribute "Analog", "Number"
attribute "ExhaustStaleTile", "String"
attribute "ExhaustStaleTemp", "Number"
attribute "ExhaustStaleHumidity", "Number"
attribute "FreshInletTile", "String"
attribute "FreshInletTemp", "Number"
attribute "FreshInletHumidity", "Number"
}
preferences {
input name: "MQTTBroker", type: "text", title: "MQTT Broker Address:", required: true, displayDuringSetup: true
input name: "username", type: "text", title: "MQTT Username:", description: "(blank if none)", required: false, displayDuringSetup: true
input name: "password", type: "password", title: "MQTT Password:", description: "(blank if none)", required: false, displayDuringSetup: true
input name: "topicSub", type: "text", title: "Topic to Subscribe:", description: "Example Topic (topic/device/#)", required: false, displayDuringSetup: true
input name: "topicCon", type: "text", title: "Topic to control:", description: "Example Topic (topic/device/#)", required: false, displayDuringSetup: true
input name: "topicPoll", type: "text", title: "Topic to poll device:", description: "Example Topic (topic/device/#)", required: false, displayDuringSetup: true
input("logEnable", "bool", title: "Enable logging", required: true, defaultValue: true)
}
}
def installed() {
log.info "installed..."
}
def poll() {
displayDebugLog("poll")
try {
topic = settings?.topicPoll
if (logEnable) log.debug "Poll: $topic/read"
interfaces.mqtt.publish(topic, "read", 1, false)
} catch (e) {
log.error "Device Poll error: ${e.message}"
}
}
// Parse incoming device messages to generate events
def parse(String description) {
try {
displayDebugLog("Parse")
displayDebugLog(description);
displayDebugLog(did);
did = device.getId()
state.DeviceID = did
// parse message
mqtt = interfaces.mqtt.parseMessage(description)
if (logEnable) log.debug mqtt.topic
if (logEnable) log.debug mqtt.payload
state.topic = mqtt.topic
json = new groovy.json.JsonSlurper().parseText(mqtt.payload)
/*
//STALE
myArray[0] = dht[0].readTemperature();
myArray[1] = dht[0].readHumidity();
//FRESH
myArray[2] = dht[1].readTemperature();
myArray[3] = dht[1].readHumidity();
//INLET
myArray[4] = dht[2].readTemperature();
myArray[5] = dht[2].readHumidity();
//EXHAUST
myArray[6] = dht[3].readTemperature();
myArray[7] = dht[3].readHumidity();
//LOFT
myArray[8] = loft.readTemperature();
myArray[9] = loft.readHumidity();
//MISC
myArray[9] = (PacketCount++);
myArray[10] = WiFi.RSSI();
myArray[11] = analogRead(0);
*/
//Double.parseDouble(
state.StaleTemp = (float)(json[0] * 100)/100.0
state.StaleHumidity = (float)(json[1] * 100)/100.0
sendEvent(name: "StaleTemp", value: state.StaleTemp)
sendEvent(name: "StaleHumidity", value: state.StaleHumidity)
sendEvent(name: "StaleTile", value: "Stale Air<br>$state.StaleTemp °C<BR>$state.StaleHumidity %")
state.FreshTemp = (float)(json[2] * 100)/100.0
state.FreshHumidity = (float)(json[3] * 100)/100.0
sendEvent(name: "FreshTemp", value: state.FreshTemp)
sendEvent(name: "FreshHumidity", value: state.FreshHumidity)
sendEvent(name: "FreshTile", value: "Fresh Air<br>$state.FreshTemp °C<BR>$state.FreshHumidity %")
state.InletTemp = (float)(json[4] * 100)/100.0
state.InletHumidity = (float)(json[5] * 100)/100.0
sendEvent(name: "InletTemp", value: state.InletTemp)
sendEvent(name: "InletHumidity", value: state.InletHumidity)
sendEvent(name: "InletTile", value: "Intake Air<br>$state.InletTemp °C<BR>$state.InletHumidity %")
state.ExhaustTemp = (float)(json[6] * 100)/100.0
state.ExhaustHumidity = (float)(json[7] * 100)/100.0
sendEvent(name: "ExhaustTemp", value: state.ExhaustTemp)
sendEvent(name: "ExhaustHumidity", value: state.ExhaustHumidity)
sendEvent(name: "ExhaustTile", value: "Exhaust Air<br>$state.ExhaustTemp °C<BR>$state.ExhaustHumidity %")
state.LoftTemp = (float)(json[8] * 100)/100.0
state.LoftHumidity = (float)(json[9] * 100)/100.0
sendEvent(name: "LoftTemp", value: state.ExhaustTemp)
sendEvent(name: "LoftHumidity", value: state.ExhaustHumidity)
sendEvent(name: "LoftTile", value: "Loft Air<br>$state.LoftTemp °C<BR>$state.LoftHumidity %")
sendEvent(name: "Packet", value: json[10])
sendEvent(name: "RSSI", value: json[11])
sendEvent(name: "Analog", value: json[12])
state.ExhaustStaleTemp = (float)( (state.StaleTemp - state.ExhaustTemp) * 1000)/1000.0
state.ExhaustStaleHumidity = (float)( (state.StaleHumidity - state.ExhaustHumidity) * 100)/100.0
sendEvent(name: "ExhaustStaleTemp", value: state.ExhaustStaleTemp)
sendEvent(name: "ExhaustStaleHumidity", value: state.ExhaustStaleHumidity)
sendEvent(name: "ExhaustStaleTile", value: "Exhaust - Stale Air<br>$state.ExhaustStaleTemp °C<BR>$state.ExhaustStaleHumidity %")
state.FreshInletTemp = (float)( (state.FreshTemp - state.InletTemp) * 1000)/1000.0
state.FreshInletHumidity = (float)( (state.StaleHumidity - state.ExhaustHumidity) * 100)/100.0
sendEvent(name: "FreshInletTemp", value: state.FreshInletTemp)
sendEvent(name: "FreshInletHumidity", value: state.FreshInletHumidity)
sendEvent(name: "FreshInletTile", value: "Fresh - Inlet Air<br>$state.FreshInletTemp °C<BR>$state.FreshInletHumidity %")
}
catch (Exception e)
{
log.error "Parse error: ${e.message}"
}
}
def updated() {
if (logEnable) log.info "Updated..."
initialize()
}
def uninstalled() {
if (logEnable) log.info "Disconnecting from mqtt"
interfaces.mqtt.disconnect()
}
def initialize() {
displayDebugLog("initialize")
if (logEnable) runIn(900, logsOff)
state.ThisCharge = 0
state.ChargeLimit = 0
state.Relay = -1
MQTTconnect()
unschedule()
schedule("0/10 * * * * ? *", MQTTconnect)
}
def MQTTconnect() {
try {
def mqttInt = interfaces.mqtt
if (mqttInt.isConnected()) {
displayDebugLog( "Allready Connected to: $MQTTBroker $topicSub")
return
}
def clientID = "hubitat-" + device.deviceNetworkId
state.clientID = clientID
displayDebugLog( "Allready Connected to: $MQTTBroker $settings?.MQTTBroker/$settings?.topicSub")
//open connection
mqttbroker = "tcp://" + settings?.MQTTBroker + ":1883"
mqttInt.connect(mqttbroker, clientID, settings?.username, settings?.password)
//give it a chance to start
pauseExecution(500)
mqttInt.subscribe(settings?.topicSub)
log.info "Connection established: $MQTTBroker $topicSub"
log.info "clientID: $clientID"
log.info "Subscribed to: $topicSub"
} catch (e) {
log.error "MQTTconnect error: ${e.message}"
}
}
def mqttClientStatus(String status) {
log.error "MQTTStatus- error: ${status}"
}
def logsOff() {
log.warn "Debug logging disabled."
device.updateSetting("logEnable", [value: "false", type: "bool"])
}
private def displayDebugLog(message) {
if (logEnable) log.debug "${device.displayName}: ${message}"
}
private def displayInfoLog(message) {
log.info "${device.displayName}: ${message}"
}
And the Arduino Sketch
#include <Arduino_JSON.h>
#include <ESP8266WiFi.h>
#include <MQTT.h>
#define LED_PIN LED_BUILTIN //LED_BUILTIN is built in LED
#include "DHT.h"
// Uncomment whatever type you're using!
#define DHTTYPE DHT11 // DHT 11
//#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
//#define DHTTYPE DHT21 // DHT 21 (AM2301)
WiFiClient net;
MQTTClient MQTTclient;
const char ssid[] = "Your Network SSID";
const char pass[] = "Your Network Password";
// the IP address for the MQTT server
char MQTTip[] = "192.168.1.71";
unsigned long UpdateCount = 12;
unsigned long TickCount = UpdateCount-1;
unsigned long PacketCount = 0;
unsigned long lastMillis = 0;
DHT dht[4] = {DHT(4, DHTTYPE),DHT(0, DHTTYPE),DHT(2, DHTTYPE),DHT(14, DHTTYPE)};
DHT loft(12, DHT11);
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200);
WiFi.begin(ssid, pass);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported by Arduino.
// You need to set the IP address directly.
MQTTclient.begin(MQTTip, net);
MQTTclient.onMessage(messageReceived);
WiFiconnect();
MQTTclient.publish("/ervdas/status", "start");
for(int i = 0;i < 4;i++)
{
dht[i].begin();
}
loft.begin();
MQTTclient.publish("/ervdas/status", "run");
}
void loop() {
MQTTclient.loop();
delay(10); // <- fixes some issues with WiFi stability
if (!MQTTclient.connected()) {
WiFiconnect();
}
// publish a message roughly every second.
if (millis() - lastMillis > (UpdateCount * 1000))
{
lastMillis = millis();
SendMQTT();
}
delay(100);
}
void WiFiconnect() {
Serial.print("checking wifi...");
Serial.println(ssid);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
digitalWrite(LED_PIN, LOW);
delay(500);
digitalWrite(LED_PIN, HIGH);
}
Serial.println("\nWiFi Connected! ");
Serial.println( WiFi.localIP());
Serial.print("\nMQTT connecting...");
while (!MQTTclient.connect(WiFi.localIP().toString().c_str() , "try", "try")) {
Serial.print(".");
delay(800);
digitalWrite(LED_PIN, LOW);
delay(200);
digitalWrite(LED_PIN, HIGH);
}
Serial.println("\nMQTT Connected!");
digitalWrite(LED_PIN, LOW);
MQTTclient.subscribe("/ervdas/control");
MQTTclient.publish("/ervdas/status", "start");
MQTTclient.publish("/ervdas/localip", "/ervdas/localip/" + WiFi.localIP().toString());
}
void SendMQTT()
{
digitalWrite(LED_PIN, LOW);
JSONVar myArray;
//STALE
myArray[0] = (!isnan(dht[0].readTemperature()) ? dht[0].readTemperature() : 999);
myArray[1] = (!isnan(dht[0].readHumidity()) ? dht[0].readHumidity() : 999);
//FRESH
myArray[2] = (!isnan(dht[1].readTemperature()) ? dht[1].readTemperature() : 999);
myArray[3] = (!isnan(dht[1].readHumidity()) ? dht[1].readHumidity() : 999);
//INLET
myArray[4] = (!isnan(dht[2].readTemperature()) ? dht[2].readTemperature() : 999);
myArray[5] = (!isnan(dht[2].readHumidity()) ? dht[2].readHumidity() : 999);
//EXHAUST
myArray[6] = (!isnan(dht[3].readTemperature()) ? dht[3].readTemperature() : 999);
myArray[7] = (!isnan(dht[3].readHumidity()) ? dht[3].readHumidity() : 999);
//LOFT
myArray[8] = (!isnan(loft.readTemperature()) ? loft.readTemperature() : 999);
myArray[9] = (!isnan(loft.readHumidity()) ? loft.readHumidity() : 999);
//MISC
myArray[10] = (PacketCount++);
myArray[11] = WiFi.RSSI();
myArray[12] = analogRead(0);
String jsonString = JSON.stringify(myArray);
MQTTclient.publish("/ervdas/sensors", jsonString);
Serial.print("/ervdas/sensors " );
Serial.print(jsonString );
Serial.println();
digitalWrite(LED_PIN, HIGH);
}
float toFixed(float f, int dp)
{
char buf[40] = {0, };
sprintf(buf, "%03d", f);
return atof(buf);
}
void messageReceived(String &topic, String &payload) {
digitalWrite(LED_PIN, LOW);
Serial.println("incoming: " + topic + " - " + payload);
if(topic == "/ervdas/control")
{
if(payload.startsWith("UpdateCount"))
{
UpdateCount = (long)payload.substring(12).toInt();
TickCount = UpdateCount -1;
}
else if(payload.startsWith("read"))
{
SendMQTT();
MQTTclient.publish("/ervdas/localip", "/ervdas/localip/" + WiFi.localIP().toString());
}
MQTTclient.publish("/ervdas/payload", topic + "/" + payload);
}
digitalWrite(LED_PIN, HIGH);
}