Let Appdaemon speak without tts and mediaplayer in hass

It is my eventual dream to code up a home security platform through HomeAssistant. Before I found HomeAssistant, I was actually doing this exact thing. Automation & Home Security through an app-based platform coded in python 3.5 … it’s great that the heavy lifting is done for me. I hadn’t thought of using HA to announce changes in temperature. I think it is useful feedback to have when there is an absence of an immediate audio/visual indicator like the UI.

These two are great ideas, thank you both!

1 Like

In regards to your app. what happens in these two scenarios:

  • User wants to specify the player
  • Player is currently in use

:slight_smile:

in this app i use mpg321 as player for tts,
i have no player in hass.
but i use vlc (through appdaemon) to play radio.
in my app i check if radio is playing, and set it out during speach.
my latest complete (and working correct) version is:

###########################################################################################
#                                                                                         
#  Rene Tode ( [email protected] )                                                            
#                                                                                         
#  2016/12/21 Germany                                                                     
#
###########################################################################################                                                                                         
#  you need to install gtts and mpg321
#  that can be done by:
#  
#  pip3 install gtts
#  sudo apt-get install mpg321
#
#  save this app in your app dir with name: sound.py
#
###########################################################################################                                                                                         
#  in the appdaemon configfile you need to set:                                           
#                                                                                         
#  [soundfunctions]                                                                       
#  module = sound                                                                         
#  class = sound                                                                          
#  soundfilesdir = /an/empty!!/temp/file/dir/                                             
#                                                                                         
###########################################################################################                                                                                         
#  then you can use it like this in any app                                               
#                                                                                         
#  sound = self.get_app("soundfunctions")                                                 
#  sound.say("Any text you like","your_language","your_priority")    
#                     
#  supported languages: * 'af' : 'Afrikaans'
#                       * 'sq' : 'Albanian'
#                       * 'ar' : 'Arabic'
#                       * 'hy' : 'Armenian'
#                       * 'bn' : 'Bengali'
#                       * 'ca' : 'Catalan'
#                       * 'zh' : 'Chinese'
#                       * 'zh-cn' : 'Chinese (Mandarin/China)'
#                       * 'zh-tw' : 'Chinese (Mandarin/Taiwan)'
#                       * 'zh-yue' : 'Chinese (Cantonese)'
#                       * 'hr' : 'Croatian'
#                       * 'cs' : 'Czech'
#                       * 'da' : 'Danish'
#                       * 'nl' : 'Dutch'
#                       * 'en' : 'English'
#                       * 'en-au' : 'English (Australia)'
#                       * 'en-uk' : 'English (United Kingdom)'
#                       * 'en-us' : 'English (United States)'
#                       * 'eo' : 'Esperanto'
#                       * 'fi' : 'Finnish'
#                       * 'fr' : 'French'
#                       * 'de' : 'German'
#                       * 'el' : 'Greek'
#                       * 'hi' : 'Hindi'
#                       * 'hu' : 'Hungarian'
#                       * 'is' : 'Icelandic'
#                       * 'id' : 'Indonesian'
#                       * 'it' : 'Italian'
#                       * 'ja' : 'Japanese'
#                       * 'ko' : 'Korean'
#                       * 'la' : 'Latin'
#                       * 'lv' : 'Latvian'
#                       * 'mk' : 'Macedonian'
#                       * 'no' : 'Norwegian'
#                       * 'pl' : 'Polish'
#                       * 'pt' : 'Portuguese'
#                       * 'pt-br' : 'Portuguese (Brazil)'
#                       * 'ro' : 'Romanian'
#                       * 'ru' : 'Russian'
#                       * 'sr' : 'Serbian'
#                       * 'sk' : 'Slovak'
#                       * 'es' : 'Spanish'
#                       * 'es-es' : 'Spanish (Spain)'
#                       * 'es-us' : 'Spanish (United States)'
#                       * 'sw' : 'Swahili'
#                       * 'sv' : 'Swedish'
#                       * 'ta' : 'Tamil'
#                       * 'th' : 'Thai'
#                       * 'tr' : 'Turkish'
#                       * 'vi' : 'Vietnamese'
#                       * 'cy' : 'Welsh'
#                                                                                       
#  for priority give "1","2","3","4" or "5"
#
###########################################################################################                                                                                         
#  you can also use:
#                                                                                        
#  sound = self.get_app("soundfunctions")                                                 
#  sound.playsound("any valid mp3 file","your_priority")
#
#  to put music in your soundlist (or sounds)
#
###########################################################################################

import appdaemon.appapi as appapi
import datetime
import tempfile
import subprocess
import os
import time as timelib
from gtts import gTTS

