Pass a closure to runIn

Why can’t we pass a closure to runIn()? For example, I’d like to be able to do something like:

runIn(300, { light.off() })

Instead of creating a new function to pass instead. I assumed that it would just work, but instead, it causes a “java.lang.ClassCastException”. Has anyone come up with a way to make this work?

Not sure.

I guess you will still have to do the long way unless I am missing something:

runIn(60, “turnOffLight”, [overwrite: false]);

def turnOffLight(){
light.off()
}

Try
runIn(300) { light.off() }

I make no promises

From:

http://docs.smartthings.com/en/latest/introduction/groovy-the-smartthings-programming-language.html#groovy-sandboxing

Ah, thanks for that info Gary. That’s unfortunate.

That wording that they chose there is kind of unusual. It might as well say, “Closures are one of the best features of Groovy, so SmartThings is not going to allow them. Haha”.

Or maybe I read that wrong, and it means that closures cannot be defined outside of methods, meaning globally. I was reading it as “other than methods”.

If that is the case, then my closure should work, right?

Perhaps. I tend to think in terms of C/C++, so “closures” aren’t in my normal “day time” toolbox.

1 Like

I agree that wording is confusing, so I removed that superfluous text. Closures are certainly used widely in the ST environment, and are discussed briefly in the tips & tricks section.

Thanks Jim, so any idea why it wouldn’t work in this case?

The runIn method accepts a method reference or the method name as a string, not a closure.

1 Like

The runIn method is scheduling things behind the scenes, our scheduling of smart apps all just take a method name to be called at the scheduled time. That means when this executes it is a new “run” of the smart app.

For a closure to be executed like you are showing we would need to keep a reference to that closure which depending on how long that runIn is for means holding way too many references in memory and that the execution would need to happen inside the same JVM which isn’t as resilient as being able to make a new call to a smart app method.

7 Likes

Thanks, that makes sense.

2 Likes

I’d wager I’m going down a similar mental path that @obycode was on. Looking at how to best apply a variable delay to an action in the rules engine I’m working on. I was hoping that SmartThings would allow me to use runIn() on a device command, but it doesn’t look like there is an easy way to do that. Just curious, @obycode, did you come up with an approach that will work for you? I’d be interested to see how you approached it if you’re comfortable sharing.

I’m probably going to avoid runIn() and just implement the delay on my end. Right now, for time based triggers (i.e. run something at 6:30 PM) I am saving the time value in the database. I have a very simple program that is scheduled to run once per minute, it queries the database for any time triggered rules and evaluates them. Any of the rules evaluate as passing are executed by sending a command to SmartThings via the API. For time based delays I’m probably going to do something similar. If a time delayed action is called, I will insert a row into the database with the time that it should execute. The program that runs each minute will be adapted to also look for any actions that need to be executed during that minute and send the execute commands.

Seems like an OK solution.

Yup, definitely running into the same problem. I’m still working on my solution… I was actually just testing it. If I get something that is not just a hack, I’ll share it for you and others. There are actually two problems, one is the inability to pass a closure to the schedule functions, and the other is the 4 scheduled events per SmartApp limit. The solution I’m working on handles both.

I think your case will be easier to handle, as you have the added benefit of the separate server there that can do polling, while my solution needs to do everything within the SmartApp (I can’t constantly poll from your iPhone).

It is possible to specify a delay parameter when sending a command to a device: http://docs.smartthings.com/en/latest/smartapp-developers-guide/devices.html?highlight=delay#sending-commands

Not sure if that addresses this specific use case, but just FYI if nothing else :slight_smile:

1 Like

Ah, very good. Going to experiment with it this weekend. Are there any limitations (max delay, number of concurrent delayed commands, etc) we should know about?

Thanks Jim!

Oh yeah, I can’t believe I forgot about that. Thanks Jim. That probably handles most of the cases.

I ended up basically managing a queue of scheduled tasks myself, and using runOnce to schedule the task at the front of the queue. When I have some time, I’ll try to separate it out enough from the rest of my code so that it could be generally useful.