Handling asynchronous methods' completion and results

Depending on the logic of the device management action, certain methods included in the action script may have to be executed in a strictly consecutive order. This is not a problem with synchronous methods, as they are always executed sequentially and the results from their execution are always available immediately as method return values. However, the confirmation of the execution of asynchronous methods and the reporting of their return values may not be available for quite a long time, and will definitely not be available as method result values as was the case with synchronous methods. This may lead to difficulties in the programming of the entire action script if another method's execution has to take this confirmation or result into account.

This problem is resolved through the special style of programming called Continuation Passing Style (CPS). Its main idea is to pass a function (a closure in Groovy terms) as a last argument that contains a logic defining how to continue the execution depending on the passed result. As defined in the official Groovy language documentation , a closure is an open, anonymous, block of code that can take arguments, return a value and be assigned to a variable. By specification, a Groovy closure can be placed anywhere in the code.

The mass management functionality of Bosch IoT Manager allows you to provide a closure to all Groovy DI API methods as an additional last argument. Such closures are executed only after the completion of the method in which this closure is a last argument.

When using the API method,

foo(arg1, arg2, arg3)

Method calls in groovy could be:

foo(arg1, arg2, arg3)
// or
// Closure postAction = { arg1, arg2 -> println "Command finished, args are: $arg1, $arg2" } foo(arg1, arg2, arg3, postAction)

If the foo method is asynchronous, the closure code will be run after the asynchronous execution has been completed, perhaps long after the script for the respective device has finished.
To ensure this, the system stores such closures in the database and restores them when confirmation of foo from the device is received. Even if this confirmation comes minutes, hours, or days after the task/rule execution on this device, the closure will remain in the database and will be run after the real asynchronous completion.

Such closures can also make use of the results of the asynchronous execution (see next section), and since they are kept in a database they will also survive a restart or a crash of the Suite service.

To summarize, Groovy closures are defined and applied in order to achieve sequential execution of asynchronous methods.

Typical closure use cases

Adding a block of code that can be called after an asynchronous execution of the method is complete

Using closures is very convenient when a new action has to be appointed explicitly after the completion of a previous asynchronous action.

Let us assume that we have to install a bundle that enables a device feature and to use that feature right after that on the targeted device.

In the following example, this device will not have a lamp feature before the installation of this bundle (com.bosch.iot.dm.lamp-5.0.3.jar) is completed. The lamp will appear as a result of the bundle installation and only then we will be able to change its color.

Closure command = {
 lamp = target.feature("Lamp:2")
 lamp.changeColor("blue")
}
target.exec(
'BundleFactory',
'install',
['http://myrepository/com.bosch.iot.dm.lamp-5.0.3.jar', ['-s'].toArray()].toArray(), command)

This is the correct approach for such use cases because the code in the closure will be executed only after the execution of the install action is completed and a confirmation has been delivered to the backend.

As a contrast, executing the same script without a closure (see the next example) will instantly send two commands to the device but the second one (changeColor) will not wait the first to be confirmed and will most probably end with an error that the Lamp feature does not exist.

bundle.install()
target.feature("Lamp:2").changeColor

In other words, the last method will most probably finish with state error as the new constructor (part of the functionality which is dynamically installed by the bundle com.bosch.dm .lamp-5.0.3.jar) won't be available.

Obtaining a result from an asynchronous method execution

In order to report results (result values and/or error messages) from asynchronous methods, a closure must be called with two arguments - result and error.

Closure command = { result, error ->
if (error == null) {
println "Failed to install bundle"
} else {
newBundleId = result
println "Installed bundle : $newBundleId"
}
}
target.feature('BundleFactory').install('http://myrepository/com.bosch.dm.lamp.state.jar', ['-s'].toArray(), true, command)

When using our Groovy APIs documentation be informed that the AsyncResult last argument, found in some methods in our groovy doc should be ignored and not included in the actual action script as it merely indicates that this concrete method is asynchronous.

In general, when calling a method with a closure, it may be called with zero, one or two arguments as presented in the following table:

Call

Method

Calling a method without a closure

target.feature('BundleFactory').install('http://myrepository/com.bosch.dm.lamp.state.jar', true)

Calling with closure having no arguments

target.feature('BundleFactory')
.install('http://myrepository/com.bosch.dm.lamp.state.jar', true, {println "Command finished"})

Calling with closure having one argument – the result value

target.feature('BundleFactory')
.install('http://myrepository/com.bosch.dm.lamp.state.jar', true, {result -> println "Command finished, installed bundle -> $result"})

Calling with closure having two arguments – the result value and the method error, if any

target.feature('BundleFactory').install('http://myrepository/com.bosch.dm.lamp.state.jar', true, { result, error ->
print "Command finished "
if (error == null) {
println "Successfully, installed bundle id : $result"
} else {
println "with error : $error"
}
})

Important

When monitoring task and rule executions, the system provides а separate and detailed execution report for each asynchronous Groovy DI API method call, i.e. for each command sent to the devices in the scope, as well as a separate report for each closure that has been supplied and executed as a post-action to some of the Groovy DI API methods.

Therefore, you can see detailed execution statuses for all commands and closures, even though one and the same script can include many of those. For further information, please check Partial executions of the groovy action