class sound(appapi.AppDaemon):

  def initialize(self):
    self.lasttime = datetime.datetime.now()
    self.totaltime = datetime.timedelta(seconds=1)
    self.aantalloops = 1
    self.minutesrunning = 0
    self.herstarts = 0
    self.sound_handle = self.run_in(self.check_soundlist,30,normal_loop="running")
    self.listen_state(self.extrastart,"input_boolean.geluidforceren")
    runtime = datetime.datetime.now() + datetime.timedelta(minutes=15)
    self.run_every(self.autoherstart,runtime,15*60)

  def autoherstart(self, kwargs):   
    #self.log("start autoherstart")
    counter = 0
    counter2 = 1
    handletest = "leeg"
    while handletest == "leeg" and counter < 30:
      try:
        time, interval, kwargs = self.info_timer(self.sound_handle)
        handletest = kwargs["normal_loop"]
        counter = 60
      except:
        handletest="leeg"
        counter = counter + 1
        counter2 = counter
        timelib.sleep(1)
    #self.log("handle kwarg: " + handletest + " aantal keer getest: " + str(counter2))
    if handletest != "running":
      self.cancel_timer(self.sound_handle)
      self.lasttime = datetime.datetime.now()
      self.totaltime = datetime.timedelta(seconds=1)
      self.minutesrunning = 0
      self.herstarts = self.herstarts + 1
      self.sound_handle2 = self.run_in(self.check_soundlist,2,normal_loop="restarted")
      #self.log("geluidsmodule herstart automatisch geforceerd")
      
    #else:
      #self.log("geluidsmodule loopt nog correct")

  def extrastart(self, entity, attribute, old, new, kwargs):
    self.cancel_timer(self.sound_handle)
    self.sound_handle2 = self.run_in(self.check_soundlist,2,normal_loop="restarted")
    self.turn_off("input_boolean.geluidforceren")
    self.log("geluidsmodule herstart geforceerd")


  def check_soundlist(self, kwargs):
    try:
      priority = self.read_prioritylist()
      fname = priority["fname"]
      tempfile = priority["tempfile"]
      tempfname = priority["tempfilename"]
      if fname != "":
        self.play(fname)
        if tempfile == "1":
          os.remove(fname)
        os.remove(tempfname)
    except:
      self.log("PROBLEM IN CHECK_SOUNDLIST")
    self.sound_handle = self.run_in(self.check_soundlist,2,normal_loop="running")
    actualtime = datetime.datetime.now()
    timedifference = actualtime - self.lasttime
    self.lasttime = actualtime
    if timedifference < datetime.timedelta(seconds=1):
       self.log("LOOP RUNNING MORE THEN ONCE")
       self.aantalloops = 2
    else:
       self.totaltime = self.totaltime + timedifference
    minutesrunning = self.totaltime.seconds
    minutesrunning = minutesrunning//60
    if minutesrunning > (self.minutesrunning + 15):
      self.minutesrunning = minutesrunning
      self.log("SOUNDFILE RUNS CORRECTLY SINCE: " + str(self.totaltime) + " RESTARTED: " + str(self.herstarts) + " TIMES. LOOPAMOUNT: " + str(self.aantalloops))
    

  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. write_in_prioritylist(priority,fname,"1")
  

  def playsound(self,file,priority):
    self. write_in_prioritylist(priority,file,"2")


  def play(self,filename):
    opnieuwaan = False
    if self.get_state("input_boolean.swr3")=="on":
      self.turn_off("input_boolean.swr3")
      opnieuwaan = True
    cmd = ['mpg321',filename]
    with tempfile.TemporaryFile() as f:
      subprocess.call(cmd, stdout=f, stderr=f)
      f.seek(0)
      output = f.read()
    if opnieuwaan:
      self.turn_on("input_boolean.swr3")
    

  def write_in_prioritylist(self,priority,file,tempfile):
    try:
      runtime = datetime.datetime.now().strftime("%d-%m-%y %H:%M:%S.%f")
      log = open(self.args["soundfilesdir"] + priority + "_" + tempfile + "_" + file[-10:], 'w')
      log.write(runtime + ";" + file)
      log.close()
    except:
      self.log("SOUNDFILEDIR PROBLEM!!")


  def read_prioritylist(self):
    toppriority = 0
    activefile = ""
    firsttime = datetime.datetime.strptime("31-12-2050","%d-%m-%Y")
    activetemp = ""
    activefnamefile = ""
    filelist = os.listdir(self.args["soundfilesdir"])
    if filelist:
      for file in filelist:
        priority = int(file[0])
        tempfile = file[2]
        tempfilename = self.args["soundfilesdir"] + file
        fnamefile = open(tempfilename, 'r')
        for line in fnamefile:
          splitline = line.split(";")
          fname = splitline[1]
          #self.log(splitline[0])
          fdate = datetime.datetime.strptime(splitline[0],"%d-%m-%y %H:%M:%S.%f")
        fnamefile.close()
        if priority >= toppriority:
          if firsttime > fdate:
            #self.log(firsttime.strftime("%d-%m-%y %H:%M:%S.%f") + " > " + fdate.strftime("%d-%m-%y %H:%M:%S.%f"))
            firsttime = fdate
            toppriority = priority
            activefile = fname
            activetemp = tempfile
            activefnamefile = tempfilename
    return {"fname":activefile,"tempfilename":activefnamefile,"tempfile":activetemp}    

