Hubitat Arduino Data Capture using MQTT for homebrew ERV project

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.

The full project

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);
 
}
1 Like

Nice :slight_smile:

If you want to stay off the internet you should look at a zigbee board.

I have one working with a homebrew NodeMCU (with WiFi disabled). Works great, pairs readily and have been running for about a week with no issues.
I love them ! They (it) opens a whole new world of possibilities.

John

Thanks I will take a look at that.
So there is a proper driver in HE for it?

I'm all local with my Ardu stuff MQTT on a networked machine

@ogiewon wrote a driver for it. His post is a few down from the initial.

Its a real simple setup. All one word commands. The useful part is it passes through all commands whether it recognizes them or not. So you can make simple commands with the NodeMCU and it will pass then through to Hubitat. Also works from Hubitat to your NodeMCU!
Simple but elegant at the same time.

I have a board on order now

Have seen this guy Andreas Spiess interesting projects with Arduino and the like https://www.youtube.com/channel/UCu7_D0o48KbfhpEohoP7YSQ

This was one I want to investigate - Time to Say Goodbye to Arduino and Go On to Micropython/ Adafruit Circuitpython

Roll your own for NZ$1500 https://greenwayerv.blogspot.com/ its eliminated condensation totally, even in September with frosts in NZ. The house is 5 years old so not many leaks, a balaned ERV seemed like my best option. The pollen filters also improve alergie symptoms in spring here too. Energy bill for September NZ$90 So economical, even temperatutre thoughout the house.

Decent low energy cost for all electric heating 3.5KW heat pump and a 2KW electric convection heater.

Energy consumption

The raw data is here

The data shows the device switching on and off under control of Hubitat

Live data feed from Hubitat cloud

Driver For PWM fan Control

	import groovy.json.JsonSlurper

	metadata {
		definition(name: "Greenway ERV Fan", namespace: "Greenway", author: "Nick Goodey") {
			capability "Initialize"
			capability "Switch"
			capability "Switch Level"

			command "on"
			command "off"

		}

		preferences {

		  section("Device") {
			input("DeviceIndex", "number", title: "Index for device relay or fan", required: true, defaultValue: 0)
		   }
		
			section("URIs") {
				input "URI", "text", title: "Device URI", required: false, defaultValue: "http://192.168.1.17"
	   
			}
			
			input("logEnable", "bool", title: "Enable logging", required: true, defaultValue: true)

		}

	}


	def installed() {
		log.info "installed..."
	}



	def updated() {
		if (logEnable) log.info "Updated..."
		initialize()
	}

	def uninstalled() {
		if (logEnable) log.info "Disconnecting from mqtt"
	}


	def initialize() {

		if (logEnable) runIn(900, logsOff)

	}


	def logsOff() {
		log.warn "Debug logging disabled."
		device.updateSetting("logEnable", [value: "false", type: "bool"])
	}
				
	def on() {
	  
		try {
			url = "$settings.URI/?RELAY=$settings.DeviceIndex%201"
			if (logEnable) log.debug url
			
			httpGet(url) { resp ->
				 if (resp.success) {
		 
					sendEvent(name: "switch", value: "on", isStateChange: true)

				}
				if (logEnable)
					if (resp.data) log.debug "${resp.data}"
			}
		} catch (Exception e) {
			log.warn "Call to on failed: ${e.message}"
		}
	}

	def off() {
		

		try {
			url = "$settings.URI/?RELAY=$settings.DeviceIndex%200"
			if (logEnable) log.debug url
			
			httpGet(url) { resp ->
				if (resp.success) {
				   
					sendEvent(name: "switch", value: "off", isStateChange: true)

				}
				if (logEnable)
					if (resp.data) log.debug "${resp.data}"
			}
		} catch (Exception e) {
			log.warn "Call to off failed: ${e.message}"
		}
	}       
				

	def setLevel(value, rate = null) {

		if(value < 10) value = 10;
		if(value > 100) value = 100;
		
		//e.g. FAN 1 100 255 max bits
		bits = (int)((value / 100) * 255);


		try {
			url = "$settings.URI/?FAN=$settings.DeviceIndex%20$bits"
			if (logEnable) log.debug url
			
			httpGet(url) { resp ->
				if (resp.success) {
									
					sendEvent(name: "level", value: value)
				}
				if (logEnable){
					log.debug "$bits"
					if (resp.data) log.debug "${resp.data}"
				}
			}
		} catch (Exception e) {
			log.warn "Call to off failed: ${e.message}"
		}

		if (value == 0) {
			sendEvent(name: "switch", value: "off")
		}

	}

New filter fan to be placed in the inlet duct. Its in a Sistema box that fits the following filter almost perfectly

Features:
Double-layer activated carbon filter cloth
High filtration efficiency
High dust holding capacity
Good temperature Resistance
Economical, practical and easy to install

There is an additional mesh filter to catch larger debris like leaves

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.