I have the same problem with say and sending TTS text to chromecast devices. Since I have different “sources” sending TTS text, combining text into one phrase was not an option for me.
I am using the following rule as a “workaround” which works very well for my purposes. I have shortened the rule (I have more in my rule), hopefully I have not introduced any errors…
I have two items:
Item TTSText: Send all Text for Text-to-speech to this item
The rule will introduce the delay based on the length of the text and send it to the second Item:
Item TTSText_Speaker: Item for say command
I had to introduce bQueueSpeakerError as failsafe. Sometimes there seems to be an error in the timer (I do not know why). bQueueSpeakerError makes sure that the queue will be processed even if there is a timer exception. Probably not the most elegant way to do this but it works for me
Maybe you can try this. Hope this helps.
import java.util.concurrent.ConcurrentLinkedQueue
val ConcurrentLinkedQueue<String> queueSpeaker = new ConcurrentLinkedQueue()
var Timer timerSpeaker = null
var boolean bQueueSpeakerError = false
val int iTTSCharacterDelay = 120 // delay in ms per character
var boolean debug = false
rule "tts"
when
Item TTSText received command
then
queueSpeaker.add(receivedCommand.toString)
if (debug) {
logInfo("TTS", "Add to queue: " + receivedCommand.toString)
}
// Check if timer exists
if (timerSpeaker === null || bQueueSpeakerError) {
if (bQueueSpeakerError) {
bQueueSpeakerError = false
logError("ERROR", "Queue error in rule tts.rules: method entered because bQueueSpeakerError is true")
}
if (debug) {
logInfo("TTS", "No timer existing - create")
}
timerSpeaker = createTimer(now, [ |
try {
// if queue is not empty
if (queueSpeaker.peek !== null) {
var cmd = queueSpeaker.poll
// get next command from queue
if (debug) {
logInfo("TTS", "queue not empty - get command: " + cmd.toString)
}
TTSText_Speaker.sendCommand(cmd)
// If string is too short, put in 10 characters
if (cmd.length() < 10) {
cmd = "1234567890"
}
// reschedule timer - time is defined in iTTSCharacterDelay
if (debug) {
logInfo("TTS", "Timer reschedule: " + (cmd.length() * iTTSCharacterDelay).toString)
}
timerSpeaker.reschedule(now.plusMillis(cmd.length() * iTTSCharacterDelay))
} else {
if (debug) {
logInfo("TTS", "Queue empty - timer end: null")
}
timerSpeaker = null
}
} catch(Exception e) {
logError("ERROR", "Queue error in rule tts.rules: " + e.toString)
bQueueSpeakerError = true
if (timerSpeaker !== null) {
logError("ERROR", "Queue error in rule tts.rules: timerSpeaker.cancel")
timerSpeaker.cancel
}
logError("ERROR", "Queue error in rule tts.rules: timerSpeaker = null")
timerSpeaker = null
}
])
}
end
rule "tts speaker"
when
Item TTSText_Speaker received command
then
say(receivedCommand.toString, "googletts:nlNLWavenetB", "chromecast:chromecast:<ID>")
end
Say commands make use of a TTS service, the return of such should be handled asynchronically (IMHO) by the binding that handles the audiosink in question (you never told which ok you use). In order the get any information about when the stream is finished playing that binding would need to raise an event, If it does would be described in the documentation The “idling” channel you showed MIGHT be useable, be we can’t tell!
Your approach appears to be similar to Design Pattern: Gate Keeper. For those using Scripted Automation, I’ve submitted a version of this DP as a library module in the Helper Library.
I’m very happy to see you are using a ConcurrelyLinkedQueue instead of a ReentrantLock or some other less safe (for Rules DSL) approach.
it is indeed based on your excellent gate keeper design pattern, thank you very much for that I did re-write pretty much everything to get rid of ReentrantLocks.
Im trying to use this rule, but it is again problems with timers in it.
Please look the rules.
Timer Rule:
import java.util.concurrent.ConcurrentLinkedQueue
val ConcurrentLinkedQueue<String> queueSpeaker = new ConcurrentLinkedQueue()
var Timer timerSpeaker = null
val int iTTSCharacterDelay = 150 // delay in ms per character
rule "TTS Queue TextToSay"
when
Item TTSTextToSay received command
then
queueSpeaker.add(receivedCommand.toString)
logInfo("TTS", "Add to queue: " + receivedCommand.toString)
// Check if timer exists
if (timerSpeaker === null) {
logInfo("TTS", "No timer existing - create")
timerSpeaker = createTimer(now, [ |
// if queue is not empty
if (queueSpeaker.peek !== null) {
var cmd = queueSpeaker.poll
// get next command from queue
logInfo("TTS", "queue not empty - get command: " + cmd.toString)
TTSOutputToSpeaker.sendCommand(cmd)
// If string is too short, put in 10 characters
if (cmd.length() < 10) {
cmd = "1234567890"
}
// reschedule timer - time is defined in iTTSCharacterDelay
logInfo("TTS", "Timer reschedule: " + (cmd.length() * iTTSCharacterDelay).toString)
timerSpeaker.reschedule(now.plusMillis(cmd.length() * iTTSCharacterDelay))
} else {
logInfo("TTS", "Queue empty - timer end: null")
timerSpeaker = null
timerSpeaker?.cancel()
}
])
}
end
then Output to speaker
rule "TTS OutputToSpeaker"
when
Item TTSOutputToSpeaker received command
then
say(receivedCommand.toString, "googletts:ruRUStandardB", "chromecast:chromecast:e338f8cdb77fb98a6dc66361aca9cfe7")
logInfo("TTS", "Am saying now: " + receivedCommand.toString)
end
and last one - commant to send the texts
rule "Send text to say"
when
Item Test changed
then
TTSTextToSay.sendCommand("One. Tell the test Text")
TTSTextToSay.sendCommand("Two. Tell the test Text")
TTSTextToSay.sendCommand("Тhree. Tell the test Text")
TTSTextToSay.sendCommand("Four. Tell the test Text")
end
I can not get all 4 textes said correctly. I thing becuase i got one one, but many timers created at once.
Here is the sample of logs
2019-11-09 20:15:29.200 [ome.event.ItemCommandEvent] - Item 'TTSTextToSay' received command One. Tell the test Text
2019-11-09 20:15:29.213 [ome.event.ItemCommandEvent] - Item 'TTSTextToSay' received command Two. Tell the test Text
2019-11-09 20:15:29.217 [vent.ItemStateChangedEvent] - TTSTextToSay changed from Four. Tell the test Text to One. Tell the test Text
2019-11-09 20:15:29.234 [vent.ItemStateChangedEvent] - TTSTextToSay changed from One. Tell the test Text to Two. Tell the test Text
2019-11-09 20:15:29.247 [ome.event.ItemCommandEvent] - Item 'TTSTextToSay' received command Тhree. Tell the test Text
2019-11-09 20:15:29.250 [ome.event.ItemCommandEvent] - Item 'TTSTextToSay' received command Four. Tell the test Text
2019-11-09 20:15:29.270 [INFO ] [g.eclipse.smarthome.model.script.TTS] - Add to queue: One. Tell the test Text
2019-11-09 20:15:29.239 [INFO ] [g.eclipse.smarthome.model.script.TTS] - Add to queue: Two. Tell the test Text
2019-11-09 20:15:29.276 [vent.ItemStateChangedEvent] - TTSTextToSay changed from Two. Tell the test Text to Тhree. Tell the test Text
2019-11-09 20:15:29.291 [vent.ItemStateChangedEvent] - TTSTextToSay changed from Тhree. Tell the test Text to Four. Tell the test Text
2019-11-09 20:15:29.290 [INFO ] [g.eclipse.smarthome.model.script.TTS] - Add to queue: Тhree. Tell the test Text
2019-11-09 20:15:29.287 [INFO ] [g.eclipse.smarthome.model.script.TTS] - No timer existing - create
2019-11-09 20:15:29.302 [INFO ] [g.eclipse.smarthome.model.script.TTS] - No timer existing - create
2019-11-09 20:15:29.307 [INFO ] [g.eclipse.smarthome.model.script.TTS] - No timer existing - create
2019-11-09 20:15:29.316 [INFO ] [g.eclipse.smarthome.model.script.TTS] - Add to queue: Four. Tell the test Text
2019-11-09 20:15:29.316 [INFO ] [g.eclipse.smarthome.model.script.TTS] - queue not empty - get command: Two. Tell the test Text
2019-11-09 20:15:29.324 [INFO ] [g.eclipse.smarthome.model.script.TTS] - queue not empty - get command: One. Tell the test Text
2019-11-09 20:15:29.329 [ome.event.ItemCommandEvent] - Item 'TTSOutputToSpeaker' received command Two. Tell the test Text
2019-11-09 20:15:29.342 [INFO ] [g.eclipse.smarthome.model.script.TTS] - Timer reschedule: 3450
2019-11-09 20:15:29.345 [INFO ] [g.eclipse.smarthome.model.script.TTS] - Timer reschedule: 3450
2019-11-09 20:15:29.351 [vent.ItemStateChangedEvent] - TTSOutputToSpeaker changed from Тhree. Tell the test Text to Two. Tell the test Text
2019-11-09 20:15:29.363 [ome.event.ItemCommandEvent] - Item 'TTSOutputToSpeaker' received command One. Tell the test Text
2019-11-09 20:15:29.387 [vent.ItemStateChangedEvent] - TTSOutputToSpeaker changed from Two. Tell the test Text to One. Tell the test Text
2019-11-09 20:15:32.809 [INFO ] [g.eclipse.smarthome.model.script.TTS] - queue not empty - get command: Тhree. Tell the test Text
2019-11-09 20:15:32.816 [ome.event.ItemCommandEvent] - Item 'TTSOutputToSpeaker' received command Тhree. Tell the test Text
2019-11-09 20:15:32.821 [INFO ] [g.eclipse.smarthome.model.script.TTS] - Timer reschedule: 3750
2019-11-09 20:15:32.835 [vent.ItemStateChangedEvent] - TTSOutputToSpeaker changed from One. Tell the test Text to Тhree. Tell the test Text
2019-11-09 20:15:33.938 [INFO ] [g.eclipse.smarthome.model.script.TTS] - Am saying now: Тhree. Tell the test Text
2019-11-09 20:15:36.584 [INFO ] [g.eclipse.smarthome.model.script.TTS] - queue not empty - get command: Four. Tell the test Text
2019-11-09 20:15:36.610 [INFO ] [g.eclipse.smarthome.model.script.TTS] - Timer reschedule: 3600
2019-11-09 20:15:36.596 [ome.event.ItemCommandEvent] - Item 'TTSOutputToSpeaker' received command Four. Tell the test Text
2019-11-09 20:15:36.618 [vent.ItemStateChangedEvent] - TTSOutputToSpeaker changed from Тhree. Tell the test Text to Four. Tell the test Text
2019-11-09 20:15:37.185 [INFO ] [g.eclipse.smarthome.model.script.TTS] - Am saying now: Four. Tell the test Text
2019-11-09 20:15:40.230 [INFO ] [g.eclipse.smarthome.model.script.TTS] - Queue empty - timer end: null
2019-11-09 20:15:59.433 [INFO ] [g.eclipse.smarthome.model.script.TTS] - Am saying now: One. Tell the test Text
2019-11-09 20:15:59.432 [INFO ] [g.eclipse.smarthome.model.script.TTS] - Am saying now: Two. Tell the test Text
In this case Screenshot by Lightshot
Three timers war created at once and only two phrases was said.
Where is my problem that i am looking for 4 days(((?
Adding the sleep there won’t help. They will still all be created close together, just one second later. You need to add a sleep between your sendCommands to TTSTextToSay.
This rule isn’t really based on my code but @mstehle’s so he might be better able to help debug it. The concept is the same as the Gatekeeper DP but it’s his code.