[SOLVED] Multiple IF Statements with Multiple Actions

Hi, I’ve been struggling to get google to answer with an appropriate response to “is the shed door open?” So, as I have little skills in coding I thought I would do it this way with recorded sound files.
I have created a virtual switch called “HouseCheck” so when activated the below rule runs.
The delay is to allow time for google to say “OK turning on House Check” before the audiofiles play.
There are 3 item states to check but I only get a response from one. Im not sure if they are all trying to play at once or if the rule is just terminating after the first response.
Any help appreciated.

var Timer atimer = null
rule "House Door Status"
when
    Item HouseCheck changed to ON
then
    val state1 = Front_Door_Status.state
    val state2 = Garage_Door_Status.state
    val state3 = SB_02_P1.state

    var volup = 30
	var voldown = 20
	//var delay1 = 3
    
    Kitchen_Volume.sendCommand(volup)
    atimer?.cancel
	atimer = createTimer(now.plusSeconds(3)) [|

    if (state1 == OPEN) 
		playSound("FrontisOpen.mp3")
	
 	if (state1 == CLOSED)
		playSound("FrontisClosed.mp3")

    if (state2 == OPEN) 
		playSound("GarageisOpen.mp3")
	
 	if (state2 == CLOSED) 
		playSound("GarageisClosed.mp3")
     
     if (state3 == OPEN) 
		playSound("ShedisOpen.mp3")
	 
 	if (state3 == CLOSED) 
		playSound("ShedisClosed.mp3")	
	]         
    Kitchen_Volume.sendCommand(voldown)
	HouseCheck.sendCommand(OFF)
end

Add a “logInfo” before every if-statement and check the openhab.log to see what is going on. I would guess, that your problem is, that everything is trying to play at once, since there is no delay between the commands. You created a timer, so that google can answer with “OK turning on House Check”. If this is needed to get a response from your rule, then you may also need this delay after each “playSound”.

I can’t say how the playSound is implemented for GoogleHome, other bindings do that in way that openHAB wouldn’t know how long the sound. Since the sound is played assynchron the rule will try to start the following sound not knowing if the AudioSink is free….

Why don’t you use a TTS Service at let your GoogleHome say a simple composed string like:

“all doors are closed”
“doors open are garage and shed”
“all doors are open”

1 Like
var Timer atimer1 = null
var Timer atimer2 = null
var Timer atimer3 = null

rule "House Door Status"
when
    Item HouseCheck changed to ON
then
    var volup = 30
    var voldown = 20
    
    Kitchen_Volume.sendCommand(volup)
    atimer1?.cancel
    atimer1 = createTimer(now.plusSeconds(3), [ |
        if (Front_Door_Status.state == OPEN) playSound("FrontisOpen.mp3")
	if (Front_Door_Status.state == CLOSED) playSound("FrontisClosed.mp3")
    ])
    atimer2?.cancel
    atimer2 = createTimer(now.plusSeconds(6), [ |
        if (Garage_Door_Status.state == OPEN) playSound("GarageisOpen.mp3")
        if (Garage_Door_Status.state == CLOSED) playSound("GarageisClosed.mp3")
    ])
    atimer3?.cancel
    atimer3 = createTimer(now.plusSeconds(9), [ |
        if (SB_02_P1.state == OPEN) playSound("ShedisOpen.mp3")
 	if (SB_02_P1.state == CLOSED) playSound("ShedisClosed.mp3")	
        Kitchen_Volume.sendCommand(voldown)
	HouseCheck.sendCommand(OFF)
    ])
end

You may have to play with the timer durations a bit
I removed the val state lines. If the state of an item changes between the start of the rule and the timer execution would would have been none the wiser.

While you can get away with that sort of format for a single line “if”, personally I always use curly braces. You must use them for multi-line “if” blocks anyway, and it serves to remind me what’s going on.

if (state1 == OPEN) { playSound("FrontisOpen.mp3") }

if (state1 == OPEN) {
   playSound("FrontisOpen.mp3")
   something else
}

Hi Opus, I tried a few TTS voices but didn’t like them as they were were not natural and I’m not smart enough to get the google cloud TTS to work. So I used google to repeat my statements and recorded them.
You are right about the delay between each action and the results playing all at once. I added more timers between them but again don’t know enough about code and where to put the brackets and what the different brackets do.
Cheers

