I'm trying to do an update where a delayed action is taken on a device, and I'm scheduling the callback with runIn() and passing it the device as part of its data. That's... not working.
It would appear that everything Hubitat knows about the device is being flattened to text and the text is supplied to the callback, not a Device object.
It's possible to rework the flow of my apps and library so that the setting name is passed through and not used to fetch the actual device object until the callback. However, that's a slightly annoying change. Is there anything else I could do across the callback boundary, like fetching a device ID and calling a function in the callback to rehydrate that into a device?
Actually I take that back. The rework is really simple in one app (just pass the strings and do settings[device] in the callback) and really difficult in the other (because the device selection is owned by the parent app and I'm calling the parent app and getting device objects back directly).
I might be able to stuff the devices into settings of the child app...? Is there a different way I should try to do this?
I don't totally understand what you're trying to do, but I think you need the app to invoke something on one of its child devices. I would use a custom device command for this. If you only have one or two functions, just make the commands function-specific (like doThis(data) and doThat(data)). If you have a bunch, make a single command that takes the desired action and any needed data as parameters (like doSomething(cmd, data)).
Or if I missed the point, can you explain more about the needed interaction between the app and its child devices?
And one other thing I learned the hard way -- you can't get a return value from a custom command. So if you need to pass back data or a response, you can use the command to put the data into the device data area and then pull that directly back in to your app. So it is a two-step process.
Here's how I do that in an app (first line is custom command, second line is getting the response):
selectedDevice.cacheCodesForApp(true)
Map intCodes = new groovy.json.JsonSlurper().parseText(selectedDevice.getDataValue("codes"))
And here's the device side (the relevant parts of the command implementation):
// store copy of codes in data space, so app can get at them
def codesStr = new groovy.json.JsonOutput().toJson(state.codes)
updateDataValue("codes", codesStr)
No, it's not a child device, but a system device the user selected in the app UI. Invoking the command on them immediately is easy (and what it currently does). Passing the devices to the app function that performs the action is fine -- the Device objects can be passed around.
I have a library which handles the action, and it gets passed Device objects from either of the apps sitting on top of it. What's breaking is trying to add a delay to the library function that performs the action: if the Device is placed in the callback data, it's not a Device when the callback happens, and the library doesn't have access to the devices in the apps.
I might need to build some kind of translator function in each app that has access to the list of devices, can be passed an ID by the callback, and returns the device that corresponds to the ID. The library function can call into the app, so that would probably work.
Because the devices are handled in different ways in the two apps. In one case, the devices are settings; in the other, they're retrieved by a call into the parent app, which has the settings. So you're right, if the library knew which way to fetch the device, it would have access, but that code doesn't know which app it's running inside.
Just set a state variable for this. RM has the same issue as to Rule 5.1 vs Button Rule 5.1. There are minor differences that impact the main library. So those just entail checking that state Boolean.
That code would send an array of Maps. What does the code look like on the other side (where it is de-serialized)? I guess I'm asking, what does that setColor method look like?
The other way to handle this is to put the method itself in a library, that is two versions of the method each in their own library, but with same method name and params. Then include the relevant library for each app. RM also uses this technique.
For example, Rule 5,1 has both Hub Variables and Local Variables, while Button Rule 5.1 only has Hub Variables. So there are two libraries with the variable methods. All of the same methods, but with the obvious differences in what they do.
I took your suggestion to set a state variable, then a hydrateDevices() function that switches on that state and returns the device based on what each app is able to pass in easily. That seems to be working, though it means more calls into the parent app. I've tried to minimize that otherwise, but that may be overoptimizing.
Yeah, I find myself doing this sometimes. Then realize that the CPU is barely touched by these things... Cleaner code usually trumps optimized code, in most circumstances.