Command Delay simulation


#1

The ST device.command with delay is not supported in HE, EG it.beep(2,[delay: 4000])

I decided to make a rudimentary delayCommand function, but my Groovy skills either are not sufficient or what i'm attempting is just not supported by the language. It throws an error attempting to execute the command. Hopefully someone knows how to do this without using a wait or delay that stop the code.

The log
2019-04-14 11:12:49.960 error java.lang.SecurityException: execute method not allowed on line 88 (execCommand)
2019-04-14 11:12:49.903 debug log.debug 'hello there'
2019-04-14 11:12:49.901 debug execCommand entered: [devCmd:log.debug 'hello there']
2019-04-14 11:12:46.894 debug testapp done
2019-04-14 11:12:46.885 debug delayCommand dateTime: Sun Apr 14 11:12:49 EDT 2019 devCmd: log.debug 'hello there'
2019-04-14 11:12:46.882 debug delayCommand entered: log.debug 'hello there' + 3000

The Code (proof of concept version)

delayCommand("log.debug 'hello there'",3000)

def delayCommand(devCmd,delayms)
{
log.debug "delayCommand entered: ${devCmd} + ${delayms}"
def unix=now()+delayms
def dateTime = new Date(unix)
log.debug "delayCommand dateTime: ${dateTime} devCmd: $devCmd"
runOnce(dateTime, execCommand, [data: [devCmd: "${devCmd}"]])
}
def execCommand(map)
{
log.debug "execCommand entered: ${map}"
log.debug map.devCmd
def theCommand=map.devCmd
theCommand.execute()
}


#2

Are you trying to get the device to beep every 2 seconds? What about instead of delaying it you use
runIn (2,beep)? But i'm no groovy expert...that's just what came to mind immediately for me. I might be completely wrong.


#3

I'm attempting to create a generic function to avoid wring many target functions. I have numerous varying device.commands with delays, beep was just an example. I also want millisecond timing delays to get half second timing, runin allows only seconds.


#4

Use runInMillis...


#5

I did not know that command existed. Thanks!


#6

I was literally typing that very response when you posted @mike.maxwell. I finally know something and you steal all my thunder! :wink: jk. It's good to know I may finally be learning a thing or two though. hehe


#7

Back to my original question.

Is there any way to do this so I can pass and delay generic commands without writing a function for each command that I want to delay

cmd="it.beep(2)"
cmd.execute()


#8

Depending upon what you're looking to do, the call() option on Closure might help:
http://groovy-lang.org/closures.html


#9

Appreciate the suggestion. Reviewed the Closure documentation, it may take me a while to understand it. :crazy_face:

I tried using
Eval.me(theCommand)

It failed
2019-04-14 13:44:28.374 java.lang.SecurityException: execute method not allowed on line 90 (execCommand)


#10

Yup, I went through a number of these ~1 wk ago, and it was amazing how locked down the container is :frowning:


#11

I give up.

Will write handlerMethods for each device command pair needed, then use the runInMillis to execute it with a delay.


#12

This is a sample of what I'm attempting to convert. I realize I have to convert the "it" to the actual device id for the delay in HE. My issue is how do I create handlerMethods for each random user device id?

// when siren is defined: wait 2 seconds allowing people to get through door, then blast a siren warning beep

	thesiren.each		
		{
		if (it.hasCommand("beep"))
			{
			it.beep([delay: 2000])
			}
		else
			{
			it.off([delay: 2500])		//double off the siren to hopefully shut it
			it.siren([delay: 2000])	
			it.off([delay: 2250])
			}
		}

#13

I've been trying various incarnations of the following code, but my Groovy skillset is not up to the task. I somehow need to pass and receive the device as an Object (I think) but depending on how I send it, it results in

a string: runIn(2, delayBeep, [data: [it: ${it}]])
or
LazyMap: runIn(2, delayBeep, [data: [it: it]])

Could really use some help with this.

