HEOS Box aus der Visualisierung ansteuern

Wir haben einige HEOS Boxen von Denon im Haus aufgestellt. Diese Boxen sind per LAN oder WLAN angeschlossen und können dann zum Streaming von Musik verwendet werden. Zu den Boxen gibt es von Denon eine App, mit der wir die Musik auf den Boxen steuern können. Wir haben aber auch das Ziel, dass wir die Boxen direkt von unserer Haus-Automatisierung (zum Beispiel vom RaspberryPi Panel) ansprechen können, so dass nicht immer das Smartphone griffbereit sein muss.

Unser erstes Ziel ist es dabei, dass wir die Musik starten und beenden, die Lautstärke regeln und durch die Playlist springen können. Wie dies geht, beschreiben wir im folgenden.

Als erstes haben wir überlegt, wie eine Oberfläche aussehen könnte, die die grundlegenden Bedürfnisse erfüllt und auf dem Raspberry Pi Display auch per Touch gut zu bedienen ist. Den ersten Entwurf seht ihr hier:

Musik Player GUI für die Heos Steuerung

Dieser wurde mit den Icons des KNX UF Iconset gebaut. Die verschiedenen Buttons müssen jetzt mit Befehlen verbunden werden, die die entsprechende HEOS Box steuert.

Erstes Ansprechen der HEOS Box mit Telnet

Als erstes versuchen wir die HEOS Box direkt per Telnet anzusprechen. Dazu machen wir unter Linux eine Konsole auf und geben folgendes ein (Wintergarten ist der DNS-Name unserer HEOS Box)

telnet Wintergarten 1255

Es öffnet sich eine Konsole, auf der wir die Befehle testen können. Hier geben wir den ersten Befehl ein:

heos://player/get_players

Nach dem Enter gibt es die Antwort per JSON. Diese sieht, nachdem sie durch einen JSON-Formatter gejagt wurde, in etwa so aus:

{
   "heos":{
      "command":"player/get_players",
      "result":"success",
      "message":""
   },
   "payload":[
      {
         "name":"Wohnzimmer",
         "pid":1134350464,
         "model":"xxx",
         "version":"xxx",
         "ip":"xxx",
         "network":"wired",
         "lineout":0,
         "serial":"xxx"
      },
      {
         "name":"Wintergarten",
         "pid":901780091,
         "model":"xxx",
         "version":"xxx",
         "ip":"xxx",
         "network":"wifi",
         "lineout":0,
         "serial":"x"
      }
   ]
}

Wie man sehen kann, bekommt man als erstes das Kommando zurück mit einer Meldung, ob das Kommando erfolgreich war. Danach folgen dann die Daten im Block payload.

Mit der Telnet Konsole haben wir jetzt eine einfache Möglichkeit weitere Kommandos aus der Spezifikation zu testen. Schauen wir uns also mal an, wie das Ganze dann in Python funktioniert.

Ansteuerung der HEOS Box mit Python

Um mit der HEOS Box zu sprechen, verwenden wir die TelnetLib von Python. Dabei ist Wintergarten wieder der Name unserer HEOS-Box. 1255 ist wie schon bei der Kommunikation per Telnet der Port, auf dem wir die Box erreichen können. In Python reichen zwei Zeilen, um die Verbindung aufzubauen.

import telnetlib
tn = telnetlib.Telnet("Wintergarten", 1255)

Jetzt können wir sehr einfach ein Kommando an die Box senden. Sinnvoll ist es als erstes die PID der Box auszulesen, da wir die für verschiedene Kommandos brauchen. Dazu führen wir wieder das Kommando heos://player/get_players aus. Wichtig ist der Abschluss mit dem Newline Symbol.

tn.write(b"heos://player/get_players\n")

Da die Antwort etwas länger ist, müssen wir sie in einer Schleife abholen. Wie oben gesehen ist sie in JSON kodiert. Daher holen wir solange Daten ab, bis der JSON Parser keinen Fehler mehr wirft. Damit können wir sicher sein, dass die Antwort auch vollständig ist. Wir verwenden die folgende Schleife:

import json

message = b''
finish = False
while not finish :
    message += tn.read_some()
    if message:
        try:
            data = json.loads(message.decode('utf-8'))
            finish = True
        except json.JSONDecodeError:
            pass
        except UnicodeDecodeError:
            pass

Der erste Fehler wird geworfen, wenn der JSON String noch nicht vollständig ist, der zweite Fehler kann auftreten, wenn Unicode Zeichen nur teilweise im String enthalten sind. Deswegen lassen wir bei den beiden Fehlern weitere Abfragen von Daten zu.

Wir brauchen diese Funktion für alle Befehle. Daher packen wir sie in eine eigene Funktion zusammen:

import telnetlib
import json

def run_command(command: str):
    tn = telnetlib.Telnet("Wintergarten", 1255)
    tn.write(command + b"\n")
    message = b''
    while True:
        message += tn.read_some()
        if message:
            try:
                data = json.loads(message.decode('utf-8'))
                return data
            except json.JSONDecodeError:
                pass
            except UnicodeDecodeError:
                pass

Befehle für die HEOS Box

Im Anschluss können wir die notwendigen Befehle an die HEOS Box senden. Dazu lesen wir als erstes die PID aus. Dies macht die folgende Funktion:

