Skip to main content

Hubitat Feeling Groovy

 Hubitat allows development of custom drivers in Groovy.



Its pretty easy to learn and the Hubitat framework allows connection and management of devices not supported by Hubitat officially like all your home-brew Arduino devices.


You can also create custom applications to extend the capabilities of the devices.




e.g Calculate dew point



app:11532020-09-04 22:39:02.775 infoRho = 1.2405 kg/m3

app:11532020-09-04 22:39:02.771 infooutsidetemp = 11.1 °C

app:11532020-09-04 22:39:02.767 infoPressure = 100.8 Kpa

app:11532020-09-04 22:39:02.763 infoAbsolute Humidity = 125.0 kg/m3

app:11532020-09-04 22:39:02.759 infoDew point temperature = 3.5 °C


Still haven't finished the absolute Humidity calculation


Credit to Bruce Ravenel for the original app code I based this on

definition(
    name: "Calculate dew point",
    namespace: "hubitat",
    author: "Bruce Ravenel",
    description: "Calculate dew point",
    category: "Convenience",
    iconUrl: "",
    iconX2Url: "")



preferences {
	page(name: "mainPage")
}

def mainPage() {
	dynamicPage(name: "mainPage", title: " ", install: true, uninstall: true) {
		section {
			
            input "thisName", "text", title: "Name this dew point calculator", submitOnChange: true
			
            if(thisName) app.updateLabel("$thisName")
			
            input "tempSensors", "capability.temperatureMeasurement", title: "Temperature Sensor", submitOnChange: true, required: true, multiple: false
            input "humidSensors", "capability.relativeHumidityMeasurement", title: "Humidity Sensor", submitOnChange: true, required: true, multiple: false
            input "pressureSensors", "capability.pressureMeasurement", title: "Outside Pressure Sensor", submitOnChange: true, required: true, multiple: false
            
            
            /*
			paragraph "Enter weight factors and offsets"
			tempSensors.each {
				input "weight$it.id", "decimal", title: "$it ($it.currentTemperature)", defaultValue: 1.0, submitOnChange: true, width: 3
				input "offset$it.id", "decimal", title: "$it Offset", defaultValue: 0.0, submitOnChange: true, range: "*..*", width: 3
			}
			input "useRun", "number", title: "Compute running average over this many sensor events:", defaultValue: 1, submitOnChange: true
			if(tempSensors) paragraph "Current sensor average is ${averageTemp()}°"
			if(useRun > 1) {
				initRun()
				if(tempSensors) paragraph "Current running average is ${averageTemp(useRun)}°"
			}
            */
		}
	}
}

def installed() {
	initialize()
}

def updated() {
	unsubscribe()
	initialize()
}

def initialize() {
	
     Map map = [: ]
    
    def averageDev = getChildDevice("DewPointCalc_${app.id}")
	
    //ChildDeviceWrapper addChildDevice(String namespace, String typeName, String deviceNetworkId) (since 2.1.9)
    //ChildDeviceWrapper addChildDevice(String namespace, String typeName, String deviceNetworkId, Map properties) (since 2.1.9)
    //Map properties
    
    if(!averageDev) 
        averageDev = addChildDevice("hubitat", "Virtual Temperature Sensor", "DewPointCalc_${app.id}", map, [label: thisName, name: thisName])
    
    /*
    void subscribe(InstalledAppWrapper app, handlerMethod)
    void subscribe(Location location, handlerMethod)
    void subscribe(DeviceWrapper device, String handlerMethod, Map options = null) (Since 2.2.1)
    void subscribe(DeviceWrapperList devices, String handlerMethod, Map options = null) (Since 2.2.1)
    void subscribe(DeviceWrapper device, String attributeName, handlerMethod, Map options = null)
    void subscribe(DeviceWrapperList devices, String attributeName, handlerMethod, Map options = null)
    void subscribe(Location location, String attributeName, handlerMethod, Map options = null)
    */
	
	subscribe(tempSensors, "temperature", handler)
    subscribe(humidSensors, "thumidity", handler)
    subscribe(pressureSensors, "pressure", handler)

}

