Is there a way to make childDevices.each an async function?

I have a custom driver Im working on to integrate Kodi machines in my house for notifications/etc from Hubitat.

This is a parent/child driver where the parent driver manages the child devices (can add/remove child devices) and each child device is configured for an individual physical Kodi machine in my house. If a child device function is called directly, it will send a message to that Kodi instance only. If the parent device function is called, it will call each child device function so all Kodi instances get the message.

The issue I have, is the childDevices.each command in the parent will wait for responses from the child devices before calling the following child device. Which works ok, unless one or more of my Kodi machines are offline, which will cause a 10 second timeout/hold before calling the next child device.

simplified parent driver code:

def parentFunction(notification){
	childDevices.each {
		try{
            log.debug "[debug text for childDevice] 1"
			it?.childFunction(notification)
            log.debug "[debug text for childDevice] 2"
		}
		catch (e) {
			if (logEnable) log.debug "Error sending childFunction to childDevice ${it.deviceNetworkId}: ${e}"
		}
	}
}

the childFunction (in the child driver) has a HubAction, and the parentFunction childDevices.each will wait for the childFunction response before calling the next child device.

screenshot of the parent device debug log:

you can see the initial call to the first childDevice was 1:50:13.202, and it waited 10 seconds for the timeout (Kodi Desktop was offline) before the remainder of the childDevices were called starting at 1:50:23.326

In this case, I do not need a response in the parent driver, and wonder if there is a flag or option to make the childDevices.each call asynchronous.

I can define a 1 second timeout for the child device HubAction call, but I would rather not have the system waiting at all between calling the child device functions and hope there is some option available for this use case.

One way to avoid this issue would be to modify the child device's childFunction(notification) call. Instead of that call being a blocking call, that prevents the parent from iterating to the next child device, you could simply have it schedule a command to be run, with arguments, 1 second from now.

So, as an example...

this current command

def childFunction(notification) {
...do something that might be a blocking call...
}

would become

def childFunction(notification) {
runIn(1, 'childFunction2', [data: notification])
}

def childFunction2(notification) {
...do something that might be a blocking call...
}

Awesome. Thanks for your quick response. That worked perfectly, and was easy to implement because of the way I initially formatted my child device functions.

1 Like

@ogiewon's suggestion is the best way to handle it.

One other thing you may want to look at is the call to kodi. Are you using and httpget to send the notification? If so take a look at using asynchttpget instead. This method won't wait around for the reply and continue with execution. Its also easier on the hub in the long run. Offline clients won't hold up execution.

1 Like

Excellent point!

Thanks for the additional option to look into. I am using a hubaction call to communicate to Kodi, and from this thread below, It looks like hubaction is similar or the same compared to using the asynchttpget, so i did not explore that route further. In my use case, it was the locking function in the parent driver during the iteration of calling the child devices and waiting for a response. Once the child device handler sends off the get request, it doesnt appear its locked waiting for a response.

A note on this solution, it won't work in a truly multithreaded manner. Meaning, if a second runIn is called while the other is already executing, you will only have 1 execution of childFunction2even thoughchildFunctionis called twice. If you want both to execute you need to specifyoverride: false`

1 Like