Anyone else coding their own automations in Groovy, instead of using the automation apps?

I had been using Webcore for years, first on SmartThings and then on Hubitat after SmartThings got rid of Groovy. I had quite a collection of over 200 Webcore pistons, in about 8 categories. It is amazing what you can do with Webcore, but it was getting so hard to troubleshoot and follow the logic as I had many pistons calling other pistons and sending them parameters, it just got to be a mess to remember even how things were setup.

So I started coding everything in Webcore into custom apps and drivers. I have a coding background and I know Java, so this helped, but there is still a learning curve to learn how Hubitat works with the classes and methods used for apps and drivers. The developer pages really help a lot.

Developer Pages

More or less, a category in Webcore became the app, and each piston become a method in the app. Global variables became either state variables or an attribute in a data driver. I had to learn to make schedules, and how to subscribe to events, but once you get the hang of it things it can go pretty quickly. What gets to be a pain is doing your own type casting, and time calculations are not the easiest to use compared to Webcore. Still, I just kept with it and used the internet to figure things out.

I use virtual drivers for data storage, and use methods in the drivers to work on the data if there is some logic needed. Then there is an app to go with the driver to run all the automations, so most of my automations are now an App/Driver combo.

Anyone else doing this? I really think it is the way to go for full control, and everything runs faster than when Webcore was running my automations.

Edit:

Here are a few examples: (not perfectly formatted for github, and there is some commented out code)

Note: I store all my scenes for a room in a single json string that gets parsed when a scene is executed for to get the device commands for a scene.

Front Scene Controller Executer
Front Sensor Data
Illuminance Data
Illuminance Calculations and Sync

Here is a very simple app to connect two switches to run my electric fireplace:
Broadlink Fireplace Connector

2 Likes

Any good luck using any of the AI coders to help with this? E.g. Github Copilot?

Interesting idea. I wonder if AI could figure out how to code Groovy apps and devices in Hubitat from scanning the developer pages? It would be interesting to see what it came up with. It would have to know how Hubitat set-up all of its classes and methods to interact with the hub.

The challenge with using CoPilot like tools is that the Groovy support in Hubitat is contained (limited) by design. It runs in a sandbox which only allows a fixed set of potential includes and a subset of Groovy language support. Additionally certain aspects of the environment are presented to you as preexisting Groovy objects with both fixed and variable properties based on the context and app developer design.