def handler(evt) {
    
    def temp = 0
	def totalh = 0
	def totalp = 0
	def outsidetemp = 0
	def Rho = 0
	 
	tempSensors.each {
		/*def offset = settings["offset$it.id"] != null ? settings["offset$it.id"] : 0*/
		/*total += (it.currentTemperature + offset) * (settings["weight$it.id"] != null ? settings["weight$it.id"] : 1)*/
 		temp += it.currentTemperature 

	}
	    
    
	humidSensors.each {
        totalh += it.currentHumidity
	}
	    
    
	pressureSensors.each {
        totalp += it.currentPressure
        outsidetemp += it.currentTemperature 
	}
	    
    dewpoint = (temp - ((100 - totalh) / 5))
    

    def averageDev = getChildDevice("DewPointCalc_${app.id}")
    
    averageDev.setTemperature( dewpoint.toDouble().round(1))    
   
    
    /*
    You can show that this matches theory by re-arranging the ideal gas law: 
    PV = mRT for pressure P, volume V, mass of the gas m, 
    gas constant R (0.167226 J/kg K) 
    and temperature T to get 
    ρ = P/RT in which ρ is density in units of m/V mass/volume (kg/m3).
    
    Temperature (°C) = 13.55
    Air pressure (hPa) 1018
    Dew point (°C) =    -6.4
    rho (kg/m3) = 1.2352
    */
    
    Rho = ((totalp.toDouble().round(1) * 10)  / (0.167226 * ( 273 + temp) )) / 16.8896534974
    
    //sendEvent(name: "Rho", value: "Rho $Rho kg/m3")
    
    
    ABShumid = Rho * totalp
    
    
     //averageDev.setHumidity( ABShumid.toDouble().round(1))    

	log.info "Dew point temperature = ${dewpoint.toDouble().round(1)} °C" 
 	log.info "Absolute Humidity = ${ABShumid.toDouble().round(1)} kg/m3" 
    log.info "Pressure  = ${ totalp.toDouble().round(1)} Kpa" 
    log.info "outsidetemp  = ${ outsidetemp.toDouble().round(1)} °C" 
    log.info "Rho  = ${ Rho.toDouble().round(4)} kg/m3" 
    
    state.OutsideTemp = "Outside temp ${ outsidetemp.toDouble().round(1)} °C "
    state.Rho = "Rho ${Rho.toDouble().round(4)} kg/m3"
    state.Baro = "Pressure ${totalp.toDouble().round(1)} Kpa"
    state.ABShumid = "ABS Humidity ${ABShumid.toDouble().round(1)} kg/m3"
    state.DewPoint = "Dew point temperature = ${dewpoint.toDouble().round(1)} °C"
    
    //averageDev.setTemperature( tile)    
 
    
    tile = "
" tile += "
Dew point ${dewpoint.toDouble().round(1)} °C
" tile += "
Outside temp ${ outsidetemp.toDouble().round(1)} °C
" tile += "
Pressure ${totalp.toDouble().round(1)} Kpa
" tile += "
Rho ${Rho.toDouble().round(4)} kg/m3
" tile += "
ABS Humidity ${ABShumid.toDouble().round(1)} kg/m3
" tile += "
" //sendEvent(getEventMap("DeviceTile", "$tile", false)) sendEvent(name: "DeviceTile", value: "$tile"); def now = new Date() sendEvent(name: "lastCheckin", value: now.format("YYYY-MM-dd-HH:mm:ss")) } private parseTest(val) { return [ name: 'illuminance', value: val, unit: '?', isStateChange: true, descriptionText: "Value is ${val} ?" ] }


Popular Posts

The Heat Exchanger

  Under construction It took around 6 hours to layer the core Based on a 60 Litre Sistema box with a 400 X 400 X 350 6 mm Coroplast core. The fan speed controller is mounted on the end so it takes a 12V 10 Amp supply from the power system and 2 analogue 0 to 5 V signals for speed demand. The pink foam is a NZ$12 exercise matt which insulates it for both sound and heat quite nicely. 60 Litre Plenum on the vent side See  https://greenwayerv.blogspot.com/2020/10/we-added-60-litre-plenum-on-vent-side.html

Home Ventilation Heat Exchanger

 

The Experiment

First things first an experiment. I had been looking around at various homemade ERV core designs and eventually decided to try a Coroplast design. The experimental core was tiny 12 X 12 X 12 cm  using 3 mm Coroplast Nice isn't it :) 20 80 mm PC cooling fans at a fixed speed four temperature probes. Here is the full scale one around 40 X 40 X 35 cm using 6 mm Coroplast A Wemos D1/R2 for instrumentation sending temperatures via MQTT to Node-Red. I'm was not totally convinced by the numbers but it did definitely exchange significant amounts of heat. I checked the calibration and it was good compared to my multi-meter TC probe. In any case it seems to work even at such a small scale. Never did understand why it seems to gain more heat that it lost from the exhaust steam, checked for leaks there were none. Any way  a hairdryer experiment showed it worked and heat was transferred too so I was happy that it would probably work at a larger scale.

Heat Exchanger Low Ambient 86% Heat Recovery

  Drop on exiting air 2.9C Heating of incoming air 2.5C So 86% of the heat recovered

The ERV Data Acquisition System

  Progress over the weekend with the ERV DAS The 999's are me testing the sensor disconnected response. I have a Pitot tube left over from an RC plane I think I might put it in the inlet air stream Pin assignment in the Arduino sketch DHT dht[ 4 ] = {DHT( 4 , DHTTYPE),DHT( 0 , DHTTYPE),DHT( 2 , DHTTYPE),DHT( 14 , DHTTYPE)}; There are 4 DHT22's inside heat exchanger lid, shown below. Sensor locations Arduino code #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 [ ] = "Network" ; const char pass [ ] = "DF@#$%" ; // the IP address for the MQTT server char MQTTip [

Drivers and Arduino Sketches for ERV Fans Control Testing

  The Hookup For proof of concept seems to run reliably in the lab. I don't want to be up and down ladders to make update I intend keeping the control system in Hubitat so I can tinker to my Hearts content. Boost fan PWM control as well as start relays. The valve position is a separate driver and device in Hubitat, This will use one of the PWM Four boost fans in the ducts for each room inlet. One fan in the two way turbo exhaust valve. The valve position is a separate driver and device in Hubitat, This will use one of the PWM pins on the Wemos and its analogue input for the position pot Hubitat Valve Position Driver import groovy.json.JsonSlurper metadata { definition(name: "Greenway ERV Turbo Valve", namespace: "Greenway", author: "Nick Goodey") { capability "Initialize" capability "Switch" capability "Switch Level" command "on" command "off" } preferences { section("Device&qu