My first app and how I stumbled through it (long read)

My first app and how I stumbled through it.

The goal of this post is to pass on what I learned writing my first App. Its a simple app but contains an example of most of the basics.

The app calculates the DEW point from a temperature reading and a humidity reading. It is somewhat rough but functions correctly. I believe it is best to present it here at this point so I can explain what I learned writing this app without there being additional code for niceties.

Before I go on I would like to thank @bravenel , @bertabcd1235, and @csteel who helped me directly. In addition there are numerous posters who have helped by writing informative posts where I could glean information to help me understand.

OK.....moving on.

I've found before I start writing any code I needed to write down the steps required. Simple concepts like this I could do by defining each step and writing it down. Without exception when I start to write the steps I have in my head I realize something won't work.

Description of the needed steps for my approach: (could be presented as a flow chart)

  • Select a single temperature from among my temperature sensors
  • Select a single humidity from among my humidity sensors
  • Calculate the DEW point (in °F) whenever either the selected temperature or selected humidity changes. To accomplish this the App had to "remember" both the temperature and humidity so when one of them changes the "last" other can be used in the calculation.
  • I found the best place to store these values was as state variables:
  • They became state.lastTEMP and state.lastHUMID. Capitalization matters!

So how to display my result? It seems the only way is to create a ChildDevice. I was hoping not to get this complicated but there is no other way (that I found) and it turns out ChildDevices are not that difficult. The childDevice instance is created by this app as long as the childDevice can be one of the already defined Virtual devices Hubitat has built in. There was no DEW Point Virtual device so I used the virtual temperature device. I learned this by following the below mentioned example from Hubitat.
At this point I am starting to see Hubitat-Groovy has the ability to create what I would have thought to be complex, with a small amount of code. For example you will see that the input method automatically setups a dropdown menu with a list of all matching devices.....real easy! I am starting to appreciate the capabilities of the Hubitat-Groovy OS(?).

My first hurdle is to understand program flow and why things happen as they do. I could find little in the way of a meaty overview, so I'll try here. Caution there will be some misstatements which I hope others can correct but this represents my understanding.

There are only two ways code in this app can run:

  • The app is "run" from the apps page. I will start at either the installed method or the updated method. Both of which call the initialize method. And of course any other methods initialize calls.
  • One of the subscribed events receives an "event". In this program both temperature (of one device) or the humidity (of one device) will cause an "event". In turn they will cause the handlerTEMP(evt) or handlerHUMID(evt) to run.

I started with the AverageTemperature.groovy example on the hubitat github site.

I learned the order of the code blocks (aka methods, in other languages subroutines etc) don't have any specific requirements. With the exception of the "definition" and "preferences" the rest can be in any order you chose.

The Program:

"definition"

This simply provides information to the Hub so the UI can show the name of the app, author etc. I know it can be expanded upon but I've not found a definitive syntax reference. It can be formatted as I have or placed all on one line.

"preferences"

I'm not sure what preferences does in the example I followed. In seems to refer to the "mainPage". I suspect in an app where there are multiple pages it likely has a more important roll. However no doubt it is required in this and every App.

The rest of the code is a collection of methods (subroutines in other languages). The execution of the remaining methods is dictated by the code, starting with either installed() or updated() and always ends calling initialize().

The below explanation references code line numbers. If you copy the App code (below) into the Hubitat UI you will be able to see the referenced line numbers.

Because we will be using a ChildDevice to display the the DEW Point calculation results we first must find if one already exists because at this point I'm not ready to create one. To display a simple number we will be using the "Virtual Temperature Device"

We will start with initialize():

We will have to initialize two things:

  • Create the child device
  • Subscribe to the devices we wish to monitor.

Line 44 we ask for a copy of the object "DEWPoint_$${app.id}" assigning it to averageDev (just a name I carried over from the example). The value of app.id is somehow the magic of the Hub OS, we don't need to consider its value. For the curious, the app.id with be in the device page DNI column of the created ChildDevice. the "DEWPoint_" is Line 45 tests if a valid object was returned. If not, there is currently no childDevice and one must be created. The addChildDevice(xxx,"Virtual Temperature Sensor, yyy) where the Virtual Temperature Sensor is simply the name of Hubitat's Virtual Temperature Sensor. Pretty simple.
Because averageDev is an object of "Virtual Temperature Sensor" device, it has a capability "temperature". This value is set by the statement: averageDev.setTemperature(some value).
Line 46 initialized the DEW Point Temperature to 0

