What do you think this is?

Just thoughts of a restless mind...

Asking my home to play music

Don't you love open source and the capabilities it provides? My constant tinkering of my home automation has no end! The latest improvement is asking the system to play my favorite music. And I remembered that the advanced capabilities of using Rhasspy with Home Assistant with as little code as possible is not clear to everybody. So here is an explanation of how this works, and how I leveraged these capabilities to satisfy my music needs.

Pre-Requisites

In order to use Home Assistant intent handling with Rhasspy, you need to define the Hass URL and an Access Token via the GUI or as described here.

Home assistant needs to have intents enabled. I opt to keep intents in a seperate file, so I added these two lines in Home Assistant's configuration.yaml :

intent: intent_script: !include intentscripts.yaml

I do not think that it makes any difference, but my examples are based on my setup, which uses Fsticuffs for intent recognition.

Predefined Intents

Home Assistant has some built-in intents. These are mentioned here. These intents need one or more parameters. We will use the intent HassTurnOn as an example. This intent needs one parameter - the name as described in the bultin intent documentation.

Rhasspy will trigger intents based on sentences file(s). A sample sentences file for some of the built-in intents is provided here.

The actual intent recognition sentence is the last line:

[HassTurnOn] turn on [the] (<entities>){name}

and when the text "turn on the kitchen light" is recognized, the HassTurnOn intent is triggered on home assistant with the parameter "name" having a value of "kitchen light".

The "kitchen light" is a "slot". Something that needs to be defined so that Rhasspy can understand that it is a variable and pass it to Home Assistant. Slots in Rhasspy are defined in slots files. And although one can create slots files with all the relevant entities that are valid for Home Assistant, there is a program that can do that for you. This program is here and needs to be installed in the system that Rhasspy is running, in the profiles directory, under a directory named "slot_programs/hass".

This program can take parameters, so when you call the program entities with the parameter light, it will return only the entities of type Light that are defined in your Home Assistant instance instead of all entities. This is what the rest of the lines of this intent in the sample sentences file do.

lights = $hass/entities,light switches = $hass/entities,switch entities = <lights> | <switches>

Just remember to re-train Rhasspy whenever you add new lights or switches in Home Assistant. Rhasspy should pick it up automatically.

Modifying the predefined intents

Lights and switches are not the only entities that we have in Home Assistant. If you would like to turn on and off TVs and Radios connected to your home assistant, you can add the media_player entity type, which would modify the lines above to be:

[HassTurnOn] lights = $hass/entities,light media = $hass/entities,media_player switches = $hass/entities,switch entities = <lights> | <switches> | <media> turn on [the] (<entities>){name}

If you would like to use a different sentence for lights and TVs, and a difference for the switches, you could modify the sentences file:

[HassTurnOn] lights = $hass/entities,light media = $hass/entities,media_player entities = <lights> | <media> turn on [the] (<entities>){name} [HassTurnOn] switches = $hass/entities,switch switch on [the] (<switches>){name}

Creating new intents

The built in intents cover a lot of cases, but not all. They are mostly on/off and similar actions. Sometimes, we just need to ask Home Assistant about something.

Using the slots program

One of the entities that Home Assistant understands is that of person. If you have device trackers for people, you can find out were they are by adding this sentence in a Rhasspy sentences file:

[IsHome] persons = $hass/entities,person where [is] (<persons>){name}

In Home Assistant you need to define the intent, and the response. Add this to the intentscripts.yaml file (be careful of yaml indentation).

IsHome: speech: text: > {% set tracker = "device_tracker." + name %} {{ name }} is at {{ states(tracker) }}

Without using the slots program

The slot program provided as an example uses the REST API endpoint /api/states to get the state of all entities. This is an indirect way of getting the entities, because there is no direct way to get the entities through the API. There is no way to get many things through the API, and areas is one of them. At the moment there is no way to retrieve the areas from Home Assistant, nor is there a way to use the area name in a jinja2 template. But that should not limit you.

Naming the devices

It is imporant to name the devices taking the area into account. For example if you have an environmental sensor, you can name the device as Bedroom environment which would create entities such as Bedroom environment temperature. This first part can be used to identify which sensor you are asking about.

Hard coding slots

You must create a slots file with the area names, as they are used in the device naming. Let's call this file as "rooms.ini". It could be as simple as :

Kitchen Bedroom

Add all the rooms in which you have environmental sensors in this file.

Creating the sentences

A sentence to retrieve the temperature could be something like :

[WeatherTemperature] what [is] the temperature in the ($rooms){room_name}

This takes the names defined in the rooms.ini slots file and sends it as room_name to Home Assistant with the intent WeatherTemperature

Creating the intent

We need to let Home Assistant know what to do with this intent, so in the intentscripts.yaml file you need to add:

WeatherTemperature: speech: text: > {% set room_name = room_name | regex_replace('\s', '_') %} {% set measurement = "sensor."+room_name+"_environment_temperature" %} The temperature in the {{room_name}} is {{ states(measurement) |int }} degrees

The regex in the first line takes care of any rooms that may contain spaces, and if your rooms are single words only, you do not need this line.

Writing new slots programs

The slots program provided is a great starting point for you to understand how it works, and write your new slots programs. Let's say that you want to control a Logitech Squeezebox through Home Assistant and Rhasspy. Here is an example how you can set up these three components to play the music you want on the player you want. Obviously, the implementation is simpler if you only have one player.

Slots programs

Logitech Media Server can be queried through API to return the players and the playlists. Documentation about this API on your Logitech Media Server at the address: http://your_mediaserver_ip:9000/html/docs/cli-api.html

A slot program to retrieve 100 playlists would be :

#!/usr/bin/env bash set -e url="http://your_mediaserver_ip:9000/jsonrpc.js" player="FF:FF:FF:FF" curl -X POST \ -H 'Content-Type: application/json' \ "${url}" \ -d '{"id":1,"method":"slim.request","params":["'${player}'",["playlists",0,100,"tags:s"]]}' \ 2> /dev/null | \ jq --raw-output '.result as $out | "\($out.playlists_loop[].playlist)"' | \ while read -r playlist; do echo ${playlist} done

Save this under the directory slot_programs/lms in your Rhasspy server as playlists Create a slot_program to retrieve the players, using the same concepts.

Rhasspy sentences

A sentence that will play the selected playlist, over the selected player will be:

[PlayPlaylist] playlists = $lms/playlists players = $lms/players play (<playlists>){playlist} [on (<players>){player}]

This will trigger the intent PlayPlayList on HomeAssistant with up to two parameters: the playlist (mandatory) and the player (optional).

Home Assistant intent

The intent will receive the playlist parameter, and may receive the player parameter. It will use the squeezebox integration to play the playlist on the defined player.

In this example it is important to decide which player is the default. This will be used if the second optional parameter is not spoken / defined / sent to Home assistant. Let's assume that the default player's name is "lounge". The intent will be (be careful with yaml indentation):

PlayPlaylist: action: - service: squeezebox.call_method target: entity_id: > media_player.{{ player | default("lounge") }} data: command: playlist parameters: - play - > {{playlist}}

As I was not happy with the speakers from my creation, I set up a Raspberry Pi with some cheap speakers somewhere else and I'm using this for outputting squeezebox, as well as Rhasspy's output. I can finally understand what the temperature in my bedroom is, while I'm listening to some relaxing music.