The situation
In HA, we often use notify
and notify groups to alert certain situations.
We have a lot of notify integrations but none for TTS.
In this How-To, you’ll learn how to
- Use variables and inputs to set a default TTS media player and TTS language.
- Create a long-lived token for REST API access.
- Make a
notify.tts_google
service, as an example for simple TTS notify services. - Make a versatile
notify.tts
service for PicoTTS, using a script. - Use our new
notify.tts
service in an automation. - Use notify groups.
- Use a TTS notifier in a HA
alert
.
Note: I’ve tested this on HA Core 0.112.2 and 0.112.3. Your mileage may vary.
TTS setup
I have set up my tts:
section in configuration.yaml
to use both PicoTTS and Google Translate TTS. Keep in mind that certain values like cache
can be only defined once, in the first entry, and are also valid for the other entries.
# Text to speech
tts:
- platform: picotts
language: 'en-GB'
base_url: https://has1.example.com
cache: false
- platform: google_translate
base_url: https://has1.example.com
language: 'en'
A default media player for TTS, and pre-recorded sound bits
I use the var
custom component to hold the entity ID of my default media player for TTS messages and have it set up as follows:
# Custom variables component (in "custom_components/var/")
var:
audio_alerts_media_player:
friendly_name: "Default Media Player for Audio Alerts"
initial_value: media_player.signalpi1
audio_alerts_base_url:
friendly_name: "Base URL for audio alert messages"
initial_value: "https://has1.example.com/local/alerts"
audio_alerts_base_path:
friendly_name: "Base (internal) path for audio alerts"
initial_value: "/home/homeassistant/.homeassistant/www/alerts"
As you can see, I also store a base URL and path for pre-recorded audio alerts and snippets, mainly for PicoTTS because it supports adding sound bits to a message. (The path will be different if you don’t use HA Core.)
My folder structure for these looks like this:
www/
alerts/
de/
picotts-beep.wav
picotts-alert09.wav
something.mp3
…
en/
picotts-beep.wav
picotts-alert09.wav
something.mp3
…
You see the files have the same name, but are recorded in different languages. I make the same distinction by language for TTS commands.
If you do not wish to install the var
component (or use another), just change the example code accordingly and insert a fixed value instead of using the variable. It’s easy.
Multi-lingual households: Select a TTS language
I’m in a multilingual household, so I wanted the TTS language to be switchable.
This is done by adding an input_select
with the needed languages, and of course by writing your automations and scripts accordingly. Since some services want a two-letter code like en
and others a code like en-GB
, I opted to use the longer one for the input selections and simply use only the first two letters of the “long code” for those services requiring a two-letter code.
input_select:
audio_language:
name: 'Audio Language'
# first 2 chars will be used as subfolder name for pre-recorded audio alerts
# full name will be used as language input to PicoTTS, first two letters for Google TTS
options:
- 'en-GB'
- 'de-DE'
If you do not need that, just replace it in the code with a hard-coded value.
Create a Long-lived REST API Token
We need a “long-lived access token” to easily access our own HA instance via the REST API. If you don’t already, have one, create it:
Click on your Profile button (bottom left in the HA menu, where your name is):
Activate Advanced Mode:
Scroll down to the bottom of your profile, to Long-Lived Access Tokens and click on Create Token:
Give it the name REST:
A popup with your new token appears:
Important: Copy the token before you click OK and store it immediately in your secrets.yaml
file:
# Long-lived (10y) token for REST API access (NOTE: Need to include "Bearer" here!)
ha_token_rest: 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiI2YWU1YTA2MmVmZjE0MjMxODZiMTE5NzdlYWM5MzU1MiIsImlhdCI6MTU5NDAwMzcxMCwiZXhwIjoxOTA5MzYzNzEwfQ.LANS95Y6TzTlUOMgwsJ73VRiyvMKDKaSMi__ZrxJLx8'
Important:
- The token is a very long string, be sure to copy all of it! (Above token shown is a fake.)
- Enclose the token in single apostrophes and add the text "Bearer " in front of it, as shown above.
You can now click OK in the HA UI. Your new token should now be shown under Long-Lived Access Tokens.
Note: This process only needs to be done once (well, every 10 years …). The token can now be used to authenticate ourselves against HA for all REST API calls.
Our first service: notify.tts_google
Setup
This is an example for making a simple notify TTS service, using HA’s REST API.
You should be able to easily adapt it to other TTS services.
In your configuration_yaml
, under notify:
, enter this:
# Notifications
notify:
# Workaround TTS notifier, uses REST and Google Translate say
# This can be used with the "alert" integration.
# I tried to make it as compatible as possible with existing 'notify' integrations.
- name: tts_google
platform: rest
resource: https://has1.example.com/api/services/tts/google_translate_say
method: POST_JSON
headers:
Authorization: !secret ha_token_rest
content-type: application/json
data_template:
# Notifications usually have a title and a message,
# so prepend the title to the message and insert
# a newline in between so the TTS engine makes a longer pause.
message: "{{ title ~ '\n' ~ message }}"
# Optional extras which can also be set in an alert's 'data' attribute.
# For 'notify', these are presented in the service data attribute 'data'.
# Since we have no script, we need to take care of missing values here.
entity_id: "{{ data.entity_id | default(states('var.audio_alerts_media_player'),true) }}"
language: "{{ (data.language | default(states('input_select.audio_language'), true))[0:2] }}"
Important:
-
Change
has1.example.com
in theresource:
line to point to your HA server’s address! -
If not using the
var:
custom component, change theentity_id:
line to something like the following, putting in the entity_id of your desired media player:entity_id: "{{ data.entity_id | default('media_player.signalpi1',true) }}"
-
If not using
input_select.audio_language
, change thelanguage:
line to reflect your desired language:language: "{{ data.language | default('en', true) }}"
-
You must restart HA after adding/making changes to this notifier.
Try it out!
After setting everything up, it’s time to try out your new notify.tts_google
notifier.
Enter Developer Tools → Services and try out the following:
Did it work? (If not, try to find out why, and repair it.)
Congratulations! Your first TTS notifier works!
A versatile notify.tts
service for PicoTTS, using a script.
Now that we’ve learned a few things about REST and notifiers and made our first simple one, let’s make a more elaborate one using PicoTTS and a script.
Why?
- PicoTTS can be used “cloud-free”, without Google or Amazon ever knowing what we speak.
- Using a script allows for easy changes later on, without having to modify things in a zillion places in
configuration.yaml
ever again. - A script can do nice extras, like adding a short sound file at the beginning of your spoken messages.
- A script is a good place to check variables, and provide good defaults for missing ones.
- With a script, you could do things like turn off all audio alerts, based on some boolean input or sensor data.
Setup
First, let’s add another notifer under the notify:
section in your configuration.yaml
, much like the one for Google TTS:
# Workaround TTS notifier, uses REST and the TTS script "Say" for audio alerts.
# This can be used with the "alert" integration.
# I tried to make it as compatible as possible with existing 'notify' integrations.
- name: tts
platform: rest
resource: https://has1.example.com/api/services/script/say
method: POST_JSON
headers:
Authorization: !secret ha_token_rest
content-type: application/json
data_template:
# Notifications usually have a title and a message,
# so prepend the title to the message and insert
# a newline in between so the TTS engine makes a longer pause.
message: "{{ title ~ '\n' ~ message }}"
# Optional extras which can also be set in an alert's 'data' attribute.
# For 'notify', these are presented in the service data attribute 'data'.
# 'script.say' takes care of missing fields or such with a 'None' value.
entity_id: "{{ data.entity_id }}"
language: "{{ data.language }}"
audiofile: "{{ data.audiofile | default('picotts-alert09.wav', False) }}"
As explained above, adapt the resource:
and possibly audiofile:
lines to your needs.
You might have noticed that the resource:
line now wants to access the /api/services/script/say
endpoint, instead of the /api/services/tts/google_translate_say
endpoint we used for our Google TTS example above.
So we need a script that will handle the TTS notify call. Put it into your scripts.yaml
file:
# Say
# Plays audio file given by "audiofile" variable to media player given by "entity_id"
# Then plays TTS message to same player
say:
alias: 'Say'
description: 'Speak a message via TTS'
fields:
entity_id:
description: 'Entity Id of the media player to use'
example: 'media_player.signalpi1'
audiofile:
description: 'Name of introductory audio file to play. MUST be WAV, pcm-s16le, mono, 16000 Hz.'
example: 'picotts-beep.wav'
message:
description: 'The text message to speak (can be a template)'
example: "It's {{ temperature }} degrees outside."
language:
description: 'The language to speak in (defaults to tts: setting)'
example: 'en-GB'
sequence:
# only alert if "Audio Alerts" is on
- condition: state
entity_id: 'input_boolean.audio_alerts'
state: 'on'
# play text message
- service: tts.picotts_say
data_template:
entity_id: "{{ entity_id | default(states('var.audio_alerts_media_player'),true) }}"
language: "{{ language | default(states('input_select.audio_language'), true) }}"
message: >
{% set language = language | default(states('input_select.audio_language'), true) %}
{% set lang = language[0:2] %}
{% if audiofile == '' %}
{% elif audiofile %}
<play file="{{ states('var.audio_alerts_base_path') }}/{{ lang }}/{{ audiofile }}"/>
{% else %}
<play file="{{ states('var.audio_alerts_base_path') }}/{{ lang }}/picotts-beep.wav"/>
{% endif %}
<volume level="60">{{ message }}</volume>
This is actually my normal script.say
that I also use in other places, like automations.
It uses the prerequisites mentioned above:
-
A well-defined folder structure for the audio files (because we don’t want to give the whole path all over, in case it changes).
-
The default audiofiles
picotts-beep.wav
andpicotts-alert09.wav
(RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 16000 Hz format) as “sound bits” to introduce a spoken TTS message. -
The
var:
custom component, to store some defaults:-
var.audio_alerts_media_player
: The default media player’s entity ID. -
var.audio_alerts_base_path
: The (internal) path to audio files, for PicoTTS.
My
var:
section looks like this:# Custom variables component (in "custom_components/var/") var: audio_alerts_media_player: friendly_name: "Default Media Player for Audio Alerts" initial_value: media_player.signalpi1 audio_alerts_base_url: friendly_name: "Base URL for audio alert messages" initial_value: "https://has1.example.com/local/alerts" audio_alerts_base_path: friendly_name: "Base (internal) path for audio alerts" initial_value: "/home/homeassistant/.homeassistant/www/alerts"
-
-
A boolean input
input_boolean.audio_alerts
, to decide if we should talk at all:input_boolean: audio_alerts: name: Audio Alerts initial: on icon: mdi:voice
-
A select input
input_select.audio_language
, to select the output language.input_select: audio_language: name: 'Audio Language' # first 2 chars will be used as subfolder name for pre-recorded audio alerts # full name will be used as language input to PicoTTS, first two letters for Google TTS options: - 'en-GB' - 'de-DE'
You can of course change all that, the code isn’t very complicated.
Note I use a volume level of 60% for PicoTTS, because it tends to produce clipping WAV files otherwise,
Remember to restart HA after these changes.
Try it out!
Let’s try it out step-by-step:
Testing script.say
First, let’s check if our script.say
works correctly—that is later used for the notifier.
I assume you’ve already created the audio files picotts-beep.wav
and picotts-alert09.wav
, for instance using a tool like Audacity, and put them in the correct places. For my installation, this would be:
www/
alerts/
de/
picotts-beep.wav
picotts-alert09.wav
…
en/
picotts-beep.wav
picotts-alert09.wav
…
Remember: These files must be in the RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 16000 Hz format for PicoTTS!
Now go to Developer Tools → Services and try out the script.say
(using your media player):
Play around with it and also check if the defaults are applied when leaving out things other than message:
.
Testing notify.tts
Now let’s try if our newly made notify.tts
service works correctly:
Again, you should be able to leave out language:
and audiofile:
and the defaults should be substituted.
Use notify.tts
in an automation
Let’s say we wish to announce a new Home Assistant update in an automation.
Put this (or something similar) into the automation:
section of your configuration.yaml
(or in automations.yaml
):
# A new update for HA is available
- alias: "Update available notification"
trigger:
- platform: state
entity_id: binary_sensor.updater
from: 'off'
to: 'on'
action:
- service: notify.tts
data_template:
title: 'Update available:'
message: >
Home Assistant {{ state_attr('binary_sensor.updater', 'newest_version') }} is available.
Check out the Release Notes at: {{ state_attr('binary_sensor.updater', 'release_notes') }}
We will now be notified via TTS whenever a new HA update is available!
Use notify groups
Hmm. We just found out that a purely spoken announcement isn’t so helpful: It might occur at night, when we’re not at home, or when TTS output has been switched off.
Remember notify groups? Looks like this would be the ideal place to add our spoken notification, because we already have a notify group notify.group_important
.
Let’s make a new notify group notify.group_important_plus_audio
! Under notify:
, in configuration.yaml
, add:
notify:
# Notify group for important alerts
- name: group_important
platform: group
services:
- service: email
- service: telegram
# Notify group for important alerts, plus audio alert
- name: group_important_plus_audio
platform: group
services:
- service: group_important
- service: tts
We now have two notify groups:
-
notify.group_important
- usesnotify.email
andnotify.telegram
(adapt to your needs) -
notify.group_important_plus_audio
- uses whatever is ingroup_important
plus our newnotify.tts
(ornotify.tts_google
)
Now let’s modify our automation above to use notify.group_important_plus_audio
:
# A new update for HA is available
- alias: "Update available notification"
trigger:
- platform: state
entity_id: binary_sensor.updater
from: 'off'
to: 'on'
action:
- service: notify.group_important_plus_audio
data_template:
title: 'Update available:'
message: >
Home Assistant {{ state_attr('binary_sensor.updater', 'newest_version') }} is available.
Check out the Release Notes at: {{ state_attr('binary_sensor.updater', 'release_notes') }}
We now get notifications via email, Telegram and TTS whenever a HA upgrade is available!
(Remember to restart HA to make the changes effective.)
Use a TTS notifier in an alert
Home Assistant alerts and alarms use notifiers extensively. Let’s go and make a garbage day alert that uses our newly created TTS notifiers!
Under alert:
in your configuration.yaml
, add this (example assumes you also use use Telegram and have your bot set up—an entire different beast):
# Alerts
alert:
garbage_day:
name: 'Garbage Day'
title: 'Reminder:'
message: 'It is time to put the rubbish bin out.'
#done_message: 'Rubbish bin put out'
entity_id: binary_sensor.garbage_day
state: 'on'
repeat: 30
can_acknowledge: true
skip_first: false
notifiers:
- group_important_plus_audio
data:
# data for 'tts'
language: 'en-GB'
# data for 'telegram'
inline_keyboard:
# rows of buttons (array of arrays)
- - ["Reminder off", "/ack garbage_day"]
- ["Help", "/help garbage_day"]
This will set up an alert as soon as the garbage_day
sensor switches to ‘on’, for every 30 minutes until acknowledged or garbage day is over.
Remember to restart HA after changing/adding alerts!
Notes:
-
We used the notify group
group_important_plus_audio
from above for this example. You could also use the newtts
ortts_google
notifiers. -
For testing, you could also use a boolean input for triggering, say
input_boolean.garbage_day_test
:input_boolean: garbage_day_test: name: "Garbage Day Test"
-
I left the
data:
for Telegram intentionally in, to show that data for more than one notifier can be added with an alert. The notifiers usually don’t care about extra data included in thedata:
section, thus ournotify.tts
andnotify.tts_google
wouldn’t care about Telegram’sinline_keyboard:
data, and Telegram wouldn’t care about thelanguage:
data for our TTS notifier.
What we learned
- Creatively adding things that aren’t there in HA.
- Structuring things helps a lot later on (like variables, folder structure, groups).
- Reading documents and trying out things can take a long time. But saves even more time in the long run.
- Fiddling with ‘code’ can be enormous fun!
Enjoy!
Oh well, and let me know about any bugs or interesting things you find!