Smart Home zum Reden bringen: openHAB und text-to-speech (tts) am raspberry pi

In manchen Situationen wäre es schön, wenn das Smart Home mit einem redet. Meine Ideen gehen hier von Erinnerungen und dem Vorlesen von anstehenden Terminen meines Kalenders bis zur Ausgabe von Warnungen, wenn die Fenster zu lange offen sind oder irgend etwas anderes nicht passt.

Als Smart Home läuft bei mir ein Raspberry Pi 3 mit openHAB – siehe meine vorherigen Artikel dazu.

Nun geht es darum, ihm das Sprechen beizubringen, ganz konkret:

  • Benutzerspezifische Texte sollen als MP3 codiert werden
  • Die MP3-Datei soll am Raspberry abgelegt werden und gecached werden
  • Die MP3-Datei soll durch den Rasbperry auch ausgegeben werden
  • Das ganze soll über openHAB einfach ansteuerbar sein

TTS (Text-To-Speech)-Service einbinden

Warum nutze ich nicht das say()-Kommando von openHAB? Ganz einfach deswegen, weil ich nicht will, dass ich für alles online sein muss. Ein Caching der (wichtigsten) Dateien ist mir wichtig, von daher hätte das Ganze so mit dem eingebauten TTS-Say-Commando nicht funktioniert.

Nun zum Thema: TTS-Services gibt es einige im Netz, allerdings will ich für den privaten Gebrauch nun kein großes kommerzielles Abo abschließen sondern das ganze am liebsten für lau nutzen. Googles TTS ist inzwischen leider per captcha geschützt und damit unbrauchbar, bei meinen Recherchen bin ich aber auf das für meine Zwecke wohl ausreichende voicerss gestoßen. Bis zu 350 Dateierzeugungen pro Tag sind für private Zwecke derzeit kostenfrei.

Das sollte genügen. Zudem glaube ich, dass viele Ausgaben („Das Fenster im Bad ist seit über 10 Minuten offen“ o.ä.) immer wieder die gleichen sind. Daher können wir die Dateien ja einfach zwischenspeichern.

Im ersten Schritt geht es nun um das Erzeugen der Audiodatei. Das Abspielen ist erst einmal noch Nebensache.

Hierzu greifen wir auf die simple, von voicerss bereitgestellte php-API zurück und nutzen auch PHP, um die Dateien zu holen und zu verwalten. Sicherlich ginge das wohl auch in openhab oder mit Shellscripten, aber ich fühle mich in PHP sehr wohl und daher will ich mir die Zeit sparen.

Zum Ablauf:

  • Der Text, der konkret ausgegeben werden soll, wird per Aufruf-Parameter an das Script übergeben.
  • Das Script nimmt diesen Wert und prüft, ob dieser Text ggf. noch im Cache liegt.
  • Liegt das File nicht im Cache, wird es über die Webservice-Schnittstelle von voicerss abgeholt.
  • Zudem wird geprüft, ob die im Cache liegenden Dateien ein Verfallsdatum (in Tagen) überschritten haben. Veraltete Dateien werden dann gelöscht.
  • Die MP3s speichern wir mit Hash des Namens, so haben wir mit Umlauten usw. im Dateinamen kein Problem und können die Files schön handhaben.

Voraussetzungen

  • Curl (php5-curl) muss installiert sein, damit die Schnittstelle genutzt werden kann.
  • Im Aufrufverzeichnis muss ein Unterverzeichnis „mp3“ angelegt werden. Dieses muss durch den Benutzer, der das PHP-Script startet, beschreibbar sein.

Das PHP-Script

Zuerst laden wir die voicerss_tts.php in das gleiche Verzeichnis, in welchem wir dann unser tts-Script platzieren.

tts.php

<?php

/*
 *
 * This script uses voicerss API to create mp3 files with custom content
 *
 * Requirements:
 * =============
 * curl must be installed for php ("sudo apt-get install php5-curl")
 * Path "mp3" must exists, created mp3s will be stored there and the
 * directory is used as cache.
 * the script executing user must be able to write to this directory
 *
 * Example:
 * ========
 * /usr/bin/php tts.php This is my example text that should be converted to mp3
 *
 */

$absolutePath = realpath(dirname(__FILE__)) . '/';
//include voicerss php lib
include_once $absolutePath . 'voicerss_tts.php';

$relativeMp3Path = 'mp3';
$apiKey = '<MY_API_KEY>'; // Replace with voiceRSS API KEY
$ttl = (60 * 60 * 24) * 31; // Files should be cached for 31 days

$args = $argv;
array_shift($args);
$text = implode(' ',$args);
$filename = $relativeMp3Path . '/' . sha1($text) . '.mp3';
if (file_exists($absolutePath.$filename)) {
    touch($absolutePath . $filename);
}

// Delete old files from cache that have reached time to life...
if ($handle = opendir($absolutePath . $relativeMp3Path)) {
    while (($item = readdir($handle)) !== false) {
        if (!in_array($item, array('.', '..'))) {
            if (filemtime($absolutePath . $relativeMp3Path . '/' . $item) < (time() - $ttl)) {
                unlink($absolutePath . $relativeMp3Path . '/' . $item);
            }
        }
    }
    closedir($handle);
}

// Create mp3 using voicerss API
if (!file_exists($absolutePath . $filename)) {
    $tts = new VoiceRSS;
    $voice = $tts->speech([
        'key' => $apiKey,
        'hl' => 'de-de',
        'src' => $text,
        'r' => '0',
        'c' => 'mp3',
        'f' => '44khz_16bit_stereo',
        'ssml' => 'false',
        'b64' => 'false'
    ]);
    file_put_contents($absolutePath . $filename, $voice);
}