Here I've run into an area I cannot explain. We've been told the program "starts" with "initialize" method. However in the next lines 47 and 48 we will be subscribing to two devices where the variables we are subscribing to have been defined in lines 27 and 28 which we say hasn't run yet. I'm going to ignore that and assume groovy runs the dynamicPage (where the inputs are inputted) when it must.

Subscribing to sensor capabilities: (lines 47 & 48)

The concept is very simple. This code sends a request to the Hubitat Database to notify us when the temperature changes on sensor "tempSensor". The OS will do this by, sending the temperature to the Method "handlerTEMP" causing it to run whenever the temperature has changed.

subscribe(tempSensor, "temperature", handlerTEMP)
subscribe(humidSensor, "humidity", handlerHUMID)

Where:

  • "tempSensor" is a variable we named and it must match the first parameter in the "input" on line 27.
  • "temperature" is the capability of the chosen sensor we wish to monitor
  • "handlerTEMP" is the name of the Method we named to handle the "event" created when the subject sensor changed temperature.

Humidity is the same.

Handler Method

handlerHUMID(evt)

This method is pretty self explanatory. The only thing I can add is the "evt" object/variable is a string even though the .value suggests a numeric. This is important because in the calculation we have to convert it to a double.

calcDEW

The calculation Method is also pretty simple nothing unique about the code.

dymanicPage / section

I left this for last to keep the Methods descriptions together. The 4 lines (25 - 28) do all the inputting.

Line 25: Inputs the variable "thisName" as "text". This will be come the Name of the App in Hubitat. This is similar to the Device label in a device driver. The title of the input is message box is "Name this DEW Point Calculator"
Line 26 check if the input is valid and updates the app label if it is.

Now the magic starts:
Line 27 Inputs the name of the Temperature you want to monitor.
It looks at all the devices, selecting those with the temperature capability and presents them to the user as a list of devices.
Because we defined "multiple:" as false, only only one sensor can be selected.
The chosen sensor is returned as an object "tempSensor" A name we made up, i.e. it is not defined in by Hubitat.

I hope this post can help someone thinking of writing their 1st App. As I write this it is difficult to remember when these concepts were unknown to me as they quickly become obvious as one writes the app.

John

App Code:

/*
This code loads and subscribes to the selected device capabilities.
2020-07-01 JohnRob

Need to:
	Consider averaging multiple sensors, writing a DEW point Virtual Driver
*/

definition(
    name: "DEW Point Calculator",
    namespace: "hubitat",
    author: "JohnRob",
    description: "DEW Point Calculator",
    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 "tempSensor", "capability.temperatureMeasurement", title: "Select Temperature Sensor", submitOnChange: true, required: true, multiple: false
			input "humidSensor", "capability.relativeHumidityMeasurement", title: "Select Humidity Sensor", submitOnChange: true, required: true, multiple: false
		} // section
	}	// dymanicPage
}	// mainPage

def installed() {
	initialize()
}

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

def initialize() {

  	def averageDev = getChildDevice("DEWPoint_${app.id}")
	if(!averageDev) averageDev = addChildDevice("hubitat", "Virtual Temperature Sensor", "DEWPoint_${app.id}", null, [label: thisName, name: thisName])
	averageDev.setTemperature(0)
	subscribe(tempSensor, "temperature", handlerTEMP)
	subscribe(humidSensor, "humidity", handlerHUMID)

	state.lastHUMID = 50		// these are in the app and will not display in the child
	state.lastTEMP = 50		//  50/50 DEWPoint = 32
}

def calcDEW() {
	def averageDev = getChildDevice("DEWPoint_${app.id}")
    log.debug "  56 state.lastTEMP ${state.lastTEMP}"
    log.debug "  57 state.lastHUMID ${state.lastHUMID}"
    operandHUMID = state.lastHUMID.toDouble()
    operandTEMP = state.lastTEMP.toDouble()
    
    def dewPoint = (operandTEMP - (9 / 25) * (100 - operandHUMID))
    log.debug "   62  dewPointI =     ${dewPointI}"
	averageDev.setTemperature(dewPoint.toInteger())
	//return
}

