Exec execute commands with two parameters?

Tags: #<Tag:0x00007f616fb2a098>

Hello everybody!
Im working on controlling my 433mhz remote controlled RF outlets with openhab and exec and i would need some help.

My current status is that my RF outlets are controlled by a shell-script that takes two parameters, device id and command.

My RF outlets are a bit peculiar, they have a unique code for each device and state, meaning device1 & ON = 1381717 while device1 & OFF = 1381719.

So, how should i configure exec to send two commands to the script?

The complete command string to be sent looks like this:
“/opt/rf433.sh 1 off” to turn device 1 off.

I verified that the script works with openhab user with the above command string, but i havent a clue how to send more than one parameter from exec…

I havent really started on the openhab config files, just experimenting wildly so far, but here´s the script:

#!/bin/bash

# must be called with a parameter
# rf433_send.sh [channel] [command]
# will send the associated command to the corresponding RF outlet over 433mhz radio.

if [ $# -lt 2 ] #Check to see if at least two parameters was supplied
then
	echo "Must be called with the channel id and command to send to the RF outlet"
	echo "example: " $0 "1 on"        # $0 is the name of the program
	echo "For help, use: " $0 " -? "
	exit 1
fi

  if [ $# -ge 2 ]       # if there were 2 or more parameters
then
	case $1 in
	"-?")       echo "Supported channel id is 1..14" ;;
	"1")  	
		case $2 in  
			"on")       echo /opt/RF433Utils/RPi_utils/codesend 1381717
						/opt/RF433Utils/RPi_utils/codesend 1381717
				;;
			"off")      echo /opt/RF433Utils/RPi_utils/codesend 1381719 
						/opt/RF433Utils/RPi_utils/codesend 1381719
				;;
			*)			echo "unknown state, should be "on" or "off""
				;;
			esac
		;;
	"2")  	
		case $2 in  
			"on")       echo /opt/RF433Utils/RPi_utils/codesend 1394005 
						/opt/RF433Utils/RPi_utils/codesend 1394005
				;;
			"off")      echo /opt/RF433Utils/RPi_utils/codesend 1394004 
						/opt/RF433Utils/RPi_utils/codesend 1394004
				;;
			*)			echo "unknown state, should be "on" or "off""
				;;
			esac
		;;
	"3")  	
		case $2 in  
			"on")  		echo /opt/RF433Utils/RPi_utils/codesend 1397077 
						/opt/RF433Utils/RPi_utils/codesend 1397077
				;;
			"off")      echo /opt/RF433Utils/RPi_utils/codesend 1397076 
						/opt/RF433Utils/RPi_utils/codesend 1397076
				;;
			*)			echo "unknown state, should be "on" or "off""
				;;
			esac
		;;
	"4")  	
		case $2 in
			"on")       echo /opt/RF433Utils/RPi_utils/codesend 1397845 
						/opt/RF433Utils/RPi_utils/codesend 1397845
				;;
			"off")      echo /opt/RF433Utils/RPi_utils/codesend 1397844 
						/opt/RF433Utils/RPi_utils/codesend 1397844
				;;
			*)			echo "unknown state, should be "on" or "off""
				;;
			esac
		;;
	*)		echo "unknown device id, should be "1"..."14""
		;;
	esac
else
	echo "input needs a second parameter"
	echo "usage: " $0 " [device-id] [state]"
	echo "device id is 1-16, state is on/off"
fi
# end of the input case
exit 0

My shell-script is dependent on a utility called 433Utils/RPi_utils from NinjaBlocks.

Somehow i managed to solve this myself!

These are my working files, in case somebody else is interested :slight_smile:

.things:

Thing exec:command:remote-send [ command="/opt/rf433_send.sh %2$s", interval=0, timeout=20, autorun=true ]

.items:

Group:Switch:OR(OFF,ON) Gplugs <poweroutlet>


Switch RF_Socket_1 <poweroutlet> (Gplugs)
Switch RF_Socket_2 <poweroutlet> (Gplugs) 
Switch RF_Socket_3 <poweroutlet> (Gplugs) 
Switch RF_Socket_4 <poweroutlet> (Gplugs) 
String PlaceHolder ""

Switch Remote_Send      { channel="exec:command:remote-send:run" }
String Remote_Send_Args { channel="exec:command:remote-send:input"  }
String Remote_Send_Out  { channel="exec:command:remote-send:output" }

.rules:

import java.util.concurrent.locks.ReentrantLock

val ReentrantLock transmitter = new ReentrantLock