1 Like

i thank you again for this workerloop.
didnt use it for TTS (because it runs right now, probable will rewrite in the future) but it did help me with the speechrecognition.

2 Likes

Hey @ReneTode, I am trying to get this set up, but when I try to install gtts it fails.

pi@raspi1:~$ sudo apt-get install gtts
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package gtts

I tried doing a sudo apt-get update but still get the same error message. mpg321 installed fine though.

I’m guessing Rene was referring to the python module rather than an apt package.

I’m sure you wont need this, but for anyone who does, the correct command is:

pip3 install gtts

Hope that helps

1 Like

Yes and thanks very much! It installed, so apparently I did end up needing it.

My apologies, I meant you wouldn’t need help with the pip command, not that you wouldn’t need the package itself. :slight_smile:

Glad you got it going!

1 Like

My apologies for misunderstanding. After re-reading I see exactly what your intent was.

Thanks again, @Zen!

2 Likes

Thx @Zen im available again tommorow. Thx for catching.
@rpitera glad you got it working.

Halfway there. I got the dependencies installed. Still have to put in your app.

Been a busy day here; got a few things done I haven’t been able to do since I started, like getting my old TrendNet camera into HA, setting up shell commands to move my Foscams around to presets, finally figured out includes with directories and fixed a DNS issue on my Pi.

Good to see you! Was about to reach out if I didn’t see you here tonight.

Tonight im back :wink:
i think you better wait with creating the app, i dont know if i have taken out any bugs since i posted it here.
tonight or to morow i will check it out and upload to my github

OK, I have to travel today anyway so no rush.

I’ve got a question. When Rene and I were talking about this a while back, we took slightly different approaches to getting the information to the speak app actually doing the talking. Rene gets the app and uses a call from the client to push the text into the priority list. I have the client fire an event and let speak handle the priority list itself.
What are the performance and other issues involved with each way? Both obviously work. But what are the up’s and down’s of each method?

OK, so now I have to wait until I see the answer to this one… LOL

It’s about the same as saying:

this car goes 150mph, but this one does 175mph. Which one should we take for a sightseeing drive along the riverbank?

Or to put it another way, relative to the performance requirements of HA which are typically in the ~1s range for a responsive system, either method is significantly faster than that. In theory I would expect sending the info via an HA event to take longer but not significantly so.

1 Like

My thought was that getting the app and having the client push it onto the priority stack would risk having two clients trying to speak at the same time, and cause problems getting to the priority stack. Sending it by event would let HA deal with any type of contention. But then again, that’s a 1/1,000,000 chance of that timing happening I guess.

That and now I know how to send and listen for custom events.

I had hacked together something similar as a bash script awhile back with stream2chromecast and a simple wget command to fetch the TTS (wget -q -U Mozilla -O sonic_temp.mp3 "http://translate.google.com/translate_tts?ie=UTF-8&total=1&idx=0&textlen=32&client=tw-ob&tl=$lang&q=$string"). (variables are populated by the script, but you get the idea)

When I converted it to appdaemon, the output speed was basically identical. I haven’t converted it to the new built in HASS TTS yet, but I have tested it, and it is basically identical as well.

There are (slight) delays in this sort of thing, but I think most of it is in the transmission back and forth to the internet, rather than local processing.

I think you might still have a concurrency issue - AppDaemon is multi-threaded so it is theoretically possible for HA to push out the events so quickly that 2 different worker threads are processing it at the same time. I solved this in my App using a Thread Safe Queue and a single worker thread pulling items off that queue.

Of course as you said, this is a small possibility corner case, but it will tend to become more of a possibility as you add to your system. The early versions of AppDaemon didn’t have any locking and worked fine for a while (I knew I would have to add it eventually but I wanted to see how long I could get away with it). When I added enough complexity and concurrent callbacks in my various Apps, I started getting errors when multiple threads were accessing internal data structures - I added appropriate locking and they were all resolved.

I might need a short refresher on locking and threading. (not today though). Most of my experience has bene on the application side of things with Oracle databases, so Oracle took care of all of that for me. I understand the concept at a high level, but I’ve never had to implement it.