def handlerHUMID(evt) {
	state.lastHUMID = evt.value
    log.debug " 65 last Humidity = ${evt.value}"
   calcDEW()
}

def handlerTEMP(evt) {
	state.lastTEMP = evt.value
    log.debug " 71 last Temperature = ${evt.value}"
   calcDEW()
}

//  --- eof ---
15 Likes

I remember my similar journey when I first got the HE. My method for figuring it out was shotgun patterning debug lines everywhere until I figured out how the code flowed...

3 Likes

I still use a similar method now, although it is generally for confirmation that the code is flowing as expected.
My one piece of advise I would give is to add LOTS of log statements in your code initially.
This way, if something doesn’t work, (and there are no obvious error logs) you can see where the code stopped (just after the last log statement) and have a good idea where to look for a problem.

Extra logging can always be removed later :slight_smile:

Andy

3 Likes

as someone starting this journey myself

Thank you @JohnRob for this!

1 Like

In addition, I found having the code line number in the debug statement helps me a lot. Sometimes is a bit of a chore to keep them at least close.

1 Like

Alternatively, I put the method name in my debug messages, that is often enough, but if not just make sure the message is unique enough to identify which one it is. Also, when I wrote a parent/child driver I ended up setting up a small set of log methods in the parent for each type, I think it was debug, info and error, then called these from the child code. The three log methods each referred to flags in the preferences to allow me to turn each type of logging on/off. You could introduce the settings in the child device if you prefer.

1 Like

I can see where this would be very helpful in a large program. Thanks

1 Like

You are exactly why I wrote this. I hope as you go through your first app and see thing that you may struggle with a little that once you get past them you add it here for the next brave soul :slight_smile:

2 Likes

Below is a diagram of my interpretation of how a device is setup in the Hubitat database. It doesn't directly relate to the above app but it gives some insight as to how a device relates to the data base. i.e. for subscriptions etc.

1 Like

I shall indeed

This is fantastic, thanks. Designing an algorithm isn’t the hard part, it’s drudgery of learning new syntax, and paths, and device names, and stepping on unique land mines. This demystifies the process and now I feel like if I want to use variables from my devices to do some math and spit out a number, I have a shot at it. Kudos for helping us lazy folks.

1 Like

Found this while hunting for your App/Driver based on another thread.
This is fantastic, as a former programmer who really hasn't written anything substantial in 12 years, this helps motivate me to make more tweaks to the apps/drivers I am using and even muster up some thoughts on some apps I might want to write myself.

THANK YOU !!

2 Likes

I am extremely happy I am able to help fellow Hub user. Thank you for the kind words.

1 Like

Also a bunch of lightweight Groovy tutorials out there, such as this one on YouTube - https://youtu.be/-7dBuqvPUjw or this all-in-one lesson https://youtu.be/B98jc8hdu9g or this in-depth dive https://youtu.be/Tyfk0uWYjxk?t=660 (starts at 11:00).

Plus a bonus look at early WebCore coding on the Hubitat https://youtu.be/FyVmAxuc32M and a more robust project on ST https://youtu.be/84f0dJTfZO8, not to mention this master class on dealing with variables in WebCore (installed on Hubitat) https://youtu.be/6d3wtjjCLiM .

Groovy is a subset of Java scripting language. It appears to be compiled and not interpreted at runtime.

It is sad that this hurdle exists. Learning how a HE Groovy app is architected and works is obscure and hard with the ST documentation (now deprecated but archived) still required reading.

Understanding an event driven app with multiple threading and child apps/drivers and inparticular the finicky updating of variables is a challenge because HE documentation remains poor, especially in a tutorial format. It’s almost a guarded secret although time remains the real culprit.

Even today there are posts been made about the scope of data and update principles.

Well done for posting your journey.. I hope it helps many people.

2 Likes

Truth! I went looking for documentation on the Wiki and was quite astonished, frankly, by the lack of resources there.

I think you will find in the early days of Windows, Microsoft had the same issue. Oddly they did better with MSDos.

Many moons ago I suggested what is needed was for some creative soul to write " Hubitat for Dummy's" also I think you undervalued your services.

However at the rate of development it would be a never ending task.

1 Like