Hi vzorglub, I will try this tonight and let you know. I know the VS Code will complain about variables inside a Lamda but I will just change them to static values.
I can also see that the timers must all start at the same time which I had no clue about as I thought the rules just did one thing after the next.
James

They do - but creating a timer is only scheduling a little job for future execution. Once scheduled, the rule moves on.

When the appointed comes around, the little job runs but the rule that spawned it has most likely finished long ago.

Hi rossko57, so only the things inside the [|…] run after the timer expires? Things after the bracket run as soon as the timer is scheduled. Is that right?
Thanks

I’m not at home so can’t hear the response but running the rule vzorglub posted and looking at the logs, the correct sound files are running 3 seconds apart which is awesome. I’ll confirm when home.
Cheers

Just one more question regarding timer functions. With the below example, when slltimer2 expires two items are tuned off and one is turned on. Then only after slltimer3 expires, that item is turned off. Note the second timer does not begin until the first has finished. I can confirm this works but is different from previous code where timers all cue at once.

  }
	
	if (activetime3) {
    	SW3_01_1.sendCommand(ON)
		SW3_01_3.sendCommand(ON)
		slltimer2?.cancel
		slltimer2 = createTimer(now.plusMinutes(10)) [|
		SW3_01_1.sendCommand(OFF)
		SW3_01_3.sendCommand(OFF)
        SW2_02_1.sendCommand(ON) 
	]
		             
    	slltimer3?.cancel
		slltimer3 = createTimer(now.plusMinutes(2)) [|
		SW2_02_1.sendCommand(OFF)
	] 
	}
	
               
end

What makes you think that?

Note that if you command something OFF that is already OFF, it doesn’t change.
You can look into events.log to see commands even when they don’t cause any changes.
You can add logInfo to timers to provide clues, and see the results in openhab.log
logInfo("myTimer", "Timer three is running")

It is possible to embed a createTimer in another Timers execution block, in order to create timers that do “cascade” in sequence the way you describe.
But your example is not a cascade.

Note also, once a timer is scheduled, it is scheduled. If you edit and re-load a rules file five minutes after it created a timer for ten minutes hence, that timer will still run at its appointed time in five more minutes. That can lead to surprises during tinkering :smiley:

Hi Rossko57, I’m sure you are right but I don’t understand how the above works then. At the end of slltimer2, which is 10 minutes, SW2_02_1 turns ON. Two minutes later it turns off after slltimer3 expires. Given the previous example, I would think the value should be 12 minutes and not 2.
The scenario is, a motion sensor trips, turning on the lounge lights for 10 minutes while I put my shoes on and grab a coffee. Then the lounge lights turn off and the front yard light turn on for two minutes so I can get to the car and go before it turns off.
Sorry if I’m missing something but this is not the behaviour of the other rule above…

Also this works a treat for original question but i’m sure it can be improved.

var Timer timer1 = null
var Timer timer2 = null
var Timer timer3 = null
var Timer timer4 = null
var Timer timer5 = null
var Timer timer6 = null

rule "House Door Status"
when
    Item HouseCheck changed to ON
then
    //var volup = 30
    //var voldown = 20
    
    Kitchen_Volume.sendCommand(40)
    timer1?.cancel
    timer1 = createTimer(now.plusSeconds(3), [ |
        if (Front_Door_Status.state == OPEN) { 
			playSound("FrontisOpen.mp3")			
			}
		if (Front_Door_Status.state != CLOSED) { 
			playSound("FrontisClosed.mp3")
		}
    ])
    
	timer2?.cancel
    timer2 = createTimer(now.plusSeconds(6), [ |
        if (Garage_Door_Status.state == OPEN) { 
			playSound("GarageisOpen.mp3")
		}
        if (Garage_Door_Status.state == CLOSED) { 
			playSound("GarageisClosed.mp3")
		}
    ])

    timer3?.cancel
    timer3 = createTimer(now.plusSeconds(9), [ |
        if (SB_02_P1.state == OPEN) { 
			playSound("ShedisOpen.mp3")
		}
 		if (SB_02_P1.state == CLOSED) { 
			 playSound("ShedisClosed.mp3")
		}
	])

	timer4?.cancel
    timer4 = createTimer(now.plusSeconds(12), [ |
        if (Front_Lights.state == ON) { 
			playSound("FrontLightsOn.mp3")
		}
 		if (Front_Lights.state == OFF) { 
			 playSound("FrontLightsOff.mp3")
		}
	])

	timer5?.cancel
    timer5 = createTimer(now.plusSeconds(15), [ |
        if (Back_Lights.state == ON) { 
			playSound("BackLightsOn.mp3")
		}
 		if (Back_Lights.state == OFF) { 
			 playSound("BackLightsOff.mp3")
		}
			HouseCheck.sendCommand(OFF)
	])
	timer6?.cancel
    timer6 = createTimer(now.plusSeconds(20), [ |
	Kitchen_Volume.sendCommand(20)
	])