rule "rf433Rule"
  when
    Member of Gplugs received command
    // Item RF_Socket_1 received command or
    // Item RF_Socket_2 received command or
    // Item RF_Socket_3 received command or
    // Item RF_Socket_4 received command 
  then

    logInfo("Power_Plug", "Member " + triggeringItem.name + " to " + receivedCommand)
    
    try {
      // Lock transmitter so other executed rules dont't use it at the same time.
      // Concurrent calls of the rule will wait till the resource is free again.
      transmitter.lock()

      // Get the item which triggered the rule
      // Split the name and get the second part, the number to set as command.
      val num = triggeringItem.name.toString.split("RF_Socket_").get(1) 
      
      // Set the command which should be executed to the output channel 
      // Auto trigger will then execute the Thing.
      if(receivedCommand == ON){
        Remote_Send_Args.sendCommand( num +" on")
      }else{
        Remote_Send_Args.sendCommand( num +" off")
      }

       // Wait for the command to complete
      Thread::sleep(100)
      if(Remote_Send.state != OFF){
        Thread::sleep(100)
      }

      // Multiple trigger do not work if there is no break here
      // maybe external skript needs some time to properly free resources.
      Thread::sleep(200)
      logInfo("Power_Plug", Remote_Send_Out.state.toString.replaceAll("\r|\n"," ") )
    }catch(Throwable t) {}
    finally {
        // Free the resource for the next call.
        transmitter.unlock()
    }
 end
sitemap rf433 label="433mhz Poweroutlets"
{
    Frame label="Poweroutlets"
    {
        Switch item=Gplugs label="All Power Plugs"
        Text item=PlaceHolder label="" icon="none"
        Switch item=RF_Socket_1 label="Power Plug 1" mappings=[ "ON"="ON", "OFF"="OFF" ]
        Switch item=RF_Socket_2 label="Power Plug 2" mappings=[ "ON"="ON", "OFF"="OFF" ]
        Switch item=RF_Socket_3 label="Power Plug 3" mappings=[ "ON"="ON", "OFF"="OFF" ]
        Switch item=RF_Socket_4 label="Power Plug 4" mappings=[ "ON"="ON", "OFF"="OFF" ]
    }
}

rf433_send.sh (place in /opt/ )

#!/bin/bash

# must be called with a parameter
# rf433_send.sh [channel] [command]
# will send the associated command to the corresponding RF outlet over 433mhz radio.

if [ $# -lt 2 ] #Check to see if at least two parameters was supplied
then
	echo "Must be called with the channel id and command to send to the RF outlet"
	echo "example: " $0 "1 on"        # $0 is the name of the program
	echo "For help, use: " $0 " -? "
	exit 1
fi

  if [ $# -ge 2 ]       # if there were 2 or more parameters
then
	case $1 in
	"-?")       echo "Supported channel id is 1..14" ;;
	"1")  	
		case $2 in  
			"on")       echo /opt/RF433Utils/RPi_utils/codesend 1381717
						/opt/RF433Utils/RPi_utils/codesend 1381717
				;;
			"off")      echo /opt/RF433Utils/RPi_utils/codesend 1381719 
						/opt/RF433Utils/RPi_utils/codesend 1381719
				;;
			*)			echo "unknown state, should be "on" or "off""
				;;
			esac
		;;
	"2")  	
		case $2 in  
			"on")       echo /opt/RF433Utils/RPi_utils/codesend 1394005 
						/opt/RF433Utils/RPi_utils/codesend 1394005
				;;
			"off")      echo /opt/RF433Utils/RPi_utils/codesend 1394004 
						/opt/RF433Utils/RPi_utils/codesend 1394004
				;;
			*)			echo "unknown state, should be "on" or "off""
				;;
			esac
		;;
	"3")  	
		case $2 in  
			"on")  		echo /opt/RF433Utils/RPi_utils/codesend 1397077 
						/opt/RF433Utils/RPi_utils/codesend 1397077
				;;
			"off")      echo /opt/RF433Utils/RPi_utils/codesend 1397076 
						/opt/RF433Utils/RPi_utils/codesend 1397076
				;;
			*)			echo "unknown state, should be "on" or "off""
				;;
			esac
		;;
	"4")  	
		case $2 in
			"on")       echo /opt/RF433Utils/RPi_utils/codesend 1397845 
						/opt/RF433Utils/RPi_utils/codesend 1397845
				;;
			"off")      echo /opt/RF433Utils/RPi_utils/codesend 1397844 
						/opt/RF433Utils/RPi_utils/codesend 1397844
				;;
			*)			echo "unknown state, should be "on" or "off""
				;;
			esac
		;;
	*)		echo "unknown device id, should be "1"..."14""
		;;
	esac
else
	echo "input needs a second parameter"
	echo "usage: " $0 " [device-id] [state]"
	echo "device id is 1-16, state is on/off"
fi
# end of the input case
exit 0

Please note that the shellscript is only prepared for device id 1 thru 4.

