OK, so you are running into a classic problem with multi-threaded event driven concurrent programming The problem is that the real world isn’t our friend and things happen randomly and all together
I think that the way to solve this is to have a common piece of code that controls access to all the spheech. When a program wants to say something, it tells the common code, and the common code maintains a queue and makes sure that only one thing is being said at once, It sounds complicated but Python has good tools to help with this and I can try and help you figure it out if you like. This is probably the only sensible way to solve the problem without crippling AppDaemon.
If we manage to figure it out it would make a great additional example App!
EDIT: It looks like you are part way there, you seem to have the speak and play all in a single App already?
With a little restructuring this could work fine:
Modify speak and say to put the parameter info on a queue rather than do it directly
Create a worker thread to read from the queue, do the speak or say, then maybe wait a second or two before grabbing the next one.
This is pretty easy as it is how AppDaemon works internally.
that would be cool, you could also give items a priority in the queue, so critical things could go to the top ( things like “excuse me but the smoke alarm in the kitchen says you are burning dinner”). Those things could be prioritized (at the coders direction) above things like “excuse me, I’m detecting a methane buildup in the restroom, do you want me to turn on the exhaust fan”. I’m going to build a sensor for that last one.
LOL - the methane thing sounds cool And Also, I have a hunch that this App would be useful for other situations - maybe we can make a universal notification App to avoid recoding stuff all over the place.
Or maybe another approach would be to allow a way to enqueue requests to AppDaemon’s worker threads since the Que and workers are alread in place. Let me think about that.
EDIT: That’ won;t work as there are multiple threads but maybe some kind of a singleton capability in the Apps …
the speach functions are now in an empty app.
so lets run that with run_secondly.
put the names from the sounds to play (spoken tekst is translated to sound before play) in a list.
in the callback:
if list is empty do nothing, else play sound and wait 2 seconds.
making 5 lists to put the names in could give 5 priorities.
the only thing i need to find out is how to delete sound_priority1[0] from list sound_priority1, making sound_priority1[1] to sound_priority1[0]
I was going with a dictionary type of idea where the primary dictionary like below. Then just using a min value type of thing to get the smallest item out of the dictionary.
{“1”:{“1”:“Hi There”},{“2”:“This is the high priority queue”},
“2”:{“1”:“Hello world”},{“2”:“this is the medium priority queue”}}
and so on and so on.
thats why i dont like dicts.
i need to look at that 10 times
i guess it could work but the messagenumber keeps counting that way.
i thought more in this way:
filelist={"1":["file1","file2",...],"2":["file1","file2,...]}
for prioritylist in filelist:
for file in prioritylist:
self.play(file)
prioritylist.remove(file)
return
dont know why but it wont work.
the app keeps running for a while but at a random point it stops after a few loops.
and filling the list isnt working, because i never find anything in it in the loop.
what i tried:
import appdaemon.appapi as appapi
import datetime
import tempfile
import subprocess
import os
from gtts import gTTS
class speaknow(appapi.AppDaemon):
def initialize(self):
self.filelist = {"1":["empty"],"2":["empty"],"3":["empty"],"4":["empty"],"5":["empty"]}
self.run_in(self.check_soundlist,2)
def check_soundlist(self, kwargs):
self.log("runsound is running")
for priorityfilelist in self.filelist.values():
for file in priorityfilelist:
if file != "empty":
self.log("file gevonden: " + file)
self.play(file)
priorityfilelist.remove(file)
os.remove(fname)
self.run_in(self.check_soundlist,2)
return
self.log("priorityfilelist was empty")
self.log("list was empty, restart check")
self.run_in(self.check_soundlist,2)
def say(self,text,lang,priority):
with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as f:
fname = f.name
tts = gTTS(text=text, lang=lang)
tts.save(fname)
self.filelist[priority].append(fname)
def playsound(self,file,priority):
self.filelist[priority].append(file)
def play(self,filename):
cmd = ['omxplayer','-o','local',filename]
with tempfile.TemporaryFile() as f:
subprocess.call(cmd, stdout=f, stderr=f)
f.seek(0)
output = f.read()
check_soundlist actually does the talking based on what is in the filelist
say gets called and populates filelist. Right?
does filelist look right at the end of say if you do a self.log(“filelist ={}”.format(self.filelist))
does the list look right at the beginning of check_soundlist?
Can you trace it and narrow it down to where it’s going haywire with some debug log statements?
where did soundfunctions come from? The code you sent was using “speak”. I made a couple of changes and it seems to work.
import appdaemon.appapi as appapi
import datetime
import tempfile
import subprocess
import os
from gtts import gTTS
class speaknow(appapi.AppDaemon):
def initialize(self):
self.filelist = {"1":["empty"],"2":["empty"],"3":["empty"],"4":["empty"],"5":["empty"]}
self.run_in(self.check_soundlist,2)
def check_soundlist(self, kwargs):
self.log("runsound is running")
for priorityfilelist in self.filelist.values():
for file in priorityfilelist:
if file != "empty":
self.log("file gevonden: " + file)
self.play(file)
priorityfilelist.remove(file)
os.remove(file)
self.run_in(self.check_soundlist,2)
return
self.log("priorityfilelist was empty")
self.log("list was empty, restart check")
self.run_in(self.check_soundlist,2)
def say(self,text,lang,priority):
with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as f:
fname = f.name
tts = gTTS(text=text, lang=lang)
tts.save(fname)
self.filelist[priority].append(fname)
def playsound(self,file,priority):
self.filelist[priority].append(file)
def play(self,filename):
cmd = ['omxplayer','-o','local',filename]
with tempfile.TemporaryFile() as f:
subprocess.call(cmd, stdout=f, stderr=f)
f.seek(0)
output = f.read()
(hass) hass@hass:~/appdaemon/conf/apps$ cat test.py
import appdaemon.appapi as appapi
class test(appapi.AppDaemon):
def initialize(self):
self.log("about to fire SPEAK event")
self.run_in(self.say_something,2)
def say_something(self, kwargs):
priority="1"
speaktext = "i am alive"
sound = self.get_app("speak")
sound.say(speaktext,"nl",priority)
(hass) hass@hass:~/appdaemon/conf/apps$
i renamed it to soundfunctions because i got the same error over and over with key error speak.
and because i used speak at more places i needed to be sure.
i dont know what it is.
i tried abandoning the dict and made lists in list, didnt work.
i tried writing filenames to a dir. didnt work.
restarted appdaemon many times.
I’m running HA and AD from a virtual environment. I’m not hearing anything over my TV speakers.
If I copy the MP3 files to a hold directory and play them individually (had to use sudo to do this), they chop off the end. I get “I am A{click}” most of the time.
Other than the fact it isn’t saying anything, I have the I am alive thing in a loop inserting that text at random intervals between 5 and 10 seconds. Now that’s not enough to put any type of stress on this, it’s not even enough to get more than one message in at a time. But the files are being generated and cleaned up as expected.
virtual enviroment shouldnt effect anything.
if your sound comes over the hdmi then LOCAL should be changed to HDMI or the part
,’-o’,‘local’
can be deleted completely.
i had a chop off for some short sentences too.
but only for some.
by the way you should use “en” instead off “nl” for english instead of dutch.