thebeepers.each		//thebeepers is an input setting of devices
	{
	
         if (it.hasCommand("beep"))
		{
		log.debug "issued beep delayed 2000"
 //			it.beep([delay: 2000])
		runIn(2, delayBeep, [data: [it: it]])
		}
    

def delayBeep(map)
{
log.debug "delayBeep entered: ${map}"
//	thebeepers.beep()    this works but cant be used 
def it=map.it
it.beep()                       //this fails
}

groovy.lang.MissingMethodException: No signature of method: groovy.json.internal.LazyMap.beep() is applicable for argument types: () values: [] Possible solutions: grep(), grep(java.lang.Object), sleep(long), sleep(long, groovy.lang.Closure), dump(), get(java.lang.Object) on line 141 (delayBeep)


#14

What about a construct more like:

def delayMe(todo) {
	x = todo.remove(0)
	if (x instanceof Integer) {
		log.info "Scheduling for +${x}ms"
	    runInMillis(x, "delayMe", [data: todo])
    } else {
        switch (x) {
            case "beep":
                log.info "Handle Beep"
                break
            case "off":
                log.info "Handle Off"
                break
            case "siren":
                log.info "Handle Siren"
                break
            defaut:
                log.error("Unhandled ${x}")
        }
	    if (!todo.isEmpty()) runInMillis(0, "delayMe", [data: todo])
    }
}

and then the invoking construct that's driving the overall delayed-execution sequence:

    runInMillis(0, "delayMe", [data: [2000, "beep"]])

OR

    runInMillis(0, "delayMe", [data: [2500, "off", 2000, "siren", 2250, "off"]])

Basically, a little queue of things to do that the delayMe handler gets called upon to execute (via runInMillis)

I haven't tried passing Device objects as the values, so you could try that for your use-case. If it doesn't work correctly, then these can be the NID's of the target devices, and a Device lookup via it's handle can be made inside the delayMe function.

I'd avoid redefining/re-using the it variable for another purpose, to avoid any potentially whacky behaviour :wink:


#15

Appreciate the response.
Developing the command structure to replace the ST command delay or filtering out the delay: nnnn map is not the problem, the big issue for me is attempting to get dynamic device and command names to execute in the target routine, your "delayMe".


#16

Yes there is. Sorry, tuning in late.

You can call a method using a string for the name. So, suppose you have a list of method names, and a delay parameter.

def methodList = ["abc", "def", "xyz"]

this."${methodList[index]}"(delayParam)

That is how you can parameterize the method that is called. Rule 3.0 uses this approach extensively. One thing to note, this won't work inside a closure, as this would refer to the closure, not to the app or driver as you want it to. However, you could simply put that code outside the closure and call it from the closure to get it to work.


#17

Appreciate the response. However I need a targetMethod that handles a dynamic deviceid, versus the input setting device name as shown in my earlier example below

def delayBeep(map)
{
log.debug "delayBeep entered: ${map}"
//	thebeepers.beep()    this works but cant be used 
def dvc=map.it
dvc.beep()                       //this fails with a lazyMap error
}
```.

#18

There is not a method available that converts device id to a device. That would represent a security hole, where an app could take over devices without the user having selected them.


#19

Everything is running local on HE, and I somehow obtained a non owned foreign device id, would it even be possible to target that device from my local hub by issuing a command? For example if someone's device id (not real) is D0B0064:050C I could issue a command to it?

I understand the security issue and not wanting to have a user target a device id they do not own, but would it be too much overhead to verify the user owns the dynamic device prior to executing the command.


#20

Are you trying to control a device not selected by an input? You didn't show above where you came up with the devices you want to pass to delayBeep().

As long as the devices are selected from an input somewhere, you can do this.

For example:

def genericDelay(dev, command, delayMS) {
    runInMillis(delayMS, commander, [data: [dev: dev, cmd: command]])
}

def commander(data) {
    def dev = data.dev
    def cmd = data.cmd
    dev."$cmd"()
}

This can go further if the device commands take parameters. This is how Custom Commands in RM are built and work. There are Custom Actions coming that use a similar technique to send any command and any parameters to any device.