My RF outlets are from Luxorparts, so if you have another brand, you might need to record your codes with a FM reciever first.

In general - yep, you have the right approach. You can only pass one param to exec binding, so must combine everything into a single string to pass.

What is the purpose of this lot?
while-loops in rules are scary; always a risk of looping forever, and rarely needed in openHABs event driven environment.

while(Remote_Send.state != OFF) {
Right there is a race condition.
You’ve set your exec command Thing up for autorun.
So, shortly after you trigger it with Remote_Send_Args.sendCommand() earlier on in your rule, the binding will turn Remote_Send ON to indicate script running. Shortly after that, the script will end and the binding will turn Remote_Send OFF again.
“Shortly” - that’s a bit vague. It all depends on many factors.

Now, the problem is that when your rule arrives at while(Remote_Send.state != OFF) { , the binding may or may not have already issued the start-ON. It may even have already issued the finished-OFF … and there’s a loop forever trap for your rule. You’re waiting forever for a script to start that has already finished.

I strongly recommend “don’t do that”.
Note that if you mess the params part up and the script cannot run, you get into wait-forever-to-begin loop as well.

If you want to do something when exec run goes ON - display a message when the script begins, say - then trigger a rule from it

when
   Item Remote_Send changed to ON

If you want to display a message or analyse results when script ends - use a separate rule for that too.

when
   Item Remote_Send changed from ON to OFF
1 Like

And in those rare cases where you really do need to wait for something and can’t trigger a Rule when it happens (which is not the case here) you can use Design Pattern: Looping Timers so while you are in a check/wait loop at least you are not consuming any resources while waiting.

1 Like

thanks for the input, i have now modified the rules with the following modification:

      // Wait for the command to complete
      Thread::sleep(100)
      if(Remote_Send.state != OFF){
        Thread::sleep(100)
      }

      // Multiple trigger do not work if there is no break here
      // maybe external skript needs some time to properly free resources.
      Thread::sleep(200)

You didn’t listen.

Im sorry, i might be a bit slow, but i fail to understand what your´e referring to…?
I did remove all while-loops…

or are you referring to the Thread::sleep() commands?

Im guessing that wait-times are needed in order to give the FM transmitter time to do its job and im not grasping how the timer should be implemented, i dont understand how that timer.reschedule() would run anything after the timer ran out…
…I am extra thick today, got a fever…

Yes. Short sleeps are on a few occasions necessary, but always present a risk of clagging your system up and preventing it doing other tasks.
If you’re not even sure what they are there for, it is especially dubious.

Can we see the whole rule again now, and try to evaluate it?

That’s okay, this asynchronous stuff is NOT easy to grasp.

Im pretty sure the timeout is to allow the transmitter to finish its job, but im not the original author of the script, i merely modified it heavily in order to adapt it to the RF transmitter.

Here´s the current rules-file:

import java.util.concurrent.locks.ReentrantLock

val ReentrantLock transmitter = new ReentrantLock

rule "rf433Rule"
  when
    Member of Gplugs received command
    // Item RF_Socket_1 received command or
    // Item RF_Socket_2 received command or
    // Item RF_Socket_3 received command or
    // Item RF_Socket_4 received command 
  then

    logInfo("Power_Plug", "Member " + triggeringItem.name + " to " + receivedCommand)
    
    try {
      // Lock transmitter so other executed rules dont't use it at the same time.
      // Concurrent calls of the rule will wait till the resource is free again.
      transmitter.lock()

      // Get the item which triggered the rule
      // Split the name and get the second part, the number to set as command.
      val num = triggeringItem.name.toString.split("RF_Socket_").get(1) 
      
      // Set the command which should be executed to the output channel 
      // Auto trigger will then execute the Thing.
      if(receivedCommand == ON){
        Remote_Send_Args.sendCommand( num +" on")
	Thread::sleep(50)
        Remote_Send_Args.sendCommand( num +" on")
	Thread::sleep(50)
        Remote_Send_Args.sendCommand( num +" on")
      	Thread::sleep(50)
      }else{
        Remote_Send_Args.sendCommand( num +" off")
	Thread::sleep(50)
        Remote_Send_Args.sendCommand( num +" off")
	Thread::sleep(50)
        Remote_Send_Args.sendCommand( num +" off")
      	Thread::sleep(50)
      }
	
      // Wait for the command to complete
      if(Remote_Send.state != OFF){
        Thread::sleep(100)
      }

      // Multiple trigger do not work if there is no break here
      // maybe external skript needs some time to properly free resources.
      Thread::sleep(200)
      logInfo("Power_Plug", Remote_Send_Out.state.toString.replaceAll("\r|\n"," ") )
    }catch(Throwable t) {}
    finally {
        // Free the resource for the next call.
        transmitter.unlock()
    }
 end

As you see, i added a couple of extra “send commands”, to repeat the ON/OFF signals 3 times since the RF outlets doesnt always react to the first command, even from the origial remote control, its reccomended to keep the remote button pressed untill the light goes on.

According to the RF sniffer program, the remote transmits aproximately 4-10 codes per second.

As i understand it, the Thread::sleep() value is in milliseconds?

Understood, sometimes you just have to cobble something up.
It is better to use positive feedback for retries, but I’m guessing you’re just sending blindly here? No status report from the device?

Okay, so the task is to rate-limit commands sent to RF, while at the same time duplicating commands.
May I ask how many RF devices you have / are likely to get?
It is possible to construct an elaborate command queuing management system … but overkill for a single device.

Let’s do it the clonky way for now.

Tweak that a little to start with; if the script messes up you don’t want the exec blocking everything for 20 seconds. Reduce to 2 or 3, to allow time for hiccups. If the script hasn’t finished by then, it’s not going to.

Explanation, that’s a lock used here to give a one-at-a-time effect on executing the rule that uses it.
That’s probably a good idea where the rule takes a relatively long time.
But with caution - errors in rules can be spiteful and never release the lock, and that just ends the day for any further use until reloaded.
We must keep the rule simple and bombproof, to be be error free in all circumstances.

Okay, so here your rule grabs the lock if available.
Note if it is not avail - the rule waits for it.
It is possible to just abort instead, but you probably do want this queuing action.

But let’s rearrange a little - you want the lock/queue action at the earliest possible moment in the rule. Confident in your rule triggering now, shift the logInfo() to minimize work before locking.

then
   
    try {
      // Lock transmitter so other executed rules dont't use it at the same time.
      // Concurrent calls of the rule will wait till the resource is free again.
      transmitter.lock()
      logInfo("Power_Plug", "Member " + triggeringItem.name + " to " + receivedCommand)

Alright, let’s make the “action” part a bit slicker.

     var rfparam = num + " off"
     if (receivedCommand == ON){
        rfparam = num + " on"
     }
     Remote_Send_Args.sendCommand( rfparam )
     Thread::sleep(100)
     Remote_Send_Args.sendCommand( rfparam )
     Thread::sleep(100)
     Remote_Send_Args.sendCommand( rfparam )

Despite the horror of sleep, it is the simplest way to do this.
In light of

sleeps between extended to 100mS.
You may wish to experiment with 150 or 200 to improve your chances.

Okay, to the end part of this section. As already discussed, there is little point in testing the exec’s RUN state here. We don’t know if it has started or is already all over when testing inline in the rule, because it all happens asynchronously.
And frankly we don’t care when just blindly pushing messages.
Plus we’ve sent three commands by now … which response are you looking at, exactly?

All we really want to do is wait a suitable time before releasing the lock, and allowing further use.
A simple 200 or 250mS sleep will do here, between last command and lock release.

By all means do that last logInfo() for luck - it’ll probably give you the last commands result by now.

Rules aren’t very good at arriving at the catch() block in case of error, but let’s at least try to see whats happened if it does

} catch(Throwable t) {
   logInfo("Power_Plug", "try-catch throws error " + t)
}

Recognize this is a dirty fix ; your rule will grab a system thread for a second or so (like an hour in Pi years) and possibly prevent other business being done.
In reality, with just one or four plugs being switched once or twice a day, that’ll be acceptable.

If you plan to fill the house with these things, better management is well advised.

I’ve a better approach to handling this presented in Design Pattern: Gate Keeper. That eliminates the dangerous lock and implements the sleeps in a better way. If you use Scripted Automation, I’ve submitted this as a library capability at https://github.com/openhab-scripters/openhab-helper-libraries/pull/232.

Essentially you just sendCommand the commands one after the other which triggers a Rule that adds the commands to a queue and works them off with the 50 msec between both commands.

But also already implemented in the above DP.

And this in itself is a problem. If you have five of these Rules running waiting for access to the lock, no other Rules will be able to run. That is the danger of locks above and beyond those you’ve already mentioned.

I do agree with this though. For a few plugs blocking the Rule probably isn’t that big of a deal. But if you a lot you will want to use Gatekeeper.

Im only using 4 sockets at this moment, but since they cost less than 10€/piece, i was considering adding a bunch of them, as a safety-timer on the coffe mashine for instance.
But the ikea tradfri outlets are actually not that expensive, so i might opt for those instead…

The gatekeeper solution looks impressive and it would really be neat to have a command-que, but i kind of agree that it seems overkill at this moment. :slight_smile:

I just finished updating the rules and things files and everything seems to be working just fine! :slight_smile:

Thanks folks, for all your help and input!