Rhasspy with openHAB, part I: voice announcements

Posted by

In this feature, information flows only in one direction: from openHAB to me, no speech recognition involved.

How openHAB communicates with the text-to-speech engine

Bird’s eye view: to trigger a spoken announcement, openHAB publishes an MQTT message that contains all the relevant information: what to say, and on which satellite to say it. When the message is received by the master Rhasspy service, it converts the text to audio and then streams it to the specified satellite.

openHAB talks to the MQTT broker installed as part of Rhasspy, in my setup on a server called stt-server. This is introduced to openHAB as a bridge, in /etc/openhab/things/mqtt-bridge.things:

Bridge mqtt:broker:rh-mq "Rhasspy-Mosquitto" @ "System" 
[ host="stt-server", secure=false, clientID="oh4" ]

There is one Thing definition for all of this, with a separate Channel for each satellite, and a separate Item for each satellite capable of audio output. Assigning a text to one of these Items will send out the MQTT message that triggers Rhasspy to speak the text.

In /etc/openhab/things/rhasspy.things I have (tested with openHAB 3.3 and openHAB 4.1.1)

Thing mqtt:topic:rh-mq:say (mqtt:broker:rh-mq) {
 Channels:
  Type string: espD    [ commandTopic="hermes/tts/say", 
               formatBeforePublish="{\"text\":\"%s\",\"siteId\":\"espD\"}" ]
  Type string: espG    [ commandTopic="hermes/tts/say", 
               formatBeforePublish="{\"text\":\"%s\",\"siteId\":\"espG\"}" ]
  Type string: raspi7  [ commandTopic="hermes/tts/say", 
               formatBeforePublish="{\"text\":\"%s\",\"siteId\":\"raspi7\"}" ]
  Type string: raspi11 [ commandTopic="hermes/tts/say", 
               formatBeforePublish="{\"text\":\"%s\",\"siteId\":\"raspi11\"}" ]
  Type string: raspi14 [ commandTopic="hermes/tts/say", 
               formatBeforePublish="{\"text\":\"%s\",\"siteId\":\"raspi14\"}" ]
}

In /etc/openhab/items/rhasspy.items I have (note how the names of the Items always start with “say_“, followed by the siteId of the satellite)

Group gSay "Group: TTS audio sinks"
String say_espD     (gSay) { channel="mqtt:topic:rh-mq:say:espD" }     
String say_espG     (gSay) { channel="mqtt:topic:rh-mq:say:espG" }     
String say_raspi7   (gSay) { channel="mqtt:topic:rh-mq:say:raspi7" }
String say_raspi11  (gSay) { channel="mqtt:topic:rh-mq:say:raspi11" }
String say_raspi14  (gSay) { channel="mqtt:topic:rh-mq:say:raspi14" }

All of those Items representing “speaking satellites” are members of a gSay group, which allows me to find a group member, or do something for all group members, in a rule (see below).

How to make people pay attention to the announcement

Have you ever noticed that public announcements, e.g. at an airport or on an airplane, are always preceded by some chime or jingle, to catch your attention? Ding-dong, please fasten your seat belts. This is necessary to help you focus your attention on what is about to be announced. Rhasspy doesn’t offer that directly, as far as I know, so I had to make openHAB do it instead.

With Rhasspy, you can stream a WAV audio file directly to one of the satellites, by publishing chunks of audio via MQTT. I created a small script /etc/openhab/dingdong.sh that can be called from openHAB rules. it expects the siteId (name) of the satellite as its sole argument.

#!/bin/bash
if [ -z "$1" ] ; then
    echo "No argument supplied"
    exit 1
fi
uuid=$(uuidgen)
topic=topic=hermes/audioServer/$1/playBytes/$uuid
sound=/etc/openhab/sounds/dingdong.wav
mosquitto_pub -h stt-server -t $topic -s < $sound
sleep 2.5

The jingle is in /etc/openhab/sounds/dingdong.wav, a short mono WAV file at 44100 kHz sample rate, just about 1.5 seconds long. Pick whatever sound pleases your ears …

Now we just need a dummy string Item Rhasspy_NotifySite, and a rule that is triggered when that Item is set to a new text.

In /etc/openhab/rules/rhasspy.rules we have

rule "notify site"
when 
    Item Rhasspy_NotifySite received update 
then 
    val theArgs = newState.toString.split(":")
    if (theArgs.size != 2) { return; }
    
    val String siteId = theArgs.get(0)
    val String theText = theArgs.get(1)
    val String theSatelliteName = 'say_' + siteId
    var theSatellite = gSay.members.findFirst[ t | t.name==theSatelliteName]
    logInfo('rhasspy',"at {} say '{}'", siteId, theText)

    if (theSatellite!==null) {
        executeCommandLine(Duration.ofSeconds(3), '/etc/openhab/dingdong.sh', siteId )
        theSatellite.sendCommand(theText)
    }
end

How to broadcast an announcement to all satellites

This just needs one more dummy Item, named Rhasspy_NotifyAllSites, and a rule that is triggered when you assign a text to the Item.

In /etc/openhab/rules/rhasspy.rules we have

rule "notify all sites"
when 
    Item Rhasspy_NotifyAllSites received update 
then 
    if (IsLive.state == OFF) { return; }
    val String theText = newState.toString

    gSay.members.forEach[ i |
        val String site = i.name.replace("say_","")
        logInfo('rhasspy',"say '{}' at {}", theText, site)
        executeCommandLine(Duration.ofSeconds(3), '/etc/openhab/dingdong.sh', site )
        i.sendCommand(theText)
    ]
end 

How to use voice alerts in openHAB

To trigger an announcement in a rule, just assign a string like “siteId : what to say” to Item Rhasspy_NotifySite, using the postUpdate() or sendCommand() methods. If you set the Item to “raspi11:the washer has finished“, it will speak the announcement “the washer has finished” on the satellite named raspi11. To speak an announcement on all satellites, just assign a string to Item Rhasspy_NotifyAllSites.

Leave a Reply