I'm writing an app. The app has a function to count devices by the value of an attribute. The idea was to call the function to update the count, and then use the count for some additional logic. The problem I'm having is that the count function isn't always completing before the rest of the logic runs.
So...there's an app that creates a child device. This function will update the child device with the count by state. This works perfectly fine for the most part.
I have a function that's called when there's a closed event from one of the monitored devices. This is where it gets hooky. I need the getCurrentCount function to finished, before the rest of this function goes.
contactClosedHandler
def contactClosedHandler(evt) {
logDebug "Contact closed, checking status count..."
getCurrentCount()
def device = getChildDevice(state.contactDevice)
def TotalOpen = device.currentValue("TotalOpen")
def TotalClosed = device.currentValue("TotalClosed")
if (TotalClosed < contactSensors.size())
{
logDebug "Not all closed; leaving virtual device as open"
device.open()
}
else
{
logDebug "All closed; setting virtual device as closed"
device.close()
}
}
I also realize that I maybe could remove some redundancy by returning TotalOpen/TotalClose instead of relying on pulling it from the device, but I have no idea how to code that at this point.
This is my first attempt at a full app and I have no coding background...please be gentle.
Just because of the way Groovy (and drivers/Hubitat) works, your getCurrentCount() call inside contactClosedHandler() should finish running before the next line (def device = ...) executes. That is typical behavior unless you're dealing with an asynchronous method (e.g., Hubitat's httpasync... methods). You can prove this by inserting logging inside getCurrentCount(), maybe a line at the beginning and one at the end, and also a couple similar lines inside contactClosedHandler() before and after the call to this method. You should see that getCurrentCount() returns before your log entry after that call inside contactClosedHandler() prints.
What's happening here is likely one of two things. One could be an effect of the fact that Hubitat caches device attribute values during a single execution of ... drivers, at least, but maybe apps, too (I'm actually not sure on this). Calling device.currentValue("TotalOpen", true) instead of device.currentValue("TotalOpen") will force it to read from the database directly instead of the cache and might help with this (that second parameter is optional, defaults to false, and specifies whether the cache should be skipped). The other possibility is that it might take a small amount of time for the event to actually be fully "committed" to the database, so if this doesn't help or it's still not reliable enough, that's probably it. For that, one workaround would be to save this value in state for your app (basically caching it yourself--state is a builtin Map object all drivers and apps have available, and you can write things to it like state.myKey = myValue), then use that value instead of querying the device.
PS - I'm not sure this would actually cause any problems anywhere, but it is convention for attribute names in Hubitat to start with a lowercase letter, like totalOpen.
I did continue to toy with the code after posting. I found that adding a 100 millisecond pause after calling getCurrentCount would clear things up.
I thought everything was ran synchronously by default which is part of why I was stumped. I guess your assessment about the database update would make sense. I'll do some testing with your suggestions and see what happens.
The variable capitalization hasn't caused any problems that I've noticed, but I'll take the feedback.
def open() {
def descriptionText = "${device.displayName} - one or more sensors are open"
if (txtEnable) log.info "${descriptionText}"
if (logEnable) log.debug "There are ${device.currentValue("TotalClosed",true)} sensors closed"
if (logEnable) log.debug "There are ${device.currentValue("TotalOpen",true)} sensors open"
sendEvent(name: "contact", value: "open")
}
That I'm not sure about; seems the true in device.currentValue(x, true) should return the non-cached value, so my guess is again that it's not quite enough time for the event/new database value to have been fully committed.
Are these parent/child apps and drivers? If so, one solution would be to make a single authoritative source for this value, perhaps in the app (and you might want to make it either singleThreaded: true in the definition or use atomicState instead of state in case multiple instances might run at the same time, or want to). You can create a method in the app that a child device can call to retrieve this value (it cannot access state on the parent app directly). I'm not saying this is the most elegant solution--I don't know enough about your app/driver design or goals to say that. It's just a possible solution.
Something else I don't understand is what open() is supposed to do on this device. This isn't a command needed for a contact sensor driver, even a virtual/aggregated one. It's found on Hubitat's Virtual Contact Sensor driver because, being a virtual device, it needs a way to manipulate the simulated "state" of the device. If yours is effectively read-only with the value ultimately determined by the app based on real device states that the app tracks, there's no reason to have it. But this could just be me not understanding what it does.
Oh...this thing is all over the place because, like most things, I had an initial idea and what that actually looks like has changed a few times. Part of this has just been me muddling through this as a way to sink my teeth some form of proper coding and to hopefully fill a need for folks.
This will be app/device relationship. The idea is the app will allow you to create a virtual device to represent a group of a sensor type for the purpose of group monitoring. In this case, to monitor a group of contact sensors for open/close status that includes a count of how many sensors are in a particular status. This started with me just grabbing the omnisensor sample driver from Hubitat's github.
To your point...there is no use for the commands being present in the driver. I suppose I could just set the on/off attribute from the app (since it's doing the monitoring anyhow). I had the thought to enable two way control for some of the devices, but maybe that can be version 2.0 .
I actually had this thing working perfectly, but I had the count portions written in three different spots. So, I created the getCurrentCount function, and here we are.
If you don't need (and certainly if you don't want) users to run commands on the device--which for a sensor-type device is the norm--then I'd say just remove the custom commands from the driver. They're necessary in virtual drivers that simulate real devices because you need some way to change the attribute values, since there's no real-world device they'd read from, and I assume that's where you got them from and kept them. But it sounds like your device is just way to report out some values calculated by the app. You can do that from either the app or the driver, or split it up between the two however you want (with the timing of the events being sent vs. committed being the main concern with some things being split, as above)--but it seems like eliminating the custom command (or commands--I assume you kept a close(), too?) might be a good thing. Whether it takes care of all of your problems depends on what the rest of the code looks like, I suppose.
Yeah...you were right. I'm going to get rid of the commands here. I do plan on adding some devices that will allow for two way control, but I think that can wait for now. I really do appreciate you chiming in and, as always, providing some very thorough advice.