end

I can only comment on what you show/tell us. For that on/off timer rule fragment, these timers are NOT cascaded.
Of course, I don’t know what else the rule does nor how often it runs. Now it seems it is triggered by a motion sensor, so I can guess it actually gets triggered many times.

Okay, so every time this fragment runs it turns things ON immediately, and schedules X for off in ten and Y for off in two minutes.
I cannot see if your timer “handles” have been declared as global variables or not. This makes a big difference to behaviour.

If they are not global, new timers are created every time the rule runs, and there will be many OFFs in the future. Probably interspersed with ONs from new motion triggers.

If they are global, you effectively reschedule the OFF timers. So long as new motion triggers arrive more often than two minutes, the OFF actions will be deferred.

I repeat the advice to look in events.log for a clearer understanding. Add logInfo to your rule to trace progress.

1 Like

I’d like to recommend another option here, which is a simple state machine:

// always define global var and val at top of the file!

var Timer tStatus = null                                                                                          // timer for house status
var Number nStatus                                                                                                // step for playing status 

rule "House Door Status"
when
    Item HouseCheck changed to ON                                                                                 // maybe better use received command ON
then
    Kitchen_Volume.sendCommand(40)                                                                                // set volume
    tStatus?.cancel                                                                                               // delete existing timer
    nStatus = 0                                                                                                   // reset timer
    tStatus = createTimer(now.plusMillis(500), [ |                                                                // start initial timer
        var String strPlaysound                                                                                   // which file to play
        nStatus += 1                                                                                              // or use nStatus = nStatus + 1
        switch (nStatus) {
            case 1: {                                                                                             // first run
                strPlaysound = if (Front_Door_Status.state == OPEN) "FrontisOpen.mp3" else "FrontisClosed.mp3"    // ternary operator is way more simple
            }
            case 2: {                                                                                             //second run...
                strPlaysound = if (Garage_Door_Status.state == OPEN) "GarageisOpen.mp3" else "GarageisClosed.mp3"
            }
            case 3: {
                strPlaysound = if (SB_02_P1.state == OPEN) "ShedisOpen.mp3" else "ShedisClosed.mp3"
            }
            case 4: {
                strPlaysound = if (Front_Lights.state == ON) "FrontLightsOn.mp3" else "FrontLightsOff.mp3"
            }
            case 5: {
                strPlaysound = if (Back_Lights.state == ON) "BackLightsOn.mp3" else "BackLightsOff.mp3"
            }
            default: {                                                                                            // last run
                Kitchen_Volume.sendCommand(20)                                                                    // reset volume
                HouseCheck.sendCommand(OFF)                                                                       // reset triggering item
                tStatus = null                                                                                    // reset timer
            }
        }
        if(nStatus < 6) {                                                                                         // if not last step
            playSound(strPlaysound)                                                                               // play sound
            tStatus.reschedule(now.plusSeconds(3))                                                                // reschedule timer
        }
    ])
end

This way you won’t need more than one timer, and it’s serialized automaticly.

Please be aware that openHAB has an asynchronous workflow. playSound() will be started and the rule will immediately do the next line of code. Same aplies for postUpdate() and sendCommand(), therefor the timer. Of course the scheduled time has to be longer than duration of played sound.

1 Like

Hi Guys,
I just wanted to say thanks to you both for your responses before too much time passed. I’m away for a few days and will look at this when I get home. When I see someones code I can see the logic in it but certainly can’t start from scratch myself.
Udo, could you please confirm the double close bracket between each case.

Cheers

So if each sound file is 2 seconds long does the timer need to be longer than the sum or just the longest single file?
Also, the first timer needs to be long enough for google to say “OK turning on HouseCheck” before it plays the first sound file.
Cheers

Just the longest one.

Correct

Hi Udo,
So this is what I went with and unfortunately all I get is the volume up (40) and volume down (20) with nothing in between. I’m sure its the bracket locations that I have wrong.
Any help appreciated.
James

var Timer tStatus = null                                                                                          // timer for house status
var Number nStatus                                                                                                // step for playing status 

