Mein erster Raspberry, der auch die Hausautomation steuert, ist bereits als squeezebox via squeezelite tätig, nun möchte ich an anderer Stelle ebenso einen WLAN-Lautsprecher haben. Auch hier habe ich mich für einen Raspberry Pi 3 und für die Soundausgabe für eine HifiBerry AMP+ Soundkarte inkl. 2x25Watt-Verstärker entschieden. Daran habe ich zwei alte aber gute Boxen gehängt und das alles ergibt durchaus einen schönen Klang.
Zuerst habe ich versucht, via squeezebox-Actions zu arbeiten, da bin ich aber schnell an die Grenzen gestoßen. Teils spielte die Squeezebox direkt mit den Squeezebox-Actions übermittelte mp3s mehrfach ab und zudem, wenn hintereinander mehrere mp3s abgespielt werden sollen, wirds schwierig, weil er die Dateien sofort abspielt und nicht erst in eine Queue einreiht. Daher habe ich squeezelite wieder entfernt und ein eigenes Script – unter Rückgriff auf Shell-Programme zum Abspielen von Streams und Sounds – geschrieben.
Use-Cases:
- Das System soll einen Planungsfehler beim Hausbau ausgleichen und im Erdgeschoss die Klingelfunktion übernehmen. Die normale Klingel ist ungünstig platziert und daher bei Musik im Wohnzimmer nicht immer hörbar.
Dazu ist ein Klingelsensor in openHAB eingebunden und die Boxen sollen dann (sofort) den Klingelton abspielen. - Verschiedene weitere Events sollen per TTS ausgegeben werden. Dazu gehören informationen über eingehende Anrufe (wer ruft an) oder das automatische Abspielen der Anrufbeantworter-Nachrichten, wenn man ins Wohnzimmer geht.
Auch hier gibt es verschiedene Events, welche jeweils eine entsprechende Priorität haben sollen. - Webradio soll abspielbar sein und wenn das Radio durch oben aufgeführte Events unterbrochen wird, dann soll der Stream danach gleich wieder aufgenommen werden.
- Die Lautstärke des Webradios soll steuerbar sein und – falls Streams mp3s unterbrechen – soll jedes Objekt mit der dafür vorgesehen Lautstärke weitergespielt werden.
- Text soll ausgsprochen wiedergegeben werden können.
Meine Anforderungen sind daher:
- Der Pi muss Audiodateien und Stream übernehmen können
- Die Audiowiedergabe muss wieder gestoppt werden könnne (wichtig für Streams)
- Eine Warteschlange muss implementiert werden und nacheinander müssen die entsprechenden Files abgespielt werden
- Wird ein Stream durch ein MP3 unterbrochen, so soll nach Abspielen der MP3s in der Queue der Stream wieder geladen werden
- Die Lautstärke soll regelbar sein und auch persistiert werden
- TTS: Text muss übergebbar sein und als Audio ausgegeben und entspr. in die Queue eingereiht werden.
Was wird benötigt?
- Ein Raspberry Pi 3 ohne Netzteil ca. 40 €
- Eine microSD-Karte (ich habe 16GB gewählt) ca 10 €
- Ein Netzteil für den Hifiberry Amp+ 60W 12V 5A, ca 16 €; es versorgt auch den Raspberry, der braucht dafür kein Netzteil mehr
- Den Hifiberry amp+ Aufsatz (Soundkarte mit Verstärker), ca 60 €
- Beliebige passive Boxen (ich hatte noch alte rumstehen).
Summe ohne Boxen: 126 €.
Konfiguration des Ziel-Raspberry Pi
Das Installieren von HifiBerry amp+ sowie von Raspbian selbst werde ich hier nicht aufzeigen, das ist in folgenden Tutorials beschrieben:
Dann gehts los:
Grundsätzlich muss der Pi Kommandos entgegen nehmen können. Dies wäre per ssh automatisiert möglich oder – da ich Scripte gerne mit PHP umsetze – über einen kleinen Apachen, der am Ziel-Pi läuft.
Hierzu loggen wir uns als root ein oder führen alternativ alle Befehle mittels „sudo“ aus.
apt-get install php5 mpv
…installiert alle hierzu benötigten Pakete. Der Apache fährt nun bei jedem Systemstart mit hoch und ist über die IP des Pis auf Port 80 ansprechbar.
mpv ist als abspieltool dann über die Kommandozeile gleichermaßen ansprechbar.
Unter /var/www/html (Standard http-Dokumenten-Rootverzeichnis unter Raspbian) erstellen wir ein Verzeichnis „streampi“.
Dieses Verzeichnis übergeben wir komplett dem www-data User, damit der Webserver-Benutzer, unter dem der Apache ausgeführt wird, auch darauf schreibend zugreifen kann.
mkdir /var/www/html/streampi touch /var/www/html/streampi/streampi.php touch /var/www/html/streampi/streampi.db touch /var/www/html/streampi/streampi.volume chown -R www-data.www-data /var/www/html/streampi
streampi.db dient zum Speichern der Queue sowie des aktuellen / letzten Titels, daher muss diese anfangs keinen Inhalt beinhalten und es genügt, diese leer zu erstellen.
Die streampi.php befüllen wir mit folgendem Inhalt:
<?php // Some configuration $ttsScript = '/usr/bin/php ' . realpath(dirname(__FILE__)) . '/includes/tts.php'; $volumeFile = 'streampi.volume'; $amixer = '/usr/bin/amixer'; $mpv = '/usr/bin/mpv --really-quiet '; $dbFile = 'streampi.db'; $telUrl = 'http://localhost/fritzpi/lookup.php?nr='; // Handle input $prio = (int) $_REQUEST['prio']; $file = $_REQUEST['file']; $type = strtolower($_REQUEST['type']); $text = $_REQUEST['text']; $tel = $_REQUEST['tel']; // addon for fritz box. replaces %NR% with looked up number that is passed via "tel" parameter if (isset($tel) && (strlen($tel) > 0)) { $name = file_get_contents($telUrl . $tel); $text = str_replace('NUMMER', $name, $text); } if (!in_array($type, array('stream', 'stop', 'volume', 'tts'))) { $type = 'mp3'; } if ($type == 'volume') { $volume = (int) $_REQUEST['value']; if ($volume < 0) { $volume = 0; } else if ($volums > 200) { $volume = 200; } file_put_contents($volumeFile, $volume); setVolume($volume); } else { // TTS service called? Then we will handle this request first if ($type == 'tts') { if ($text != '') { $command = $ttsScript . ' ' . str_replace('ß', 'ss', $text); $ttsFile = shell_exec($command); // now we will proceed as if the tts file was given via parameter $file = $ttsFile; $type = 'mp3'; } else { exit; } } else if ($type == 'stream') { // streams have always prio 999 (lowest priority) $prio = 999; } // Check if mpv is running already $mpvRunning = (substr_count(shell_exec('ps aux | grep mpv'), "\n") > 2); // Get Database $db = getDB(); // Stop playing if necessary if ($type == 'stop') { exec('killall mpv'); $db = array(); storeDB($db); $mpvRunning = false; } else if ($file != '') { // playing already? cancel, if stream is played, and play then, otherwise queue $key = (str_pad($prio, 3, 0, STR_PAD_LEFT) . ( 32483067507 - time() )); $playItem = array( 'file' => $file, 'type' => $type, 'prio' => $prio, 'volume' => (int) $_REQUEST['volume'], 'key' => $key ); if ((($mpvRunning) && ($db['last']['type'] == 'stream')) || !$mpvRunning) { if ($mpvRunning) { exec('killall mpv'); $lastItem = $db['last']; $db['queue'][$lastItem['key']] = $lastItem; } $db['last'] = $playItem; $mpvRunning = false; } // add to Queue. Play Method does sorting later... $db['queue'][$playItem['key']] = $playItem; storeDB($db); if (!$mpvRunning) { playNext(); } } } function storeDB($db) { global $dbFile; file_put_contents($dbFile, serialize($db)); } function getDB() { global $dbFile; $dbSource = unserialize(file_get_contents($dbFile)); if (!is_array($dbSource)) { $db = array('last' => array(), 'queue' => array()); } else { return $dbSource; } return $db; } function playNext() { global $mpv; $db = getDB(); $queue = $db['queue']; // sort queue ksort($queue); $playItem = array_shift($queue); if ($playItem['key'] != '') { // Set volume level if ($playItem['volume'] > 0) { setVolume($playItem['volume']); } else { setVolume(getVolume(false)); } // Modify database $db = array( 'last' => $playItem, 'queue' => $queue ); storeDB($db); // stream? play in background! if ($playItem['type'] == 'stream') { exec($mpv . ' ' . $playItem['file'] . ' > /dev/null 2>/dev/null &'); } else { exec($mpv . ' ' . $playItem['file']); playNext(); } } } function getVolume($fromFileOnly = false) { global $volumeFile; if (isset($_REQUEST['volume']) && $fromFileOnly) { $volume = $_REQUEST['volume']; } else { $volume = file_get_contents($volumeFile); } if (!($volume >= 0) && !($volume <= 200)) { $volume = 100; } return $volume; } function setVolume($value) { global $amixer; $cmd = $amixer . ' sset Master ' . $value . ' unmute'; exec($cmd); }
Damit aber der Apache Audio ausgeben darf muss der www-data-Benutzer noch der Gruppe „audio“ hinzugefügt werden und der Apache muss dann neu gestartet werden, damit die Änderung auch berücksichtigt werden kann:
usermod -aG audio www-data service apache2 restart
Wichtig ist noch, dass das Script nur sehr rudimentär ist (im Moment) und die übergebenen Parameter nicht geprüft werden, bevor sie an exec übergeben werden. Theoretisch kann man damit auch etwas kaputt machen, ich für mich sehe aktuell zumindest in meinem Bereich kein großes Problem, da das Netz von außen geschützt ist und niemand drauf zugreifen kann. Hier muss man ggf. noch einen Zugriffsschutz draufpacken.
Konfiguration des Smarthome-Raspberry Pi
Auf dem openHAB-Pi nutzen wir die http-Actions um auf dem Ziel-Pi das Script anzustoßen.
MP3-Klingel
Im Beispiel hier ist das in meine Klingel-Anlage integriert, eine Rule löst aus, wenn der Klingelsensor abschlägt und dieser stößt dann per HTTP-GET-Request die Klingel an.
rule "Türklingel wurde gedrückt" when Item klingel changed from OFF to ON then logInfo('rules','klingel wurde ausgelöst') sendHttpGetRequest('http://<raspberry_ip_address>/streampi/streampi.php?file=/home/pi/klingel.mp3');
Webradio
Eine Auswahl an ausgewählten Radiostreams ist als Webradio in meine Sitemap integriert.
items-Datei
Number webradioKuecheEG "Webradio Küche EG" <network> Number webradioKuecheEGvolume "Webradio Küche Volume [%d]" <network>
sitemap-Datei
Frame label="Squeezebox-Player" { Selection item=squeezeboxEG mappings=[1="Aus",2="Radio 2Day München",3="SWR3",4="Bayern 1",5="Charivari"] Setpoint item=webradioKuecheEGvolume minValue=0 maxValue=200 step=10 }
rules-Datei
rule "Webradio Raspberry 2 geschalten" when Item webradioKuecheEG received command then switch(receivedCommand) { case 1: { sendHttpGetRequest('http://<IP-ADDRESS>/streampi/streampi.php?type=stop'); } case 2: { sendHttpGetRequest('http://<IP-ADDRESS>/streampi/streampi.php?type=stop'); sendHttpGetRequest('http://<IP-ADDRESS>/streampi/streampi.php?type=stream&file=http://radio2day.ip-streaming.net/radio2day.m3u'); } case 3: { sendHttpGetRequest('http://<IP-ADDRESS>/streampi/streampi.php?type=stop'); sendHttpGetRequest('http://<IP-ADDRESS>/streampi/streampi.php?type=stream&file=http://mp3-live.swr3.de/swr3_m.m3u'); } case 4: { sendHttpGetRequest('http://<IP-ADDRESS>/streampi/streampi.php?type=stop'); sendHttpGetRequest('http://<IP-ADDRESS>/streampi/streampi.php?type=stream&file=http://streams.br.de/bayern1nbopf_2.m3u'); } case 5: { sendHttpGetRequest('http://<IP-ADDRESS>/streampi/streampi.php?type=stop'); sendHttpGetRequest('http://<IP-ADDRESS>/streampi/streampi.php?type=stream&file=http://www.vtuner.com/vtunerweb/mms/m3u33003.m3u'); } } end rule "Lautstärkenänderung Raspberry 2" when Item webradioKuecheEGvolume received update then sendHttpGetRequest('http://<IP-ADDRESS>/streampi/streampi.php?type=volume&value='+ webradioKuecheEGvolume.state end
TTS-Service anbinden
Zur Sprachausgabe habe ich ja bereits gebloggt, und genau das Script verwenden wir einfach wieder und legen es in einem Unterordner „includes“ ab.
Wenn alle Dateien platziert wurden und das mp3-Ablageverzeichnis konfiguriert wurde am besten dem Unterverzeichnis includes ebenso www-data als Besitzer geben, denn es ist nun ja der Apache-Benutzer, der schreibenden Zugriff benötigt:
chown -R www-data.www-data /var/www/http/streampi/includes
Wenn dann in einer Rule Text ausgegeben werden soll, ist das ebenso einfach möglich wie das Abspielen von Streams oder MP3s, allerdings verwenden wir – um bei Leerzeichen usw. in den Sätzen nicht in Probleme zu laufen – hier nicht die GET sondern die POST Methode.
sendHttpPostRequest('<IP-ADDRESS>/streampi/streampi.php','application/x-www-form-urlencoded','type=tts&prio1&volume=140&text=Achtung eine wichtige Durchsage')
Zusätzlich zeigt das Beispiel auf, wie mit Prioritäten und Lautstärken gearbeitet werden kann. Durch Angabe von Prio=1 wird das File als nächstes abgespielt, auch wenn weitere in der Queue sind. Zudem wird die Lautstärke unabhängig von der über openhab konfigurierten Lautstärke auf 140 gesetzt. Dies gilt natürlich nur für diesen Satz, danach wird die Lautstärke automatisch wieder zurück gesetzt.
Damit kann man nun vieles Umsetzen: Ansagen, wenn das Telefon klingelt (ggf. wer dran ist), Erinnerungen an Termine, und und und. Die Möglichkeiten sind eher unbegrenzt.
Viel Spaß damit!
Hallo ,
hab soweit alle deine Schritte abgearbeitet, soweit alles gut.
Jedoch wenn ich versuche auf dem entfernten RAspberry ein Sound abzuspielen, kommt leider kein Ton.
WWW-Data ist in der gruppe Audio und hat soweit auch alle Rechte die PHP Daten und sowie der Sound-Datei.
Könnte es sein das es noch eine PHP Datei fehlt? Da In der streampi.php einen Verweis auf die lookup.php in Zeile 9 gibt. Jedoch wird bei deinem Beitrag nicht auf diese PHP eingegangen, oder habe ich da was übersehen?
Ich hoffe du kannst mir da weiterhelfen.
Vielen Dank
Grüß Andy