// Return the filename
exit($absolutePath . $filename);

Dem Raspberry Pi Soundausgaben beibringen

Zuerst einmal Boxen an den Raspberry anschließen.
Dann brauchen wir die Software: Mit mpg123 lassen sich mp3 files einfach abspielen. alsa wird verwendet, um die Lautstärke des Rasbperry Pi zu steuern.

sudo apt-get install mpg123 alsa-base alsa-utils

Dann idealerweise gleich die Lautstärke hochdrehen.

alsamixer

Mit den Cursortasten nach oben einfach auf’s Maximum gehen und dann mit ESC wieder raus. Ansonsten ist es so leise, das man je nach Boxen meinen könnte, das Ganze funktioniert nicht…

Für später verwenden wir amixer und setzen die Lautstärke von der Kommandozeile aus, dazu mehr später.

MP3-Dateien spielen wir mit mpg123 ab, Beispiel:

mpg123 mp3/3cc55048ade69c0b4b9ee035663050a078802470.mp3

Das Steuern der Lautstärke sowie das Abspielen der Dateien selbst wollen wir später über das exec-Binding von openHAB erledigen.

Lautsprecher in openHAB integrieren und steuerbar machen

In openHAB wollen wir die Lautstärke steuern können. Werte von 0 – 100 sollen gesetzt werden können. Mir genügen 5er-Schritte.

Items-Datei

Number eg_speaker_volume "Lautstärke [%d %%]"

Sitemap-Datei

Frame label="Lautsprecher" {
     Setpoint item=eg_speaker_volume minValue=0 maxValue=100 step=5
}

Rules-Datei zum Initialisieren

Ein Initialwert sollte per Initialization-Rule gesetzt werden und idealerweise wird der Wert mit einer Persistence persisitiert.

rule "Initialization"
 when 
   System started
 then
   postUpdate(eg_speaker_volume,    100)
end

Lautstärkenänderungen müssen natürlich ans System durchgegeben werden.

Leider habe ich es nicht hinbekommen, per executeCommand direct via amixer das Volume-Level zu setzen, daher habe ich mir ein Hilfsscript geschaffen, welches „irgendwo“ abgelegt werden kann und als root ausgeführt werden soll.

Hilfsscript: Datei „controlVolume.sh“

Daher habe ich ein kleines Shellscript geschaffen, welches ich dem Benutzer openhab erlaube als root auszuführen und welches einfach nur den Lautstärkenpegel an amixer weiterreicht.

#!/bin/sh
/usr/bin/amixer sset 'PCM' $1% unmute

Eintrag in visudo (roter Text zu ergänzen, je nachdem, wo das Script liegen soll), denn dem user openhab ist es nicht möglich, die Lautstärke selbst zu setzen. Die rote Ergänzung ist hierbei wichtig.

sudo visudo
# User privilege specification
root    ALL=(ALL:ALL) ALL
openhab ALL=NOPASSWD: /opt/raspberry-remote/send, /etc/openhab/php-addon/controlVolume.sh*

Die Datei braucht noch ein paar Rechte:

sudo chmod o+x controlVolume.sh
sudo chmod 755 controlVolume.sh
sudo chown root.root controlVolume.sh

Rule-Datei für Lautstärkenänderung

Falls man für Debuggingzwecke die Ausgaben im Log sehen willl einfach das logInfo auskommentieren. Ersetze einfach den Pfad durch den selbst gewählten

Via executeCommand wird als root obiges Hilfsscript ausgeführt und als Parameter wird der neu gesetzte Volume-Wert übergeben.

rule "Lautstärkenänderung"
when
    Item eg_speaker_volume received update
then
    val String myCommand = "sudo -u root /etc/openhab/php-addon/controlVolume.sh " + eg_speaker_volume.state 
    var String tmp = executeCommandLine(myCommand, 5000)
//    logInfo('rules',myCommand + ' RETURNS: ' + tmp)
end

Textausgaben bei bestimmten Ereignissen abspielen

Nun können wir die Aufrufe beliebig einbinden. Sicherlich kann man die Aufrufe noch vereinfachen, aber nach aktuellem Stand ist das nun auch ganz einfach möglich. Hier habe ich zum Beispiel die Lautstärkenänderung angepasst, so dass gleichzeitig ausgegeben wird, wie Laut der Lautsprecher nun gestellt wurde. Somit hat man gleich einen Test, ob die Lautstärke zu laut oder zu leise ist.

Rules-Datei:

rule "Lautstärkenänderung"
when
    Item eg_speaker_volume received update
then

    var String myCommand = "sudo /etc/openhab/php-addon/controlVolume.sh " + eg_speaker_volume.state 
    var String tmp = executeCommandLine(myCommand, 5000)
    logInfo('rules',myCommand + ' RETURNS: ' + tmp)

    // create mp3
    val ttsText = 'Die Lautstärke wurde auf ' + eg_speaker_volume.state + ' Prozent gestellt'
    myCommand = "/usr/bin/php /etc/openhab/php-addon/tts.php " + ttsText 
    var String playFile = executeCommandLine(myCommand, 5000)
    logInfo('rules',myCommand + ' RETURNS: ' + playFile)
    
    // play mp3
    myCommand = "sudo /usr/bin/mpg123 " + playFile
    playFile = executeCommandLine(myCommand, 5000)
    tmp = executeCommandLine(myCommand, 5000)
    logInfo('rules',myCommand + ' RETURNS: ' + tmp)
    } 
end

Viel Spaß beim Ausprobieren.

2 Antworten auf „Smart Home zum Reden bringen: openHAB und text-to-speech (tts) am raspberry pi“

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.