Say() can not say two or more phrases

Hello,

The problem is if have two or more say() at once, for example

Say(“first phrase”)
Say(“second phrase”)
Say(“third phrase”)

It says only last one(((
There is a gong of first phrase and break, gong of second and break
At least the gong and it says last phrase complete.

What to do, to hear all three phrases completly?

If it means, i use google speak service.

Thanks.

You need to wait for the first phrase to finish to start the next one

1 Like

U mean use delay? Timer or what is better way?

But if the phrase not the same and genetated dynamically?

Timer is better

Good luck

Hahaha))) :grin:
Thanks!

Really no way to make the trigger that first phrase was already said?

By the way - it the problem of google tts or OH global?

Yes really

More OH. It has no way of knowing how long the phrase will last and there is no trigger back from the TTS to say “I am finished”

2 Likes

I have seen the same behaviour.

I’ve thought about waiting for the player state (my case a Chromecast Audio) to change back to Stop before issuing the next phrase.

I haven’t tried it yet, as I just compiled the phrases into 1 command (text string) instead.

For example,

Say(“first phrase”+". "+“second phrase"+". "+“third phrase”)

Or as an example

say("The outdoor temperature is "+outdoortemp.state+" degrees C and the wind speed is "+windspeed.state+" miles per hour")

Hi,

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:

  1. 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:

  1. 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 :wink:

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

Best regards,
Matt

1 Like

Thanks, Matt! Will try it.

But what if to use this trigger of idle state of speaker http://prntscr.com/prybb3
Has anyone tried?

Have a go and let us know

I would, but not not at home and can not(((

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!

I use Chromecast.

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.

Hi Rich,

it is indeed based on your excellent gate keeper design pattern, thank you very much for that :slight_smile: I did re-write pretty much everything to get rid of ReentrantLocks.

1 Like

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 http://prntscr.com/puq4v6
Three timers war created at once and only two phrases was said.

Where is my problem that i am looking for 4 days(((?

There was an issue where timers wouldn’t be created if they were created within quick succession:

It should be fixed on recent milestones and you can probably workaround it by adding some sleeps.

I tought also this way!

I added a sleep (just to test, because as i know - speel is bad idea for rules), after this line

queueSpeaker.add(receivedCommand.toString)
 Thread::sleep(1000)

but it do not help(((

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.

Rich, it was just test command.
I can not add sleep there, because commands comes from diffenent rules and i do not know when it will come (((