Bose SoundTouch PHP Gateway – Teil 1

Wie bereits im Beitrag zum Bose SoundTouch 10 Multiroom Lautsprecher beschrieben, ist die Bose SoundTouch API nicht zur direkten Integration in den Loxone Miniserver geeignet. Deshalb habe ich angefangen, ein Bose SoundTouch PHP Gateway zu schreiben. Diese nimmt die Befehle vom Loxone Miniserver entgegen und bereitet diese so auf, dass die Bose SoundTouch Lautsprecher angesteuert werden können. Im Skript sind bisher nur die wichtigen Funktionen wie “Power”, “Volume” und “Select Preset” eingebaut.
Vorab das Bose SoundTouch PHP Gateway ist noch nicht fertig. Weitere Funktionen wie Sound Files von einem UPnP / DLNA Media Server abspielen, werden definitiv noch im zweiten Teil nachgeliefert. Hier liegt das Problem aktuell noch an Details wie z. B. die Wiedergabe der letzten Quelle, wenn ein Soundfile abgespielt wurde. Eventuell schreibe ich noch eine kleine Statuswebseite, die dann in die Loxone Config mit dem Baustein “Website” eingebunden werden kann. Da ich mit festen IP Adressen arbeite, ist ein Discovery der Geräte aktuell nicht geplant. Fehlen noch weitere Funktionen? Dann nutzt gerne die Kommentarfunktion und schreibt sie auf!

Loxone Config

Es wird einmalig ein “Virtueller Ausgang” für die Schnittstelle angelegt. Hier muss unter “Adresse” nur der Link zum Bose SoundTouch PHP Gateway also dem Webserver eingetragen werden. Die Adresse zur Bose SoundTouch API wird im nachfolgendem Skript definiert. Alle weiteren Felder können auf Standard bleiben.Bose SoundTouch PHP Gateway - Loxone Virtueller Ausgang

Für jeden Befehl wird dann ein “Virtueller Ausgang Befehl” angelegt. Hier müssen dann mindestens zwei bzw. drei Parameter über “Befehl bei EIN” an die PHP Schnittstelle übergeben werden. Device ist das letzte Oktett der IP Adresse vom Bose SoundTouch Lautsprecher, Action ist der eigentliche Befehl und Value der entsprechende Wert. Folgend ein paar Beispiele für den “Befehl bei EIN” wenn der Bose SoundTouch z. B. die IP Adresse 192.168.1.171 hat und das PHP Skript mit dem Namen api.php im Ordner bose abgelegt wurde.

Befehl Beschreibung
/bose/api.php?device=171&action=key&value=preset_1 Wählt Preset 1
/bose/api.php?device=171&action=power&value=1 Schaltet den Bose SoundTouch an
/bose/api.php?device=171&action=volup&value=5 Lautstärke um 5 erhöhen
/bose/api.php?device=171&action=setvol&value= Anpassung Lautstärke
/bose/api.php?device=171&action=setvol&value=30 Setzt die Lautstärke auf 30
/bose/api.php?device=171 Einfache Statusseite

SoundTouch PHP Gateway

Das Bose SoundTouch PHP Gateway benötigt neben einem Webserver mit PHP zusätzlich das PHP-cURL Modul. Über den folgenden Befehl kann das Modul installiert werden.

apt-get install php5-curl

Im Skript muss noch zwingend die IP Adresse “192.168.1.” in der Variable DeviceIP auf die Umgebung angepasst werden. Mehr muss aktuell nicht angepasst werden.

<html>
<head>
	<meta charset="utf-8" />
	<title>BOSE SOUNDTOUCH</title>
</head>

<body>

<?php

$device = $_GET['device'];
$action = $_GET['action'];
$value = $_GET['value'];

if (!is_numeric($device) || empty($device)) 
{
	die ('Wrong or missing parameter Device');
}

//////////////////////////////////////////
// Variablen
$DeviceIP = "192.168.1.". $device;