The documentation is sparse (problem #2) but you'd want to point the learning to the developer documentation at Developer Documentation | Hubitat Documentation in addition to a variety of code samples. Even then I think you'd still need to be strong capable of general debug principles and able to learn the language and environment quickly.

Keep in mind many code samples are not "current" (problem #3) as the platform evolves but not all developers evolve with it. Also the application and driver environment have been "forgiving" in so much as they have not always been strongly enforcing requirements in the past (problem #4). Various changes may break that forgiveness and you will be left holding the debug bag.

I wound up taking over an abandoned app and driver codebase because the original developer left it incomplete from both a feature and a minimum requirements standpoint. Recent platform enhancements are tightening up on expecting those required elements. The forgiveness level is less than it used to be but yet still quite forgiving.

Anyway if you are remotely competent as a developer, it's not a difficult platform to take up. But you will bang your head on walls beyond basic capabilities as you explore the sandbox and try to work through the missing documentation.

4 Likes

Robert Morris (@bertabcd1234) is very responsive in enhancing and updating the documentation as missing elements are pointed out. The documentation has vastly improved over the past few years due to his efforts.

5 Likes

Ideally I think the key is to ask questions early, before it gets to that point.... But I can't say I haven't been there myself, the trick is spotting you are heading in that direction. The Community is a great resource and wherever possible take the view, like @672southmain pointed out, to try and contribute to plugging any gaps or confusion in the documentation as you go.

2 Likes

The key here is knowing what to ask in order to obtain the answer you most need. The earlier you are in Hubitat development the harder it is to ask useful (non repetitive) questions. Also the harder it is to separate both existing and new community comments of value from ones that provide inaccurate or even outdated information.

Like it took me a little while early on to understand you have no less than three places to cache data in a driver and maybe why you might use one vs the other (and how to create, update, and delete each). And despite the sparse available documentation I still beat my head against the wall of data/state updates and current vs future context. Like only today did I read that ultimately Current States really should only be for event triggering data changes. You can use it for more, but the trade is that you get lots of extra events in the device event history. Likely not a problem, but could be. May be a performance drag for an event driven ecosystem. However device attributes go into current states, and thus create events. But yet device attributes go there, so ¯_(ツ)_/¯

Now the full context: If a new developer managing a device type that has a variable feature set (one company designs the controller, other companies modify and implement the controller), where should they store the feature capability set such that they can model the driver behavior correctly for the device as installed by the user? Does that belong as attributes that go into Current States, as State Variables (state) or as Device Details->Data? These properties of the device are set from the factory, but have to be discovered to model the driver behavior through required and optional commands appropriately. What are the tradeoffs and potential impacts of each storage repository? I mean unless the original development of Hubitat was overly organic, I's like to suspect that the internal developers had architectural reasons for the various and sundry data holds.

Attributes as Current States obtained by methods() and State Variables as state obtained by direct access (value) is quite confusing to new developers just learning the environment, there really ought to be some terminology clean up.

This may come across as a rant and maybe it is, but it's not mean to be mean spirited but a description of the process of learning, especially trying to become effective quickly.

A detailed in depth tutorial on driver and app data management should be its own first class documentation section. Hubitat should probably take positions on what data goes where and why to guide a more consistent cross developer output.

Another little tidbit that might be worth documenting clearly. These are like API endpoint calls:

  • parent.method()
  • child.method()

The code in the method executes in the context of it's placement. That is parent.methodname() executes in the application context even when called form a child driver. It's quite important and useful for upward and downward communication of information. Also another potential repository of data storage and access for a child driver/device. It also matters for where logging takes place and how.

1 Like

And speaking of data, here's another documentation miss.

Here are the Attributes/ Current States:

Here are the definitions:

Let’s look specifically at bondBreezeSupport which is declared as an integer in that code snippet.

It is set as an integer using:
sendEvent( name:"bondBreezeSupport", value:1 )

However, when later retrieved it is not an integer:

def junk = device.currentValue( "bondBreezeSupport" )
Integer junkInt1 = device.currentValue( "bondBreezeSupport" )
Integer junkInt2 = device.currentValue( "bondBreezeSupport" ).toInteger()
logEvent( "toggleBreeze(): bondBreezeSupport data 'raw': ${junk}, Integer raw value: ${junkInt1.toString()}, Integer Converted value: ${junkInt2.toString()}", "info" )

Result:

Note the raw value of “49” which is the ASCII code for the character “1”.

I declared the attribute an integer and sent it an integer. Hubitat does something expected, intelligent and displays a visual “1” (the user can read the intended value).

However when retrieved, I as the developer need to know all attributes are really strings regardless of declaration or actual data type submitted. That’s kinda important when doing numerical comparisons. It’s also not clearly documented behavior. It turns out Hubitat (not Groovy as far as I can tell) did a full on data type conversion on storage.

You’d think declared an integer, sent in as an integer, that Hubitat would do what is necessary to return the intended integer. As a missed expectation on my part, that caused some unexpected behavior. While 1 is less than 2, “1” (actually 49, char cast as integer in a mathematical comparison) is greater than 2. Go figure. Groovy is only loosely typed, and then mix in Hubitat driven behind the scenes one-way type conversion and you get troublesome behavior.

1 Like

also i have found. looking at others example groovy code really helps.. if you look at my git hub i have a whole bunch of code both apps and device drivers hosted..

a lot are ones i wrote but some are other peoples code where i have custom modifications that i wanted..

2 Likes

Generally, the data I store in drivers is data that will possible be consumed by another app, usually through an event trigger, but also just to get it with currentValue to make a decision. I can just include the driver device in settings of some other app and get access to all the values and make event subscriptions if needed. The other reason I use attributes is for Dashboards, it is easy to put that value on a dashboard if it is an attribute.

For states that stay within the app or driver, I just use the state variables. I have not found a need for atomic variables, as all my states are more for future reference than immediate retrieval.

1 Like

Examples are key when starting out. I found a simple app that would sync a ceiling fan to a dimmer, and allow for "speeds" within ranges. That was fairly easy to understand, and it demonstrated how to put devices in settings, how to subscribe to events from the devices, and how to call a method with the event to get the event value to work with. From there it is just a matter of start coding, and as you run into things you don't know how to do yet, you figuring it out, hoping that the developer page needed is not just a list of to do documentation.

1 Like

Prime example from my driver, I'm taking over someone else's abandoned but quite well used app/driver code base. Without forcing people to completely rip out their current Hubitat configuration, I am updating and upgrading driver functionality which relies upon knowing certain device parameters. Despite all best efforts to instruct people, hardly anyone runs configure() when they update the driver. This leaves the driver code sometimes in a state where what it expect isn't present yet in the device attributes. So the code detects the missing data and forces a configure(), but here's the rub, I can't use the configure() state or attribute updates in the current execution context so I have to force configure then abandon the action requested by the user and leave a trace in the logs saying "try again".

Or in the alternative maybe use atomic state in order to salvage the situation and execute the current requested action after getting the missing attributes into place.

FWIW in my experience (and I could be wrong) state variables are the same as atomicState variables the only difference is how the variables are written and retrieved. Using atomicState.varname forces the app to go to the database for the value and also update it to the database while state grabs it from memory. Before the singleThreaded feature was added I had to use atomicState quite a bit because of situations where multiple instances of the app running and I needed a single source of truth for a value.

Except, "integer" is not a valid data type for an Attribute.

From the Hubitat Documentation

2 Likes

Fair point, although were it I to use number what should I expect as the behavior? I likely missed that copying examples from other's code which is also why I'm not a huge fan of learning by reading non-official code.

Intuitively I would expect the data type going in and coming out to be directly usable in a mathematical context, otherwise it's an alias for "string". I'll have to test it.

It's also another example of the Hubitat environment being maybe too forgiving.

Edit: validated that using "number" provides the expected behavior without developer type conversion post retrieval. My bad for trusting other code samples and not cross referencing the correct documentation.

Yes, from what I found a "number" type is like a generic type for any integer, float, double etc. You have to turn it into the the type of number you want when using it.