def get_pid():
data = run_command(b"heos://player/get_players")
pid = ""
for entry in data["payload"]:
if entry["name"] == "Wintergarten":
pid = entry["pid"]
return pid

Die obige Funktion filtert aus der Liste aller gefundenen Boxen die im Wintergarten heraus. Das HEOS Protokoll ist so aufgebaut, dass man über eine Box auch die anderen steuern kann. Wir könnten also an die HEOS Box im Wintergarten auch Befehle für die HEOS Box im Wohnzimmer schicken.

Hier muss man jedoch etwas aufpassen, dass es nicht zu seltsamen Effekten kommt.

Nun aber zu den eigentlichen Befehlen. Als erstes senden wir dazu den Play Befehl:

pid = get_pid()
run_command(b"heos://player/set_play_state?pid=" + str(pid).encode() + b"&state=play")

Analog dazu sieht dann auch der Pause Befehl aus:

run_command(b"heos://player/set_play_state?pid=" + str(pid).encode() + b"&state=pause")

Bei den beiden Befehlen interessiert uns die Antwort nicht. Das ist schon anders, wenn wir wissen wollen, in welchem Zustand der Player gerade ist und welches Lied er gerade abspielt.

Dazu verwenden wir die beiden Befehle heos://player/get_play_state?pid=xxx und heos://player/get_now_playing_media?pid=xxx. Spannend ist hier vor allem, wie die Antwort aussieht. Daher hier die Ergebnisse direkt aus der Telnet Konsole (mit dem Formatter bearbeitet):

// Kommando: heos://player/get_play_state?pid=901780091
{
   "heos":{
      "command":"player/get_play_state",
      "result":"success",
      "message":"pid=901780091&state=stop"
   }
}
// Kommando heos://player/get_now_playing_media?pid=901780091
{
   "heos":{
      "command":"player/get_now_playing_media",
      "result":"success",
      "message":"pid=901780091"
   },
   "payload":{
      "type":"song",
      "song":"Why So Serious?",
      "album":"The Dark Knight [Original Motion Picture Soundtrack] [The Collectors Edition]",
      "artist":"",
      "image_url":"xxx",
      "album_id":"xxx",
      "mid":"xxx",
      "qid":57,
      "sid":1024
   },
   "options":[]
}

Diese so erzeugten Daten müssen jetzt nachdem wir den Befehl per Python schicken nur noch geparsed werden. Dies geht recht einfach per Regular Expression.

pid = get_pid()

data = run_command(b"heos://player/get_play_state?pid=" + str(pid).encode())
play_state = re.search("(?<=&state=)[a-z]+", data["heos"]["message"])

data = run_command(b"heos://player/get_now_playing_media?pid=" + str(pid).encode())
act_music = data["payload"]["song"]

Auf diesem Weg können jetzt die Daten an das Django Template übergeben werden.

Ausblick

In den Beispielen haben wir den HEOS Box Namen noch fest einkodiert. Dies wollen wir in einer zukünftigen Version aber ändern um die Auswahl der anzusprechenen Box zu ermöglichen. Auch weitere Funktionen, wie zum Beispiel Playlists oder die Auswahl der Soundquelle stehen noch auf der ToDo Liste.

Links

3 Antworten auf „HEOS Box aus der Visualisierung ansteuern“

  1. Hallo,
    Ich würde gern sowas ähnliches machen, nur andersherum.
    Ich habe einen Marantz Melodie Receiver, der mir den Heos Stream ins Haus blasen kann.
    Da mir für die Küche und den Garten die nativen Heos Speaker eindeutig zu teuer sind und ich noch ein paar alte Funklautsprecher hier habe, möchte ich diese gern zusammenbringen.
    Ich überlege, die Funktechnik aus den Speakern rauszuwerfen und einen Pi Zero entweder mit der hifiberry oder dem pirate audio Komponenten zu ersetzen.
    Ich habe nur so überhaupt keinen Plan, wie ich den Pi dazu bringen kann, die Heos Signale anzunehmen bzw. sich als Heos Speaker auszugeben. Könnt ihr mir da helfen?
    Am besten wäre es noch, wenn ich die somit entstandenen neuen speaker sowohl als Heos als auch via Bluetooth AirPlay ansteuern könnte. Meint ihr, das geht.
    Besten Dank für die Infos, Bert

    1. Hallo Bert,

      vielen Dank für deine ausführliche Beschreibung, ich denke ich konnte ganz gut nachvollziehen was Du Dir wünschst.

      Leider habe ich keine konkrete Lösung für dich, kann Dir aber vielleicht ein paar Tipps geben, wo Du anfangen kannst. Wenn Du per UPnP mal alle Dienste in deinem Netz abfragst, wirst Du recht schnell sehen, dass es für die Heos Box sehr viele verschiedene Dienste gibt. Meine Lösung spricht nur mit dem Konfigurationsdienst der Box. Dafür findet man halt auch die Spec bei Denon. Damit Du eine Box simulieren kannst, müsstest Du jetzt alle anderen Dienste auch nachimplementieren. Ich denke das wäre ein Riesen-Aufwand auch ohne Spezifikation.

      Wahrscheinlich ist es einfacher auf eine Linux-Implementierung zurückzugreifen. Vielleicht erfüllt ja schon MPD deine Anforderungen, wenn nicht würde ich schauen, ob es neuere Applikationen gibt.

Schreibe einen Kommentar

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

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.