rule "House Door Status"
when
    Item HouseCheck received command ON                                                                                 // maybe better use received command ON
then
    Kitchen_Volume.sendCommand(40)                                                                                // set volume
    tStatus?.cancel                                                                                               // delete existing timer
    nStatus = 0                                                                                                   // reset timer
    tStatus = createTimer(now.plusSeconds(3), [ |                                                                // start initial timer
        var String strPlaysound                                                                                    // which file to play
        nStatus = nStatus + 1                                                                                     // or use nStatus = nStatus + 1  or  nStatus += 1 
        switch (nStatus) {
            case 1: {                                                                                             // first run
                strPlaysound = if (Front_Door_Status.state == OPEN) "FrontisOpen.mp3" else "FrontisClosed.mp3"    // ternary operator is way more simple
                }
           
            case 2: {                                                                                             //second run...
                strPlaysound = if (Garage_Door_Status.state == OPEN) "GarageisOpen.mp3" else "GarageisClosed.mp3"
                }
            
            case 3: {
                strPlaysound = if (SB_02_P1.state == OPEN) "ShedisOpen.mp3" else "ShedisClosed.mp3"
                }
            
            case 4: {
                strPlaysound = if (Front_Lights.state == ON) "FrontLightsOn.mp3" else "FrontLightsOff.mp3"
                }
            
            case 5: {
                strPlaysound = if (Back_Lights.state == ON) "BackLightsOn.mp3" else "BackLightsOff.mp3"
                }

            case 6: {
                strPlaysound = if (Back_Door_Status.state == OPEN) "BackDoorOpen.mp3" else "BackDoorClosed.mp3"
                }
            
            default: {                                                                                            // last run
                Kitchen_Volume.sendCommand(20)                                                                    // reset volume
                HouseCheck.sendCommand(OFF)                                                                       // reset triggering item
                tStatus = null                                                                                    // reset timer
                }
                }
        if (nStatus < 7) {                                                                                         // if not last step
            playSound(strPlaysound)                                                                               // play sound
            tStatus.reschedule(now.plusSeconds(3))
            }                                                                                                     // reschedule timer
        
    ])
    
    
end

Here is a log sample.

2019-09-11 09:18:59.917 [ome.event.ItemCommandEvent] - Item 'HouseCheck' received command ON

2019-09-11 09:18:59.929 [vent.ItemStateChangedEvent] - HouseCheck changed from OFF to ON

2019-09-11 09:19:01.057 [ome.event.ItemCommandEvent] - Item 'Kitchen_Volume' received command 40

2019-09-11 09:19:01.064 [vent.ItemStateChangedEvent] - Kitchen_Volume changed from 20 to 40

2019-09-11 09:19:04.061 [ome.event.ItemCommandEvent] - Item 'Kitchen_Volume' received command 20

2019-09-11 09:19:04.074 [ome.event.ItemCommandEvent] - Item 'HouseCheck' received command OFF

==> /var/log/openhab2/openhab.log <==

2019-09-11 09:19:04.070 [ERROR] [org.quartz.core.JobRunShell         ] - Job DEFAULT.2019-09-11T09:19:04.049+10:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {

  var strPlaysound

  <null>.nStatus = <XBinaryOperationImplCustom>

  org.eclipse.xtext.xbase.impl.XSwitchExpressionImpl@5e966e

  org.eclipse.xtext.xbase.impl.XIfExpressionImpl@6cc589

} ] threw an unhandled Exception: 

java.lang.NullPointerException: File cannot be played as fileName is null.

	at java.util.Objects.requireNonNull(Objects.java:228) ~[?:?]

	at org.eclipse.smarthome.core.audio.internal.AudioManagerImpl.playFile(AudioManagerImpl.java:176) ~[?:?]

	at org.eclipse.smarthome.core.audio.internal.AudioManagerImpl.playFile(AudioManagerImpl.java:161) ~[?:?]

	at org.eclipse.smarthome.model.script.actions.Audio.playSound(Audio.java:40) ~[?:?]

	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]

	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]

	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]

	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeOperation(XbaseInterpreter.java:1086) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeOperation(XbaseInterpreter.java:1061) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._invokeFeature(XbaseInterpreter.java:1047) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeFeature(XbaseInterpreter.java:992) ~[?:?]

	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:151) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:902) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:865) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:224) ~[?:?]

	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:447) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:228) ~[?:?]

	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:460) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:244) ~[?:?]

	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]