//////////////////////////////////////////
// Functions

    // Press Key
    function PressKey($key)
    {
		global $DeviceIP;

        $xmldata1 = "<key state=press sender=Gabbo>". $key ."</key>";
        $xmldata2 = "<key state=release sender=Gabbo>". $key ."</key>";
        for ($i=1 ; $i < 3 ; $i++) {
            $curl = curl_init();
            curl_setopt_array($curl, array(
                CURLOPT_URL => "http://". $DeviceIP .":8090/key",
                CURLOPT_HEADER => false,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_POST => true,
                CURLOPT_POSTFIELDS => ${"xmldata".$i},
                CURLOPT_HTTPHEADER => array("Content-type: text/xml")));
            $result = curl_exec($curl);
            curl_close($curl);
        }
    }

    // Status
    function Status()
    {
		global $DeviceIP;

        $curl = curl_init();
        curl_setopt_array($curl, array(
            CURLOPT_URL => "http://". $DeviceIP .":8090/now_playing",
            CURLOPT_HEADER => false,
            CURLOPT_RETURNTRANSFER => true));
        $result = curl_exec($curl);
	curl_close($curl);

        $xmldata = new SimpleXMLElement ($result);

        $source = $xmldata->ContentItem["source"];
	$power = true;
        switch ($source) {
            case "STANDBY":
                $mode = "Standby";
                $power = false;
            break;

            case "AUX":
                $mode = "AUX";
            break;

            case "STORED_MUSIC":
                $mode = "NAS";
            break;
			
            case "BLUETOOTH":
                $mode = "Bluetooth";
            break;

            case "INVALID_SOURCE":
                $mode = "Fehler";
            break;
			
            case "INTERNET_RADIO":
                $mode = "Internet Radio";
            break;
			
	    case "SPOTIFY":
                $mode = "Spotify";
            break;

            case "BLUETOOTH":
                $mode = "Bluetooth";
            break;
			
            default:
                $mode = "";
                $power = false;
	}

        // get now playing
        $nowplaying = utf8_decode($xmldata->stationName);

        // get description
        $description = utf8_decode($xmldata->description);

        // get logo
        $logourl = utf8_decode($xmldata->art);

        // return results       
        return array(
			"mode"    	=> $mode,
			"power"   	=> $power,
			"nowplaying"    => $nowplaying,
			"description"   => $description,
			"logourl"       => $logourl
		);
    }

// Volume
function get_volume()
    {
		global $DeviceIP;

        $curl = curl_init();
        curl_setopt_array($curl, array(
            CURLOPT_URL => "http://". $DeviceIP .":8090/volume",
            CURLOPT_HEADER => 0,
            CURLOPT_RETURNTRANSFER => 1));
        $result = curl_exec($curl);
        $xmldata = new SimpleXMLElement($result);
        $targetvolume = (integer)$xmldata->targetvolume;
        $actualvolume = (integer)$xmldata->actualvolume;
        $mutedvolume = (integer)$xmldata->muteenabled;
        // return results
        return array(   "targetvolume"  => $targetvolume,
                        "actualvolume"  => $actualvolume,
                        "mutedvolume"   => $mutedvolume);
    }

function set_volume($volume)
    {
		global $DeviceIP;

        $xmldata = "<volume>". $volume ."</volume>";
        $curl = curl_init();
        curl_setopt_array($curl, array(
            CURLOPT_URL => "http://". $DeviceIP .":8090/volume",
            CURLOPT_HEADER => 0,
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_POST => 1,
            CURLOPT_POSTFIELDS => $xmldata,
            CURLOPT_HTTPHEADER => array("Content-type: text/xml")));
        $result = curl_exec($curl);
        curl_close($curl);
    }

//////////////////////////////////////////

$Device = Status();

//////////////////////////////////////////

switch ($action)
{
	case "key":
		$value = strtoupper($value);
		PressKey($value);
	break;

	case "power":
		if (is_null($value))
		{
			PressKey("POWER");
			echo "Switch Power State";
		}
		else
		{
			if ($Device['power'] == 1 && $value == 0)
			{
				echo "Switch Power Off";
				PressKey("POWER");
			}

			if ($Device['power'] == 0 && $value == 1)
			{
				echo "Switch Power On";
				PressKey("POWER");
			}
		}
	break;
	
	case "getvol":
		$vol = get_volume();
		echo $vol['targetvolume'];
	break;

	case "setvol":
		set_volume($value);
		echo $value;
	break;

	case "volup":
		$vol = get_volume();
		$targetvol = $vol['targetvolume'] + $value;
		set_volume($targetvol);
		echo $targetvol;
	break;

	case "voldown":
		$vol = get_volume();
		$targetvol = $vol['targetvolume'] - $value;
		set_volume($targetvol);
		echo $targetvol;
	break;

	default:
	echo "Status: " . $Device[mode];
	echo "<br>";
	echo $Device[nowplaying];
	echo "<br>";
	echo $Device[description];
	echo "<br>";
	echo "<img src=" . $Device[logourl] . ">";
	break;
}

?>

