I would like to have a TTS announcement over all available squeezeboxes in my house when the doorbell rings. I have simply made a rule that creates an announcement and sends it to a squeezebox, then it creates the same announcement again for the next squeezebox, etc.
when
Item Doorbell changed from CLOSED to OPEN
then
squeezeboxSpeak(“sbwerkplaats”, “There is someone at the door”)
squeezeboxSpeak(“sbslaapkamer”, “There is someone at the door”)
end
First of all it is not very elegant that the announcement needs to be created multiple times, but worse is that when a squeezebox is not available (separated from mains), the system waits for a timeout before addressing the next squeezebox, which means I get the announcement too late.
Failed solutions so far:
I would like to create a group with active squeezeboxes to send the command to. Then I only need to ‘TTS’ the announcement once and only sent it to active boxes. Problem is I think that the command is sent to the playerID’s directly and not to items. So no luck.
Other option: check before each command whether the player is available. It turns out that checking the status of the ‘Power’ item (which is actually a command, I know) doesn’t do any good. It seems that this variable does not contain the availability of the player.
I guess this is typically a use case for TTS. Am I missing something or is there a smarter way of doing this?
You can’t create a group out of actions, only Items. The wiki still documents an http:stream command you can assign to Items but it says it is obsolete so don’t know if it still works or if so, will continue to work into the future. And even if it does work, without looking at the code, I have no reason to believe right now that sending the command to the Group of Items will send it to them all simultaneously or in order so it may not solve your problem anyway.
Ideas that come to mind include:
create a proxy switch for each sqeezebox and sendCommand ON to those proxy switches from your Doorbell rule. Then create a rule for each proxy switch to call the Speak action for that device. What this is doing is kicking off multiple threads (one per proxy switch) running in parallel so even if one sqeezebox isn’t available and times out it doesn’t keep the others waiting.
create a very short Timer to send the command along the lines of:
createTimer(now.plusMilliseconds(5), [| squeezeboxSpeak("sbwerkplaats", "There is someone at the door") ]
createTimer(now.plusMilliseconds(5), [| squeezeboxSpeak("sbslaapkamer", "There is someone at the door")]
The concept is the same as the previous option, it will kick off another thread, this time using a Timer instead of the event processor. And again, because it is running in a separate thread, one of them timing out will not block the others from immediately running. You may need to experiment with how many milliseconds you need to set the timer for to make sure that it actually goes off.
If you want them to trigger really really close together, you will probably need to use the JSR233 binding and code this rule in Jython which will run more efficiently. You will still want to run each call to the actions in a separate thread so one being unavailable won’t block the rest.
Or you could add a virtual items for each Squeezebox and one for all devices for announcements - then in your doorbell rule just send an update to the all devices item, where you have a rule which forwards the message to each individual device item, which in turn runs the squeezeboxSpeak action.
This will ensure each device receives the announcement instantly, and it becomes very easy to add/remove devices (just by updates the all devices forwarding rule).
It also makes it easy to add logic around when the announcement should be allowed or not - i.e. add a check in the all devices rule to suppress the announcement if it is after 10pm or before 6am. Likewise you can add suppression rules to individual devices via their own announcement item.
I find this sort of abstraction makes for much easier configuration of rules. But might be a bit over the top for your case!
Well, it seems I am missing some libraries or something and I can’t figure out how to solve it.
Trying the first option: using ‘createTimer’ and ‘now’ I think I need to import org.openhab.model.script.actions.timer for the first function, and org.joda.time.* for the second one. Now my ‘rules’ file is as follows:
import org.openhab.core.library.types.*
import org.openhab.model.script.actions.*
import org.openhab.action.squeezebox.*
import org.openhab.io.squeezeserver.*
import org.joda.time.*
rule "Doorbell ringing"
when
Item Doorbell changed from CLOSED to OPEN
then
createTimer(now().plusMilliseconds(5), [ |squeezeboxSpeak("sbslaapkamer", "There is someone at the door") ])
createTimer(now().plusMillisecones(5), [ |squeezeboxSpeak("sbwerkplaats", "There is someone at the door") ])
end
This is throwing an error:
2016-05-30 10:21:37.312 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule ‘Doorbell ringing’: The name ‘.plusMilliseconds()’ cannot be resolved to an item or type.
So this looks like the ‘now’ function is not known so I figured I was missing the joda library somehow.
I tried to add the joda library to my system but then I get lost.
Downloading the joda time library (https://github.com/JodaOrg/joda-time/releases/download/v2.9.4/joda-time-2.9.4-dist.tar.gz) I find a joda-time-2.9.3.jar file in that package. I placed that file in the Openhab addon directory as well as in the general Java one, renamed it to a filename containing periods i.l.o. hyphens, but it doesn’t seem to work.
I actually think I am making it more complex than it really is. Can someone please put me on the right track here.
Thanks, that helped: I had to use ‘plusMillis’ instead of ‘plusMilliseconds’; I didn’t think to check that. Only thing is that it does not make a difference as the system is still waiting for the timeout before the next squeezebox is addressed. I’ll see if I can figure out the other options you guys gave.
I could test it much easier: just making a rule per squeezebox does the same thing: five times the same trigger and just the one squeezeboxSpeak in each execution block.
Which leads to the next problem: the TTS engine is asked five times the exact same thing at the same moment. That sometimes leads to an incomplete tts.mp3, so sometimes only half the sentence is spoken.
I tried adding createTriggers with different timing to the execution blocks to make sure there is some time between the TTS requests, but somehow, even though I am using the separate rules, it seems I have to wait for commands to timeout before the last one is executed. So apart from being a very dirty solution it is one that does not work.
I leave it with the separate rules for now and don’t worry too much about half sentences.
some thoughts about the squeezebox implementation as it is right now:
Since I don’t have to construct the announcement (no variables like time or temperature, it’s fixed), it would be OK for me to just send an mp3 to the squeezeboxes. It would also give me the option to use a sound other than spoken text. I can’t do that now because I then don’t have the option to resume the currently playing stream.
It would be even more flexible if this was combined with separating the TTS action from playing the mp3 itself. Then a tts.mp3 could be constructed once and send to all squeezeboxes. Worrying about concurrency (another rule creating a tts.mp3) could be solved by naming the created mp3 from within the TTS creation command, making them unique per rule.
Maybe one day I can write it myself but it would be a little beyond me right now…