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”
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
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.
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
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.
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.
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.
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
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
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) ~[?:?]