9 Gedanken zu „Bose SoundTouch PHP Gateway – Teil 1“

  1. Hallo Stefan,
    vorab erst einmal ein dickes Lob für Deine Mühe hier.
    Ich habe die gleiche Konfiguration wie du… 🙂 Bose überall 🙂
    Nun soll dieses System über Loxone steuerbar sein.
    Was benötige ich noch ? Raspberry sollte genügen?
    Die Installation des Rasp. Erfolgt wie?
    Gibt es da noch ein Tool was installiert werden muss?
    Curl… Die anzulegende Konfig bzw. Datei muss wo abgelegt werden?
    Muss für jede Soundtouch eine eigene Installation angelegt werden?
    Loxone klar … jede IP anlegen mit einem VA … ?!

    Danke für Deine Zeit und Mühe 

    1. Hallo Marcell,

      vielen Dank für das Lob! Bisher haben wir hier nur einen Bose stehen. Das kann sich aber noch ändern, je nachdem wie sich die Amazon Echo Produkte so machen.

      Du benötigst vorzugsweise ein Linux System wie den Raspberry Pi. Ich würde das Raspbian OS bevorzugen und den Apache Webserver und PHP5 (mit Curl) installieren.

      Das Skript legst du dann mit der Endung .php in das Webroot ( voraussichtlich /var/www/html ) oder wie in der Anleitung beschrieben z. B. in den Ordner bose.

      Du kannst über den HTML Parameter “device” jeden Bose SoundTouch über dessen IP ansteuern, vorausgesetzt alle befinden sich im gleichen Netzwerk. Du benötigst also für jeden SoundTouch und Befehl einen “Virtuellen Ausgang Befehl”.

      Jeder Bose SoundTouch und der Raspberry Pi sollte möglichst über deinen DHCP Server eine statische IP Adresse bekommen, sonst kann es passieren das der DHCP Server eine neue Adresse vergibt und es nicht mehr funktioniert.

      Viele Grüße
      Stefan

        1. Hallo Peter,

          Sorry das ich mich erst jetzt bei dir melde, von mir aus kannst du das Skript gerne für dein Projekt verwenden! Es kann Spuren von anderen Skripten enthalten, aus denen ich Schnipsel angepasst und übernommen habe.

          Viele Grüße
          Stefan

  2. Hallo zusammen,

    ich bin gespannt auf Rückmeldungen derjienigen die die Integration bereits durch haben; hat alles geklappt und funktioniert wie erwartet? Bekomme bald Loxone und mache mir schonmal Gedanken wie ich die noch zu kaufenden Bose-Welt dort integrieren kann…

    Danke an alle für die Rückmeldung. Sebastian

  3. hallo,
    bei mir funktioniert die schnittstelle nicht.
    ich habe einen minidlna und apache2 server aufgesetzt.
    im webbrowser erscheint noch die IP von der Bose Soundtouch bei AUfruf des Scripts.
    dann ist Ende.
    Hat jemand eine Idee?

  4. Ich hab mehrere Soundtouch-Geräte (in verschiedenen Räumen) und nutze gern die Funktion der Gruppe (über die App), um in allen Räumen die gleiche Musik zu hören. Glaubst du das lässt sich auch über die REST API realisieren, bzw. hier mit integrieren?
    Ich würde gerne eine Funktion bauen, mit der ich quasi die Musik aus einem anderen Raum in diesem Raum (in dem dann per Taster die Funktion aufgerufen wird) abspielen kann.
    Also wenn ich jetzt in der Küche bin und im Wohnzimmer läuft Musik, dann würde ich in der Küche den Taster drücken und es müsste folgendes passieren:
    1. erkennen ob irgendwo Musik läuft (geht ja mit dem now-playing soweit jetzt schon, indem ich einfach der Reihe nach alle durchgeh) ==> ja im Wohnzimmer läuft musik
    2. bei dem Gerät im Wohnzimmer das Gerät von der Küche mit in die Gruppe aufnehmen ==> hier wäre jetzt die Frage wie das geht.

    hast du eine Idee?

    1. Hallo Bernhard,

      das sollte, wenn ich es richtig verstanden habe, über die Zonen möglich sein. Hierzu benötigt es aber ein wenig mehr “Intelligenz” im Script. Da ich nur einen Soundtouch habe, habe ich noch nie mit Zonen gearbeitet. Vermutlich können über die Befehle “addZoneSlave” und “RemoveZoneSlave” weitere Soundtouch Geräte einer Zone die vorher über “setZone” erstellt wurde, hinzugefügt werden. Über WebSockets kann die API auch direkt über Änderungen am Soundtouch informiert werden und somit sollte auch ohne Probleme eine Steuerung über App, Lokal und über die REST API möglich sein. Ich würde dann aber eher auf Node.js als Sprache wechseln, alleine schon wegen der Websockets und der Logik, die sich dann auch ein paar Sachen merken muss. Ich hoffe, das ich dir ein wenig weiterhelfen konnte.

      Viele Grüße
      Stefan

  5. Hallo Stefan,

    vielen Dank für deine Antwort.
    Auch wenn mir das leider technisch dann zu hoch war. bei nodes.js bin ich raus….
    Aber wenn ich dich richtig verstanden habe, werde ich aber da mit deinem PHP-Gateway erstmal nicht weit kommen, richtig? Dann werde ich erstmal den Standard machen und wenn ich mal viel zeit hab, mich mit einer anderen API auseinandersetzen.

    Gruß
    Bernhard

Schreibe einen Kommentar

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