Skip to content
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. JavaScript
  5. [Vorlage] Denon HEOS Script

NEWS

  • UPDATE 31.10.: Amazon Alexa - ioBroker Skill läuft aus ?
    apollon77A
    apollon77
    48
    3
    8.5k

  • Monatsrückblick – September 2025
    BluefoxB
    Bluefox
    13
    1
    2.1k

  • Neues Video "KI im Smart Home" - ioBroker plus n8n
    BluefoxB
    Bluefox
    16
    1
    2.7k

[Vorlage] Denon HEOS Script

Geplant Angeheftet Gesperrt Verschoben JavaScript
javascripttemplate
357 Beiträge 48 Kommentatoren 77.0k Aufrufe 44 Watching
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • J jwedenig

    das passiert, wenn ich die Objekte lösche und das Skript wieder neu starte, um die Objekte zu erzeugen

    web.0	2020-02-28 09:28:07.539	info	(26785) ==>Connected system.user.admin from ::ffff:192.168.1.159
    web.0	2020-02-28 09:28:05.988	info	(26785) ==>Connected system.user.admin from ::ffff:192.168.1.159
    javascript.1	2020-02-28 09:26:58.908	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] signed in: success
    javascript.1	2020-02-28 09:26:58.030	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] signed in: success
    javascript.1	2020-02-28 09:26:58.021	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: [HeosPlayer 192.168.1.177] starting HEOS player for IP 192.168.1.177
    javascript.1	2020-02-28 09:26:58.013	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] connected to 192.168.1.177
    javascript.1	2020-02-28 09:26:58.012	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] connected to HEOS
    javascript.1	2020-02-28 09:26:58.010	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] connecting to 192.168.1.177 ...
    javascript.1	2020-02-28 09:26:57.269	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: registered 1 subscription and 0 schedules
    javascript.1	2020-02-28 09:26:57.267	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] connecting to HEOS ...
    javascript.1	2020-02-28 09:26:57.263	info	(16326) Start javascript script.js.Weldscripts.Javascript.Heos_neu
    javascript.1	2020-02-28 09:26:54.113	error	(16326) error in onStop callback: TypeError: this.nodessdp_client.destroy is not a function
    javascript.1	2020-02-28 09:26:54.112	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] disconnecting from HEOS ...
    javascript.1	2020-02-28 09:26:54.110	info	(16326) Stop script script.js.Weldscripts.Javascript.Heos_neu
    javascript.1	2020-02-28 09:26:46.169	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] signed in: success
    javascript.1	2020-02-28 09:26:45.882	warn	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] Command_not_recognized
    javascript.1	2020-02-28 09:26:45.879	warn	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] Command_not_recognized
    javascript.1	2020-02-28 09:26:45.496	warn	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] Command_not_recognized
    javascript.1	2020-02-28 09:26:45.494	warn	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] Command_not_recognized
    javascript.1	2020-02-28 09:26:45.476	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] command:
    javascript.1	2020-02-28 09:26:45.474	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] command:
    javascript.1	2020-02-28 09:26:45.415	warn	(16326) at TCP.onStreamRead [as onread] (internal/stream_base_commons.js:94:17)
    javascript.1	2020-02-28 09:26:45.415	warn	(16326) at Socket.Readable.push (_stream_readable.js:224:10)
    javascript.1	2020-02-28 09:26:45.415	warn	(16326) at readableAddChunk (_stream_readable.js:269:11)
    javascript.1	2020-02-28 09:26:45.415	warn	(16326) at addChunk (_stream_readable.js:288:12)
    javascript.1	2020-02-28 09:26:45.415	warn	(16326) at Socket.emit (events.js:198:13)
    javascript.1	2020-02-28 09:26:45.415	warn	(16326) at Socket.net_client.on (script.js.Weldscripts.Javascript.Heos_neu:341:51)
    javascript.1	2020-02-28 09:26:45.414	warn	(16326) at Heos.onData (script.js.Weldscripts.Javascript.Heos_neu:358:14)
    javascript.1	2020-02-28 09:26:45.414	warn	(16326) at Heos.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:478:44)
    javascript.1	2020-02-28 09:26:45.414	warn	(16326) at HeosPlayer.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:726:26)
    javascript.1	2020-02-28 09:26:45.414	warn	(16326) at HeosPlayer.setState (script.js.Weldscripts.Javascript.Heos_neu:691:4)
    javascript.1	2020-02-28 09:26:45.413	warn	(16326) at setState (/opt/iobroker/node_modules/iobroker.javascript/lib/sandbox.js:1425:20)
    javascript.1	2020-02-28 09:26:45.412	warn	(16326) State "javascript.1.heos.192_168_1_177.duration_MMSS" not found
    javascript.1	2020-02-28 09:26:45.412	warn	(16326) at TCP.onStreamRead [as onread] (internal/stream_base_commons.js:94:17)
    javascript.1	2020-02-28 09:26:45.412	warn	(16326) at Socket.Readable.push (_stream_readable.js:224:10)
    javascript.1	2020-02-28 09:26:45.412	warn	(16326) at readableAddChunk (_stream_readable.js:269:11)
    javascript.1	2020-02-28 09:26:45.412	warn	(16326) at addChunk (_stream_readable.js:288:12)
    javascript.1	2020-02-28 09:26:45.411	warn	(16326) at Socket.emit (events.js:198:13)
    javascript.1	2020-02-28 09:26:45.411	warn	(16326) at Socket.net_client.on (script.js.Weldscripts.Javascript.Heos_neu:341:51)
    javascript.1	2020-02-28 09:26:45.411	warn	(16326) at Heos.onData (script.js.Weldscripts.Javascript.Heos_neu:358:14)
    javascript.1	2020-02-28 09:26:45.411	warn	(16326) at Heos.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:478:44)
    javascript.1	2020-02-28 09:26:45.410	warn	(16326) at HeosPlayer.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:725:26)
    javascript.1	2020-02-28 09:26:45.410	warn	(16326) at HeosPlayer.setState (script.js.Weldscripts.Javascript.Heos_neu:691:4)
    javascript.1	2020-02-28 09:26:45.410	warn	(16326) at setState (/opt/iobroker/node_modules/iobroker.javascript/lib/sandbox.js:1425:20)
    javascript.1	2020-02-28 09:26:45.409	warn	(16326) State "javascript.1.heos.192_168_1_177.duration" not found
    javascript.1	2020-02-28 09:26:45.409	warn	(16326) at TCP.onStreamRead [as onread] (internal/stream_base_commons.js:94:17)
    javascript.1	2020-02-28 09:26:45.409	warn	(16326) at Socket.Readable.push (_stream_readable.js:224:10)
    javascript.1	2020-02-28 09:26:45.409	warn	(16326) at readableAddChunk (_stream_readable.js:269:11)
    javascript.1	2020-02-28 09:26:45.408	warn	(16326) at addChunk (_stream_readable.js:288:12)
    javascript.1	2020-02-28 09:26:45.408	warn	(16326) at Socket.emit (events.js:198:13)
    javascript.1	2020-02-28 09:26:45.408	warn	(16326) at Socket.net_client.on (script.js.Weldscripts.Javascript.Heos_neu:341:51)
    javascript.1	2020-02-28 09:26:45.408	warn	(16326) at Heos.onData (script.js.Weldscripts.Javascript.Heos_neu:358:14)
    javascript.1	2020-02-28 09:26:45.408	warn	(16326) at Heos.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:478:44)
    javascript.1	2020-02-28 09:26:45.407	warn	(16326) at HeosPlayer.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:724:26)
    javascript.1	2020-02-28 09:26:45.407	warn	(16326) at HeosPlayer.setState (script.js.Weldscripts.Javascript.Heos_neu:691:4)
    javascript.1	2020-02-28 09:26:45.407	warn	(16326) at setState (/opt/iobroker/node_modules/iobroker.javascript/lib/sandbox.js:1425:20)
    javascript.1	2020-02-28 09:26:45.406	warn	(16326) State "javascript.1.heos.192_168_1_177.cur_pos_MMSS" not found
    javascript.1	2020-02-28 09:26:45.406	warn	(16326) at TCP.onStreamRead [as onread] (internal/stream_base_commons.js:94:17)
    javascript.1	2020-02-28 09:26:45.406	warn	(16326) at Socket.Readable.push (_stream_readable.js:224:10)
    javascript.1	2020-02-28 09:26:45.405	warn	(16326) at readableAddChunk (_stream_readable.js:269:11)
    javascript.1	2020-02-28 09:26:45.405	warn	(16326) at addChunk (_stream_readable.js:288:12)
    javascript.1	2020-02-28 09:26:45.405	warn	(16326) at Socket.emit (events.js:198:13)
    javascript.1	2020-02-28 09:26:45.405	warn	(16326) at Socket.net_client.on (script.js.Weldscripts.Javascript.Heos_neu:341:51)
    javascript.1	2020-02-28 09:26:45.404	warn	(16326) at Heos.onData (script.js.Weldscripts.Javascript.Heos_neu:358:14)
    javascript.1	2020-02-28 09:26:45.404	warn	(16326) at Heos.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:478:44)
    javascript.1	2020-02-28 09:26:45.404	warn	(16326) at HeosPlayer.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:723:26)
    javascript.1	2020-02-28 09:26:45.403	warn	(16326) at HeosPlayer.setState (script.js.Weldscripts.Javascript.Heos_neu:691:4)
    javascript.1	2020-02-28 09:26:45.403	warn	(16326) at setState (/opt/iobroker/node_modules/iobroker.javascript/lib/sandbox.js:1425:20)
    javascript.1	2020-02-28 09:26:45.402	warn	(16326) State "javascript.1.heos.192_168_1_177.cur_pos" not found
    javascript.1	2020-02-28 09:26:45.401	warn	(16326) at TCP.onStreamRead [as onread] (internal/stream_base_commons.js:94:17)
    javascript.1	2020-02-28 09:26:45.401	warn	(16326) at Socket.Readable.push (_stream_readable.js:224:10)
    javascript.1	2020-02-28 09:26:45.400	warn	(16326) at readableAddChunk (_stream_readable.js:269:11)
    javascript.1	2020-02-28 09:26:45.400	warn	(16326) at addChunk (_stream_readable.js:288:12)
    javascript.1	2020-02-28 09:26:45.400	warn	(16326) at Socket.emit (events.js:198:13)
    javascript.1	2020-02-28 09:26:45.399	warn	(16326) at Socket.net_client.on (script.js.Weldscripts.Javascript.Heos_neu:341:51)
    javascript.1	2020-02-28 09:26:45.399	warn	(16326) at Heos.onData (script.js.Weldscripts.Javascript.Heos_neu:358:14)
    javascript.1	2020-02-28 09:26:45.399	warn	(16326) at Heos.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:478:44)
    javascript.1	2020-02-28 09:26:45.398	warn	(16326) at HeosPlayer.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:726:26)
    javascript.1	2020-02-28 09:26:45.398	warn	(16326) at HeosPlayer.setState (script.js.Weldscripts.Javascript.Heos_neu:691:4)
    javascript.1	2020-02-28 09:26:45.398	warn	(16326) at setState (/opt/iobroker/node_modules/iobroker.javascript/lib/sandbox.js:1425:20)
    javascript.1	2020-02-28 09:26:45.397	warn	(16326) State "javascript.1.heos.192_168_1_177.duration_MMSS" not found
    javascript.1	2020-02-28 09:26:45.397	warn	(16326) at TCP.onStreamRead [as onread] (internal/stream_base_commons.js:94:17)
    javascript.1	2020-02-28 09:26:45.396	warn	(16326) at Socket.Readable.push (_stream_readable.js:224:10)
    javascript.1	2020-02-28 09:26:45.396	warn	(16326) at readableAddChunk (_stream_readable.js:269:11)
    javascript.1	2020-02-28 09:26:45.396	warn	(16326) at addChunk (_stream_readable.js:288:12)
    javascript.1	2020-02-28 09:26:45.396	warn	(16326) at Socket.emit (events.js:198:13)
    javascript.1	2020-02-28 09:26:45.395	warn	(16326) at Socket.net_client.on (script.js.Weldscripts.Javascript.Heos_neu:341:51)
    javascript.1	2020-02-28 09:26:45.395	warn	(16326) at Heos.onData (script.js.Weldscripts.Javascript.Heos_neu:358:14)
    javascript.1	2020-02-28 09:26:45.395	warn	(16326) at Heos.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:478:44)
    javascript.1	2020-02-28 09:26:45.395	warn	(16326) at HeosPlayer.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:725:26)
    javascript.1	2020-02-28 09:26:45.393	warn	(16326) at HeosPlayer.setState (script.js.Weldscripts.Javascript.Heos_neu:691:4)
    javascript.1	2020-02-28 09:26:45.393	warn	(16326) at setState (/opt/iobroker/node_modules/iobroker.javascript/lib/sandbox.js:1425:20)
    javascript.1	2020-02-28 09:26:45.391	warn	(16326) State "javascript.1.heos.192_168_1_177.duration" not found
    javascript.1	2020-02-28 09:26:45.391	warn	(16326) at TCP.onStreamRead [as onread] (internal/stream_base_commons.js:94:17)
    javascript.1	2020-02-28 09:26:45.391	warn	(16326) at Socket.Readable.push (_stream_readable.js:224:10)
    javascript.1	2020-02-28 09:26:45.390	warn	(16326) at readableAddChunk (_stream_readable.js:269:11)
    javascript.1	2020-02-28 09:26:45.390	warn	(16326) at addChunk (_stream_readable.js:288:12)
    javascript.1	2020-02-28 09:26:45.390	warn	(16326) at Socket.emit (events.js:198:13)
    javascript.1	2020-02-28 09:26:45.390	warn	(16326) at Socket.net_client.on (script.js.Weldscripts.Javascript.Heos_neu:341:51)
    javascript.1	2020-02-28 09:26:45.390	warn	(16326) at Heos.onData (script.js.Weldscripts.Javascript.Heos_neu:358:14)
    javascript.1	2020-02-28 09:26:45.389	warn	(16326) at Heos.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:478:44)
    javascript.1	2020-02-28 09:26:45.389	warn	(16326) at HeosPlayer.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:724:26)
    javascript.1	2020-02-28 09:26:45.389	warn	(16326) at HeosPlayer.setState (script.js.Weldscripts.Javascript.Heos_neu:691:4)
    javascript.1	2020-02-28 09:26:45.388	warn	(16326) at setState (/opt/iobroker/node_modules/iobroker.javascript/lib/sandbox.js:1425:20)
    javascript.1	2020-02-28 09:26:45.388	warn	(16326) State "javascript.1.heos.192_168_1_177.cur_pos_MMSS" not found
    javascript.1	2020-02-28 09:26:45.385	warn	(16326) at TCP.onStreamRead [as onread] (internal/stream_base_commons.js:94:17)
    javascript.1	2020-02-28 09:26:45.385	warn	(16326) at Socket.Readable.push (_stream_readable.js:224:10)
    javascript.1	2020-02-28 09:26:45.384	warn	(16326) at readableAddChunk (_stream_readable.js:269:11)
    javascript.1	2020-02-28 09:26:45.384	warn	(16326) at addChunk (_stream_readable.js:288:12)
    javascript.1	2020-02-28 09:26:45.383	warn	(16326) at Socket.emit (events.js:198:13)
    javascript.1	2020-02-28 09:26:45.383	warn	(16326) at Socket.net_client.on (script.js.Weldscripts.Javascript.Heos_neu:341:51)
    javascript.1	2020-02-28 09:26:45.382	warn	(16326) at Heos.onData (script.js.Weldscripts.Javascript.Heos_neu:358:14)
    javascript.1	2020-02-28 09:26:45.382	warn	(16326) at Heos.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:478:44)
    javascript.1	2020-02-28 09:26:45.381	warn	(16326) at HeosPlayer.parseResponse (script.js.Weldscripts.Javascript.Heos_neu:723:26)
    javascript.1	2020-02-28 09:26:45.381	warn	(16326) at HeosPlayer.setState (script.js.Weldscripts.Javascript.Heos_neu:691:4)
    javascript.1	2020-02-28 09:26:45.380	warn	(16326) at setState (/opt/iobroker/node_modules/iobroker.javascript/lib/sandbox.js:1425:20)
    javascript.1	2020-02-28 09:26:45.379	warn	(16326) State "javascript.1.heos.192_168_1_177.cur_pos" not found
    javascript.1	2020-02-28 09:26:45.348	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] signed in: success
    javascript.1	2020-02-28 09:26:45.333	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: [HeosPlayer 192.168.1.177] starting HEOS player for IP 192.168.1.177
    javascript.1	2020-02-28 09:26:45.280	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] connected to 192.168.1.177
    javascript.1	2020-02-28 09:26:45.279	info	(16326) script.js.Weldscripts.Javascript.Heos_neu: [Heos] connected to HEOS
    
    D Offline
    D Offline
    Desastro
    schrieb am zuletzt editiert von
    #88

    @jwedenig

    Hi

    Hast du bei der neuen Instanz in den Einstellungen "node-ssdp" bei den zusätzlichen Modulen mit eingetragen?
    Mehr fällt mir da grad auch nicht ein.

    Gruß Thomas

    J 1 Antwort Letzte Antwort
    0
    • D Desastro

      @jwedenig

      Hi

      Hast du bei der neuen Instanz in den Einstellungen "node-ssdp" bei den zusätzlichen Modulen mit eingetragen?
      Mehr fällt mir da grad auch nicht ein.

      Gruß Thomas

      J Offline
      J Offline
      jwedenig
      Most Active
      schrieb am zuletzt editiert von
      #89

      @Desastro ja, habe ich, danke

      1 Antwort Letzte Antwort
      0
      • UhulaU Uhula

        @Lacoste9 MD CSS v2, z.B. von hier: https://github.com/Uhula/ioBroker-Material-Design-Style

        @Meister-Mopper Den Fehler ignorieren, ist ein überflüssiger Aufruf im destroy, ist in meiner neuesten Version schon behoben. Ich versuche demnächst mal aus dem Script einen Adapter zu machen.

        @BobBruni Mit Spotify habe ich es noch nicht versucht, denke aber nicht, dass es gehen wird, da Spotify nicht native von HEOS unterstützt wird, sondern selbst deren Client "nachlädt" (siehe die HEOS App, die macht dad auch). Ich werde mal im API nachlesen, demnächst.

        C Offline
        C Offline
        chrisblu
        schrieb am zuletzt editiert von
        #90

        Hallo Uhula,

        ich bin immer noch total begeistert von deinem Skript und der Heos-Steuerung über den Broker.

        @Uhula sagte in [Vorlage] Denon HEOS Script:

        @Meister-Mopper Den Fehler ignorieren, ist ein überflüssiger Aufruf im destroy, ist in meiner neuesten Version schon behoben. Ich versuche demnächst mal aus dem Script einen Adapter zu machen.

        Ist die neuste Version von deinem Skript schon erhältlich, oder ist das die vom 15.11.?
        Ich bin auch schon sehr gespannt auf deinen Adapter. Wenn Du Tester brauchst, bin ich gerne dabei.

        Bei mir bekomme ich nach wie vor von den mp3s über DLNA oder Heos Netzwerkfreigabe keine Infos/Tags/Titel angezeigt.

        Viele Grüße, Christian

        UhulaU 1 Antwort Letzte Antwort
        0
        • C chrisblu

          Hallo Uhula,

          ich bin immer noch total begeistert von deinem Skript und der Heos-Steuerung über den Broker.

          @Uhula sagte in [Vorlage] Denon HEOS Script:

          @Meister-Mopper Den Fehler ignorieren, ist ein überflüssiger Aufruf im destroy, ist in meiner neuesten Version schon behoben. Ich versuche demnächst mal aus dem Script einen Adapter zu machen.

          Ist die neuste Version von deinem Skript schon erhältlich, oder ist das die vom 15.11.?
          Ich bin auch schon sehr gespannt auf deinen Adapter. Wenn Du Tester brauchst, bin ich gerne dabei.

          Bei mir bekomme ich nach wie vor von den mp3s über DLNA oder Heos Netzwerkfreigabe keine Infos/Tags/Titel angezeigt.

          Viele Grüße, Christian

          UhulaU Offline
          UhulaU Offline
          Uhula
          schrieb am zuletzt editiert von
          #91

          @chrisblu Bei mir läuft bereits das neue Script - recht stabil, unterstützt auch Raumgruppen; muss es aber noch an den neuen "0_userdata.0" State-Ordner anpassen, dann ist es javascript-Instanz unabhängig. Btw. die States werden dann nicht mehr unter der Player-IP-Adresse, sondern unter der Player-ID abgelegt (kam bei mir hin und wieder vor, das Player einen neue IP bekamen ..., die ID bleibt konstant).

          Zum Adapter dauert es noch, habe erst einmal das MD CSS v2 Projekt priorisiert.

          Uhula - Leise und Weise
          Ex: ioBroker on Gigabyte NUC Proxmox

          1 Antwort Letzte Antwort
          1
          • C Offline
            C Offline
            chrisblu
            schrieb am zuletzt editiert von
            #92

            @Uhula, das klingt gut. Bin schon gespannt.
            Danke für die Info, viele Grüße, Christian

            1 Antwort Letzte Antwort
            0
            • W Offline
              W Offline
              withstu
              schrieb am zuletzt editiert von
              #93

              @Uhula erstmal vielen Dank für deine Arbeit. Ich habe einige Bugs in deinem ersten Script gefixt (@jwedenig es werden auch wieder alle Song Infos aktualisiert). Auch wenn Version 2 bereits unterwegs ist, will ich euch meine Änderungen nicht vorenthalten. Unter anderem ist mir aufgefallen, dass das Filtern und Verarbeiten der Pakete vom node-ssdp nicht korrekt funktioniert, wenn eine Hue Bridge im Netzwerk funkt oder Pakete unvollständig sind. Zudem habe ich noch ein connected Flag für jeden Player hinzugefügt. Dieses muss leider extern über ein Script resettet werden z.B. in Kombination mit dem Ping Adapter. Für diesen Fall habe ich mir ein Blockly Script gebaut, welches das connected Flag resettet und bei Player Updates das Heos Script neu startet. Bei Bedarf kann ich das auch noch teilen:

              /****************************
               * HEOS Script for ioBroker
               ****************************
               *  23.01.2018 Uhula, MIT License, no warranty, use on your own risc
               * 
               * Wichtig!
               ****************************
               * (a) Im Javascript-Adapter muss in der Instanz-Konfiguration "node-ssdp" mit angegeben werden!
               *     Alternativ kann die Bibliothek auch komplett über "npm install node-ssdp" installiert
               *     werden.
               * (b) Wenn mit Favoriten (Presets) gearbeitet werden soll, müssen die HEOS-Konto Logindaten in
               *     den beiden Konstanten HEOS_USERNAME (EmailAdr) und HEOS_PASSWORD hier im Script in der 
               *     Konfiguration angegeben werden (so, wie in der HEOS App)!
               * (c) Eine Konfiguration von IP-Adressen und Player-IDs ist nicht notwendig!
               * (d) U.U. kann es notwendig sein das Script nach dem 1.Start zu beenden und nach 30 Sek erneut zu starten, da
               *     die Neuanlage der ioBroker States etwas Zeit benötigt. Warnungen sind dabei zu ignorieren. ;-)
               *
               * 
               * Funktion
               ****************************
               * Das Script stellt zwei JS Klassen zur Steuerung der Denon HEOS Geräte zur Verfügung. Die
               * class Heos dient dabei dem Erkennen und Steuern der HEOS Geräte. Die class HeosPlayer dient der 
               * Interpretation der Antworten der Heos Geräte.
               * 
               * Das Script muss lediglich gestartet werden, es sucht dann im Netzwerk nach HEOS Playern und
               * erzeugt in der aktuellen Javascript-Instanz einen Subeintrag für die Favoriten und je einen Subeintrag
               * für jeden gefundenen Player. Dort wiederum werden State-Variablen zur Aufnahme der Player-Daten angelegt.
               * 
               * Das Script erzeugt auch on-Handler um auf Steuerungen über vis und andere Scripte an den State-Variablen
               * reagieren zu können.
               * 
               * Beim Beenden des Scripts werden alle Verbindungen und on-Handler geschlossen.
               * 
               * Das Script ermöglicht die HEOS Player zu steuern, es soll aber nicht die HEOS App ersetzen. Dazu fehlen
               * etliche Funktionen wie Playlisten-Handhabung, Gruppensteuerung usw.
               * 
               * 
               * class Heos
               ****************************
               * (a) Erkennen von HEOS Geräten (Playern)
               *     Das Erkennen der HEOS Geräte findet via UPD unter Nutzung von node-ssdp statt. node-ssdp muss dazu im 
               *     Javascript-Adapter in der Konfiguration mit angegeben werden!
               * (b) der Kommunikation über TelNet 
               *     Es wird genau eine TelNet Verbindung zu einem HEOS Gerät aufgebaut, dieses reicht die Sendungen
               *     und Antworten zentral weiter
               * (c) der Ermittlung von Favoriten (Presets) und 
               *     Für jeden Favorit wird ein ioBroker State heos.presets.n mit entsprechenden Sub-States erzeugt.
               *     Zur Nutzung der Presets ist ein SignIn notwendig, hierzu müssen HEOS_USERNAME und HEOS_PASSWORD
               *     gesetzt werden.
               * (d) dem Instanziieren der class HeosPlayer je HEOS Gerät. 
               *     Je HEOS Gerät wird ein ioBroker-State mit der IP-Adresse des Players erzeugt, dieser State erhält
               *     diverse Sub-States
               * 
               * State-Variable
               * --------------
               * .heos.command(cmd) // write
               *   Hierüber können der Heos-Klasse Befehle übergeben werden, für cmd gilt:
               * 
               *   connect      
               *       Verbindung zu HEOS aufbauen bzw. erneut aufbauen (praktisch ein reset)
               *   disconnect   
               *       Verbindung zu HEOS beenden
               *   load_presets
               *       lädt die Favoriten neu
               * 
               *   alle anderen cmd-Werte werden "as is" versucht an HEOS zu senden, damit ist z.B. auch das
               *   Gruppieren von HEOS Speakern möglich.
               * 
               *   group/set_group?pid=<pid1>,<pid2>,...
               *       setzen einer Gruppe, die pids sind die der Player wie sie unter den Objekten
               *       für jeden Player abgelegt sind. Statt diese direkt zu verwenden, kann man auch
               *       die ioBroker binding-Funktionalität nutzen und Platzhalter verwenden.
               *       Bsp: group/set_group?pid={javascript.1.heos.192_168_2_46.pid},{javascript.1.heos.192_168_2_43.pid}
               * 
               *   group/set_group?pid=<pid1>  
               *       hebt die Gruppierung wieder auf
               *       Bsp: group/set_group?pid={javascript.1.heos.192_168_2_46.pid}
               * 
               * 
               * Favoriten (je Favorit ein Unterordner n=1 bis Anzahl)
               * 
               * .heos.presets.<n>.image_url (read)
               *   Bild des Favoriten 
               * 
               * .heos.presets.<n>.name (read)
               *   Name des Favoriten
               * 
               * .heos.presets.<n>.playable (read)
               *   Spielbar? true/false
               * 
               * .heos.presets.<n>.type (read)
               *   Typ des Favoriten (station, ...)
               *    
               *  
               * class HeosPlayer 
               ****************************
               * (a) der Steuerung genau eines HEOS Gerätes. Hierzu wird die zentrale Telnet Verbindung der class Heos genutzt.
               * (b) dem Erzeugen der ioBroker-States zum Speichern der Geräte-Werte
               * (b) der Auswertung von Antworten der HEOS Geräte und Zuweisung an die entsprechenden ioBroker-States
               * 
               * State-Variable
               * --------------
               * .heos.<player_ip>.command (write)
               *   Hierüber können dem Heos-Player Befehle übergeben werden. Diese werden im Klartext in die State-Variable
               *   geschrieben. Getrennt durch das | Zeichen können mehrere Befehle hintereinander eingetragen werden.
               *   Bsp: Setzen der Lautstärke auf 20 und Abspielen des 1.Favoriten
               *        set_volume&level=20|play_preset&preset=1
               * 
               *   set_volume&level=0|1|..|100          : Setzt die gewünschte Lautstärke 
               *   set_play_state&state=play|pause|stop : Startet und stoppt die Wiedergabe
               *   set_play_mode&repeat=on_all|on_one|off&shuffle=on|off: Setzt Wiederholung und Zufallsweidergabe
               *   set_mute&state=on|off                : Stumm schalten oder nicht
               *   volume_down&step=1..10               : Lautstärke verringern um   
               *   volume_up&step=1..10                 : Lauststäre erhöhen um
               *   play_next                            : Nächsten Titel spielen
               *   play_previous                        : Vorherigen Titel spielen
               *   play_preset&preset=1|2|..|n          : Favorit Nr n abspielen
               *   play_stream&url=url_path             : URL-Stream abspielen
               * 
               *   Befehle, die intern genutzt werden und nicht aufgerufen werden müssen:
               *
               *   get_volume              : Füllt den .heos.<player_ip>.volume State
               *   get_play_state          : Füllt den .heos.<player_ip>.play_state State
               *   get_play_mode           : Füllt den .heos.<player_ip>.play_mode State 
               *   get_now_playing_media   : Füllt die .heos.<player_ip>.now_playing_media_... States
               *
               *
               * .heos.<player_ip>.cur_pos (read)
               *   lfd. Position der Wiedergabe in [Sek]
               * 
               * .heos.<player_ip>.cur_pos_MMSS (read)
               *   lfd. Position der Wiedergabe im Format MM:SS
               * 
               * .heos.<player_ip>.duration (read)
               *   Länge des aktuellen Titels in [Sek]
               * 
               * .heos.<player_ip>.duration_MMSS (read)
               *   Länge des aktuellen Titels im Format MM:SS
               * 
               * .heos.<player_ip>.ip (read)
               *   IP-Adresse des HEOS Players
               * 
               * .heos.<player_ip>.last_error (read)
               *   Text des letzten Fehlers, der vom Player gesendet wurde. Wird bei jedem neuen Befehl zurückgesetzt. 
               *   Wenn man bspw. versucht eine Wiedergabe über Amazon o.ä. zu starten und es gibt aber keine Verbindung
               *   dahin, steht hier der Fehlertext drin
               * 
               * .heos.<player_ip>.model (read)
               *   Modell des HEOS Players
               * 
               * .heos.<player_ip>.mute (read write)
               *   Boolsche Variable, die anzeigt ob der Player gemutet (Stumm geschaltet) wurde. Hierüber kann auch ein 
               *   mute gesetzt werden
               * 
               * .heos.<player_ip>.name (read)
               *   Name des HEOS Players
               * 
               * .heos.<player_ip>.now_playing_media_... (read)
               *   Eine Gruppe von States, in welchen Infos über den aktuellen Titel gespeichert werden, wird automatisch
               *   aktualisiert. 
               * 
               * .heos.<player_ip>.pid (read)
               *   Player-ID des HEOS Players
               * 
               * .heos.<player_ip>.play_mode_repeat (read write)
               *   Repeat-Modus der Wiedergabe. Mögliche Werte: on_all|on_one|off  
               * 
               * .heos.<player_ip>.play_mode_shuffle (read write)
               *   Zufallswiedergabe true/false
               * 
               * .heos.<player_ip>.play_state (read write)
               *   Zustand des Players. Mögliche Werte play|pause|stop
               * 
               * .heos.<player_ip>.serial (read)
               *   Seriennummmer des HEOS Players
               * 
               * .heos.<player_ip>.volume (read write)
               *   Lautstärke im Bereich 0 - 100. 
               * 
               * 
               * 
               * Weiterführende Links
               ****************************
               * HEOS CLI Protokoll: http://rn.dmglobal.com/euheos/HEOS_CLI_ProtocolSpecification.pdf
               * http://forum.iobroker.net/viewtopic.php?f=30&t=5693&p=115554#p115554
               * 
               **/
               
              /****************************
               * Konfiguration
               ****************************/
               
              const HEOS_USERNAME = '';
              const HEOS_PASSWORD = '';
               
               
              /****************************
               * ab hier nichts mehr ändern ;-) 
               ****************************/
               
              var net = require('net');
               
              const stateDISCONNECTED = 0;
              const stateCONNECTING = 1;
              const stateCONNECTED = 2;
               
              /********************
               * class Heos
               ********************/
              class Heos  {
               
              constructor() {
                  this.init();
               
                  createState( this.statePath+'command', '', {name: 'Kommando für Heos-Script' });
                  createState( this.statePath+'connected', false, {name: 'Verbunden?' });
                  createState( this.statePath+'last_error', '', {name: 'Letzter Fehler' });
                  on({id: this.statePath+'command', change: "any"}, (obj) => {
                          this.executeCommand( obj.state.val );
                      });
              }
               
              logDebug(msg) { console.debug('[Heos] '+msg) };
              log(msg) { console.log('[Heos] '+msg); }
              logWarn(msg) { console.warn('[Heos] '+msg); }
              logError(msg) { console.error('[Heos] '+msg); }
               
               
              init() {
                  this.statePath = 'javascript.'+instance+'.heos.';
                  this.players = [];
                  this.net_client = undefined;
                  this.nodessdp_client = undefined;
               
                  this.ip='';
                  this.msgs = [];
                  this.lastResponse = '';
                  this.state = stateDISCONNECTED;
                  this.unfinishedResponses = '';
                  this.ssdpSeartTargetName = 'urn:schemas-denon-com:device:ACT-Denon:1';
              }
               
              connect() { 
                  try {
                      this.log('connecting to HEOS ...');
                      setState( this.statePath+"connected", false );
                      const NodeSSDP = require('node-ssdp').Client;
              	    this.nodessdp_client = new NodeSSDP();
              	    this.nodessdp_client.explicitSocketBind = true;
              	    this.nodessdp_client.on('response', (headers, statusCode, rinfo) => this.onNodeSSDPResponse(headers, statusCode, rinfo) );
              	    this.nodessdp_client.on('error', error => {	client.close(); this.logError(error); });
                      this.nodessdp_client.search(this.ssdpSeartTargetName);
                  } catch(err) { this.logError( 'connect: '+err.message ); } 
                  
              }
               
               
              /** Alle Player stoppen und die TelNet Verbindung schließen 
               **/
              disconnect() {
                  this.log('disconnecting from HEOS ...');
                  unsubscribe('javascript.'+instance+'.heos');
              
                  var i=0;
                  for (i=0; i<this.players.length; i++) {
                      var playerStatePath = this.players[i].heosPlayer.statePath;
                      setState( playerStatePath+"connected", false );
                  }
               
                  if (typeof this.net_client!=='undefined') {
                      this.registerChangeEvents( false );
                      
                      this.net_client.destroy();
                      this.net_client.unref();
                  }
                  if (typeof this.nodessdp_client!=='undefined') {
                      this.nodessdp_client.stop();
                  }
                  setState( this.statePath+"connected", false );
                  this.log('disconnected from HEOS');
              }
               
              executeCommand(cmd) {
                  this.log('command: '+cmd);
                  switch (cmd) {
                      case 'load_presets' :
                          this.getMusicSources();
                          break;
                      case 'connect' :
                          this.disconnect();
                          this.init();
                          this.connect();
                          break;
                      case 'disconnect' :
                          this.disconnect();
                          break;
                      default:
                          if (this.state == stateCONNECTED) {
                              this.msgs.push( 'heos://'+cmd+'\n' );
                              this.sendNextMsg();
                          }
                  
                  }
              }
              
              sleep(milliseconds) {
                 return new Promise(resolve => setTimeout(resolve, milliseconds));
              }
              
              /** es wurde mindestens ein Player erkannt, nun über dessen IP alle bekannten HEOS Player
               *  durch senden von "player/get_players" ermitteln
               */
              onNodeSSDPResponse(headers, statusCode, rinfo) { try {
                  // rinfo {"address":"192.168.2.225","family":"IPv4","port":53871,"size":430}
                  if (typeof this.net_client=='undefined') {
                      if(headers.ST === this.ssdpSeartTargetName){
                          this.ip = rinfo.address;
                          this.log('connecting to '+this.ip+' ...');
                          this.net_client = net.connect({host:this.ip, port:1255});
                          this.net_client.setKeepAlive(true, 5000);
                  
                          this.state = stateCONNECTING;
                  
                          this.net_client.on('error',(error) => {
                              this.logError(error);
                              this.disconnect();
                          }); 
                      
                          this.net_client.on('connect',  () => {
                              setState( this.statePath+"connected", true );
                              this.log('connected to HEOS');
                              this.state = stateCONNECTED;
                              this.log('connected to '+this.ip);
                              this.getPlayers();
                              this.registerChangeEvents( true );
                              this.signIn();
                              this.getMusicSources();
                          });
                          
                          // Gegenseite hat die Verbindung geschlossen 
                          this.net_client.on('end',  () => {              
                              this.logWarn('HEOS closed the connection to '+this.ip);
                              this.disconnect();
                          });
                  
                          // timeout
                          this.net_client.on('timeout',  () => {              
                              this.logWarn('Timeout trying connect to '+this.ip);
                              this.disconnect();
                          });
                      
                          // Datenempfang
                          this.net_client.on('data', (data) => this.onData(data)  );
                      } else {
                          this.log('Getting wrong ssdp entry. Keep trying...');
                      }
                  }
              } catch(err) { this.logError( 'onNodeSSDPResponse: '+err.message ); } }
              
              removeFirstOccurrence(str, searchstr)       {
              	var index = str.indexOf(searchstr);
              	if (index === -1) {
              		return str;
              	}
              	return str.slice(0, index) + str.slice(index + searchstr.length);
              }
               
              /** es liegen Antwort(en) vor
               **/
              onData(data) {  try {
                  data = data.toString();
                  this.logDebug("received data:" + data);
                  data=data.replace(/[\n\r]/g, '');    // Steuerzeichen "CR" entfernen   
                  // es können auch mehrere Antworten vorhanden sein! {"heos": ... } {"heos": ... }
                  // diese nun in einzelne Antworten zerlegen
                  data = this.unfinishedResponses + data;
                  this.unfinishedResponses = '';
              
                  data=data.replace(/{\s*"heos"\s*:/g, '|{"heos":');  
                  var responses = data.split('|');
                  for (var r=0; r<responses.length; r++ ) {
                      if(responses[r].trim().length > 0){
                          try {
                              JSON.parse(responses[r]);
                              this.parseResponse(responses[r]);
                          } catch(e) {
                              this.logDebug("invalid json (error: " + e.message + "): " + responses[r]);
                              this.unfinishedResponses += responses[r];
                          }
                      }
                  }
                  // wenn weitere Msg zum Senden vorhanden sind, die nächste senden
                  if (this.msgs.length>0)
                      this.sendNextMsg();
              } catch(err) { this.logError( 'onData: '+err.message ); } }
               
              /** Antwort(en) verarbeiten. Sich wiederholende Antworten ignorieren
               **/
              parseResponse (response) { try {
                  if (response == this.lastResponse || response.indexOf("command under process") > 0 )
                      return
                  this.lastResponse = response;        
                  
                  var jmsg;
                  var i;
                  var jdata = JSON.parse(response);
                  if ( !jdata.hasOwnProperty('heos') || !jdata.heos.hasOwnProperty('command') || !jdata.heos.hasOwnProperty('message')) 
                      return;
               
                  // msg auswerten
                  try {
                      jmsg = '{"' + decodeURI(jdata.heos.message).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g,'":"').replace(/\s/g,'_') + '"}';
                      jmsg = JSON.parse(jmsg);
                  } catch(err) {
                      jmsg = {};
                  }
                  this.logDebug('parse response: '+response);
                  this.logDebug('parse message: '+JSON.stringify(jmsg));
               
                  // cmd auswerten
                  var cmd = jdata.heos.command.split('/');
                  var cmd_group = cmd[0];
                  cmd = cmd[1];
              
                  // result ?
                  var result = 'success';
                  if (jdata.heos.hasOwnProperty('result') ) result = jdata.heos.result;
                  if ( result!='success' ) { 
                      setState(this.statePath+'last_error', cmd + ' | ' + jmsg.text);
                      this.logWarn(jmsg.text);
                  }
               
                  switch (cmd_group) {
                      case 'player':
                          switch (cmd) {
                                  // {"heos": {"command": "player/get_players", "result": "success", "message": ""}, 
                                  //  "payload": [{"name": "HEOS Bar", "pid": 1262037998, "model": "HEOS Bar", "version": "1.430.160", "ip": "192.168.2.225", "network": "wifi", "lineout": 0, "serial": "ADAG9170202780"}, 
                                  //              {"name": "HEOS 1 rechts", "pid": -1746612370, "model": "HEOS 1", "version": "1.430.160", "ip": "192.168.2.201", "network": "wifi", "lineout": 0, "serial": "AMWG9170934429"}, 
                                  //              {"name": "HEOS 1 links", "pid": 68572158, "model": "HEOS 1", "version": "1.430.160", "ip": "192.168.2.219", "network": "wifi", "lineout": 0, "serial": "AMWG9170934433"}
                                  //             ]}
                              case 'get_players' :
                                  if ( (jdata.hasOwnProperty('payload')) && (this.players.length===0) ) {
                                      for (i=0; i<jdata.payload.length; i++) {
                                          var player = jdata.payload[i];
                                          this.players.push(player);
                                      }
                                      this.startPlayers();
                                  } else if(jdata.playload.length != this.players.length){
                                      this.log("New Player detected. Restart.")
                                      this.disconnect();
                                  }                    
                                  break;
                          }
                          break;
               
                      // {"heos": {"command": "browse/get_music_sources", "result": "success", "message": ""}, 
                      //  "payload": [{"name": "Amazon", "image_url": "https://production...png", "type": "music_service", "sid": 13}, 
                      //              {"name": "TuneIn", "image_url": "https://production...png", "type": "music_service", "sid": 3}, 
                      //              {"name": "Local Music", "image_url": "https://production...png", "type": "heos_server", "sid": 1024}, 
                      //              {"name": "Playlists", "image_url": "https://production...png", "type": "heos_service", "sid": 1025}, 
                      //              {"name": "History", "image_url": "https://production...png", "type": "heos_service", "sid": 1026}, 
                      //              {"name": "AUX Input", "image_url": "https://production...png", "type": "heos_service", "sid": 1027}, 
                      //              {"name": "Favorites", "image_url": "https://production...png", "type": "heos_service", "sid": 1028}]}
                      case 'browse':
                          switch (cmd) {
                              case 'get_music_sources' :
                                  if ( (jdata.hasOwnProperty('payload')) ) {
                                      this.logDebug("Payload:"+jdata.payload.toString());
                                      for (i=0; i<jdata.payload.length; i++) {
                                          var source = jdata.payload[i];
                                          if (source.name=='Favorites') {
                                              this.browse(source.sid);
                                          }
                                      }
                                  }
               
                                  break;
                                  
              	    // {"heos": {"command": "browse/browse", "result": "success", "message": "pid=1262037998&sid=1028&returned=5&count=5"}, 
              	    //  "payload": [{"container": "no", "mid": "s17492", "type": "station", "playable": "yes", "name": "NDR 2 (Adult Contemporary Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s17492q.png"}, 
              	    //              {"container": "no", "mid": "s158432", "type": "station", "playable": "yes", "name": "Absolut relax (Easy Listening Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s158432q.png"}, 
              	    //              {"container": "no", "mid": "catalog/stations/A1W7U8U71CGE50/#chunk", "type": "station", "playable": "yes", "name": "Ed Sheeran", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/EdSheeran._SX200_SY200_.jpg"}, 
              	    //              {"container": "no", "mid": "catalog/stations/A1O1J39JGVQ9U1/#chunk", "type": "station", "playable": "yes", "name": "Passenger", "image_url": "https://images-na.ssl-images-amazon.com/images/I/71DsYkU4QaL._SY500_CR150,0,488,488_SX200_SY200_.jpg"}, 
              	    //              {"container": "no", "mid": "catalog/stations/A316JYMKQTS45I/#chunk", "type": "station", "playable": "yes", "name": "Johannes Oerding", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/JohannesOerding._SX200_SY200_.jpg"}], 
              	    //  "options": [{"browse": [{"id": 20, "name": "Remove from HEOS Favorites"}]}]}                    
                              case 'browse' :
                                  if ( (jdata.hasOwnProperty('payload')) ) {
                                      for (i=0; i<jdata.payload.length; i++) {
                                          var preset = jdata.payload[i];
                                          createState( this.statePath+'presets.'+(i+1)+'.name', preset.name, {name: 'Favoritenname ' });
                                          createState( this.statePath+'presets.'+(i+1)+'.playable', (preset.playable=='yes'?true:false), {name: 'Favorit ist spielbar' });
                                          createState( this.statePath+'presets.'+(i+1)+'.type', preset.type, {name: 'Favorittyp' });
                                          createState( this.statePath+'presets.'+(i+1)+'.image_url', preset.image_url, {name: 'Favoritbild' });
                                      }
                                  }
                                  break;
                          }     
                          break;
               
                      case 'system':
                          switch (cmd) {
                              case 'sign_in' :
                                  this.log('signed in: '+jdata.heos.result);
                                  break;
                          }     
                          break;
                  }
               
               
                  // an die zugehörigen Player weiterleiten
                  if ( jmsg.hasOwnProperty('pid') ) {
                      for (i=0; i<this.players.length; i++)
                          if (jmsg.pid==this.players[i].heosPlayer.pid) {
                              this.players[i].heosPlayer.parseResponse (jdata, jmsg, cmd_group, cmd);
                              break;
                          }
                  }
               
                  
              } catch(err) { this.logError( 'parseResponse: '+err.message+'\n '+response ); } }
               
               
              /** sucht die zur ip passenden player-Insanz
               **/
              sendCommandToPlayer(objID, cmd ) {
                 var ip = objID;
                 ip = ip.split('.');
                 var ip_ = ip[3];
                 ip = ip_.replace(/_/g,'.');
                 for (var p=0; p<this.players.length; p++ ) 
                     if (this.players[p].ip == ip ) {
                         this.players[p].heosPlayer.sendCommand( cmd );
                         break;
                     }
                     
              } 
               
              /** Für die gefundenen HEOS Plyer entsprechende class HeosPlayer Instanzen bilden 
               **/
              startPlayers() { try {
                  var i=0;
                  for (i=0; i<this.players.length; i++) {
                     this.players[i].heosPlayer = new HeosPlayer( this, this.players[i]  );
                  }
                  // Events setzen
                  for (i=0; i<this.players.length; i++) {
                      var statePath = this.players[i].heosPlayer.statePath;
                      // on-Event für command
                      on({id: statePath+'command', change: "any"}, (obj) => {
                          this.sendCommandToPlayer( obj.id, obj.state.val );
                      });
               
                      // on-Event für volume (nur wenn ack=false, also über vis)
                      on({id: statePath+'volume', change:'ne', ack:false }, (obj) => {
                          this.sendCommandToPlayer( obj.id, 'set_volume&level='+obj.state.val );
                      });
                      // on-Event für mute (nur wenn ack=false, also über vis)
                      on({id: statePath+'mute', change: 'ne', ack:false}, (obj) => {
                          this.sendCommandToPlayer( obj.id, 'set_mute&state='+ (obj.state.val === true ? 'on' : 'off') );
                      });
                      // on-Event für play_mode_shuffle (nur wenn ack=false, also über vis)
                      on({id: statePath+'play_mode_shuffle', change: 'ne', ack:false}, (obj) => {
                          this.sendCommandToPlayer( obj.id, 'set_play_mode&shuffle='+ (obj.state.val === true ? 'on' : 'off') );
                      });
                      // on-Event für play_state (nur wenn ack=false, also über vis)
                      on({id: statePath+'play_state', change: 'ne', ack:false}, (obj) => {
                          this.sendCommandToPlayer( obj.id, 'set_play_state&state='+ obj.state.val );
                      });
                  }    
              } catch(err) { this.logError( 'startPlayers: '+err.message ); } }
               
               
               
              getPlayers() {
                  if (this.state == stateCONNECTED) {
                      this.msgs.push( 'heos://player/get_players\n' );
                      this.sendNextMsg();
                  }
              }
               
              registerChangeEvents( b ) {
                  if (this.state == stateCONNECTED) {
                      if (b) this.msgs.push( 'heos://system/register_for_change_events?enable=on' );
                      else this.msgs.push( 'heos://system/register_for_change_events?enable=off' );
                      this.sendNextMsg();
                  }
              }
               
              signIn() {
                  if (this.state == stateCONNECTED) {
                      // heos://system/sign_in?un=heos_username&pw=heos_password
                      this.msgs.push( 'heos://system/sign_in?un='+HEOS_USERNAME+'&pw='+HEOS_PASSWORD );
                      this.sendNextMsg();
                  }
              }
               
              getMusicSources() {
                  if (this.state == stateCONNECTED) {
                      // heos://browse/get_music_sources
                      this.msgs.push( 'heos://browse/get_music_sources' );
                      this.sendNextMsg();
                  }
                  
              }
               
              browse(sid) {
                  if (this.state == stateCONNECTED) {
                      // heos://browse/browse?sid=source_id
                      this.msgs.push( 'heos://browse/browse?sid='+sid );
                      this.sendNextMsg();
                  }
                  
              }
               
               
              sendNextMsg () {
                  if (this.msgs.length>0) {
                      var msg = this.msgs.shift();
                      this.sendMsg(msg);
                  }
              }
               
              // Nachricht an player senden
              sendMsg (msg) {
                  this.net_client.write(msg + "\n");
                  this.logDebug("data sent: "+ msg);
              }
               
               
              } // end of class Heos
               
              /********************
               * class HeosPlayer
               ********************/
               
              class HeosPlayer  {
               
              constructor(heos, player) {
                  this.heos = heos;
                  this.ip = player.ip;
                  this.name = player.name;
                  this.pid = player.pid;
                  this.model = player.model;
                  this.serial = player.serial;
               
                  this.statePath = 'javascript.'+instance+'.heos.'+this.ip.replace(/\./g,'_')+".";
               
                  this.log('starting HEOS player for IP '+this.ip);
               
                  this.createStates();
               
                  // Initialisierung der States nach 5 Sek
                  setTimeout(() => {
                      this.setState('ip',this.ip);
                      this.setState('name',this.name);
                      this.setState('pid',this.pid);
                      this.setState('model',this.model);
                      this.setState('serial',this.serial);
                      this.sendCommand('get_play_state|get_play_mode|get_now_playing_media|get_volume');
                  }, 5000 );
                  setTimeout(() => {
                      this.setState('connected', true);
                  }, 10000 );
              }
               
              static version () { return "0.2"; }
               
              logDebug(msg) { console.debug('[HeosPlayer '+this.ip+'] '+msg); }
              log(msg) { console.log('[HeosPlayer '+this.ip+'] '+msg); }
              logWarn(msg) { console.warn('[HeosPlayer '+this.ip+'] '+msg); }
              logError(msg) { console.error('[HeosPlayer '+this.ip+'] '+msg); }
               
               
              /** Anlage der ioBroker States für den Player
               **/
              createStates() {
                  const states = [
                      { id:"connected",                   name:"Verbunden?"},
                      { id:"command",                     name:"Kommando für HEOS Aufrufe"},
                      { id:"ip",                          name:"IP Adresse"},
                      { id:"pid",                         name:"Player-ID "},
                      { id:"name",                        name:"Name des Players"},
                      { id:"model",                       name:"Modell des Players"},
                      { id:"serial",                      name:"Seriennummer des Players"},
                      { id:"last_error",                  name:"Letzter Fehler"},
                      { id:"volume",                      name:"Aktuelle Lautstärke"},
                      { id:"mute",                        name:"Mute aktiviert?"},
                      { id:"play_state",                  name:"Aktueller Wiedergabezustand"},
                      { id:"play_mode_repeat",            name:"Wiedergabewiederholung"},
                      { id:"play_mode_shuffle",           name:"Zufällige Wiedergabe"},
                      { id:"now_playing_media_type",      name:"Aktuelle Wiedergabemedium"},
                      { id:"now_playing_media_song",      name:"Aktuelles Lied"},
                      { id:"now_playing_media_station",   name:"Aktuelle Station"},
                      { id:"now_playing_media_album",     name:"Aktuelle Wiedergabemedium"},
                      { id:"now_playing_media_artist",    name:"Aktueller Artist"},
                      { id:"now_playing_media_image_url", name:"Aktuelles Coverbild"},
                      { id:"now_playing_media_album_id",  name:"Aktuelle Album-ID"},
                      { id:"now_playing_media_mid",       name:"Aktuelle mid"},
                      { id:"now_playing_media_qid",       name:"Aktuelle qid"},
                      { id:"cur_pos",                     name:"lfd. Position"},
                      { id:"duration",                    name:"Dauer"},
                      { id:"cur_pos_MMSS",                name:"lfd. Position MM:SS"},
                      { id:"duration_MMSS",               name:"Dauer MM:SS"}
                  ];
               
                  var def = "";
                  for (var s=0; s<states.length; s++) {
                      var state = states[s];
                      if (this.hasOwnProperty(state.id)) def=this[state.id]; else def='';
                      createState( this.statePath+state.id, def, {name: state.name });
                  }
                  
              }
               
               
              /** wandelt einen sek Wert in MM:SS Darstellung um
               **/
              toMMSS (s) {
                  var sec_num = parseInt(s, 10); 
                  var minutes = Math.floor(sec_num  / 60);
                  var seconds = sec_num - (minutes * 60);
                  if (seconds < 10) {seconds = "0"+seconds;}
                  return minutes+':'+seconds;
              }
               
              /** setState wrapper
               **/
              setState(id,val) {
                 setState(this.statePath+id, val, true);    
              }
               
              /** Auswertung der empfangenen Daten
               **/
              parseResponse (jdata, jmsg, cmd_group, cmd) { try {
                  this.logDebug('response type - cmd_group: ' + cmd_group + " - cmd: " + cmd);
                  switch (cmd_group) {
                      case 'event':
                          switch (cmd) {
                              case 'player_playback_error' :
                                  this.setState('last_error', jmsg.error.replace(/_/g,' '));
                                  this.logError(jmsg.error.replace(/_/g,' '));
                                  break;
                              case 'player_state_changed' :
                                  this.setState("play_state", jmsg.state);
                                  break;
                              case 'player_volume_changed' :
                                  this.setState("volume", jmsg.level );
                                  this.setState("mute", (jmsg.mute=='on' ? true : false) );
                                  break;
                              case 'player_repeat_mode_changed' :
                                  this.setState("play_mode_shuffle", jmsg.shuffle );
                                  break;
                              case 'player_shuffle_mode_changed' :
                                  this.setState("play_mode_repeat", jmsg.repeat );
                                  break;
                              case 'player_now_playing_changed' :
                                  this.sendCommand('get_now_playing_media');
                                  break;
                              case 'player_now_playing_progress' :
                                  this.setState("cur_pos", jmsg.cur_pos / 1000);
                                  this.setState("cur_pos_MMSS", this.toMMSS(jmsg.cur_pos / 1000));
                                  this.setState("duration", jmsg.duration / 1000);
                                  this.setState("duration_MMSS", this.toMMSS(jmsg.duration / 1000));
                                  break;
                          }        
                          break;
                          
                          
                      case 'player':
                          switch (cmd) {
                              case 'set_volume' :
                              case 'get_volume' :
                                  if ( getState(this.statePath+"volume").val != jmsg.level)
                                      this.setState("volume", jmsg.level);
                                  break;
                              case 'set_mute' :
                              case 'get_mute' :
                                  this.setState("mute", (jmsg.state=='on' ? true : false) );
                                  break;
                              case 'set_play_state' :
                              case 'get_play_state' :
                                  this.setState("play_state", jmsg.state);
                                  break;
                              case 'set_play_mode' :
                              case 'get_play_mode' :
                                  this.setState("play_mode_repeat", jmsg.repeat);
                                  this.setState("play_mode_shuffle", (jmsg.shuffle=='on'?true:false) );
                                  break;
                              case 'get_now_playing_media' :
                                  this.setState("now_playing_media_type", jdata.payload.type);
                                  this.setState("now_playing_media_song", jdata.payload.song);
                                                          this.setState("now_playing_media_album", jdata.payload.album);
                                      this.setState("now_playing_media_artist", jdata.payload.artist);
                                      this.setState("now_playing_media_image_url", jdata.payload.image_url);
                                      this.setState("now_playing_media_album_id", jdata.payload.album_id);
                                      this.setState("now_playing_media_mid", jdata.payload.mid);
                                      this.setState("now_playing_media_qid", jdata.payload.qid);
                                  // type == station
                                  if (jdata.payload.type=='station') {
                                      this.setState("now_playing_media_station", jdata.payload.station);
                                  } else {
                                      this.setState("now_playing_media_station", null);
                                  }
                                  break;
                          }
                          break;
                  } // switch
               
               
              } catch(err) { this.logError( 'parseResponse: '+err.message ); } }
               
              /** cmd der Form "cmd&param"  werden zur msg heos+cmd+pid+&param aufbereitet
                  cmd der Form "cmd?param"  werden zur msg heos+cmd+?param aufbereitet
               **/
              commandToMsg (cmd) {
                  var param = cmd.split('&');
                  cmd = param[0];
                  if ( param.length > 1 ) param='&'+param[1]; else param=''; 
                  var cmd_group = 'player';
               
                  switch (cmd) {
                      case 'get_play_state':
                      case 'get_play_mode':
                      case 'get_now_playing_media':
                      case 'get_volume':
                      case 'play_next':
                      case 'play_previous':
                      case 'set_mute':       // &state=on|off        
                      case 'set_volume':     // &level=1..100   
                      case 'volume_down':    // &step=1..10   
                      case 'volume_up':      // &step=1..10
                      case 'set_play_state': // &state=play|pause|stop
                      case 'set_play_mode':  // &repeat=on_all|on_one|off  shuffle=on|off
                          break;
               
                      // browse            
                      case 'play_preset':    // heos://browse/play_preset?pid=player_id&preset=preset_position
                          cmd_group = 'browse';
                          break;
                      case 'play_stream':    // heos://browse/play_stream?pid=player_id&url=url_path
                          cmd_group = 'browse';
                          break;
                          
                  }        
                  return 'heos://'+cmd_group+'/'+cmd+'?pid=' + this.pid + param;
              }
               
              /** Nachricht (command) an player senden
                  es sind auch mehrere commands, getrennt mit | erlaubt
                  bsp: set_volume&level=20|play_preset&preset=1
               **/
              sendCommand (command) {
                  this.setState('last_error', '');
                  var cmds = command.split('|');
                  for (var c=0; c<cmds.length; c++) {
                      this.heos.msgs.push( this.commandToMsg(cmds[c]) );
                  }
                  this.heos.sendNextMsg();
              }
               
               
              } // end of HeosPlayer
               
               
              /* -----
                 Heos
                 ----- */
              // Heos Instanz erzeugen und verbinden   
              var heos = new Heos( );
              heos.connect();
               
              // wenn das Script beendet wird, dann auch die Heos Instanz beenden
              onStop(function () { 
                  heos.disconnect(); 
              }, 0 );
              
              C UhulaU 2 Antworten Letzte Antwort
              0
              • W withstu

                @Uhula erstmal vielen Dank für deine Arbeit. Ich habe einige Bugs in deinem ersten Script gefixt (@jwedenig es werden auch wieder alle Song Infos aktualisiert). Auch wenn Version 2 bereits unterwegs ist, will ich euch meine Änderungen nicht vorenthalten. Unter anderem ist mir aufgefallen, dass das Filtern und Verarbeiten der Pakete vom node-ssdp nicht korrekt funktioniert, wenn eine Hue Bridge im Netzwerk funkt oder Pakete unvollständig sind. Zudem habe ich noch ein connected Flag für jeden Player hinzugefügt. Dieses muss leider extern über ein Script resettet werden z.B. in Kombination mit dem Ping Adapter. Für diesen Fall habe ich mir ein Blockly Script gebaut, welches das connected Flag resettet und bei Player Updates das Heos Script neu startet. Bei Bedarf kann ich das auch noch teilen:

                /****************************
                 * HEOS Script for ioBroker
                 ****************************
                 *  23.01.2018 Uhula, MIT License, no warranty, use on your own risc
                 * 
                 * Wichtig!
                 ****************************
                 * (a) Im Javascript-Adapter muss in der Instanz-Konfiguration "node-ssdp" mit angegeben werden!
                 *     Alternativ kann die Bibliothek auch komplett über "npm install node-ssdp" installiert
                 *     werden.
                 * (b) Wenn mit Favoriten (Presets) gearbeitet werden soll, müssen die HEOS-Konto Logindaten in
                 *     den beiden Konstanten HEOS_USERNAME (EmailAdr) und HEOS_PASSWORD hier im Script in der 
                 *     Konfiguration angegeben werden (so, wie in der HEOS App)!
                 * (c) Eine Konfiguration von IP-Adressen und Player-IDs ist nicht notwendig!
                 * (d) U.U. kann es notwendig sein das Script nach dem 1.Start zu beenden und nach 30 Sek erneut zu starten, da
                 *     die Neuanlage der ioBroker States etwas Zeit benötigt. Warnungen sind dabei zu ignorieren. ;-)
                 *
                 * 
                 * Funktion
                 ****************************
                 * Das Script stellt zwei JS Klassen zur Steuerung der Denon HEOS Geräte zur Verfügung. Die
                 * class Heos dient dabei dem Erkennen und Steuern der HEOS Geräte. Die class HeosPlayer dient der 
                 * Interpretation der Antworten der Heos Geräte.
                 * 
                 * Das Script muss lediglich gestartet werden, es sucht dann im Netzwerk nach HEOS Playern und
                 * erzeugt in der aktuellen Javascript-Instanz einen Subeintrag für die Favoriten und je einen Subeintrag
                 * für jeden gefundenen Player. Dort wiederum werden State-Variablen zur Aufnahme der Player-Daten angelegt.
                 * 
                 * Das Script erzeugt auch on-Handler um auf Steuerungen über vis und andere Scripte an den State-Variablen
                 * reagieren zu können.
                 * 
                 * Beim Beenden des Scripts werden alle Verbindungen und on-Handler geschlossen.
                 * 
                 * Das Script ermöglicht die HEOS Player zu steuern, es soll aber nicht die HEOS App ersetzen. Dazu fehlen
                 * etliche Funktionen wie Playlisten-Handhabung, Gruppensteuerung usw.
                 * 
                 * 
                 * class Heos
                 ****************************
                 * (a) Erkennen von HEOS Geräten (Playern)
                 *     Das Erkennen der HEOS Geräte findet via UPD unter Nutzung von node-ssdp statt. node-ssdp muss dazu im 
                 *     Javascript-Adapter in der Konfiguration mit angegeben werden!
                 * (b) der Kommunikation über TelNet 
                 *     Es wird genau eine TelNet Verbindung zu einem HEOS Gerät aufgebaut, dieses reicht die Sendungen
                 *     und Antworten zentral weiter
                 * (c) der Ermittlung von Favoriten (Presets) und 
                 *     Für jeden Favorit wird ein ioBroker State heos.presets.n mit entsprechenden Sub-States erzeugt.
                 *     Zur Nutzung der Presets ist ein SignIn notwendig, hierzu müssen HEOS_USERNAME und HEOS_PASSWORD
                 *     gesetzt werden.
                 * (d) dem Instanziieren der class HeosPlayer je HEOS Gerät. 
                 *     Je HEOS Gerät wird ein ioBroker-State mit der IP-Adresse des Players erzeugt, dieser State erhält
                 *     diverse Sub-States
                 * 
                 * State-Variable
                 * --------------
                 * .heos.command(cmd) // write
                 *   Hierüber können der Heos-Klasse Befehle übergeben werden, für cmd gilt:
                 * 
                 *   connect      
                 *       Verbindung zu HEOS aufbauen bzw. erneut aufbauen (praktisch ein reset)
                 *   disconnect   
                 *       Verbindung zu HEOS beenden
                 *   load_presets
                 *       lädt die Favoriten neu
                 * 
                 *   alle anderen cmd-Werte werden "as is" versucht an HEOS zu senden, damit ist z.B. auch das
                 *   Gruppieren von HEOS Speakern möglich.
                 * 
                 *   group/set_group?pid=<pid1>,<pid2>,...
                 *       setzen einer Gruppe, die pids sind die der Player wie sie unter den Objekten
                 *       für jeden Player abgelegt sind. Statt diese direkt zu verwenden, kann man auch
                 *       die ioBroker binding-Funktionalität nutzen und Platzhalter verwenden.
                 *       Bsp: group/set_group?pid={javascript.1.heos.192_168_2_46.pid},{javascript.1.heos.192_168_2_43.pid}
                 * 
                 *   group/set_group?pid=<pid1>  
                 *       hebt die Gruppierung wieder auf
                 *       Bsp: group/set_group?pid={javascript.1.heos.192_168_2_46.pid}
                 * 
                 * 
                 * Favoriten (je Favorit ein Unterordner n=1 bis Anzahl)
                 * 
                 * .heos.presets.<n>.image_url (read)
                 *   Bild des Favoriten 
                 * 
                 * .heos.presets.<n>.name (read)
                 *   Name des Favoriten
                 * 
                 * .heos.presets.<n>.playable (read)
                 *   Spielbar? true/false
                 * 
                 * .heos.presets.<n>.type (read)
                 *   Typ des Favoriten (station, ...)
                 *    
                 *  
                 * class HeosPlayer 
                 ****************************
                 * (a) der Steuerung genau eines HEOS Gerätes. Hierzu wird die zentrale Telnet Verbindung der class Heos genutzt.
                 * (b) dem Erzeugen der ioBroker-States zum Speichern der Geräte-Werte
                 * (b) der Auswertung von Antworten der HEOS Geräte und Zuweisung an die entsprechenden ioBroker-States
                 * 
                 * State-Variable
                 * --------------
                 * .heos.<player_ip>.command (write)
                 *   Hierüber können dem Heos-Player Befehle übergeben werden. Diese werden im Klartext in die State-Variable
                 *   geschrieben. Getrennt durch das | Zeichen können mehrere Befehle hintereinander eingetragen werden.
                 *   Bsp: Setzen der Lautstärke auf 20 und Abspielen des 1.Favoriten
                 *        set_volume&level=20|play_preset&preset=1
                 * 
                 *   set_volume&level=0|1|..|100          : Setzt die gewünschte Lautstärke 
                 *   set_play_state&state=play|pause|stop : Startet und stoppt die Wiedergabe
                 *   set_play_mode&repeat=on_all|on_one|off&shuffle=on|off: Setzt Wiederholung und Zufallsweidergabe
                 *   set_mute&state=on|off                : Stumm schalten oder nicht
                 *   volume_down&step=1..10               : Lautstärke verringern um   
                 *   volume_up&step=1..10                 : Lauststäre erhöhen um
                 *   play_next                            : Nächsten Titel spielen
                 *   play_previous                        : Vorherigen Titel spielen
                 *   play_preset&preset=1|2|..|n          : Favorit Nr n abspielen
                 *   play_stream&url=url_path             : URL-Stream abspielen
                 * 
                 *   Befehle, die intern genutzt werden und nicht aufgerufen werden müssen:
                 *
                 *   get_volume              : Füllt den .heos.<player_ip>.volume State
                 *   get_play_state          : Füllt den .heos.<player_ip>.play_state State
                 *   get_play_mode           : Füllt den .heos.<player_ip>.play_mode State 
                 *   get_now_playing_media   : Füllt die .heos.<player_ip>.now_playing_media_... States
                 *
                 *
                 * .heos.<player_ip>.cur_pos (read)
                 *   lfd. Position der Wiedergabe in [Sek]
                 * 
                 * .heos.<player_ip>.cur_pos_MMSS (read)
                 *   lfd. Position der Wiedergabe im Format MM:SS
                 * 
                 * .heos.<player_ip>.duration (read)
                 *   Länge des aktuellen Titels in [Sek]
                 * 
                 * .heos.<player_ip>.duration_MMSS (read)
                 *   Länge des aktuellen Titels im Format MM:SS
                 * 
                 * .heos.<player_ip>.ip (read)
                 *   IP-Adresse des HEOS Players
                 * 
                 * .heos.<player_ip>.last_error (read)
                 *   Text des letzten Fehlers, der vom Player gesendet wurde. Wird bei jedem neuen Befehl zurückgesetzt. 
                 *   Wenn man bspw. versucht eine Wiedergabe über Amazon o.ä. zu starten und es gibt aber keine Verbindung
                 *   dahin, steht hier der Fehlertext drin
                 * 
                 * .heos.<player_ip>.model (read)
                 *   Modell des HEOS Players
                 * 
                 * .heos.<player_ip>.mute (read write)
                 *   Boolsche Variable, die anzeigt ob der Player gemutet (Stumm geschaltet) wurde. Hierüber kann auch ein 
                 *   mute gesetzt werden
                 * 
                 * .heos.<player_ip>.name (read)
                 *   Name des HEOS Players
                 * 
                 * .heos.<player_ip>.now_playing_media_... (read)
                 *   Eine Gruppe von States, in welchen Infos über den aktuellen Titel gespeichert werden, wird automatisch
                 *   aktualisiert. 
                 * 
                 * .heos.<player_ip>.pid (read)
                 *   Player-ID des HEOS Players
                 * 
                 * .heos.<player_ip>.play_mode_repeat (read write)
                 *   Repeat-Modus der Wiedergabe. Mögliche Werte: on_all|on_one|off  
                 * 
                 * .heos.<player_ip>.play_mode_shuffle (read write)
                 *   Zufallswiedergabe true/false
                 * 
                 * .heos.<player_ip>.play_state (read write)
                 *   Zustand des Players. Mögliche Werte play|pause|stop
                 * 
                 * .heos.<player_ip>.serial (read)
                 *   Seriennummmer des HEOS Players
                 * 
                 * .heos.<player_ip>.volume (read write)
                 *   Lautstärke im Bereich 0 - 100. 
                 * 
                 * 
                 * 
                 * Weiterführende Links
                 ****************************
                 * HEOS CLI Protokoll: http://rn.dmglobal.com/euheos/HEOS_CLI_ProtocolSpecification.pdf
                 * http://forum.iobroker.net/viewtopic.php?f=30&t=5693&p=115554#p115554
                 * 
                 **/
                 
                /****************************
                 * Konfiguration
                 ****************************/
                 
                const HEOS_USERNAME = '';
                const HEOS_PASSWORD = '';
                 
                 
                /****************************
                 * ab hier nichts mehr ändern ;-) 
                 ****************************/
                 
                var net = require('net');
                 
                const stateDISCONNECTED = 0;
                const stateCONNECTING = 1;
                const stateCONNECTED = 2;
                 
                /********************
                 * class Heos
                 ********************/
                class Heos  {
                 
                constructor() {
                    this.init();
                 
                    createState( this.statePath+'command', '', {name: 'Kommando für Heos-Script' });
                    createState( this.statePath+'connected', false, {name: 'Verbunden?' });
                    createState( this.statePath+'last_error', '', {name: 'Letzter Fehler' });
                    on({id: this.statePath+'command', change: "any"}, (obj) => {
                            this.executeCommand( obj.state.val );
                        });
                }
                 
                logDebug(msg) { console.debug('[Heos] '+msg) };
                log(msg) { console.log('[Heos] '+msg); }
                logWarn(msg) { console.warn('[Heos] '+msg); }
                logError(msg) { console.error('[Heos] '+msg); }
                 
                 
                init() {
                    this.statePath = 'javascript.'+instance+'.heos.';
                    this.players = [];
                    this.net_client = undefined;
                    this.nodessdp_client = undefined;
                 
                    this.ip='';
                    this.msgs = [];
                    this.lastResponse = '';
                    this.state = stateDISCONNECTED;
                    this.unfinishedResponses = '';
                    this.ssdpSeartTargetName = 'urn:schemas-denon-com:device:ACT-Denon:1';
                }
                 
                connect() { 
                    try {
                        this.log('connecting to HEOS ...');
                        setState( this.statePath+"connected", false );
                        const NodeSSDP = require('node-ssdp').Client;
                	    this.nodessdp_client = new NodeSSDP();
                	    this.nodessdp_client.explicitSocketBind = true;
                	    this.nodessdp_client.on('response', (headers, statusCode, rinfo) => this.onNodeSSDPResponse(headers, statusCode, rinfo) );
                	    this.nodessdp_client.on('error', error => {	client.close(); this.logError(error); });
                        this.nodessdp_client.search(this.ssdpSeartTargetName);
                    } catch(err) { this.logError( 'connect: '+err.message ); } 
                    
                }
                 
                 
                /** Alle Player stoppen und die TelNet Verbindung schließen 
                 **/
                disconnect() {
                    this.log('disconnecting from HEOS ...');
                    unsubscribe('javascript.'+instance+'.heos');
                
                    var i=0;
                    for (i=0; i<this.players.length; i++) {
                        var playerStatePath = this.players[i].heosPlayer.statePath;
                        setState( playerStatePath+"connected", false );
                    }
                 
                    if (typeof this.net_client!=='undefined') {
                        this.registerChangeEvents( false );
                        
                        this.net_client.destroy();
                        this.net_client.unref();
                    }
                    if (typeof this.nodessdp_client!=='undefined') {
                        this.nodessdp_client.stop();
                    }
                    setState( this.statePath+"connected", false );
                    this.log('disconnected from HEOS');
                }
                 
                executeCommand(cmd) {
                    this.log('command: '+cmd);
                    switch (cmd) {
                        case 'load_presets' :
                            this.getMusicSources();
                            break;
                        case 'connect' :
                            this.disconnect();
                            this.init();
                            this.connect();
                            break;
                        case 'disconnect' :
                            this.disconnect();
                            break;
                        default:
                            if (this.state == stateCONNECTED) {
                                this.msgs.push( 'heos://'+cmd+'\n' );
                                this.sendNextMsg();
                            }
                    
                    }
                }
                
                sleep(milliseconds) {
                   return new Promise(resolve => setTimeout(resolve, milliseconds));
                }
                
                /** es wurde mindestens ein Player erkannt, nun über dessen IP alle bekannten HEOS Player
                 *  durch senden von "player/get_players" ermitteln
                 */
                onNodeSSDPResponse(headers, statusCode, rinfo) { try {
                    // rinfo {"address":"192.168.2.225","family":"IPv4","port":53871,"size":430}
                    if (typeof this.net_client=='undefined') {
                        if(headers.ST === this.ssdpSeartTargetName){
                            this.ip = rinfo.address;
                            this.log('connecting to '+this.ip+' ...');
                            this.net_client = net.connect({host:this.ip, port:1255});
                            this.net_client.setKeepAlive(true, 5000);
                    
                            this.state = stateCONNECTING;
                    
                            this.net_client.on('error',(error) => {
                                this.logError(error);
                                this.disconnect();
                            }); 
                        
                            this.net_client.on('connect',  () => {
                                setState( this.statePath+"connected", true );
                                this.log('connected to HEOS');
                                this.state = stateCONNECTED;
                                this.log('connected to '+this.ip);
                                this.getPlayers();
                                this.registerChangeEvents( true );
                                this.signIn();
                                this.getMusicSources();
                            });
                            
                            // Gegenseite hat die Verbindung geschlossen 
                            this.net_client.on('end',  () => {              
                                this.logWarn('HEOS closed the connection to '+this.ip);
                                this.disconnect();
                            });
                    
                            // timeout
                            this.net_client.on('timeout',  () => {              
                                this.logWarn('Timeout trying connect to '+this.ip);
                                this.disconnect();
                            });
                        
                            // Datenempfang
                            this.net_client.on('data', (data) => this.onData(data)  );
                        } else {
                            this.log('Getting wrong ssdp entry. Keep trying...');
                        }
                    }
                } catch(err) { this.logError( 'onNodeSSDPResponse: '+err.message ); } }
                
                removeFirstOccurrence(str, searchstr)       {
                	var index = str.indexOf(searchstr);
                	if (index === -1) {
                		return str;
                	}
                	return str.slice(0, index) + str.slice(index + searchstr.length);
                }
                 
                /** es liegen Antwort(en) vor
                 **/
                onData(data) {  try {
                    data = data.toString();
                    this.logDebug("received data:" + data);
                    data=data.replace(/[\n\r]/g, '');    // Steuerzeichen "CR" entfernen   
                    // es können auch mehrere Antworten vorhanden sein! {"heos": ... } {"heos": ... }
                    // diese nun in einzelne Antworten zerlegen
                    data = this.unfinishedResponses + data;
                    this.unfinishedResponses = '';
                
                    data=data.replace(/{\s*"heos"\s*:/g, '|{"heos":');  
                    var responses = data.split('|');
                    for (var r=0; r<responses.length; r++ ) {
                        if(responses[r].trim().length > 0){
                            try {
                                JSON.parse(responses[r]);
                                this.parseResponse(responses[r]);
                            } catch(e) {
                                this.logDebug("invalid json (error: " + e.message + "): " + responses[r]);
                                this.unfinishedResponses += responses[r];
                            }
                        }
                    }
                    // wenn weitere Msg zum Senden vorhanden sind, die nächste senden
                    if (this.msgs.length>0)
                        this.sendNextMsg();
                } catch(err) { this.logError( 'onData: '+err.message ); } }
                 
                /** Antwort(en) verarbeiten. Sich wiederholende Antworten ignorieren
                 **/
                parseResponse (response) { try {
                    if (response == this.lastResponse || response.indexOf("command under process") > 0 )
                        return
                    this.lastResponse = response;        
                    
                    var jmsg;
                    var i;
                    var jdata = JSON.parse(response);
                    if ( !jdata.hasOwnProperty('heos') || !jdata.heos.hasOwnProperty('command') || !jdata.heos.hasOwnProperty('message')) 
                        return;
                 
                    // msg auswerten
                    try {
                        jmsg = '{"' + decodeURI(jdata.heos.message).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g,'":"').replace(/\s/g,'_') + '"}';
                        jmsg = JSON.parse(jmsg);
                    } catch(err) {
                        jmsg = {};
                    }
                    this.logDebug('parse response: '+response);
                    this.logDebug('parse message: '+JSON.stringify(jmsg));
                 
                    // cmd auswerten
                    var cmd = jdata.heos.command.split('/');
                    var cmd_group = cmd[0];
                    cmd = cmd[1];
                
                    // result ?
                    var result = 'success';
                    if (jdata.heos.hasOwnProperty('result') ) result = jdata.heos.result;
                    if ( result!='success' ) { 
                        setState(this.statePath+'last_error', cmd + ' | ' + jmsg.text);
                        this.logWarn(jmsg.text);
                    }
                 
                    switch (cmd_group) {
                        case 'player':
                            switch (cmd) {
                                    // {"heos": {"command": "player/get_players", "result": "success", "message": ""}, 
                                    //  "payload": [{"name": "HEOS Bar", "pid": 1262037998, "model": "HEOS Bar", "version": "1.430.160", "ip": "192.168.2.225", "network": "wifi", "lineout": 0, "serial": "ADAG9170202780"}, 
                                    //              {"name": "HEOS 1 rechts", "pid": -1746612370, "model": "HEOS 1", "version": "1.430.160", "ip": "192.168.2.201", "network": "wifi", "lineout": 0, "serial": "AMWG9170934429"}, 
                                    //              {"name": "HEOS 1 links", "pid": 68572158, "model": "HEOS 1", "version": "1.430.160", "ip": "192.168.2.219", "network": "wifi", "lineout": 0, "serial": "AMWG9170934433"}
                                    //             ]}
                                case 'get_players' :
                                    if ( (jdata.hasOwnProperty('payload')) && (this.players.length===0) ) {
                                        for (i=0; i<jdata.payload.length; i++) {
                                            var player = jdata.payload[i];
                                            this.players.push(player);
                                        }
                                        this.startPlayers();
                                    } else if(jdata.playload.length != this.players.length){
                                        this.log("New Player detected. Restart.")
                                        this.disconnect();
                                    }                    
                                    break;
                            }
                            break;
                 
                        // {"heos": {"command": "browse/get_music_sources", "result": "success", "message": ""}, 
                        //  "payload": [{"name": "Amazon", "image_url": "https://production...png", "type": "music_service", "sid": 13}, 
                        //              {"name": "TuneIn", "image_url": "https://production...png", "type": "music_service", "sid": 3}, 
                        //              {"name": "Local Music", "image_url": "https://production...png", "type": "heos_server", "sid": 1024}, 
                        //              {"name": "Playlists", "image_url": "https://production...png", "type": "heos_service", "sid": 1025}, 
                        //              {"name": "History", "image_url": "https://production...png", "type": "heos_service", "sid": 1026}, 
                        //              {"name": "AUX Input", "image_url": "https://production...png", "type": "heos_service", "sid": 1027}, 
                        //              {"name": "Favorites", "image_url": "https://production...png", "type": "heos_service", "sid": 1028}]}
                        case 'browse':
                            switch (cmd) {
                                case 'get_music_sources' :
                                    if ( (jdata.hasOwnProperty('payload')) ) {
                                        this.logDebug("Payload:"+jdata.payload.toString());
                                        for (i=0; i<jdata.payload.length; i++) {
                                            var source = jdata.payload[i];
                                            if (source.name=='Favorites') {
                                                this.browse(source.sid);
                                            }
                                        }
                                    }
                 
                                    break;
                                    
                	    // {"heos": {"command": "browse/browse", "result": "success", "message": "pid=1262037998&sid=1028&returned=5&count=5"}, 
                	    //  "payload": [{"container": "no", "mid": "s17492", "type": "station", "playable": "yes", "name": "NDR 2 (Adult Contemporary Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s17492q.png"}, 
                	    //              {"container": "no", "mid": "s158432", "type": "station", "playable": "yes", "name": "Absolut relax (Easy Listening Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s158432q.png"}, 
                	    //              {"container": "no", "mid": "catalog/stations/A1W7U8U71CGE50/#chunk", "type": "station", "playable": "yes", "name": "Ed Sheeran", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/EdSheeran._SX200_SY200_.jpg"}, 
                	    //              {"container": "no", "mid": "catalog/stations/A1O1J39JGVQ9U1/#chunk", "type": "station", "playable": "yes", "name": "Passenger", "image_url": "https://images-na.ssl-images-amazon.com/images/I/71DsYkU4QaL._SY500_CR150,0,488,488_SX200_SY200_.jpg"}, 
                	    //              {"container": "no", "mid": "catalog/stations/A316JYMKQTS45I/#chunk", "type": "station", "playable": "yes", "name": "Johannes Oerding", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/JohannesOerding._SX200_SY200_.jpg"}], 
                	    //  "options": [{"browse": [{"id": 20, "name": "Remove from HEOS Favorites"}]}]}                    
                                case 'browse' :
                                    if ( (jdata.hasOwnProperty('payload')) ) {
                                        for (i=0; i<jdata.payload.length; i++) {
                                            var preset = jdata.payload[i];
                                            createState( this.statePath+'presets.'+(i+1)+'.name', preset.name, {name: 'Favoritenname ' });
                                            createState( this.statePath+'presets.'+(i+1)+'.playable', (preset.playable=='yes'?true:false), {name: 'Favorit ist spielbar' });
                                            createState( this.statePath+'presets.'+(i+1)+'.type', preset.type, {name: 'Favorittyp' });
                                            createState( this.statePath+'presets.'+(i+1)+'.image_url', preset.image_url, {name: 'Favoritbild' });
                                        }
                                    }
                                    break;
                            }     
                            break;
                 
                        case 'system':
                            switch (cmd) {
                                case 'sign_in' :
                                    this.log('signed in: '+jdata.heos.result);
                                    break;
                            }     
                            break;
                    }
                 
                 
                    // an die zugehörigen Player weiterleiten
                    if ( jmsg.hasOwnProperty('pid') ) {
                        for (i=0; i<this.players.length; i++)
                            if (jmsg.pid==this.players[i].heosPlayer.pid) {
                                this.players[i].heosPlayer.parseResponse (jdata, jmsg, cmd_group, cmd);
                                break;
                            }
                    }
                 
                    
                } catch(err) { this.logError( 'parseResponse: '+err.message+'\n '+response ); } }
                 
                 
                /** sucht die zur ip passenden player-Insanz
                 **/
                sendCommandToPlayer(objID, cmd ) {
                   var ip = objID;
                   ip = ip.split('.');
                   var ip_ = ip[3];
                   ip = ip_.replace(/_/g,'.');
                   for (var p=0; p<this.players.length; p++ ) 
                       if (this.players[p].ip == ip ) {
                           this.players[p].heosPlayer.sendCommand( cmd );
                           break;
                       }
                       
                } 
                 
                /** Für die gefundenen HEOS Plyer entsprechende class HeosPlayer Instanzen bilden 
                 **/
                startPlayers() { try {
                    var i=0;
                    for (i=0; i<this.players.length; i++) {
                       this.players[i].heosPlayer = new HeosPlayer( this, this.players[i]  );
                    }
                    // Events setzen
                    for (i=0; i<this.players.length; i++) {
                        var statePath = this.players[i].heosPlayer.statePath;
                        // on-Event für command
                        on({id: statePath+'command', change: "any"}, (obj) => {
                            this.sendCommandToPlayer( obj.id, obj.state.val );
                        });
                 
                        // on-Event für volume (nur wenn ack=false, also über vis)
                        on({id: statePath+'volume', change:'ne', ack:false }, (obj) => {
                            this.sendCommandToPlayer( obj.id, 'set_volume&level='+obj.state.val );
                        });
                        // on-Event für mute (nur wenn ack=false, also über vis)
                        on({id: statePath+'mute', change: 'ne', ack:false}, (obj) => {
                            this.sendCommandToPlayer( obj.id, 'set_mute&state='+ (obj.state.val === true ? 'on' : 'off') );
                        });
                        // on-Event für play_mode_shuffle (nur wenn ack=false, also über vis)
                        on({id: statePath+'play_mode_shuffle', change: 'ne', ack:false}, (obj) => {
                            this.sendCommandToPlayer( obj.id, 'set_play_mode&shuffle='+ (obj.state.val === true ? 'on' : 'off') );
                        });
                        // on-Event für play_state (nur wenn ack=false, also über vis)
                        on({id: statePath+'play_state', change: 'ne', ack:false}, (obj) => {
                            this.sendCommandToPlayer( obj.id, 'set_play_state&state='+ obj.state.val );
                        });
                    }    
                } catch(err) { this.logError( 'startPlayers: '+err.message ); } }
                 
                 
                 
                getPlayers() {
                    if (this.state == stateCONNECTED) {
                        this.msgs.push( 'heos://player/get_players\n' );
                        this.sendNextMsg();
                    }
                }
                 
                registerChangeEvents( b ) {
                    if (this.state == stateCONNECTED) {
                        if (b) this.msgs.push( 'heos://system/register_for_change_events?enable=on' );
                        else this.msgs.push( 'heos://system/register_for_change_events?enable=off' );
                        this.sendNextMsg();
                    }
                }
                 
                signIn() {
                    if (this.state == stateCONNECTED) {
                        // heos://system/sign_in?un=heos_username&pw=heos_password
                        this.msgs.push( 'heos://system/sign_in?un='+HEOS_USERNAME+'&pw='+HEOS_PASSWORD );
                        this.sendNextMsg();
                    }
                }
                 
                getMusicSources() {
                    if (this.state == stateCONNECTED) {
                        // heos://browse/get_music_sources
                        this.msgs.push( 'heos://browse/get_music_sources' );
                        this.sendNextMsg();
                    }
                    
                }
                 
                browse(sid) {
                    if (this.state == stateCONNECTED) {
                        // heos://browse/browse?sid=source_id
                        this.msgs.push( 'heos://browse/browse?sid='+sid );
                        this.sendNextMsg();
                    }
                    
                }
                 
                 
                sendNextMsg () {
                    if (this.msgs.length>0) {
                        var msg = this.msgs.shift();
                        this.sendMsg(msg);
                    }
                }
                 
                // Nachricht an player senden
                sendMsg (msg) {
                    this.net_client.write(msg + "\n");
                    this.logDebug("data sent: "+ msg);
                }
                 
                 
                } // end of class Heos
                 
                /********************
                 * class HeosPlayer
                 ********************/
                 
                class HeosPlayer  {
                 
                constructor(heos, player) {
                    this.heos = heos;
                    this.ip = player.ip;
                    this.name = player.name;
                    this.pid = player.pid;
                    this.model = player.model;
                    this.serial = player.serial;
                 
                    this.statePath = 'javascript.'+instance+'.heos.'+this.ip.replace(/\./g,'_')+".";
                 
                    this.log('starting HEOS player for IP '+this.ip);
                 
                    this.createStates();
                 
                    // Initialisierung der States nach 5 Sek
                    setTimeout(() => {
                        this.setState('ip',this.ip);
                        this.setState('name',this.name);
                        this.setState('pid',this.pid);
                        this.setState('model',this.model);
                        this.setState('serial',this.serial);
                        this.sendCommand('get_play_state|get_play_mode|get_now_playing_media|get_volume');
                    }, 5000 );
                    setTimeout(() => {
                        this.setState('connected', true);
                    }, 10000 );
                }
                 
                static version () { return "0.2"; }
                 
                logDebug(msg) { console.debug('[HeosPlayer '+this.ip+'] '+msg); }
                log(msg) { console.log('[HeosPlayer '+this.ip+'] '+msg); }
                logWarn(msg) { console.warn('[HeosPlayer '+this.ip+'] '+msg); }
                logError(msg) { console.error('[HeosPlayer '+this.ip+'] '+msg); }
                 
                 
                /** Anlage der ioBroker States für den Player
                 **/
                createStates() {
                    const states = [
                        { id:"connected",                   name:"Verbunden?"},
                        { id:"command",                     name:"Kommando für HEOS Aufrufe"},
                        { id:"ip",                          name:"IP Adresse"},
                        { id:"pid",                         name:"Player-ID "},
                        { id:"name",                        name:"Name des Players"},
                        { id:"model",                       name:"Modell des Players"},
                        { id:"serial",                      name:"Seriennummer des Players"},
                        { id:"last_error",                  name:"Letzter Fehler"},
                        { id:"volume",                      name:"Aktuelle Lautstärke"},
                        { id:"mute",                        name:"Mute aktiviert?"},
                        { id:"play_state",                  name:"Aktueller Wiedergabezustand"},
                        { id:"play_mode_repeat",            name:"Wiedergabewiederholung"},
                        { id:"play_mode_shuffle",           name:"Zufällige Wiedergabe"},
                        { id:"now_playing_media_type",      name:"Aktuelle Wiedergabemedium"},
                        { id:"now_playing_media_song",      name:"Aktuelles Lied"},
                        { id:"now_playing_media_station",   name:"Aktuelle Station"},
                        { id:"now_playing_media_album",     name:"Aktuelle Wiedergabemedium"},
                        { id:"now_playing_media_artist",    name:"Aktueller Artist"},
                        { id:"now_playing_media_image_url", name:"Aktuelles Coverbild"},
                        { id:"now_playing_media_album_id",  name:"Aktuelle Album-ID"},
                        { id:"now_playing_media_mid",       name:"Aktuelle mid"},
                        { id:"now_playing_media_qid",       name:"Aktuelle qid"},
                        { id:"cur_pos",                     name:"lfd. Position"},
                        { id:"duration",                    name:"Dauer"},
                        { id:"cur_pos_MMSS",                name:"lfd. Position MM:SS"},
                        { id:"duration_MMSS",               name:"Dauer MM:SS"}
                    ];
                 
                    var def = "";
                    for (var s=0; s<states.length; s++) {
                        var state = states[s];
                        if (this.hasOwnProperty(state.id)) def=this[state.id]; else def='';
                        createState( this.statePath+state.id, def, {name: state.name });
                    }
                    
                }
                 
                 
                /** wandelt einen sek Wert in MM:SS Darstellung um
                 **/
                toMMSS (s) {
                    var sec_num = parseInt(s, 10); 
                    var minutes = Math.floor(sec_num  / 60);
                    var seconds = sec_num - (minutes * 60);
                    if (seconds < 10) {seconds = "0"+seconds;}
                    return minutes+':'+seconds;
                }
                 
                /** setState wrapper
                 **/
                setState(id,val) {
                   setState(this.statePath+id, val, true);    
                }
                 
                /** Auswertung der empfangenen Daten
                 **/
                parseResponse (jdata, jmsg, cmd_group, cmd) { try {
                    this.logDebug('response type - cmd_group: ' + cmd_group + " - cmd: " + cmd);
                    switch (cmd_group) {
                        case 'event':
                            switch (cmd) {
                                case 'player_playback_error' :
                                    this.setState('last_error', jmsg.error.replace(/_/g,' '));
                                    this.logError(jmsg.error.replace(/_/g,' '));
                                    break;
                                case 'player_state_changed' :
                                    this.setState("play_state", jmsg.state);
                                    break;
                                case 'player_volume_changed' :
                                    this.setState("volume", jmsg.level );
                                    this.setState("mute", (jmsg.mute=='on' ? true : false) );
                                    break;
                                case 'player_repeat_mode_changed' :
                                    this.setState("play_mode_shuffle", jmsg.shuffle );
                                    break;
                                case 'player_shuffle_mode_changed' :
                                    this.setState("play_mode_repeat", jmsg.repeat );
                                    break;
                                case 'player_now_playing_changed' :
                                    this.sendCommand('get_now_playing_media');
                                    break;
                                case 'player_now_playing_progress' :
                                    this.setState("cur_pos", jmsg.cur_pos / 1000);
                                    this.setState("cur_pos_MMSS", this.toMMSS(jmsg.cur_pos / 1000));
                                    this.setState("duration", jmsg.duration / 1000);
                                    this.setState("duration_MMSS", this.toMMSS(jmsg.duration / 1000));
                                    break;
                            }        
                            break;
                            
                            
                        case 'player':
                            switch (cmd) {
                                case 'set_volume' :
                                case 'get_volume' :
                                    if ( getState(this.statePath+"volume").val != jmsg.level)
                                        this.setState("volume", jmsg.level);
                                    break;
                                case 'set_mute' :
                                case 'get_mute' :
                                    this.setState("mute", (jmsg.state=='on' ? true : false) );
                                    break;
                                case 'set_play_state' :
                                case 'get_play_state' :
                                    this.setState("play_state", jmsg.state);
                                    break;
                                case 'set_play_mode' :
                                case 'get_play_mode' :
                                    this.setState("play_mode_repeat", jmsg.repeat);
                                    this.setState("play_mode_shuffle", (jmsg.shuffle=='on'?true:false) );
                                    break;
                                case 'get_now_playing_media' :
                                    this.setState("now_playing_media_type", jdata.payload.type);
                                    this.setState("now_playing_media_song", jdata.payload.song);
                                                            this.setState("now_playing_media_album", jdata.payload.album);
                                        this.setState("now_playing_media_artist", jdata.payload.artist);
                                        this.setState("now_playing_media_image_url", jdata.payload.image_url);
                                        this.setState("now_playing_media_album_id", jdata.payload.album_id);
                                        this.setState("now_playing_media_mid", jdata.payload.mid);
                                        this.setState("now_playing_media_qid", jdata.payload.qid);
                                    // type == station
                                    if (jdata.payload.type=='station') {
                                        this.setState("now_playing_media_station", jdata.payload.station);
                                    } else {
                                        this.setState("now_playing_media_station", null);
                                    }
                                    break;
                            }
                            break;
                    } // switch
                 
                 
                } catch(err) { this.logError( 'parseResponse: '+err.message ); } }
                 
                /** cmd der Form "cmd&param"  werden zur msg heos+cmd+pid+&param aufbereitet
                    cmd der Form "cmd?param"  werden zur msg heos+cmd+?param aufbereitet
                 **/
                commandToMsg (cmd) {
                    var param = cmd.split('&');
                    cmd = param[0];
                    if ( param.length > 1 ) param='&'+param[1]; else param=''; 
                    var cmd_group = 'player';
                 
                    switch (cmd) {
                        case 'get_play_state':
                        case 'get_play_mode':
                        case 'get_now_playing_media':
                        case 'get_volume':
                        case 'play_next':
                        case 'play_previous':
                        case 'set_mute':       // &state=on|off        
                        case 'set_volume':     // &level=1..100   
                        case 'volume_down':    // &step=1..10   
                        case 'volume_up':      // &step=1..10
                        case 'set_play_state': // &state=play|pause|stop
                        case 'set_play_mode':  // &repeat=on_all|on_one|off  shuffle=on|off
                            break;
                 
                        // browse            
                        case 'play_preset':    // heos://browse/play_preset?pid=player_id&preset=preset_position
                            cmd_group = 'browse';
                            break;
                        case 'play_stream':    // heos://browse/play_stream?pid=player_id&url=url_path
                            cmd_group = 'browse';
                            break;
                            
                    }        
                    return 'heos://'+cmd_group+'/'+cmd+'?pid=' + this.pid + param;
                }
                 
                /** Nachricht (command) an player senden
                    es sind auch mehrere commands, getrennt mit | erlaubt
                    bsp: set_volume&level=20|play_preset&preset=1
                 **/
                sendCommand (command) {
                    this.setState('last_error', '');
                    var cmds = command.split('|');
                    for (var c=0; c<cmds.length; c++) {
                        this.heos.msgs.push( this.commandToMsg(cmds[c]) );
                    }
                    this.heos.sendNextMsg();
                }
                 
                 
                } // end of HeosPlayer
                 
                 
                /* -----
                   Heos
                   ----- */
                // Heos Instanz erzeugen und verbinden   
                var heos = new Heos( );
                heos.connect();
                 
                // wenn das Script beendet wird, dann auch die Heos Instanz beenden
                onStop(function () { 
                    heos.disconnect(); 
                }, 0 );
                
                C Offline
                C Offline
                chrisblu
                schrieb am zuletzt editiert von
                #94

                @withstu großartig, das funktioniert gut. Jetzt gehen auch die Titel und Cover bei DLNA-Wiedergabe bei mir wieder. Vielen Dank fürs teilen.
                Wie genau meinst Du das mit dem Ping Adapter? Fragst Du ab, ob die Box verbunden ist und wenn wieder da, verbindest Du neu?
                Könntest Du bitte das Blockly Skript auch posten, das bei Player-Updates neu startet?
                Vielen Dank,
                Christian

                W 1 Antwort Letzte Antwort
                0
                • W withstu

                  @Uhula erstmal vielen Dank für deine Arbeit. Ich habe einige Bugs in deinem ersten Script gefixt (@jwedenig es werden auch wieder alle Song Infos aktualisiert). Auch wenn Version 2 bereits unterwegs ist, will ich euch meine Änderungen nicht vorenthalten. Unter anderem ist mir aufgefallen, dass das Filtern und Verarbeiten der Pakete vom node-ssdp nicht korrekt funktioniert, wenn eine Hue Bridge im Netzwerk funkt oder Pakete unvollständig sind. Zudem habe ich noch ein connected Flag für jeden Player hinzugefügt. Dieses muss leider extern über ein Script resettet werden z.B. in Kombination mit dem Ping Adapter. Für diesen Fall habe ich mir ein Blockly Script gebaut, welches das connected Flag resettet und bei Player Updates das Heos Script neu startet. Bei Bedarf kann ich das auch noch teilen:

                  /****************************
                   * HEOS Script for ioBroker
                   ****************************
                   *  23.01.2018 Uhula, MIT License, no warranty, use on your own risc
                   * 
                   * Wichtig!
                   ****************************
                   * (a) Im Javascript-Adapter muss in der Instanz-Konfiguration "node-ssdp" mit angegeben werden!
                   *     Alternativ kann die Bibliothek auch komplett über "npm install node-ssdp" installiert
                   *     werden.
                   * (b) Wenn mit Favoriten (Presets) gearbeitet werden soll, müssen die HEOS-Konto Logindaten in
                   *     den beiden Konstanten HEOS_USERNAME (EmailAdr) und HEOS_PASSWORD hier im Script in der 
                   *     Konfiguration angegeben werden (so, wie in der HEOS App)!
                   * (c) Eine Konfiguration von IP-Adressen und Player-IDs ist nicht notwendig!
                   * (d) U.U. kann es notwendig sein das Script nach dem 1.Start zu beenden und nach 30 Sek erneut zu starten, da
                   *     die Neuanlage der ioBroker States etwas Zeit benötigt. Warnungen sind dabei zu ignorieren. ;-)
                   *
                   * 
                   * Funktion
                   ****************************
                   * Das Script stellt zwei JS Klassen zur Steuerung der Denon HEOS Geräte zur Verfügung. Die
                   * class Heos dient dabei dem Erkennen und Steuern der HEOS Geräte. Die class HeosPlayer dient der 
                   * Interpretation der Antworten der Heos Geräte.
                   * 
                   * Das Script muss lediglich gestartet werden, es sucht dann im Netzwerk nach HEOS Playern und
                   * erzeugt in der aktuellen Javascript-Instanz einen Subeintrag für die Favoriten und je einen Subeintrag
                   * für jeden gefundenen Player. Dort wiederum werden State-Variablen zur Aufnahme der Player-Daten angelegt.
                   * 
                   * Das Script erzeugt auch on-Handler um auf Steuerungen über vis und andere Scripte an den State-Variablen
                   * reagieren zu können.
                   * 
                   * Beim Beenden des Scripts werden alle Verbindungen und on-Handler geschlossen.
                   * 
                   * Das Script ermöglicht die HEOS Player zu steuern, es soll aber nicht die HEOS App ersetzen. Dazu fehlen
                   * etliche Funktionen wie Playlisten-Handhabung, Gruppensteuerung usw.
                   * 
                   * 
                   * class Heos
                   ****************************
                   * (a) Erkennen von HEOS Geräten (Playern)
                   *     Das Erkennen der HEOS Geräte findet via UPD unter Nutzung von node-ssdp statt. node-ssdp muss dazu im 
                   *     Javascript-Adapter in der Konfiguration mit angegeben werden!
                   * (b) der Kommunikation über TelNet 
                   *     Es wird genau eine TelNet Verbindung zu einem HEOS Gerät aufgebaut, dieses reicht die Sendungen
                   *     und Antworten zentral weiter
                   * (c) der Ermittlung von Favoriten (Presets) und 
                   *     Für jeden Favorit wird ein ioBroker State heos.presets.n mit entsprechenden Sub-States erzeugt.
                   *     Zur Nutzung der Presets ist ein SignIn notwendig, hierzu müssen HEOS_USERNAME und HEOS_PASSWORD
                   *     gesetzt werden.
                   * (d) dem Instanziieren der class HeosPlayer je HEOS Gerät. 
                   *     Je HEOS Gerät wird ein ioBroker-State mit der IP-Adresse des Players erzeugt, dieser State erhält
                   *     diverse Sub-States
                   * 
                   * State-Variable
                   * --------------
                   * .heos.command(cmd) // write
                   *   Hierüber können der Heos-Klasse Befehle übergeben werden, für cmd gilt:
                   * 
                   *   connect      
                   *       Verbindung zu HEOS aufbauen bzw. erneut aufbauen (praktisch ein reset)
                   *   disconnect   
                   *       Verbindung zu HEOS beenden
                   *   load_presets
                   *       lädt die Favoriten neu
                   * 
                   *   alle anderen cmd-Werte werden "as is" versucht an HEOS zu senden, damit ist z.B. auch das
                   *   Gruppieren von HEOS Speakern möglich.
                   * 
                   *   group/set_group?pid=<pid1>,<pid2>,...
                   *       setzen einer Gruppe, die pids sind die der Player wie sie unter den Objekten
                   *       für jeden Player abgelegt sind. Statt diese direkt zu verwenden, kann man auch
                   *       die ioBroker binding-Funktionalität nutzen und Platzhalter verwenden.
                   *       Bsp: group/set_group?pid={javascript.1.heos.192_168_2_46.pid},{javascript.1.heos.192_168_2_43.pid}
                   * 
                   *   group/set_group?pid=<pid1>  
                   *       hebt die Gruppierung wieder auf
                   *       Bsp: group/set_group?pid={javascript.1.heos.192_168_2_46.pid}
                   * 
                   * 
                   * Favoriten (je Favorit ein Unterordner n=1 bis Anzahl)
                   * 
                   * .heos.presets.<n>.image_url (read)
                   *   Bild des Favoriten 
                   * 
                   * .heos.presets.<n>.name (read)
                   *   Name des Favoriten
                   * 
                   * .heos.presets.<n>.playable (read)
                   *   Spielbar? true/false
                   * 
                   * .heos.presets.<n>.type (read)
                   *   Typ des Favoriten (station, ...)
                   *    
                   *  
                   * class HeosPlayer 
                   ****************************
                   * (a) der Steuerung genau eines HEOS Gerätes. Hierzu wird die zentrale Telnet Verbindung der class Heos genutzt.
                   * (b) dem Erzeugen der ioBroker-States zum Speichern der Geräte-Werte
                   * (b) der Auswertung von Antworten der HEOS Geräte und Zuweisung an die entsprechenden ioBroker-States
                   * 
                   * State-Variable
                   * --------------
                   * .heos.<player_ip>.command (write)
                   *   Hierüber können dem Heos-Player Befehle übergeben werden. Diese werden im Klartext in die State-Variable
                   *   geschrieben. Getrennt durch das | Zeichen können mehrere Befehle hintereinander eingetragen werden.
                   *   Bsp: Setzen der Lautstärke auf 20 und Abspielen des 1.Favoriten
                   *        set_volume&level=20|play_preset&preset=1
                   * 
                   *   set_volume&level=0|1|..|100          : Setzt die gewünschte Lautstärke 
                   *   set_play_state&state=play|pause|stop : Startet und stoppt die Wiedergabe
                   *   set_play_mode&repeat=on_all|on_one|off&shuffle=on|off: Setzt Wiederholung und Zufallsweidergabe
                   *   set_mute&state=on|off                : Stumm schalten oder nicht
                   *   volume_down&step=1..10               : Lautstärke verringern um   
                   *   volume_up&step=1..10                 : Lauststäre erhöhen um
                   *   play_next                            : Nächsten Titel spielen
                   *   play_previous                        : Vorherigen Titel spielen
                   *   play_preset&preset=1|2|..|n          : Favorit Nr n abspielen
                   *   play_stream&url=url_path             : URL-Stream abspielen
                   * 
                   *   Befehle, die intern genutzt werden und nicht aufgerufen werden müssen:
                   *
                   *   get_volume              : Füllt den .heos.<player_ip>.volume State
                   *   get_play_state          : Füllt den .heos.<player_ip>.play_state State
                   *   get_play_mode           : Füllt den .heos.<player_ip>.play_mode State 
                   *   get_now_playing_media   : Füllt die .heos.<player_ip>.now_playing_media_... States
                   *
                   *
                   * .heos.<player_ip>.cur_pos (read)
                   *   lfd. Position der Wiedergabe in [Sek]
                   * 
                   * .heos.<player_ip>.cur_pos_MMSS (read)
                   *   lfd. Position der Wiedergabe im Format MM:SS
                   * 
                   * .heos.<player_ip>.duration (read)
                   *   Länge des aktuellen Titels in [Sek]
                   * 
                   * .heos.<player_ip>.duration_MMSS (read)
                   *   Länge des aktuellen Titels im Format MM:SS
                   * 
                   * .heos.<player_ip>.ip (read)
                   *   IP-Adresse des HEOS Players
                   * 
                   * .heos.<player_ip>.last_error (read)
                   *   Text des letzten Fehlers, der vom Player gesendet wurde. Wird bei jedem neuen Befehl zurückgesetzt. 
                   *   Wenn man bspw. versucht eine Wiedergabe über Amazon o.ä. zu starten und es gibt aber keine Verbindung
                   *   dahin, steht hier der Fehlertext drin
                   * 
                   * .heos.<player_ip>.model (read)
                   *   Modell des HEOS Players
                   * 
                   * .heos.<player_ip>.mute (read write)
                   *   Boolsche Variable, die anzeigt ob der Player gemutet (Stumm geschaltet) wurde. Hierüber kann auch ein 
                   *   mute gesetzt werden
                   * 
                   * .heos.<player_ip>.name (read)
                   *   Name des HEOS Players
                   * 
                   * .heos.<player_ip>.now_playing_media_... (read)
                   *   Eine Gruppe von States, in welchen Infos über den aktuellen Titel gespeichert werden, wird automatisch
                   *   aktualisiert. 
                   * 
                   * .heos.<player_ip>.pid (read)
                   *   Player-ID des HEOS Players
                   * 
                   * .heos.<player_ip>.play_mode_repeat (read write)
                   *   Repeat-Modus der Wiedergabe. Mögliche Werte: on_all|on_one|off  
                   * 
                   * .heos.<player_ip>.play_mode_shuffle (read write)
                   *   Zufallswiedergabe true/false
                   * 
                   * .heos.<player_ip>.play_state (read write)
                   *   Zustand des Players. Mögliche Werte play|pause|stop
                   * 
                   * .heos.<player_ip>.serial (read)
                   *   Seriennummmer des HEOS Players
                   * 
                   * .heos.<player_ip>.volume (read write)
                   *   Lautstärke im Bereich 0 - 100. 
                   * 
                   * 
                   * 
                   * Weiterführende Links
                   ****************************
                   * HEOS CLI Protokoll: http://rn.dmglobal.com/euheos/HEOS_CLI_ProtocolSpecification.pdf
                   * http://forum.iobroker.net/viewtopic.php?f=30&t=5693&p=115554#p115554
                   * 
                   **/
                   
                  /****************************
                   * Konfiguration
                   ****************************/
                   
                  const HEOS_USERNAME = '';
                  const HEOS_PASSWORD = '';
                   
                   
                  /****************************
                   * ab hier nichts mehr ändern ;-) 
                   ****************************/
                   
                  var net = require('net');
                   
                  const stateDISCONNECTED = 0;
                  const stateCONNECTING = 1;
                  const stateCONNECTED = 2;
                   
                  /********************
                   * class Heos
                   ********************/
                  class Heos  {
                   
                  constructor() {
                      this.init();
                   
                      createState( this.statePath+'command', '', {name: 'Kommando für Heos-Script' });
                      createState( this.statePath+'connected', false, {name: 'Verbunden?' });
                      createState( this.statePath+'last_error', '', {name: 'Letzter Fehler' });
                      on({id: this.statePath+'command', change: "any"}, (obj) => {
                              this.executeCommand( obj.state.val );
                          });
                  }
                   
                  logDebug(msg) { console.debug('[Heos] '+msg) };
                  log(msg) { console.log('[Heos] '+msg); }
                  logWarn(msg) { console.warn('[Heos] '+msg); }
                  logError(msg) { console.error('[Heos] '+msg); }
                   
                   
                  init() {
                      this.statePath = 'javascript.'+instance+'.heos.';
                      this.players = [];
                      this.net_client = undefined;
                      this.nodessdp_client = undefined;
                   
                      this.ip='';
                      this.msgs = [];
                      this.lastResponse = '';
                      this.state = stateDISCONNECTED;
                      this.unfinishedResponses = '';
                      this.ssdpSeartTargetName = 'urn:schemas-denon-com:device:ACT-Denon:1';
                  }
                   
                  connect() { 
                      try {
                          this.log('connecting to HEOS ...');
                          setState( this.statePath+"connected", false );
                          const NodeSSDP = require('node-ssdp').Client;
                  	    this.nodessdp_client = new NodeSSDP();
                  	    this.nodessdp_client.explicitSocketBind = true;
                  	    this.nodessdp_client.on('response', (headers, statusCode, rinfo) => this.onNodeSSDPResponse(headers, statusCode, rinfo) );
                  	    this.nodessdp_client.on('error', error => {	client.close(); this.logError(error); });
                          this.nodessdp_client.search(this.ssdpSeartTargetName);
                      } catch(err) { this.logError( 'connect: '+err.message ); } 
                      
                  }
                   
                   
                  /** Alle Player stoppen und die TelNet Verbindung schließen 
                   **/
                  disconnect() {
                      this.log('disconnecting from HEOS ...');
                      unsubscribe('javascript.'+instance+'.heos');
                  
                      var i=0;
                      for (i=0; i<this.players.length; i++) {
                          var playerStatePath = this.players[i].heosPlayer.statePath;
                          setState( playerStatePath+"connected", false );
                      }
                   
                      if (typeof this.net_client!=='undefined') {
                          this.registerChangeEvents( false );
                          
                          this.net_client.destroy();
                          this.net_client.unref();
                      }
                      if (typeof this.nodessdp_client!=='undefined') {
                          this.nodessdp_client.stop();
                      }
                      setState( this.statePath+"connected", false );
                      this.log('disconnected from HEOS');
                  }
                   
                  executeCommand(cmd) {
                      this.log('command: '+cmd);
                      switch (cmd) {
                          case 'load_presets' :
                              this.getMusicSources();
                              break;
                          case 'connect' :
                              this.disconnect();
                              this.init();
                              this.connect();
                              break;
                          case 'disconnect' :
                              this.disconnect();
                              break;
                          default:
                              if (this.state == stateCONNECTED) {
                                  this.msgs.push( 'heos://'+cmd+'\n' );
                                  this.sendNextMsg();
                              }
                      
                      }
                  }
                  
                  sleep(milliseconds) {
                     return new Promise(resolve => setTimeout(resolve, milliseconds));
                  }
                  
                  /** es wurde mindestens ein Player erkannt, nun über dessen IP alle bekannten HEOS Player
                   *  durch senden von "player/get_players" ermitteln
                   */
                  onNodeSSDPResponse(headers, statusCode, rinfo) { try {
                      // rinfo {"address":"192.168.2.225","family":"IPv4","port":53871,"size":430}
                      if (typeof this.net_client=='undefined') {
                          if(headers.ST === this.ssdpSeartTargetName){
                              this.ip = rinfo.address;
                              this.log('connecting to '+this.ip+' ...');
                              this.net_client = net.connect({host:this.ip, port:1255});
                              this.net_client.setKeepAlive(true, 5000);
                      
                              this.state = stateCONNECTING;
                      
                              this.net_client.on('error',(error) => {
                                  this.logError(error);
                                  this.disconnect();
                              }); 
                          
                              this.net_client.on('connect',  () => {
                                  setState( this.statePath+"connected", true );
                                  this.log('connected to HEOS');
                                  this.state = stateCONNECTED;
                                  this.log('connected to '+this.ip);
                                  this.getPlayers();
                                  this.registerChangeEvents( true );
                                  this.signIn();
                                  this.getMusicSources();
                              });
                              
                              // Gegenseite hat die Verbindung geschlossen 
                              this.net_client.on('end',  () => {              
                                  this.logWarn('HEOS closed the connection to '+this.ip);
                                  this.disconnect();
                              });
                      
                              // timeout
                              this.net_client.on('timeout',  () => {              
                                  this.logWarn('Timeout trying connect to '+this.ip);
                                  this.disconnect();
                              });
                          
                              // Datenempfang
                              this.net_client.on('data', (data) => this.onData(data)  );
                          } else {
                              this.log('Getting wrong ssdp entry. Keep trying...');
                          }
                      }
                  } catch(err) { this.logError( 'onNodeSSDPResponse: '+err.message ); } }
                  
                  removeFirstOccurrence(str, searchstr)       {
                  	var index = str.indexOf(searchstr);
                  	if (index === -1) {
                  		return str;
                  	}
                  	return str.slice(0, index) + str.slice(index + searchstr.length);
                  }
                   
                  /** es liegen Antwort(en) vor
                   **/
                  onData(data) {  try {
                      data = data.toString();
                      this.logDebug("received data:" + data);
                      data=data.replace(/[\n\r]/g, '');    // Steuerzeichen "CR" entfernen   
                      // es können auch mehrere Antworten vorhanden sein! {"heos": ... } {"heos": ... }
                      // diese nun in einzelne Antworten zerlegen
                      data = this.unfinishedResponses + data;
                      this.unfinishedResponses = '';
                  
                      data=data.replace(/{\s*"heos"\s*:/g, '|{"heos":');  
                      var responses = data.split('|');
                      for (var r=0; r<responses.length; r++ ) {
                          if(responses[r].trim().length > 0){
                              try {
                                  JSON.parse(responses[r]);
                                  this.parseResponse(responses[r]);
                              } catch(e) {
                                  this.logDebug("invalid json (error: " + e.message + "): " + responses[r]);
                                  this.unfinishedResponses += responses[r];
                              }
                          }
                      }
                      // wenn weitere Msg zum Senden vorhanden sind, die nächste senden
                      if (this.msgs.length>0)
                          this.sendNextMsg();
                  } catch(err) { this.logError( 'onData: '+err.message ); } }
                   
                  /** Antwort(en) verarbeiten. Sich wiederholende Antworten ignorieren
                   **/
                  parseResponse (response) { try {
                      if (response == this.lastResponse || response.indexOf("command under process") > 0 )
                          return
                      this.lastResponse = response;        
                      
                      var jmsg;
                      var i;
                      var jdata = JSON.parse(response);
                      if ( !jdata.hasOwnProperty('heos') || !jdata.heos.hasOwnProperty('command') || !jdata.heos.hasOwnProperty('message')) 
                          return;
                   
                      // msg auswerten
                      try {
                          jmsg = '{"' + decodeURI(jdata.heos.message).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g,'":"').replace(/\s/g,'_') + '"}';
                          jmsg = JSON.parse(jmsg);
                      } catch(err) {
                          jmsg = {};
                      }
                      this.logDebug('parse response: '+response);
                      this.logDebug('parse message: '+JSON.stringify(jmsg));
                   
                      // cmd auswerten
                      var cmd = jdata.heos.command.split('/');
                      var cmd_group = cmd[0];
                      cmd = cmd[1];
                  
                      // result ?
                      var result = 'success';
                      if (jdata.heos.hasOwnProperty('result') ) result = jdata.heos.result;
                      if ( result!='success' ) { 
                          setState(this.statePath+'last_error', cmd + ' | ' + jmsg.text);
                          this.logWarn(jmsg.text);
                      }
                   
                      switch (cmd_group) {
                          case 'player':
                              switch (cmd) {
                                      // {"heos": {"command": "player/get_players", "result": "success", "message": ""}, 
                                      //  "payload": [{"name": "HEOS Bar", "pid": 1262037998, "model": "HEOS Bar", "version": "1.430.160", "ip": "192.168.2.225", "network": "wifi", "lineout": 0, "serial": "ADAG9170202780"}, 
                                      //              {"name": "HEOS 1 rechts", "pid": -1746612370, "model": "HEOS 1", "version": "1.430.160", "ip": "192.168.2.201", "network": "wifi", "lineout": 0, "serial": "AMWG9170934429"}, 
                                      //              {"name": "HEOS 1 links", "pid": 68572158, "model": "HEOS 1", "version": "1.430.160", "ip": "192.168.2.219", "network": "wifi", "lineout": 0, "serial": "AMWG9170934433"}
                                      //             ]}
                                  case 'get_players' :
                                      if ( (jdata.hasOwnProperty('payload')) && (this.players.length===0) ) {
                                          for (i=0; i<jdata.payload.length; i++) {
                                              var player = jdata.payload[i];
                                              this.players.push(player);
                                          }
                                          this.startPlayers();
                                      } else if(jdata.playload.length != this.players.length){
                                          this.log("New Player detected. Restart.")
                                          this.disconnect();
                                      }                    
                                      break;
                              }
                              break;
                   
                          // {"heos": {"command": "browse/get_music_sources", "result": "success", "message": ""}, 
                          //  "payload": [{"name": "Amazon", "image_url": "https://production...png", "type": "music_service", "sid": 13}, 
                          //              {"name": "TuneIn", "image_url": "https://production...png", "type": "music_service", "sid": 3}, 
                          //              {"name": "Local Music", "image_url": "https://production...png", "type": "heos_server", "sid": 1024}, 
                          //              {"name": "Playlists", "image_url": "https://production...png", "type": "heos_service", "sid": 1025}, 
                          //              {"name": "History", "image_url": "https://production...png", "type": "heos_service", "sid": 1026}, 
                          //              {"name": "AUX Input", "image_url": "https://production...png", "type": "heos_service", "sid": 1027}, 
                          //              {"name": "Favorites", "image_url": "https://production...png", "type": "heos_service", "sid": 1028}]}
                          case 'browse':
                              switch (cmd) {
                                  case 'get_music_sources' :
                                      if ( (jdata.hasOwnProperty('payload')) ) {
                                          this.logDebug("Payload:"+jdata.payload.toString());
                                          for (i=0; i<jdata.payload.length; i++) {
                                              var source = jdata.payload[i];
                                              if (source.name=='Favorites') {
                                                  this.browse(source.sid);
                                              }
                                          }
                                      }
                   
                                      break;
                                      
                  	    // {"heos": {"command": "browse/browse", "result": "success", "message": "pid=1262037998&sid=1028&returned=5&count=5"}, 
                  	    //  "payload": [{"container": "no", "mid": "s17492", "type": "station", "playable": "yes", "name": "NDR 2 (Adult Contemporary Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s17492q.png"}, 
                  	    //              {"container": "no", "mid": "s158432", "type": "station", "playable": "yes", "name": "Absolut relax (Easy Listening Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s158432q.png"}, 
                  	    //              {"container": "no", "mid": "catalog/stations/A1W7U8U71CGE50/#chunk", "type": "station", "playable": "yes", "name": "Ed Sheeran", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/EdSheeran._SX200_SY200_.jpg"}, 
                  	    //              {"container": "no", "mid": "catalog/stations/A1O1J39JGVQ9U1/#chunk", "type": "station", "playable": "yes", "name": "Passenger", "image_url": "https://images-na.ssl-images-amazon.com/images/I/71DsYkU4QaL._SY500_CR150,0,488,488_SX200_SY200_.jpg"}, 
                  	    //              {"container": "no", "mid": "catalog/stations/A316JYMKQTS45I/#chunk", "type": "station", "playable": "yes", "name": "Johannes Oerding", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/JohannesOerding._SX200_SY200_.jpg"}], 
                  	    //  "options": [{"browse": [{"id": 20, "name": "Remove from HEOS Favorites"}]}]}                    
                                  case 'browse' :
                                      if ( (jdata.hasOwnProperty('payload')) ) {
                                          for (i=0; i<jdata.payload.length; i++) {
                                              var preset = jdata.payload[i];
                                              createState( this.statePath+'presets.'+(i+1)+'.name', preset.name, {name: 'Favoritenname ' });
                                              createState( this.statePath+'presets.'+(i+1)+'.playable', (preset.playable=='yes'?true:false), {name: 'Favorit ist spielbar' });
                                              createState( this.statePath+'presets.'+(i+1)+'.type', preset.type, {name: 'Favorittyp' });
                                              createState( this.statePath+'presets.'+(i+1)+'.image_url', preset.image_url, {name: 'Favoritbild' });
                                          }
                                      }
                                      break;
                              }     
                              break;
                   
                          case 'system':
                              switch (cmd) {
                                  case 'sign_in' :
                                      this.log('signed in: '+jdata.heos.result);
                                      break;
                              }     
                              break;
                      }
                   
                   
                      // an die zugehörigen Player weiterleiten
                      if ( jmsg.hasOwnProperty('pid') ) {
                          for (i=0; i<this.players.length; i++)
                              if (jmsg.pid==this.players[i].heosPlayer.pid) {
                                  this.players[i].heosPlayer.parseResponse (jdata, jmsg, cmd_group, cmd);
                                  break;
                              }
                      }
                   
                      
                  } catch(err) { this.logError( 'parseResponse: '+err.message+'\n '+response ); } }
                   
                   
                  /** sucht die zur ip passenden player-Insanz
                   **/
                  sendCommandToPlayer(objID, cmd ) {
                     var ip = objID;
                     ip = ip.split('.');
                     var ip_ = ip[3];
                     ip = ip_.replace(/_/g,'.');
                     for (var p=0; p<this.players.length; p++ ) 
                         if (this.players[p].ip == ip ) {
                             this.players[p].heosPlayer.sendCommand( cmd );
                             break;
                         }
                         
                  } 
                   
                  /** Für die gefundenen HEOS Plyer entsprechende class HeosPlayer Instanzen bilden 
                   **/
                  startPlayers() { try {
                      var i=0;
                      for (i=0; i<this.players.length; i++) {
                         this.players[i].heosPlayer = new HeosPlayer( this, this.players[i]  );
                      }
                      // Events setzen
                      for (i=0; i<this.players.length; i++) {
                          var statePath = this.players[i].heosPlayer.statePath;
                          // on-Event für command
                          on({id: statePath+'command', change: "any"}, (obj) => {
                              this.sendCommandToPlayer( obj.id, obj.state.val );
                          });
                   
                          // on-Event für volume (nur wenn ack=false, also über vis)
                          on({id: statePath+'volume', change:'ne', ack:false }, (obj) => {
                              this.sendCommandToPlayer( obj.id, 'set_volume&level='+obj.state.val );
                          });
                          // on-Event für mute (nur wenn ack=false, also über vis)
                          on({id: statePath+'mute', change: 'ne', ack:false}, (obj) => {
                              this.sendCommandToPlayer( obj.id, 'set_mute&state='+ (obj.state.val === true ? 'on' : 'off') );
                          });
                          // on-Event für play_mode_shuffle (nur wenn ack=false, also über vis)
                          on({id: statePath+'play_mode_shuffle', change: 'ne', ack:false}, (obj) => {
                              this.sendCommandToPlayer( obj.id, 'set_play_mode&shuffle='+ (obj.state.val === true ? 'on' : 'off') );
                          });
                          // on-Event für play_state (nur wenn ack=false, also über vis)
                          on({id: statePath+'play_state', change: 'ne', ack:false}, (obj) => {
                              this.sendCommandToPlayer( obj.id, 'set_play_state&state='+ obj.state.val );
                          });
                      }    
                  } catch(err) { this.logError( 'startPlayers: '+err.message ); } }
                   
                   
                   
                  getPlayers() {
                      if (this.state == stateCONNECTED) {
                          this.msgs.push( 'heos://player/get_players\n' );
                          this.sendNextMsg();
                      }
                  }
                   
                  registerChangeEvents( b ) {
                      if (this.state == stateCONNECTED) {
                          if (b) this.msgs.push( 'heos://system/register_for_change_events?enable=on' );
                          else this.msgs.push( 'heos://system/register_for_change_events?enable=off' );
                          this.sendNextMsg();
                      }
                  }
                   
                  signIn() {
                      if (this.state == stateCONNECTED) {
                          // heos://system/sign_in?un=heos_username&pw=heos_password
                          this.msgs.push( 'heos://system/sign_in?un='+HEOS_USERNAME+'&pw='+HEOS_PASSWORD );
                          this.sendNextMsg();
                      }
                  }
                   
                  getMusicSources() {
                      if (this.state == stateCONNECTED) {
                          // heos://browse/get_music_sources
                          this.msgs.push( 'heos://browse/get_music_sources' );
                          this.sendNextMsg();
                      }
                      
                  }
                   
                  browse(sid) {
                      if (this.state == stateCONNECTED) {
                          // heos://browse/browse?sid=source_id
                          this.msgs.push( 'heos://browse/browse?sid='+sid );
                          this.sendNextMsg();
                      }
                      
                  }
                   
                   
                  sendNextMsg () {
                      if (this.msgs.length>0) {
                          var msg = this.msgs.shift();
                          this.sendMsg(msg);
                      }
                  }
                   
                  // Nachricht an player senden
                  sendMsg (msg) {
                      this.net_client.write(msg + "\n");
                      this.logDebug("data sent: "+ msg);
                  }
                   
                   
                  } // end of class Heos
                   
                  /********************
                   * class HeosPlayer
                   ********************/
                   
                  class HeosPlayer  {
                   
                  constructor(heos, player) {
                      this.heos = heos;
                      this.ip = player.ip;
                      this.name = player.name;
                      this.pid = player.pid;
                      this.model = player.model;
                      this.serial = player.serial;
                   
                      this.statePath = 'javascript.'+instance+'.heos.'+this.ip.replace(/\./g,'_')+".";
                   
                      this.log('starting HEOS player for IP '+this.ip);
                   
                      this.createStates();
                   
                      // Initialisierung der States nach 5 Sek
                      setTimeout(() => {
                          this.setState('ip',this.ip);
                          this.setState('name',this.name);
                          this.setState('pid',this.pid);
                          this.setState('model',this.model);
                          this.setState('serial',this.serial);
                          this.sendCommand('get_play_state|get_play_mode|get_now_playing_media|get_volume');
                      }, 5000 );
                      setTimeout(() => {
                          this.setState('connected', true);
                      }, 10000 );
                  }
                   
                  static version () { return "0.2"; }
                   
                  logDebug(msg) { console.debug('[HeosPlayer '+this.ip+'] '+msg); }
                  log(msg) { console.log('[HeosPlayer '+this.ip+'] '+msg); }
                  logWarn(msg) { console.warn('[HeosPlayer '+this.ip+'] '+msg); }
                  logError(msg) { console.error('[HeosPlayer '+this.ip+'] '+msg); }
                   
                   
                  /** Anlage der ioBroker States für den Player
                   **/
                  createStates() {
                      const states = [
                          { id:"connected",                   name:"Verbunden?"},
                          { id:"command",                     name:"Kommando für HEOS Aufrufe"},
                          { id:"ip",                          name:"IP Adresse"},
                          { id:"pid",                         name:"Player-ID "},
                          { id:"name",                        name:"Name des Players"},
                          { id:"model",                       name:"Modell des Players"},
                          { id:"serial",                      name:"Seriennummer des Players"},
                          { id:"last_error",                  name:"Letzter Fehler"},
                          { id:"volume",                      name:"Aktuelle Lautstärke"},
                          { id:"mute",                        name:"Mute aktiviert?"},
                          { id:"play_state",                  name:"Aktueller Wiedergabezustand"},
                          { id:"play_mode_repeat",            name:"Wiedergabewiederholung"},
                          { id:"play_mode_shuffle",           name:"Zufällige Wiedergabe"},
                          { id:"now_playing_media_type",      name:"Aktuelle Wiedergabemedium"},
                          { id:"now_playing_media_song",      name:"Aktuelles Lied"},
                          { id:"now_playing_media_station",   name:"Aktuelle Station"},
                          { id:"now_playing_media_album",     name:"Aktuelle Wiedergabemedium"},
                          { id:"now_playing_media_artist",    name:"Aktueller Artist"},
                          { id:"now_playing_media_image_url", name:"Aktuelles Coverbild"},
                          { id:"now_playing_media_album_id",  name:"Aktuelle Album-ID"},
                          { id:"now_playing_media_mid",       name:"Aktuelle mid"},
                          { id:"now_playing_media_qid",       name:"Aktuelle qid"},
                          { id:"cur_pos",                     name:"lfd. Position"},
                          { id:"duration",                    name:"Dauer"},
                          { id:"cur_pos_MMSS",                name:"lfd. Position MM:SS"},
                          { id:"duration_MMSS",               name:"Dauer MM:SS"}
                      ];
                   
                      var def = "";
                      for (var s=0; s<states.length; s++) {
                          var state = states[s];
                          if (this.hasOwnProperty(state.id)) def=this[state.id]; else def='';
                          createState( this.statePath+state.id, def, {name: state.name });
                      }
                      
                  }
                   
                   
                  /** wandelt einen sek Wert in MM:SS Darstellung um
                   **/
                  toMMSS (s) {
                      var sec_num = parseInt(s, 10); 
                      var minutes = Math.floor(sec_num  / 60);
                      var seconds = sec_num - (minutes * 60);
                      if (seconds < 10) {seconds = "0"+seconds;}
                      return minutes+':'+seconds;
                  }
                   
                  /** setState wrapper
                   **/
                  setState(id,val) {
                     setState(this.statePath+id, val, true);    
                  }
                   
                  /** Auswertung der empfangenen Daten
                   **/
                  parseResponse (jdata, jmsg, cmd_group, cmd) { try {
                      this.logDebug('response type - cmd_group: ' + cmd_group + " - cmd: " + cmd);
                      switch (cmd_group) {
                          case 'event':
                              switch (cmd) {
                                  case 'player_playback_error' :
                                      this.setState('last_error', jmsg.error.replace(/_/g,' '));
                                      this.logError(jmsg.error.replace(/_/g,' '));
                                      break;
                                  case 'player_state_changed' :
                                      this.setState("play_state", jmsg.state);
                                      break;
                                  case 'player_volume_changed' :
                                      this.setState("volume", jmsg.level );
                                      this.setState("mute", (jmsg.mute=='on' ? true : false) );
                                      break;
                                  case 'player_repeat_mode_changed' :
                                      this.setState("play_mode_shuffle", jmsg.shuffle );
                                      break;
                                  case 'player_shuffle_mode_changed' :
                                      this.setState("play_mode_repeat", jmsg.repeat );
                                      break;
                                  case 'player_now_playing_changed' :
                                      this.sendCommand('get_now_playing_media');
                                      break;
                                  case 'player_now_playing_progress' :
                                      this.setState("cur_pos", jmsg.cur_pos / 1000);
                                      this.setState("cur_pos_MMSS", this.toMMSS(jmsg.cur_pos / 1000));
                                      this.setState("duration", jmsg.duration / 1000);
                                      this.setState("duration_MMSS", this.toMMSS(jmsg.duration / 1000));
                                      break;
                              }        
                              break;
                              
                              
                          case 'player':
                              switch (cmd) {
                                  case 'set_volume' :
                                  case 'get_volume' :
                                      if ( getState(this.statePath+"volume").val != jmsg.level)
                                          this.setState("volume", jmsg.level);
                                      break;
                                  case 'set_mute' :
                                  case 'get_mute' :
                                      this.setState("mute", (jmsg.state=='on' ? true : false) );
                                      break;
                                  case 'set_play_state' :
                                  case 'get_play_state' :
                                      this.setState("play_state", jmsg.state);
                                      break;
                                  case 'set_play_mode' :
                                  case 'get_play_mode' :
                                      this.setState("play_mode_repeat", jmsg.repeat);
                                      this.setState("play_mode_shuffle", (jmsg.shuffle=='on'?true:false) );
                                      break;
                                  case 'get_now_playing_media' :
                                      this.setState("now_playing_media_type", jdata.payload.type);
                                      this.setState("now_playing_media_song", jdata.payload.song);
                                                              this.setState("now_playing_media_album", jdata.payload.album);
                                          this.setState("now_playing_media_artist", jdata.payload.artist);
                                          this.setState("now_playing_media_image_url", jdata.payload.image_url);
                                          this.setState("now_playing_media_album_id", jdata.payload.album_id);
                                          this.setState("now_playing_media_mid", jdata.payload.mid);
                                          this.setState("now_playing_media_qid", jdata.payload.qid);
                                      // type == station
                                      if (jdata.payload.type=='station') {
                                          this.setState("now_playing_media_station", jdata.payload.station);
                                      } else {
                                          this.setState("now_playing_media_station", null);
                                      }
                                      break;
                              }
                              break;
                      } // switch
                   
                   
                  } catch(err) { this.logError( 'parseResponse: '+err.message ); } }
                   
                  /** cmd der Form "cmd&param"  werden zur msg heos+cmd+pid+&param aufbereitet
                      cmd der Form "cmd?param"  werden zur msg heos+cmd+?param aufbereitet
                   **/
                  commandToMsg (cmd) {
                      var param = cmd.split('&');
                      cmd = param[0];
                      if ( param.length > 1 ) param='&'+param[1]; else param=''; 
                      var cmd_group = 'player';
                   
                      switch (cmd) {
                          case 'get_play_state':
                          case 'get_play_mode':
                          case 'get_now_playing_media':
                          case 'get_volume':
                          case 'play_next':
                          case 'play_previous':
                          case 'set_mute':       // &state=on|off        
                          case 'set_volume':     // &level=1..100   
                          case 'volume_down':    // &step=1..10   
                          case 'volume_up':      // &step=1..10
                          case 'set_play_state': // &state=play|pause|stop
                          case 'set_play_mode':  // &repeat=on_all|on_one|off  shuffle=on|off
                              break;
                   
                          // browse            
                          case 'play_preset':    // heos://browse/play_preset?pid=player_id&preset=preset_position
                              cmd_group = 'browse';
                              break;
                          case 'play_stream':    // heos://browse/play_stream?pid=player_id&url=url_path
                              cmd_group = 'browse';
                              break;
                              
                      }        
                      return 'heos://'+cmd_group+'/'+cmd+'?pid=' + this.pid + param;
                  }
                   
                  /** Nachricht (command) an player senden
                      es sind auch mehrere commands, getrennt mit | erlaubt
                      bsp: set_volume&level=20|play_preset&preset=1
                   **/
                  sendCommand (command) {
                      this.setState('last_error', '');
                      var cmds = command.split('|');
                      for (var c=0; c<cmds.length; c++) {
                          this.heos.msgs.push( this.commandToMsg(cmds[c]) );
                      }
                      this.heos.sendNextMsg();
                  }
                   
                   
                  } // end of HeosPlayer
                   
                   
                  /* -----
                     Heos
                     ----- */
                  // Heos Instanz erzeugen und verbinden   
                  var heos = new Heos( );
                  heos.connect();
                   
                  // wenn das Script beendet wird, dann auch die Heos Instanz beenden
                  onStop(function () { 
                      heos.disconnect(); 
                  }, 0 );
                  
                  UhulaU Offline
                  UhulaU Offline
                  Uhula
                  schrieb am zuletzt editiert von
                  #95

                  @withstu Die verstümmelten JSON Pakete habe ich bei mir auch, ohne HUE Bridge, primär, wenn nicht gar ausschlißlich, beim Übertragen der "browse/browse" (z.B. Presets), also großen Datenmengen. Aber nicht immer, nur zufällig. Deinen JSON-Test werde ich übernehmen. Wo hast du noch Änderungen eingebaut? Dann kann ich das in die v2 übernehmen, welche bei mir schon mit "0_userdata.0" States arbeitet. Danke dir.

                  Uhula - Leise und Weise
                  Ex: ioBroker on Gigabyte NUC Proxmox

                  W 1 Antwort Letzte Antwort
                  0
                  • C chrisblu

                    @withstu großartig, das funktioniert gut. Jetzt gehen auch die Titel und Cover bei DLNA-Wiedergabe bei mir wieder. Vielen Dank fürs teilen.
                    Wie genau meinst Du das mit dem Ping Adapter? Fragst Du ab, ob die Box verbunden ist und wenn wieder da, verbindest Du neu?
                    Könntest Du bitte das Blockly Skript auch posten, das bei Player-Updates neu startet?
                    Vielen Dank,
                    Christian

                    W Offline
                    W Offline
                    withstu
                    schrieb am zuletzt editiert von
                    #96

                    @chrisblu Ich habe für jeden Lautsprecher einen Ping Adapter angelegt. Sobald sich da was ändert und in den HEOS Objekten der Player noch nicht verbunden ist, starte ich das Script neu (bei mir sind die Player nicht dauerhaft verbunden). Übrigens ich hatte total vergessen, dass das connected Flag der Player im disconnect wieder auf false gesetzt wird.
                    Mein Blockly Heos Script Starter sieht so aus (kann bestimmt auch eleganter gelöst werden, ich nutze jedoch iobroker erst seit kurzem):

                    var DelayedUnlock;
                    
                    
                    schedule("*/15 * * * * *", function () {
                      if (getState("0_userdata.0.scriptData.HEOSScriptStarterRunning").val == true && ((new Date().getTime()) - getState("0_userdata.0.scriptData.HEOSScriptStarterRunning").lc) / 1000 > 300) {
                        setState("0_userdata.0.scriptData.HEOSScriptStarterRunning"/*HEOSScriptStarterRunning*/, false);
                      }
                      if (getState("0_userdata.0.scriptData.HEOSScriptStarterRunning").val == false) {
                        setState("0_userdata.0.scriptData.HEOSScriptStarterRunning"/*HEOSScriptStarterRunning*/, true);
                        DelayedUnlock = false;
                        if (getState("javascript.0.scriptEnabled.common.HEOS").val == true && getState("javascript.0.heos.connected").val == false) {
                          console.log('HEOS not connected. Shutdown.');
                          setState("javascript.0.scriptEnabled.common.HEOS"/*scriptEnabled.common.HEOS*/, false);
                        } else if (getState("javascript.0.scriptEnabled.common.HEOS").val == true && (getState("javascript.0.heos.192_168_178_43.connected").val != getState("ping.0.server.192_168_178_43").val || getState("javascript.0.heos.192_168_178_44.connected").val != getState("ping.0.server.192_168_178_44").val || getState("javascript.0.heos.192_168_178_45.connected").val != getState("ping.0.server.192_168_178_45").val || getState("javascript.0.heos.192_168_178_46.connected").val != getState("ping.0.server.192_168_178_46").val)) {
                          console.log('HEOS Player updated. Shutdown.');
                          setState("javascript.0.scriptEnabled.common.HEOS"/*scriptEnabled.common.HEOS*/, false);
                        } else {
                          if (getState("ping.0.server.192_168_178_45").val == false && getState("ping.0.server.192_168_178_44").val == false && getState("ping.0.server.192_168_178_43").val == false && getState("ping.0.server.192_168_178_46").val == false) {
                            if (getState("javascript.0.scriptEnabled.common.HEOS").val == true) {
                              console.log('No Players. Shutdown.');
                              setState("javascript.0.scriptEnabled.common.HEOS"/*scriptEnabled.common.HEOS*/, false);
                              DelayedUnlock = true;
                            }
                          } else {
                            if (getState("javascript.0.scriptEnabled.common.HEOS").val == false) {
                              console.log('Players available. Startup.');
                              setState("javascript.0.scriptEnabled.common.HEOS"/*scriptEnabled.common.HEOS*/, true);
                              DelayedUnlock = true;
                            }
                          }
                        }
                        if (DelayedUnlock == true) {
                          setStateDelayed("0_userdata.0.scriptData.HEOSScriptStarterRunning"/*HEOSScriptStarterRunning*/, false, 60000, false);
                        } else {
                          setState("0_userdata.0.scriptData.HEOSScriptStarterRunning"/*HEOSScriptStarterRunning*/, false);
                        }
                      }
                    });
                    

                    Zu jedem Player gibt es dann noch ein Script, welches im Notfall das connected Flag wieder auf false setzt (und automatisch die Musik und Lautstärker steuert :-) ) :

                    var DelayedUnlock, Volume, Preset;
                    
                    
                    on({id: new RegExp('javascript\\.0\\.heos\\.192_168_178_45\\.mute' + "|" + 'javascript\\.0\\.heos\\.192_168_178_45\\.connected' + "|" + 'ping\\.0\\.server\\.192_168_178_45' + "|" + 'javascript\\.0\\.heos\\.connected'), change: "ne"}, function (obj) {
                        if (getState("0_userdata.0.scriptData.BadezimmerAutoPlayRunning").val == false) {
                        setState("0_userdata.0.scriptData.BadezimmerAutoPlayRunning"/*scriptData.BadezimmerAutoPlayRunning*/, true);
                        DelayedUnlock = false;
                        if (getState("javascript.0.heos.connected").val == false && getState("javascript.0.heos.192_168_178_45.connected").val == true && getState("ping.0.server.192_168_178_45").val == false) {
                          setState("javascript.0.heos.192_168_178_45.connected"/*Verbunden?*/, false);
                        }
                        if (getState("javascript.0.heos.192_168_178_45.connected").val == true && getState("javascript.0.heos.192_168_178_45.mute").val == false && (getState("javascript.0.heos.192_168_178_45.play_state").val == 'stop' || getState("javascript.0.heos.192_168_178_45.play_state").val == 'pause')) {
                          console.log('Starte Musik im Badezimmer.');
                          if ((new Date().getHours()) >= 22 || (new Date().getHours()) <= 7) {
                            Volume = getState("0_userdata.0.scriptData.NachtVolume").val;
                          } else {
                            Volume = getState("0_userdata.0.scriptData.TagVolume").val;
                          }
                          setState("javascript.0.heos.192_168_178_45.volume"/*Aktuelle Lautstärke*/, Volume);
                          if (getState("javascript.0.heos.192_168_178_45.now_playing_media_album_id").val.indexOf('ios-ipod-library') + 1 > 0 || getState("javascript.0.heos.192_168_178_45.last_error").val.length > 0) {
                            if ((new Date().getDay() === 0 ? 7 : new Date().getDay()) == 0) {
                              Preset = getState("0_userdata.0.scriptData.SonntagPreset").val;
                            } else {
                              Preset = getState("0_userdata.0.scriptData.DefaultPreset").val;
                            }
                            setState("javascript.0.heos.192_168_178_45.command"/*Kommando für HEOS Aufrufe*/, ('play_preset&preset=' + String(Preset)));
                          } else {
                            setState("javascript.0.heos.192_168_178_45.play_state"/*Aktueller Wiedergabezustand*/, 'play');
                          }
                          DelayedUnlock = true;
                        }
                        if (DelayedUnlock == true) {
                          setStateDelayed("0_userdata.0.scriptData.BadezimmerAutoPlayRunning"/*scriptData.BadezimmerAutoPlayRunning*/, false, 60000, false);
                        } else {
                          setState("0_userdata.0.scriptData.BadezimmerAutoPlayRunning"/*scriptData.BadezimmerAutoPlayRunning*/, false);
                        }
                      }
                    });
                    
                    schedule("* * * * *", function () {
                      if (getState("javascript.0.heos.192_168_178_45.connected").val == true) {
                        if ((new Date().getHours()) >= 22 || (new Date().getHours()) <= 7) {
                          if (getState("0_userdata.0.scriptData.BadezimmerAutoVolume").val != 'Nacht') {
                            Volume = getState("0_userdata.0.scriptData.NachtVolume").val;
                            if (getState("javascript.0.heos.192_168_178_45.volume").val != Volume) {
                              setState("javascript.0.heos.192_168_178_45.volume"/*Aktuelle Lautstärke*/, Volume);
                            }
                            setState("0_userdata.0.scriptData.BadezimmerAutoVolume"/*BadezimmerAutoVolume*/, 'Nacht');
                          }
                        } else {
                          if (getState("0_userdata.0.scriptData.BadezimmerAutoVolume").val != 'Tag') {
                            Volume = getState("0_userdata.0.scriptData.TagVolume").val;
                            if (getState("javascript.0.heos.192_168_178_45.volume").val != Volume) {
                              setState("javascript.0.heos.192_168_178_45.volume"/*Aktuelle Lautstärke*/, Volume);
                            }
                            setState("0_userdata.0.scriptData.BadezimmerAutoVolume"/*BadezimmerAutoVolume*/, 'Tag');
                          }
                        }
                      }
                    });
                    
                    C 1 Antwort Letzte Antwort
                    0
                    • UhulaU Uhula

                      @withstu Die verstümmelten JSON Pakete habe ich bei mir auch, ohne HUE Bridge, primär, wenn nicht gar ausschlißlich, beim Übertragen der "browse/browse" (z.B. Presets), also großen Datenmengen. Aber nicht immer, nur zufällig. Deinen JSON-Test werde ich übernehmen. Wo hast du noch Änderungen eingebaut? Dann kann ich das in die v2 übernehmen, welche bei mir schon mit "0_userdata.0" States arbeitet. Danke dir.

                      W Offline
                      W Offline
                      withstu
                      schrieb am zuletzt editiert von
                      #97

                      @Uhula Das mit den verstümmelten Pakten war bei mir auch zufällig und auch immer bei den Presets. Habe folgendes im Script geändert:

                      • Player connected Flag hinzugefügt (connect und disconnect)
                      • this.nodessdp_client.stop() anstatt destroy
                      • onNodeSSDPResponse Funktion: Filter von Nicht-HEOS Packeten hinzugefügt.
                      • onDate Funktion: JSON parse check hinzugefügt
                      • parseResponse Funktion: Filter von "command under process" Paketen hinzugefügt
                      • Switch get_players: Wenn neue Player erkannt wurden, wird ein disconnect durchgeführt (eleganter wäre natürlich die neuen Player ganze ohne disconnect bzw. restart des Scripts aufzunehmen bzw. zu entfernen)
                      • Das Event player_mute_changed gibt es nicht mehr. Habe das Aktualisieren des Mute States in player_volume_changed aufgenommen
                      • get_now_playing_media aktualisiert jetzt wieder alle Song States
                      • Einige Debug Messages...

                      Btw. die Funktionen sleep und removeFirstOccurrence im Script werden nicht mehr verwendet und können entfernt werden.

                      UhulaU 1 Antwort Letzte Antwort
                      0
                      • W withstu

                        @Uhula Das mit den verstümmelten Pakten war bei mir auch zufällig und auch immer bei den Presets. Habe folgendes im Script geändert:

                        • Player connected Flag hinzugefügt (connect und disconnect)
                        • this.nodessdp_client.stop() anstatt destroy
                        • onNodeSSDPResponse Funktion: Filter von Nicht-HEOS Packeten hinzugefügt.
                        • onDate Funktion: JSON parse check hinzugefügt
                        • parseResponse Funktion: Filter von "command under process" Paketen hinzugefügt
                        • Switch get_players: Wenn neue Player erkannt wurden, wird ein disconnect durchgeführt (eleganter wäre natürlich die neuen Player ganze ohne disconnect bzw. restart des Scripts aufzunehmen bzw. zu entfernen)
                        • Das Event player_mute_changed gibt es nicht mehr. Habe das Aktualisieren des Mute States in player_volume_changed aufgenommen
                        • get_now_playing_media aktualisiert jetzt wieder alle Song States
                        • Einige Debug Messages...

                        Btw. die Funktionen sleep und removeFirstOccurrence im Script werden nicht mehr verwendet und können entfernt werden.

                        UhulaU Offline
                        UhulaU Offline
                        Uhula
                        schrieb am zuletzt editiert von Uhula
                        #98

                        @withstu Vielen Dank withstu. Ich habe die meisten Dinge davon übernommen, andere anders implementiert. Wenn du Interesse hast, kannst du die neuste Version testen. Hier die Liste der Änderungen:

                        v2.0 13.03.2020
                        • das Script überprüft nun, ob alle notwendigen states für Heos und HeosPlayer im ioBroker korrekt erzeugt wurden und gibt eine Warnung mit Script-Neustartaufforderung aus, wenn die states nicht vorhanden sind. Dieses ist notwendig, da das Anlegen von states asynchron erfolgt und einige ms dauern kann
                        • (WICHTIG!) die states werden nicht mehr in der aktuellen Javascript-Instanz und auch nicht mehr unter der IP des Heos-Players gespeichert, sondern Javascript-Instanzunabhängig unter 0_userdata.0 und seiner Player-ID (pid), da diese sich nicht ändert. Die IP hingegen u.U. schon. Also statt "javascript.X.heos.192_168_2_43" nun als "0_userdata.0.heos.12345678". Wer bereits auf die alten Werte im VIS zugreift, muss dieses im VIS anpassen!
                        • es wurden Befehle/states für die Gruppensteuerung der Heos-Player hinzugefügt. Beim Start werden die Gruppen ausgelesen und in den Player-states des Gruppenleiters (der erste genannte Player) gespeichert. Über dessen states kann die Gruppe gesteuert werden:
                          • group_leader : Ist Gruppenleiter
                          • group_member : Ist Gruppenmitglied
                          • group_pid : Player pids in der Gruppe
                          • group_volume : Lautstärke wenn Gruppen-Leiter
                          • group_name : Name der Gruppe
                          • group_mute : Gruppe gemutet?
                        • alle 60 Sek wird überprüft, ob noch alle HEOS-Player erreichbar sind bzw. neue/reconnectete hinzugekommen sind. Neue/reconnectete werden der Liste der HEOS-Player hinzugefügt, fehlende werden gestoppt. Da hierzu das HEOS-Netzwerk gefragt wird (get_players Meldung), kann es bis zu 5 Min dauern bis Änderungen erkannt werden. Der neue HEOS-Player-State "connected" wird entsprechend gesetzt
                        • jeder HEOS-Player hat einen neuen state "connected", dieser wird nach erfolgreichem Start auf true gesetzt, bei korrektem Beenden des Scripts auf false
                        • diverse Filter/Korrekturen aus dem Forum übernommen, vielen dank an withstu

                        Die Script-Datei: heos_new.js

                        Eine Beispiel-view für die Gruppen: heos_new_groupview.json (nutzt das MDCSS v2, aber an den Widgets kann man auch so das Prinzip erkennen)

                        Uhula - Leise und Weise
                        Ex: ioBroker on Gigabyte NUC Proxmox

                        W 1 Antwort Letzte Antwort
                        0
                        • UhulaU Uhula

                          @withstu Vielen Dank withstu. Ich habe die meisten Dinge davon übernommen, andere anders implementiert. Wenn du Interesse hast, kannst du die neuste Version testen. Hier die Liste der Änderungen:

                          v2.0 13.03.2020
                          • das Script überprüft nun, ob alle notwendigen states für Heos und HeosPlayer im ioBroker korrekt erzeugt wurden und gibt eine Warnung mit Script-Neustartaufforderung aus, wenn die states nicht vorhanden sind. Dieses ist notwendig, da das Anlegen von states asynchron erfolgt und einige ms dauern kann
                          • (WICHTIG!) die states werden nicht mehr in der aktuellen Javascript-Instanz und auch nicht mehr unter der IP des Heos-Players gespeichert, sondern Javascript-Instanzunabhängig unter 0_userdata.0 und seiner Player-ID (pid), da diese sich nicht ändert. Die IP hingegen u.U. schon. Also statt "javascript.X.heos.192_168_2_43" nun als "0_userdata.0.heos.12345678". Wer bereits auf die alten Werte im VIS zugreift, muss dieses im VIS anpassen!
                          • es wurden Befehle/states für die Gruppensteuerung der Heos-Player hinzugefügt. Beim Start werden die Gruppen ausgelesen und in den Player-states des Gruppenleiters (der erste genannte Player) gespeichert. Über dessen states kann die Gruppe gesteuert werden:
                            • group_leader : Ist Gruppenleiter
                            • group_member : Ist Gruppenmitglied
                            • group_pid : Player pids in der Gruppe
                            • group_volume : Lautstärke wenn Gruppen-Leiter
                            • group_name : Name der Gruppe
                            • group_mute : Gruppe gemutet?
                          • alle 60 Sek wird überprüft, ob noch alle HEOS-Player erreichbar sind bzw. neue/reconnectete hinzugekommen sind. Neue/reconnectete werden der Liste der HEOS-Player hinzugefügt, fehlende werden gestoppt. Da hierzu das HEOS-Netzwerk gefragt wird (get_players Meldung), kann es bis zu 5 Min dauern bis Änderungen erkannt werden. Der neue HEOS-Player-State "connected" wird entsprechend gesetzt
                          • jeder HEOS-Player hat einen neuen state "connected", dieser wird nach erfolgreichem Start auf true gesetzt, bei korrektem Beenden des Scripts auf false
                          • diverse Filter/Korrekturen aus dem Forum übernommen, vielen dank an withstu

                          Die Script-Datei: heos_new.js

                          Eine Beispiel-view für die Gruppen: heos_new_groupview.json (nutzt das MDCSS v2, aber an den Widgets kann man auch so das Prinzip erkennen)

                          W Offline
                          W Offline
                          withstu
                          schrieb am zuletzt editiert von
                          #99

                          @Uhula Danke für die neue Version. Ich habe an der V2 noch 3 kleine Änderungen vorgenommen.

                          • Im get_now_playing_media fehlt die album_id
                          if (jdata.payload.hasOwnProperty('album'))
                             this.setState("now_playing_media_album", jdata.payload.album);
                          if (jdata.payload.hasOwnProperty('album_id'))
                             this.setState("now_playing_media_album_id", jdata.payload.album_id);
                          if (jdata.payload.hasOwnProperty('artist'))
                             this.setState("now_playing_media_artist", jdata.payload.artist);
                          
                          • Die unfinishedResponses habe ich wieder hinzugefügt (siehe meine Änderungen), sonst werden bei mir die Presets nicht geholt, da nicht vollständige Pakete verworfen werden.
                          init() {
                              this.statePath = '0_userdata.0.heos.';
                              this.players = [];
                              this.getPlayersInterval = undefined;
                              this.net_client = undefined;
                              this.nodessdp_client = undefined;
                          
                              this.ip='';
                              this.msgs = [];
                              this.lastResponse = '';
                              this.state = stateDISCONNECTED;
                              this.unfinishedResponses = '';
                              this.ssdpSearchTargetName = 'urn:schemas-denon-com:device:ACT-Denon:1';
                          }
                          [...]
                          onData(data) {  try {
                              data = data.toString();
                              data=data.replace(/[\n\r]/g, '');    // Steuerzeichen "CR" entfernen   
                              // es können auch mehrere Antworten vorhanden sein! {"heos": ... } {"heos": ... }
                              // diese nun in einzelne Antworten zerlegen
                              data = this.unfinishedResponses + data;
                              this.unfinishedResponses = '';
                              
                              data=data.replace(/{"heos":/g, '|{"heos":');    
                              var responses = data.split('|');
                              responses.shift();
                              for (var r=0; r<responses.length; r++ ) if(responses[r].trim().length > 0) {
                                  try {
                                      JSON.parse(responses[r]); // check ob korrektes JSON Array
                                      this.parseResponse(responses[r]);
                                  } catch(e) {
                                      this.logDebug('onData: invalid json (error: ' + e.message + '): ' + responses[r]);
                                      this.unfinishedResponses += responses[r];
                                  }
                              }
                              // wenn weitere Msg zum Senden vorhanden sind, die nächste senden
                              if (this.msgs.length>0)
                                  this.sendNextMsg();
                          } catch(err) { this.logError( 'onData: '+err.message ); } }
                          
                          • Das leere setInterval in startPlayer() habe ich entfernt:
                              this.setInterval(() => {
                                  
                              }, 10000);
                          

                          Bisher läuft das Skript ganz gut. Läuft allerdings in einen Fehler, wenn alle Player ausgestellt wurden (war beim alten Script auch schon so): Error [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed

                          1 Antwort Letzte Antwort
                          0
                          • W Offline
                            W Offline
                            withstu
                            schrieb am zuletzt editiert von
                            #100

                            @Uhula Irgendwie funktioniert der keep alive Mechanismus vom net socket nicht korrekt. Auch wenn ich das Device ausgeschaltet habe, wurde das Timeout Event nicht geworfen. Habe jetzt den HEOS Heartbeat zusätzlich implementiert. Sollte der Master mal ausgeschaltet werden und der Heartbeat Timeout 60 Sekunden keine Reaktion von dem Device bekommen, wird nun ein reconnect bzw. eine Suche nach einem neuen Master durchgeführt. Zusätzlich wird die ssdp Suche jetzt so oft wiederholt, bis ein Device gefunden wurde.

                            /****************************
                            ### HEOS Script for ioBroker
                            
                            #### Funktion
                            
                            Das Script stellt zwei JS Klassen zur Steuerung der Denon HEOS Geräte zur Verfügung. Die class Heos dient dabei dem Erkennen und Steuern der HEOS Geräte. Die class HeosPlayer dient der Interpretation der Antworten der Heos Geräte.
                             
                            Das Script muss lediglich gestartet werden, es sucht dann im Netzwerk nach HEOS Playern und erzeugt in der aktuellen Javascript-Instanz einen Subeintrag für die Favoriten und je einen Subeintrag für jeden gefundenen Player. Dort wiederum werden State-Variablen zur Aufnahme der Player-Daten angelegt.
                             
                            Das Script erzeugt auch on-Handler um auf Steuerungen über vis und andere Scripte an den State-Variablen reagieren zu können.
                             
                            Beim Beenden des Scripts werden alle Verbindungen und on-Handler geschlossen.
                            
                            Das Script ermöglicht die HEOS Player zu steuern, es soll aber nicht die HEOS App ersetzen. 
                             
                              
                            #### Wichtig!
                            
                            * Im Javascript-Adapter muss in der Instanz-Konfiguration "node-ssdp" mit angegeben werden! Alternativ kann die Bibliothek auch komplett über "npm install node-ssdp" installiert werden.
                            * Wenn mit Favoriten (Presets) gearbeitet werden soll, müssen die HEOS-Konto Logindaten in den beiden Konstanten HEOS_USERNAME (EmailAdr) und HEOS_PASSWORD hier im Script in der Konfiguration angegeben werden (so, wie in der HEOS App)!
                            * Eine Konfiguration von IP-Adressen und Player-IDs ist nicht notwendig!
                            * U.U. kann es notwendig sein das Script nach dem 1.Start zu beenden und nach 30 Sek erneut zu starten, da die Neuanlage der ioBroker States etwas Zeit benötigt. Warnungen sind dabei zu ignorieren. ;-)
                            
                            #### Updates
                             
                            ##### v2.0 13.03.2020
                            * das Script überprüft nun, ob alle notwendigen states für Heos und HeosPlayer im ioBroker korrekt erzeugt wurden und gibt eine Warnung mit Script-Neustartaufforderung aus, wenn die states nicht vorhanden sind. Dieses ist notwendig, da das Anlegen von states asynchron erfolgt und einige ms dauern kann
                            * (WICHTIG!) die states werden nicht mehr in der aktuellen Javascript-Instanz und auch nicht mehr unter der IP des Heos-Players gespeichert, sondern Javascript-Instanzunabhängig unter 0_userdata.0 und seiner Player-ID (pid), da diese sich nicht ändert. Die IP hingegen u.U. schon. Also statt "javascript.X.heos.192_168_2_43" nun als "0_userdata.0.heos.12345678". Wer bereits auf die alten Werte im VIS zugreift, muss dieses im VIS anpassen!  
                            * es wurden Befehle/states für die Gruppensteuerung der Heos-Player hinzugefügt. Beim Start werden die Gruppen ausgelesen und in den Player-states des Gruppenleiters (der erste genannte Player) gespeichert. Über dessen states kann die Gruppe gesteuert werden: 
                                group_volume: dient der Steuerung der Gruppen-Lautstärke
                                group_mute: dient der Steuerung der Gruppen-Mutes
                                group_leader: ist true, wenn der Player der Gruppenleiter ist
                                group_member: ist true, wenn der Player Mitglied einer Gruppe ist
                            * alle 60 Sek wird überprüft, ob noch alle HEOS-Player erreichbar sind bzw. neue/reconnectete hinzugekommen sind. Neue/reconnectete werden der Liste der HEOS-Player hinzugefügt, fehlende werden gestoppt. Da hierzu das HEOS-Netzwerk gefragt wird (get_players Meldung), kann es bis zu 5 Min dauern bis Änderungen erkannt werden. Der neue HEOS-Player-State "connected" wird entsprechend gesetzt
                            * jeder HEOS-Player hat einen neuen state "connected", dieser wird nach erfolgreichem Start auf true gesetzt, bei korrektem Beenden des Scripts auf false
                            * diverse Filter/Korrekturen aus dem Forum übernommen, vielen dank an withstu
                              
                              
                             #### class Heos
                             
                             Diese Basisklasse dient 
                            
                            * dem Erkennen von HEOS Geräten (Playern). Das Erkennen der HEOS Geräte findet via UPD unter Nutzung von node-ssdp statt. node-ssdp muss dazu im Javascript-Adapter in der Konfiguration mit angegeben werden!
                            * der Kommunikation über TelNet. Es wird genau eine TelNet Verbindung zu einem HEOS Gerät aufgebaut, dieses reicht die Sendungen und Antworten zentral weiter
                            * der Ermittlung von Favoriten (Presets). Für jeden Favorit wird ein ioBroker State heos.presets.n mit entsprechenden Sub-States erzeugt. Zur Nutzung der Presets ist ein SignIn notwendig, hierzu müssen HEOS_USERNAME und HEOS_PASSWORD gesetzt werden.
                            * dem Instanziieren der class HeosPlayer je HEOS Gerät. Je HEOS Gerät wird ein ioBroker-State mit der IP-Adresse des Players erzeugt, dieser State erhält diverse Sub-States
                              
                            ##### State-Variable: Heos
                             
                            Werden angelegt unter "0_userdata.0.heos."
                            
                            * connected: true wenn die Verbindung zu mindestens einem HEOS-Player besteht
                            * script_version: Version des Scripts, dient dazu Script-Updates zu erkennen
                            * last_error: Letzter Fehlertext
                            * command(cmd): Hierüber können der Heos-Klasse Befehle übergeben werden, für cmd gilt:
                                "connect": Verbindung zu HEOS aufbauen bzw. erneut aufbauen (praktisch ein reset)
                                "disconnect": Verbindung zu HEOS beenden
                                "load_presets": lädt die Favoriten neu
                              
                                "group/set_group?pid=<pid1>,<pid2>,...": setzen einer Gruppe, die pids sind die der Player wie sie unter den Objekten für jeden Player abgelegt sind. Bsp: "group/set_group?pid=12345678,12345679". 
                             
                               "group/set_group?pid=<pid1>" : hebt die Gruppierung des Players mit der pid1 wieder auf. Bsp: "group/set_group?pid=12345678"
                               "...": alle anderen cmd-Werte werden "as is" versucht an HEOS zu senden
                             
                              
                            * presets.<n>: Hierunter werden die Favoriten, Presets angelegt. Je Favorit ein Unterordner n=1 bis Anzahl. Darunter befinden sich dann die States, welche den Inhalt der Favoriten aufnehmen:
                             
                            * image_url (read): Bild des Favoriten 
                            * name (read): Name des Favoriten
                            * playable (read): Spielbar? true/false
                            * type (read): Typ des Favoriten (station, ...)
                             
                                
                               
                             #### class HeosPlayer 
                             
                             Diese Klasse dient
                            * der Steuerung genau eines HEOS Gerätes. Hierzu wird die zentrale Telnet Verbindung der class Heos genutzt.
                            * dem Erzeugen der ioBroker-States zum Speichern der Geräte-Werte
                            * der Auswertung von Antworten der HEOS Geräte und Zuweisung an die entsprechenden ioBroker-States
                             
                            ##### State-Variable
                            
                            Werden angelegt unter "0_userdata.0.heos.<pid>"
                            
                            * connected : true, wenn eine Verbindung besteht, sonst false
                            * command(cmd): Hierüber können dem Heos-Player Befehle übergeben werden. Diese werden im Klartext in die State-Variable geschrieben. Getrennt durch das | Zeichen können mehrere hintereinander eingetragen werden. Bsp: Setzen der Lautstärke auf 20 und Abspielen des 1.Favoriten "set_volume&level=20|play_preset&preset=1|set_play_state&state=play". Folgende cmd sind erlaubt:
                            
                              "set_volume&level=0|1|..|100" : Setzt die gewünschte Lautstärke 
                              "set_play_state&state=play|pause|stop" : Startet und stoppt die Wiedergabe
                              "set_play_mode&repeat=on_all|on_one|off&shuffle=on|off": Setzt Wiederholung und Zufallsweidergabe
                              "set_mute&state=on|off" : Stumm schalten oder nicht
                              "volume_down&step=1..10" : Lautstärke verringern um   
                              "volume_up&step=1..10"  : Lauststäre erhöhen um
                              "play_next"  : Nächsten Titel spielen
                              "play_previous" : Vorherigen Titel spielen
                              "play_preset&preset=1|2|..|n" : Favorit Nr n abspielen
                              "play_stream&url=url_path" : URL-Stream abspielen
                             
                            * cur_pos (read) : lfd. Position der Wiedergabe in [Sek]
                            * cur_pos_MMSS (read) : lfd. Position der Wiedergabe im Format MM:SS
                            * duration (read) : Länge des aktuellen Titels in [Sek]
                            * duration_MMSS (read) : Länge des aktuellen Titels im Format MM:SS
                            * ip (read): IP-Adresse des HEOS Players
                            * last_error (read): Text des letzten Fehlers, der vom Player gesendet wurde. Wird bei jedem neuen Befehl zurückgesetzt. Wenn man bspw. versucht eine Wiedergabe über Amazon o.ä. zu starten und es gibt aber keine Verbindung dahin, steht hier der Fehlertext drin
                            * model (read): Modell des HEOS Players
                            * mute (read write): Boolsche Variable, die anzeigt ob der Player gemutet (Stumm geschaltet) wurde. Hierüber kann auch ein mute gesetzt werden
                            * name (read): Name des HEOS Players
                            * now_playing_media_... (read) : Eine Gruppe von States, in welchen Infos über den aktuellen Titel gespeichert werden, wird automatisch aktualisiert. 
                            * pid (read): Player-ID des HEOS Players
                            * play_mode_repeat (read write) : Repeat-Modus der Wiedergabe. Mögliche Werte: on_all|on_one|off  
                            * play_mode_shuffle (read write) : Zufallswiedergabe true/false
                            * play_state (read write): Zustand des Players. Mögliche Werte play|pause|stop
                            * serial (read): Seriennummmer des HEOS Players
                            * volume (read write): Lautstärke im Bereich 0 - 100. 
                            * group_leader : Ist Gruppenleiter
                            * group_member : Ist Gruppenmitglied
                            * group_pid : Player pids in der Gruppe
                            * group_volume : Lautstärke wenn Gruppen-Leiter
                            * group_name : Name der Gruppe
                            * group_mute : Gruppe gemutet?
                            
                             
                            #### Weiterführende Links
                            
                            * HEOS CLI Protokoll: http://rn.dmglobal.com/euheos/HEOS_CLI_ProtocolSpecification.pdf
                            * http://forum.iobroker.net/viewtopic.php?f=30&t=5693&p=115554#p115554
                             
                             
                            (c) Uhula, MIT License, no warranty, use on your own risc
                             
                            */
                            
                            /****************************
                             * Konfiguration
                             ****************************/
                             
                            const HEOS_USERNAME = '';
                            const HEOS_PASSWORD = '';
                            
                            
                            /****************************
                             * ab hier nichts mehr ändern ;-) 
                             ****************************/
                            
                            var net = require('net');
                            
                            const stateDISCONNECTED = 0;
                            const stateSEARCHING = 1;
                            const stateCONNECTING = 2;
                            const stateCONNECTED = 3;
                            const SCRIPTVERSION = '2.0/2020-03-13';
                            
                            /********************
                             * class Heos
                             ********************/
                            class Heos  {
                            
                            logDebug(msg) { console.debug('[Heos] '+msg); }
                            log(msg) { console.log('[Heos] '+msg); }
                            logWarn(msg) { console.warn('[Heos] '+msg); }
                            logError(msg) { console.error('[Heos] '+msg); }
                            
                            constructor() {
                                this.init();
                                this.states = [
                                    { id:"command",        common:{name:"Kommando für HEOS Aufrufe"}                   },
                                    { id:"connected",      common:{name:"Verbunden?",                     write:false, def:false } },
                                    { id:"last_error",     common:{name:"Letzte Fehler",                  write:false} },
                                    { id:"script_version", common:{name:"Installierte Script-Version", write:false } }
                                ];
                                // beim 1.Start nur die States erzeugen
                                if ( !this.existState("script_version") || (this.getState('script_version').val!=SCRIPTVERSION) ) {
                                    this.installed = false;
                                    this.logWarn('creating HEOS states for version '+SCRIPTVERSION+', please start script again');
                                    // Anlage der States 
                                    for (var s=0; s<this.states.length; s++) {
                                        this.setState( this.states[s].id );
                                    }
                                    this.setState('script_version', SCRIPTVERSION);
                                }
                                else {   
                                    this.installed = true;
                                    on({id: this.statePath+'command', change: "any"}, (obj) => {
                                            this.executeCommand( obj.state.val );
                                        });
                                }
                            }
                            
                            
                            
                            // Initialisierung
                            init() {
                                this.statePath = '0_userdata.0.heos.';
                                this.players = [];
                                this.heartbeatInterval = undefined;
                                this.heartbeatTimeout = undefined;
                                this.ssdpSearchInterval = undefined;
                                this.getPlayersInterval = undefined;
                                this.net_client = undefined;
                                this.nodessdp_client = undefined;
                            
                                this.ip='';
                                this.msgs = [];
                                this.lastResponse = '';
                                this.state = stateDISCONNECTED;
                                this.unfinishedResponses = '';
                                this.ssdpSearchTargetName = 'urn:schemas-denon-com:device:ACT-Denon:1';
                            }
                            
                            
                            // über den $-Operator nachsehen, ob der state bereits vorhanden ist
                            // getState().notExists geht auch, erzeugt aber Warnmeldungen!
                            existState(id) {
                                return ( $(this.statePath+id).length==0?false:true);
                            }
                            
                            // wrapper
                            getState(id) {
                                return getState(this.statePath + id);
                            }
                            
                            // wie setState(), setzt aber den statePath davor und überpüft aber ob der state vorhanden ist und erzeugt ihn,
                            // wenn er noch nicht da ist
                            setState(id,value) {
                                if ( !this.existState(id) ) this.createState(id, value, undefined);
                                else setState( this.statePath + id, value);
                            }
                            
                            // wie createState, setzt aber noch den statePath davor und schaut im states-Array nach, ob dort common-Angaben
                            // vorhanden sind (wenn der common-Parameter leer ist)
                            createState(id, value, common) {
                                if ( !this.existState(id) ) {
                                    if (common===undefined) {
                                        // id im states-Array suchen
                                        for (var i=0; i<this.states.length; i++) { 
                                            if (this.states[i].id==id) {
                                                if (this.states[i].hasOwnProperty('common'))
                                                    common = this.states[i].common;
                                               break;
                                            }   
                                        }
                                    }
                                    if ( (typeof value === 'undefined') && (common.hasOwnProperty('def'))) value = common.def;
                                    // unter "0_userdata.0"
                                    let obj = {};
                                    obj.type = 'state';
                                    obj.native = {};
                                    obj.common = common;
                                    setObject(this.statePath + id, obj, (err) => {
                                            if (err) {
                                                this.log('cant write object for state "' + this.statePath + id + '": ' + err);
                                            } else { 
                                                this.log('state "' + this.statePath + id + '" created');
                                            }
                                    });
                                  
                                    // value zeitversetzt setzen        
                                    setTimeout( setState, 3000, this.statePath + id, value );
                                }
                            }
                            
                            /** Verbindung zum HEOS System herstellen
                             **/
                            connect() { 
                                if ( !this.installed ) return;
                            
                                try {
                                    this.log("searching for HEOS devices ...")
                                    this.setState( "connected", false );
                                    this.state = stateSEARCHING;
                                    const NodeSSDP = require('node-ssdp').Client;
                            	    this.nodessdp_client = new NodeSSDP();
                            	    this.nodessdp_client.explicitSocketBind = true;
                            	    this.nodessdp_client.on('response', (headers, statusCode, rinfo) => this.onNodeSSDPResponse(headers, statusCode, rinfo) );
                                    this.nodessdp_client.on('error', error => {	this.nodessdp_client.close(); this.logError(error); });
                                    this.nodessdp_client.search(this.ssdpSearchTargetName);
                                    this.ssdpSearchInterval = setInterval(() => {
                                        this.log("still searching for HEOS devices ...")
                                        this.nodessdp_client.search(this.ssdpSearchTargetName);
                                    }, 30000);
                                } catch(err) { this.logError( 'connect: '+err.message ); } 
                                
                            }
                            
                            
                            /** Alle Player stoppen und die TelNet Verbindung schließen 
                             **/
                            disconnect() {
                                this.log('disconnecting from HEOS ...');
                                unsubscribe(this.statePath.substr(0,this.statePath.length-1));
                            
                                this.stopHeartbeat();
                                this.stopPlayers();
                            
                                if (typeof this.net_client!=='undefined') {
                                    this.registerChangeEvents( false );
                                    this.net_client.destroy();
                                    this.net_client.unref();
                                }
                                if (typeof this.nodessdp_client!=='undefined') {
                                    this.nodessdp_client.stop();
                                }
                                this.setState( "connected", false );
                                this.log('disconnected from HEOS');
                            }
                            
                            reconnect(){
                                this.log('reconnecting to HEOS ...');
                                this.disconnect();
                                this.init();
                                this.connect();
                            }
                            
                            executeCommand(cmd) {
                            //l('command: '+cmd);
                                switch (cmd) {
                                    case 'load_presets' :
                                        this.getMusicSources();
                                        break;
                                    case 'group/get_groups' :
                                        this.getGroups();
                                        break;
                                    case 'connect' :
                                        this.disconnect();
                                        this.init();
                                        this.connect();
                                        break;
                                    case 'disconnect' :
                                        this.disconnect();
                                        break;
                                    default:
                                        if (this.state == stateCONNECTED) {
                                            this.msgs.push( 'heos://'+cmd+'\n' );
                                            this.sendNextMsg();
                                        }
                                
                                }
                            }
                            /** es wurde mindestens ein Player erkannt, nun über dessen IP alle bekannten HEOS Player
                             *  durch senden von "player/get_players" ermitteln
                             */
                            onNodeSSDPResponse(headers, statusCode, rinfo) { try {
                                // rinfo {"address":"192.168.2.225","family":"IPv4","port":53871,"size":430}
                                if (typeof this.net_client=='undefined') {
                                    if(headers.ST !== this.ssdpSearchTargetName) { // korrektes SSDP
                                        this.logDebug('onNodeSSDPResponse: Getting wrong SSDP entry. Keep trying...');
                                    } else {
                                        if (this.ssdpSearchInterval!==undefined) {
                                            clearInterval(this.ssdpSearchInterval);
                                            this.ssdpSearchInterval = undefined;
                                        }
                            
                                        this.ip = rinfo.address;
                                        this.log('connecting to HEOS ('+this.ip+') ...');
                                        this.net_client = net.connect({host:this.ip, port:1255});
                                        this.net_client.setKeepAlive(true, 5000);
                                        this.net_client.setNoDelay(true);
                                        this.net_client.setTimeout(15000);
                            
                                        this.state = stateCONNECTING;
                            
                                        this.net_client.on('error',(error) => {
                                            this.logError(error);
                                            this.reconnect();
                                        }); 
                                
                                        this.net_client.on('connect',  () => {
                                            this.setState( "connected", true );
                                            this.state = stateCONNECTED;
                                            this.log('connected to HEOS ('+this.ip+')');
                                            this.getPlayers();
                                            this.registerChangeEvents( true );
                                            this.signIn();
                                            this.getMusicSources();
                                            this.getGroups();
                                            this.startHeartbeat();
                                        });
                                    
                                        // Gegenseite hat die Verbindung geschlossen 
                                        this.net_client.on('end',  () => {              
                                            this.logWarn('HEOS closed the connection to '+this.ip);
                                            this.state = stateDISCONNECTED;
                                            this.reconnect();
                                        });
                            
                                        // timeout
                                        this.net_client.on('timeout',  () => {              
                                            this.logWarn('Timeout trying connect to '+this.ip);
                                            this.state = stateDISCONNECTED;
                                            this.reconnect();
                                        });
                            
                                        // Datenempfang
                                        this.net_client.on('data', (data) => this.onData(data)  );
                                    }
                                }
                            } catch(err) { this.logError( 'onNodeSSDPResponse: '+err.message ); } }
                            
                            setLastError(err) {
                                this.logWarn(err);
                                /*
                                var val = getState(this.statePath+'last_error').val;
                                var lines = val.splitt('\n');
                                if ( lines.length > 4)
                                    lines.pop();
                                lines.unshift(err+'\n');
                            l(lines);       
                                this.setState('last_error', lines.toString());
                                */    
                            }
                            
                            
                            /** es liegen Antwort(en) vor
                             * 
                             * {"heos": {"command": "browse/browse", "result": "success", "message": "sid=1028&returned=9&count=9"}, 
                             *    "payload": [
                             *        {"container": "no", "mid": "s25529", "type": "station", "playable": "yes", "name": "NDR 1 Niedersachsen (Adult Hits)", "image_url": "http://cdn-profiles.tunein.com/s25529/images/logoq.png?t=154228"}, 
                             *        {"container": "no", "mid": "s56857", "type": "station", "playable": "yes", "name": "NDR 2 Niedersachsen 96.2 (Top 40 %26 Pop Music)", "image_url": "http://cdn-profiles.tunein.com/s56857/images/logoq.png?t=154228"}, 
                             *        {"container": "no", "mid": "s24885", "type": "station", "playable": "yes", "name": "NDR Info", "image_url": "http://cdn-profiles.tunein.com/s24885/images/logoq.png?t=1"}, {"container": "no", "mid": "s158432", "type": "station", "playable": "yes", "name": "Absolut relax (Easy Listening Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s158432q.png"}, 
                             *        {"container": "no", "mid": "catalog/stations/A316JYMKQTS45I/#chunk", "type": "station", "playable": "yes", "name": "Johannes Oerding", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/JohannesOerding._SX200_SY200_.jpg"}, 
                             *        {"container": "no", "mid": "catalog/stations/A1O1J39JGVQ9U1/#chunk", "type": "station", "playable": "yes", "name": "Passenger", "image_url": "https://images-na.ssl-images-amazon.com/images/I/71DsYkU4QaL._SY500_CR150,0,488,488_SX200_SY200_.jpg"}, 
                             *        {"container": "no", "mid": "catalog/stations/A1W7U8U71CGE50/#chunk"
                             **/
                            onData(data) {  try {
                                data = data.toString();
                                data=data.replace(/[\n\r]/g, '');    // Steuerzeichen "CR" entfernen   
                                // es können auch mehrere Antworten vorhanden sein! {"heos": ... } {"heos": ... }
                                // diese nun in einzelne Antworten zerlegen
                                data = this.unfinishedResponses + data;
                                this.unfinishedResponses = '';
                                
                                data=data.replace(/{"heos":/g, '|{"heos":');    
                                var responses = data.split('|');
                                responses.shift();
                                for (var r=0; r<responses.length; r++ ) if(responses[r].trim().length > 0) {
                                    try {
                                        JSON.parse(responses[r]); // check ob korrektes JSON Array
                                        this.parseResponse(responses[r]);
                                    } catch(e) {
                                        this.logDebug('onData: invalid json (error: ' + e.message + '): ' + responses[r]);
                                        this.unfinishedResponses += responses[r];
                                    }
                                }
                                // wenn weitere Msg zum Senden vorhanden sind, die nächste senden
                                if (this.msgs.length>0)
                                    this.sendNextMsg();
                            } catch(err) { this.logError( 'onData: '+err.message ); } }
                            
                            /** Antwort(en) verarbeiten. Sich wiederholende Antworten ignorieren
                             **/
                            parseResponse (response) { try {
                                if (response == this.lastResponse || response.indexOf("command under process") > 0 )
                                    return
                                this.lastResponse = response;        
                                
                                var jmsg;
                                var i;
                                var jdata = JSON.parse(response);
                                if ( !jdata.hasOwnProperty('heos') || !jdata.heos.hasOwnProperty('command') || !jdata.heos.hasOwnProperty('message') ) 
                                    return;
                            
                                // msg auswerten
                                try {
                                    jmsg = '{"' + decodeURI(jdata.heos.message).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g,'":"').replace(/\s/g,'_') + '"}';
                                    jmsg = JSON.parse(jmsg);
                                } catch(err) {
                                    jmsg = {};
                                }
                            
                                this.logDebug('parseResponse: '+response); 
                            
                                // result ?
                                var result = 'success';
                                if (jdata.heos.hasOwnProperty('result') ) result = jdata.heos.result;
                                if ( result!='success' ) { 
                                    this.setLastError('result='+result+', '+jmsg.text);
                                }
                            
                                // cmd auswerten
                                var cmd = jdata.heos.command.split('/');
                                var cmd_group = cmd[0];
                                cmd = cmd[1];
                                switch (cmd_group) {
                                    case 'system':
                                        switch(cmd) {
                                            case 'heart_beat':
                                                this.resetHeartbeatTimeout();
                                                break;
                                        }
                                        break;
                                    case 'event':
                                        switch (cmd) {
                                            case 'group_volume_changed' :
                                            // "heos": {"command": "event/group_volume_changed ","message": "gid='group_id'&level='vol_level'&mute='on_or_off'"}
                                            this.getGroups();
                                            break;
                                        }
                                        break;
                                    case 'player':
                                        switch (cmd) {
                                                // {"heos": {"command": "player/get_players", "result": "success", "message": ""}, 
                                                //  "payload": [{"name": "HEOS Bar", "pid": 1262037998, "model": "HEOS Bar", "version": "1.430.160", "ip": "192.168.2.225", "network": "wifi", "lineout": 0, "serial": "ADAG9170202780"}, 
                                                //              {"name": "HEOS 1 rechts", "pid": -1746612370, "model": "HEOS 1", "version": "1.430.160", "ip": "192.168.2.201", "network": "wifi", "lineout": 0, "serial": "AMWG9170934429"}, 
                                                //              {"name": "HEOS 1 links", "pid": 68572158, "model": "HEOS 1", "version": "1.430.160", "ip": "192.168.2.219", "network": "wifi", "lineout": 0, "serial": "AMWG9170934433"}
                                                //             ]}
                                            case 'get_players' :
                                                if (jdata.hasOwnProperty('payload')) {
                                                    if (jdata.payload.length != this.players.length) {
                                                        this.stopPlayers();
                                                        this.players = [];
                                                        for (i=0; i<jdata.payload.length; i++) {
                                                            var player = jdata.payload[i];
                                                            this.players.push(player);
                                                        }
                                                        this.startPlayers();
                                                    } 
                                                }
                                                break;
                                        }
                                        break;
                            
                                    // {"heos": {"command": "browse/get_music_sources", "result": "success", "message": ""}, 
                                    //  "payload": [{"name": "Amazon", "image_url": "https://production...png", "type": "music_service", "sid": 13}, 
                                    //              {"name": "TuneIn", "image_url": "https://production...png", "type": "music_service", "sid": 3}, 
                                    //              {"name": "Local Music", "image_url": "https://production...png", "type": "heos_server", "sid": 1024}, 
                                    //              {"name": "Playlists", "image_url": "https://production...png", "type": "heos_service", "sid": 1025}, 
                                    //              {"name": "History", "image_url": "https://production...png", "type": "heos_service", "sid": 1026}, 
                                    //              {"name": "AUX Input", "image_url": "https://production...png", "type": "heos_service", "sid": 1027}, 
                                    //              {"name": "Favorites", "image_url": "https://production...png", "type": "heos_service", "sid": 1028}]}
                                    case 'browse':
                                        switch (cmd) {
                                            case 'get_music_sources' :
                                                if ( (jdata.hasOwnProperty('payload')) ) {
                                                    for (i=0; i<jdata.payload.length; i++) {
                                                        var source = jdata.payload[i];
                                                        if (source.name=='Favorites') {
                                                            this.browse(source.sid);
                                                        }
                                                    }
                                                }
                            
                                                break;
                                                
                            	    // {"heos": {"command": "browse/browse", "result": "success", "message": "pid=1262037998&sid=1028&returned=5&count=5"}, 
                            	    //  "payload": [{"container": "no", "mid": "s17492", "type": "station", "playable": "yes", "name": "NDR 2 (Adult Contemporary Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s17492q.png"}, 
                            	    //              {"container": "no", "mid": "s158432", "type": "station", "playable": "yes", "name": "Absolut relax (Easy Listening Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s158432q.png"}, 
                            	    //              {"container": "no", "mid": "catalog/stations/A1W7U8U71CGE50/#chunk", "type": "station", "playable": "yes", "name": "Ed Sheeran", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/EdSheeran._SX200_SY200_.jpg"}, 
                            	    //              {"container": "no", "mid": "catalog/stations/A1O1J39JGVQ9U1/#chunk", "type": "station", "playable": "yes", "name": "Passenger", "image_url": "https://images-na.ssl-images-amazon.com/images/I/71DsYkU4QaL._SY500_CR150,0,488,488_SX200_SY200_.jpg"}, 
                            	    //              {"container": "no", "mid": "catalog/stations/A316JYMKQTS45I/#chunk", "type": "station", "playable": "yes", "name": "Johannes Oerding", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/JohannesOerding._SX200_SY200_.jpg"}], 
                            	    //  "options": [{"browse": [{"id": 20, "name": "Remove from HEOS Favorites"}]}]}                    
                                            case 'browse' :
                                                if ( (jdata.hasOwnProperty('payload')) ) {
                                                    for (i=0; i<jdata.payload.length; i++) {
                                                        var preset = jdata.payload[i];
                                                        this.createState( 'presets.'+(i+1)+'.name', preset.name, {name: 'Favoritenname ' });
                                                        this.createState( 'presets.'+(i+1)+'.playable', (preset.playable=='yes'?true:false), {name: 'Favorit ist spielbar' });
                                                        this.createState( 'presets.'+(i+1)+'.type', preset.type, {name: 'Favorittyp' });
                                                        this.createState( 'presets.'+(i+1)+'.image_url', preset.image_url, {name: 'Favoritbild' });
                                                    }
                                                }
                                                break;
                                        }     
                                        break;
                            
                                    case 'group':
                            //l('group: '+response);
                                        switch (cmd) {
                                              // { "heos":{"command":"player/set_group","result":"success",
                                              //           "message": "gid='new group_id'&name='group_name'&pid='player_id_1, player_id_2,…,player_id_n'
                                              //          } 
                                              // }
                                            case 'set_group' :
                                                this.setGroup( jmsg );
                                                break;
                            
                                            // { "heos": {"command":"group/get_volume","result":"success","message": "gid='group_id'&level='vol_level'"}
                                            case 'get_volume':
                                                if ( jmsg.hasOwnProperty('gid') ) {
                                                    if ( jmsg.hasOwnProperty('level') ) {
                                                        this.setState( jmsg.gid+'.group_volume', jmsg.level);
                                                    }
                                                }
                                                break;
                            
                                            // { "heos": {"command":"group/get_mute","result":"success","message": "gid='group_id'&state='on_or_off'"}
                                            case 'get_mute':
                                                if ( jmsg.hasOwnProperty('gid') ) {
                                                    if ( jmsg.hasOwnProperty('state') ) {
                                                        this.setState( jmsg.gid+'.group_mute', (jmsg.state=='on' ? true : false));
                                                    }
                                                }
                                                break;
                            
                            
                                            // { "heos": { "command": "player/get_groups", "result": "success", "message": "" },
                                            //   "payload": [{"name":"'group name 1'", "gid": "group id 1'",
                                            //                "players":[{"name":"player name 1","pid":"'player id1'","role":"player role 1 (leader or member)'"},
                                            //                           {"name":"player name 2","pid":"'player id2'","role":"player role 2 (leader or member)'"} 
                                            //                          ]
                                            //               },
                                            //               {"name":"'group name 2'","gid":"group id 2'",
                                            //                "players":[{"name":"player name ... 
                                            case 'get_groups' :
                                                // bisherige groups leeren
                                                var objs = $(this.statePath+'*.group_name');
                                                for (var o=0; o<objs.length; o++) setState(objs[o], 'no group');
                                                objs = $(this.statePath+'*.group_leader');
                                                for (var o=0; o<objs.length; o++) setState(objs[o], false);
                                                objs = $(this.statePath+'*.group_member');
                                                for (var o=0; o<objs.length; o++) setState(objs[o], false);
                                                objs = $(this.statePath+'*.group_pid');
                                                for (var o=0; o<objs.length; o++) setState(objs[o], '');
                            
                                                // payload mit den groups auswerten
                                                if ( (jdata.hasOwnProperty('payload')) ) {
                                                    for (i=0; i<jdata.payload.length; i++) {
                                                        var group = jdata.payload[i];
                                                        var players = group.players;
                                                        // Player IDs addieren. Hinweis: "leader" ist nicht immer der 1.Playereintrag
                                                        group.pid = "";
                                                        for (var p=0; p<players.length; p++) {
                                                            if ( players[p].role == 'leader' )
                                                                group.pid = players[p].pid + (group.pid.length>0?",":"") + group.pid;
                                                            else
                                                                group.pid = group.pid + (group.pid.length>0?",":"") + players[p].pid;
                                                        }
                                                        this.setGroup(group);
                                                    }
                                                }
                                                break;
                                                
                                        }     
                                        break;
                            
                            
                                    case 'system':
                                        switch (cmd) {
                                            case 'sign_in' :
                                                this.log('signed in: '+jdata.heos.result);
                                                break;
                                        }     
                                        break;
                                }
                            
                            
                                // an die zugehörigen Player weiterleiten
                                if ( jmsg.hasOwnProperty('pid') ) {
                                    for (i=0; i<this.players.length; i++)
                                        if (jmsg.pid==this.players[i].heosPlayer.pid) {
                                            this.players[i].heosPlayer.parseResponse (jdata, jmsg, cmd_group, cmd);
                                            break;
                                        }
                                }
                            
                                
                            } catch(err) { this.logError( 'parseResponse: '+err.message+'\n '+response ); } }
                            
                            
                            // sucht die zur pid passenden player-Insanz
                            sendCommandToPlayer(objID, cmd ) {
                               // aus der objID die pid holen
                               // objID = javascript.1.heos.394645376.command
                               objID = objID.split('.');
                               var pid = objID[3];
                               for (var p=0; p<this.players.length; p++ ) 
                                   if (this.players[p].pid == pid ) {
                                       this.players[p].heosPlayer.sendCommand( cmd );
                                       break;
                                   }
                            } 
                            
                            // Für die gefundenen HEOS Player entsprechende class HeosPlayer Instanzen bilden 
                            startPlayers() { try {
                               this.stopPlayers();
                                let i=0;
                                for (i=0; i<this.players.length; i++) {
                                   this.players[i].heosPlayer = new HeosPlayer( this, this.players[i]  );
                                }
                            
                                // alle 30 Sekunden auf neue Player untersuchen
                                this.getPlayersInterval = setInterval(() => {
                                    this.getPlayers();
                                }, 30000);
                            
                            /*
                                // Events setzen
                                for (i=0; i<this.players.length; i++) {
                                    let heosPlayer = this.players[i].heosPlayer;
                                    heosPlayer.onHandler = [];
                                    // on-Event für command
                                    heosPlayer.onHandler.push( on({id: heosPlayer.statePath+'command', change: "any"}, (obj) => {
                                        this.sendCommandToPlayer( obj.id, obj.state.val ) 
                                    }));
                            
                                    // on-Event für volume (nur wenn ack=false, also über vis)
                                    heosPlayer.onHandler.push( on({id: heosPlayer.statePath+'volume', change:'ne', ack:false }, (obj) => {
                                        this.sendCommandToPlayer( obj.id, 'set_volume&level='+obj.state.val );
                                    }));
                                    // on-Event für mute (nur wenn ack=false, also über vis)
                                    heosPlayer.onHandler.push( on({id: heosPlayer.statePath+'mute', change: 'ne', ack:false}, (obj) => {
                                        this.sendCommandToPlayer( obj.id, 'set_mute&state='+ (obj.state.val === true ? 'on' : 'off') );
                                    }));
                                    // on-Event für play_mode_shuffle (nur wenn ack=false, also über vis)
                                    heosPlayer.onHandler.push( on({id: heosPlayer.statePath+'play_mode_shuffle', change: 'ne', ack:false}, (obj) => {
                                        this.sendCommandToPlayer( obj.id, 'set_play_mode&shuffle='+ (obj.state.val === true ? 'on' : 'off') );
                                    }));
                                    // on-Event für play_state (nur wenn ack=false, also über vis)
                                    heosPlayer.onHandler.push( on({id: heosPlayer.statePath+'play_state', change: 'ne', ack:false}, (obj) => {
                                        this.sendCommandToPlayer( obj.id, 'set_play_state&state='+ obj.state.val );
                                    }));
                            
                                    // on-Event für group-volume (nur wenn ack=false, also über vis)
                                    heosPlayer.onHandler.push( on({id: heosPlayer.statePath+'group_volume', change:'ne', ack:false }, (obj) => {
                                        // "id":"javascript.1.heos.-1746612370.group_volume"
                                        var id = obj.id.split('.');
                                        this.executeCommand( 'group/set_volume?gid='+id[3]+'&level='+obj.state.val );
                                    }));
                            
                                    // on-Event für group-mute (nur wenn ack=false, also über vis)
                                    heosPlayer.onHandler.push( on({id: heosPlayer.statePath+'group_mute', change:'ne', ack:false }, (obj) => {
                                        // "id":"javascript.1.heos.-1746612370.group_volume"
                                        var id = obj.id.split('.');
                                        this.executeCommand( 'group/set_mute?gid='+id[3]+'&state='+(obj.state.val===true ? 'on':'off') );
                                           }));
                                }    
                            */
                            } catch(err) { this.logError( 'startPlayers: '+err.message ); } }
                            
                            //
                            stopPlayers() { try {
                                if (this.getPlayersInterval!==undefined) {
                                    clearInterval(this.getPlayersInterval);
                                    this.getPlayersInterval = undefined;
                                }
                                for (let i=0; i<this.players.length; i++) {
                                    let heosPlayer = this.players[i].heosPlayer;
                                    if (heosPlayer)
                                        heosPlayer.stopPlayer();
                                    // player leeren
                                    this.players[i].heosPlayer = undefined;
                                }
                            } catch(err) { this.logError( 'stopPlayers: '+err.message ); } }
                            
                            // setzen der Werte einer Group
                            setGroup(group) {
                                if ( group.hasOwnProperty('pid') ) {    
                                    // in den Playern den Groupstatus setzen
                                    var pids = group.pid.split(',');
                                      
                                    for (var i=0; i<pids.length; i++) {
                                        this.setState(pids[i]+'.group_name', (group.hasOwnProperty('name')?group.name:''));
                                        this.setState(pids[i]+'.group_pid', group.pid);
                                        this.setState(pids[i]+'.group_leader', (i==0) && (pids.length>1) );
                                        this.setState(pids[i]+'.group_member', (pids.length>1));
                                    }
                                        
                                    if ( group.hasOwnProperty('gid') ) {   
                                        // volume und mute dazu holen
                                        this.executeCommand("group/get_volume?gid="+group.gid);
                                        this.executeCommand("group/get_mute?gid="+group.gid);
                                    }
                                }
                            
                            }
                            
                            getPlayers() {
                                if (this.state == stateCONNECTED) {
                                    this.msgs.push( 'heos://player/get_players\n' );
                                    this.sendNextMsg();
                                }
                            }
                            
                            registerChangeEvents( b ) {
                                if (this.state == stateCONNECTED) {
                                    if (b) this.msgs.push( 'heos://system/register_for_change_events?enable=on' );
                                    else this.msgs.push( 'heos://system/register_for_change_events?enable=off' );
                                    this.sendNextMsg();
                                }
                            }
                            
                            signIn() {
                                if (this.state == stateCONNECTED) {
                                    // heos://system/sign_in?un=heos_username&pw=heos_password
                                    this.msgs.push( 'heos://system/sign_in?un='+HEOS_USERNAME+'&pw='+HEOS_PASSWORD );
                                    this.sendNextMsg();
                                }
                            }
                            
                            getMusicSources() {
                                if (this.state == stateCONNECTED) {
                                    // heos://browse/get_music_sources
                                    this.msgs.push( 'heos://browse/get_music_sources' );
                                    this.sendNextMsg();
                                }
                                
                            }
                            
                            getGroups() {
                                if (this.state == stateCONNECTED) {
                                    // heos://group/get_groups
                                    this.msgs.push( 'heos://group/get_groups' );
                                    this.sendNextMsg();
                                }
                                
                            }
                            
                            startHeartbeat(){
                                this.logDebug("start heartbeat interval");
                                this.heartbeatInterval = setInterval(() => {
                                    this.logDebug("heartbeat")
                                    this.msgs.push( 'heos://system/heart_beat' );
                                    this.sendNextMsg();
                                    if (this.heartbeatTimeout==undefined) {
                                        this.heartbeatTimeout = setTimeout(() => {
                                            this.log("heartbeat timeout");
                                            this.reconnect();
                                        }, 60000);
                                    }
                                }, 15000);
                            }
                            
                            resetHeartbeatTimeout(){
                                this.logDebug("reset heartbeat timeout");
                                if (this.heartbeatTimeout!==undefined) {
                                    clearTimeout(this.heartbeatTimeout);
                                    this.heartbeatTimeout = undefined;
                                }
                            }
                            
                            stopHeartbeat(){
                                this.logDebug("stop heartbeat interval");
                                if (this.heartbeatInterval!==undefined) {
                                    clearInterval(this.heartbeatInterval);
                                    this.heartbeatInterval = undefined;
                                }
                                this.resetHeartbeatTimeout();
                            }
                            
                            browse(sid) {
                                if (this.state == stateCONNECTED) {
                                    // heos://browse/browse?sid=source_id
                                    this.msgs.push( 'heos://browse/browse?sid='+sid );
                                    this.sendNextMsg();
                                }
                                
                            }
                            
                            
                            sendNextMsg () {
                                if (this.msgs.length>0) {
                                    var msg = this.msgs.shift();
                                    this.sendMsg(msg);
                                }
                            }
                            
                            // Nachricht an player senden
                            sendMsg (msg) {
                                this.net_client.write(msg + "\n");
                                this.logDebug("data sent: "+ msg);
                            }
                            
                            
                            } // end of class Heos
                            
                            /********************
                             * class HeosPlayer
                             ********************/
                            
                            class HeosPlayer  {
                            
                            logDebug(msg) { console.debug('[HeosPlayer '+this.pid+'] '+msg); }
                            log(msg) { console.log('[HeosPlayer '+this.pid+'] '+msg); }
                            logWarn(msg) { console.warn('[HeosPlayer '+this.pid+'] '+msg); }
                            logError(msg) { console.error('[HeosPlayer '+this.pid+'] '+msg); }
                            
                            constructor(heos, player) {
                                this.heos = heos;
                                this.ip = player.ip;
                                this.name = player.name;
                                this.pid = player.pid;
                                this.model = player.model;
                                this.serial = player.serial;
                                this.onHandler = [];
                            
                            //    this.statePath = 'javascript.'+instance+'.heos.'+this.ip.replace(/\./g,'_')+".";
                                this.statePath = '0_userdata.0.heos.'+this.pid+".";
                            
                                this.log('creating HEOS player with pid '+this.pid+' ('+this.ip+')');
                            
                                this.states = [
                                    { id:"connected",                   common:{name:"Verbunden?"} },
                                    { id:"command",                     common:{name:"Befehl an Heos Player senden"} },
                                    { id:"ip",                          common:{name:"IP-Adresse", write:false} },
                                    { id:"pid",                         common:{name:"Player-ID", write:false} },
                                    { id:"name",                        common:{name:"Name des Players", write:false} },
                                    { id:"model",                       common:{name:"Modell des Players", write:false} },
                                    { id:"serial",                      common:{name:"Seriennummer des Players", write:false} },
                                    { id:"last_error",                  common:{name:"Letzte Fehler", write:false} },
                                    { id:"volume",                      common:{name:"Aktuelle Lautstärke"} },
                                    { id:"mute",                        common:{name:"Mute aktiviert?"} },
                                    { id:"play_state",                  common:{name:"Aktueller Wiedergabezustand"} },
                                    { id:"play_mode_repeat",            common:{name:"Wiedergabewiederholung"} },
                                    { id:"play_mode_shuffle",           common:{name:"Zufällige Wiedergabe"} },
                                    { id:"now_playing_media_type",      common:{name:"Aktuelle Wiedergabemedium", write:false} },
                                    { id:"now_playing_media_song",      common:{name:"Aktuelles Lied", write:false} },
                                    { id:"now_playing_media_station",   common:{name:"Aktuelle Station", write:false} },
                                    { id:"now_playing_media_album",     common:{name:"Aktuelle Wiedergabemedium", write:false} },
                                    { id:"now_playing_media_artist",    common:{name:"Aktueller Artist", write:false} },
                                    { id:"now_playing_media_image_url", common:{name:"Aktuelles Coverbild", write:false} },
                                    { id:"now_playing_media_album_id",  common:{name:"Aktuelle Album-ID", write:false} },
                                    { id:"now_playing_media_mid",       common:{name:"Aktuelle mid", write:false} },
                                    { id:"now_playing_media_qid",       common:{name:"Aktuelle qid", write:false} },
                                    { id:"cur_pos",                     common:{name:"lfd. Position"} },
                                    { id:"duration",                    common:{name:"Dauer", write:false} },
                                    { id:"cur_pos_MMSS",                common:{name:"lfd. Position MM:SS"} } ,
                                    { id:"duration_MMSS",               common:{name:"Dauer MM:SS", write:false} },
                                    { id:"group_leader",                common:{name:"Ist Gruppenleiter", write:false} },
                                    { id:"group_member",                common:{name:"Ist Gruppenmitglied", write:false} },
                                    { id:"group_pid",                   common:{name:"PlayerIDs in der Gruppe", write:false} },
                                    { id:"group_volume",                common:{name:"Lautstärke wenn Gruppen-Leiter"}},
                                    { id:"group_name",                  common:{name:"Name der Gruppe", write:false} } ,
                                    { id:"group_mute",                  common:{name:"Gruppe gemutet?"} },
                                    { id:"script_version",              common:{name:"Installierte Script-Version", write:false} }
                                ];
                                // beim 1.Start die states erzeugen
                                if ( !this.existState("script_version") || this.getState('script_version').val!=SCRIPTVERSION ) {
                                    this.installed = false;
                                    this.logWarn('creating HEOS player states for version '+SCRIPTVERSION+', please start script again');
                                    // states erzeugen
                                    for (var s=0; s<this.states.length; s++) {
                                        this.setState( this.states[s].id);
                                    }
                                    this.setState('script_version', SCRIPTVERSION);
                                }
                                else {
                                    // Initialisierung der States nach 5 Sek
                                    setTimeout(() => {
                                        this.setState('ip',this.ip);
                                        this.setState('name',this.name);
                                        this.setState('pid',this.pid);
                                        this.setState('model',this.model);
                                        this.setState('serial',this.serial);
                                        this.sendCommand('get_play_state|get_play_mode|get_now_playing_media|get_volume');
                                    }, 5000 );
                                    setTimeout(() => {
                                        this.startPlayer();
                                    }, 10000 );
                                    this.installed = true;
                                }
                            }
                            
                            // über den $-Operator nachsehen, ob der state bereits vorhanden ist
                            // getState().notExists geht auch, erzeugt aber Warnmeldungen!
                            existState(id) {
                                return ( $(this.statePath+id).length==0?false:true);
                            }
                            
                            // wrapper
                            getState(id) {
                                return getState(this.statePath + id);
                            }
                            
                            // wie setState(), setzt aber den statePath davor und überpüft aber ob der state vorhanden ist und erzeugt ihn,
                            // wenn er noch nicht da ist
                            setState(id,value) {
                                if ( !this.existState(id) ) this.createState(id, value, undefined);
                                else setState( this.statePath + id, value);
                            }
                            
                            // wie createState, setzt aber noch den statePath davor und schaut im states-Array nach, ob dort common-Angaben
                            // vorhanden sind (wenn der common-Parameter leer ist)
                            createState(id, value, common) {
                                if ( !this.existState(id) ) {
                                    if (common===undefined) {
                                        // id im states-Array suchen
                                        for (var i=0; i<this.states.length; i++) { 
                                            if (this.states[i].id==id) {
                                                if (this.states[i].hasOwnProperty('common'))
                                                    common = this.states[i].common;
                                               break;
                                            }   
                                        }
                                    }
                                    if ( (typeof value === 'undefined') && (common.hasOwnProperty('def'))) value = common.def;
                                    // unter "0_userdata.0"
                                    let obj = {};
                                    obj.type = 'state';
                                    obj.native = {};
                                    obj.common = common;
                                    setObject(this.statePath + id, obj, (err) => {
                                            if (err) {
                                                this.log('cant write object for state "' + this.statePath + id + '": ' + err);
                                            } else { 
                                                this.log('state "' + this.statePath + id + '" created');
                                            }
                                    });
                                  
                                    // value zeitversetzt setzen        
                                    setTimeout( setState, 3000, this.statePath + id, value );
                                }
                            }
                            
                            
                            startPlayer() { try {
                                this.log('starting HEOS player with pid '+this.pid+' ('+this.ip+')');
                                this.onHandler = [];
                                // on-Event für command
                                this.onHandler.push( on({id: this.statePath+'command', change: "any"}, (obj) => {
                                    this.executeCommand( obj.id, obj.state.val ) 
                                }));
                            
                                // on-Event für volume (nur wenn ack=false, also über vis)
                                this.onHandler.push( on({id: this.statePath+'volume', change:'ne', ack:false }, (obj) => {
                                    this.executeCommand( obj.id, 'set_volume&level='+obj.state.val );
                                }));
                                // on-Event für mute (nur wenn ack=false, also über vis)
                                this.onHandler.push( on({id: this.statePath+'mute', change: 'ne', ack:false}, (obj) => {
                                    this.executeCommand( obj.id, 'set_mute&state='+ (obj.state.val === true ? 'on' : 'off') );
                                }));
                                // on-Event für play_mode_shuffle (nur wenn ack=false, also über vis)
                                this.onHandler.push( on({id: this.statePath+'play_mode_shuffle', change: 'ne', ack:false}, (obj) => {
                                    this.executeCommand( obj.id, 'set_play_mode&shuffle='+ (obj.state.val === true ? 'on' : 'off') );
                                }));
                                // on-Event für play_state (nur wenn ack=false, also über vis)
                                this.onHandler.push( on({id: this.statePath+'play_state', change: 'ne', ack:false}, (obj) => {
                                    this.executeCommand( obj.id, 'set_play_state&state='+ obj.state.val );
                                }));
                            
                                // on-Event für group-volume (nur wenn ack=false, also über vis)
                                this.onHandler.push( on({id: this.statePath+'group_volume', change:'ne', ack:false }, (obj) => {
                                    // "id":"javascript.1.heos.-1746612370.group_volume"
                                    var id = obj.id.split('.');
                                    this.executeCommand( 'group/set_volume?gid='+id[3]+'&level='+obj.state.val );
                                }));
                            
                                // on-Event für group-mute (nur wenn ack=false, also über vis)
                                this.onHandler.push( on({id: this.statePath+'group_mute', change:'ne', ack:false }, (obj) => {
                                    // "id":"javascript.1.heos.-1746612370.group_volume"
                                    var id = obj.id.split('.');
                                    this.heos.executeCommand( 'group/set_mute?gid='+id[3]+'&state='+(obj.state.val===true ? 'on':'off') );
                                }));
                            
                                this.setState('connected', true);
                            } catch(err) { this.logError( 'startPlayer: '+err.message ); } }
                            
                            // sucht die zur pid passenden player-Insanz
                            executeCommand(objID, cmd ) {
                                // aus der objID die pid holen
                                // objID = javascript.1.heos.394645376.command
                                objID = objID.split('.');
                                var pid = objID[3];
                                if (this.pid == pid ) {
                                    this.sendCommand( cmd );
                                }
                             } 
                             
                            
                            stopPlayer() { try {
                                this.log('stopping HEOS player with pid '+this.pid+' ('+this.ip+')');
                            
                                // events unsubcribe
                                for (let i=0; i<this.onHandler.length; i++) {
                                    if (this.onHandler[i]!==undefined) unsubscribe(this.onHandler[i]);
                                }
                                this.onHandler = [];
                                // connected zurücksetzen
                                this.setState( "connected", false );
                            } catch(err) { this.logError( 'stopPlayer: '+err.message ); } }
                            
                            /** wandelt einen sek Wert in MM:SS Darstellung um
                             **/
                            toMMSS (s) {
                                var sec_num = parseInt(s, 10); 
                                var minutes = Math.floor(sec_num  / 60);
                                var seconds = sec_num - (minutes * 60);
                                if (seconds < 10) {seconds = "0"+seconds;}
                                return minutes+':'+seconds;
                            }
                            
                            setLastError(err) { try {
                                this.logWarn(err);
                                let val = this.getState('last_error').val;
                                let lines = val.splitt('\n');
                                if ( lines.length > 4)
                                    lines.pop();
                                lines.unshift(err+'\n');
                                this.setState('last_error', lines.toString());
                            } catch(e) { this.logError( 'setLastError: '+e.message ); } }
                            
                            
                            /** Auswertung der empfangenen Daten
                             **/
                            parseResponse (jdata, jmsg, cmd_group, cmd) { try {
                                switch (cmd_group) {
                                    case 'event':
                                        switch (cmd) {
                                            case 'player_playback_error' :
                                                this.setLastError(jmsg.error.replace(/_/g,' '));
                                                break;
                                            case 'player_state_changed' :
                                                this.setState("play_state", jmsg.state);
                                                break;
                                            case 'player_volume_changed' :
                                                this.setState("volume", jmsg.level );
                                                this.setState("mute", (jmsg.mute=='on' ? true : false) );
                                                break;
                                            case 'player_repeat_mode_changed' :
                                                this.setState("play_mode_shuffle", jmsg.shuffle );
                                                break;
                                            case 'player_shuffle_mode_changed' :
                                                this.setState("play_mode_repeat", jmsg.repeat );
                                                break;
                                            case 'player_now_playing_changed' :
                                                this.sendCommand('get_now_playing_media');
                                                break;
                                            case 'player_now_playing_progress' :
                                                this.setState("cur_pos", jmsg.cur_pos / 1000);
                                                this.setState("cur_pos_MMSS", this.toMMSS(jmsg.cur_pos / 1000));
                                                this.setState("duration", jmsg.duration / 1000);
                                                this.setState("duration_MMSS", this.toMMSS(jmsg.duration / 1000));
                                                break;
                                        }        
                                        break;
                                        
                                        
                                    case 'player':
                                        switch (cmd) {
                                            case 'set_volume' :
                                            case 'get_volume' :
                                                if ( getState(this.statePath+"volume").val != jmsg.level)
                                                    this.setState("volume", jmsg.level);
                                                break;
                                            case 'set_mute' :
                                            case 'get_mute' :
                                                this.setState("mute", (jmsg.state=='on' ? true : false) );
                                                break;
                                            case 'set_play_state' :
                                            case 'get_play_state' :
                                                this.setState("play_state", jmsg.state);
                                                break;
                                            case 'set_play_mode' :
                                            case 'get_play_mode' :
                                                this.setState("play_mode_repeat", jmsg.repeat);
                                                this.setState("play_mode_shuffle", (jmsg.shuffle=='on'?true:false) );
                                                break;
                                            case 'get_now_playing_media' :
                                                this.setState("now_playing_media_type", jdata.payload.type);
                                                if (jdata.payload.hasOwnProperty('song'))
                                                    this.setState("now_playing_media_song", jdata.payload.song);
                                                if (jdata.payload.hasOwnProperty('album'))
                                                    this.setState("now_playing_media_album", jdata.payload.album);
                                                if (jdata.payload.hasOwnProperty('album_id'))
                                                    this.setState("now_playing_media_album_id", jdata.payload.album_id);
                                                if (jdata.payload.hasOwnProperty('artist'))
                                                    this.setState("now_playing_media_artist", jdata.payload.artist);
                                                if (jdata.payload.hasOwnProperty('image_url'))
                                                    this.setState("now_playing_media_image_url", jdata.payload.image_url);
                                                if (jdata.payload.hasOwnProperty('mid'))
                                                    this.setState("now_playing_media_mid", jdata.payload.mid);
                                                if (jdata.payload.hasOwnProperty('qid'))
                                                    this.setState("now_playing_media_qid", jdata.payload.qid);
                                                if (jdata.payload.hasOwnProperty('station')) {
                                                    if (jdata.payload.type=='station') {
                                                        this.setState("now_playing_media_station", jdata.payload.station);
                                                    } else {
                                                        this.setState("now_playing_media_station", null);
                                                    }
                                                }  
                                                break;
                                        }
                                        break;
                                } // switch
                            
                            
                            } catch(err) { this.logError( 'parseResponse: '+err.message ); } }
                             
                            /** cmd der Form "cmd&param"  werden zur msg heos+cmd+pid+&param aufbereitet
                                cmd der Form "cmd?param"  werden zur msg heos+cmd+?param aufbereitet
                             **/
                            commandToMsg (cmd) {
                                var param = cmd.split('&');
                                cmd = param[0];
                                if ( param.length > 1 ) param='&'+param[1]; else param=''; 
                                var cmd_group = 'player';
                            
                                switch (cmd) {
                                    case 'get_play_state':
                                    case 'get_play_mode':
                                    case 'get_now_playing_media':
                                    case 'get_volume':
                                    case 'play_next':
                                    case 'play_previous':
                                    case 'set_mute':       // &state=on|off        
                                    case 'set_volume':     // &level=1..100   
                                    case 'volume_down':    // &step=1..10   
                                    case 'volume_up':      // &step=1..10
                                    case 'set_play_state': // &state=play|pause|stop
                                    case 'set_play_mode':  // &repeat=on_all|on_one|off  shuffle=on|off
                                        break;
                            
                                    // browse            
                                    case 'play_preset':    // heos://browse/play_preset?pid=player_id&preset=preset_position
                                        cmd_group = 'browse';
                                        break;
                                    case 'play_stream':    // heos://browse/play_stream?pid=player_id&url=url_path
                                        cmd_group = 'browse';
                                        break;
                                        
                                }        
                                return 'heos://'+cmd_group+'/'+cmd+'?pid=' + this.pid + param;
                            }
                            
                            /** Nachricht (command) an player senden
                                es sind auch mehrere commands, getrennt mit | erlaubt
                                bsp: set_volume&level=20|play_preset&preset=1
                             **/
                            sendCommand (command) {
                                if ( !this.installed ) return;
                            
                                var cmds = command.split('|');
                                for (var c=0; c<cmds.length; c++) {
                                    this.heos.msgs.push( this.commandToMsg(cmds[c]) );
                                }
                                this.heos.sendNextMsg();
                            }
                            
                            
                            } // end of HeosPlayer
                            
                            
                            /* -----
                               Heos
                               ----- */
                            // Heos Instanz erzeugen und verbinden   
                            var heos = new Heos( );
                            heos.connect();
                            
                            // wenn das Script beendet wird, dann auch die Heos Instanz beenden
                            onStop(function () { 
                                heos.disconnect(); 
                            }, 0 );
                            
                            UhulaU 1 Antwort Letzte Antwort
                            0
                            • W withstu

                              @Uhula Irgendwie funktioniert der keep alive Mechanismus vom net socket nicht korrekt. Auch wenn ich das Device ausgeschaltet habe, wurde das Timeout Event nicht geworfen. Habe jetzt den HEOS Heartbeat zusätzlich implementiert. Sollte der Master mal ausgeschaltet werden und der Heartbeat Timeout 60 Sekunden keine Reaktion von dem Device bekommen, wird nun ein reconnect bzw. eine Suche nach einem neuen Master durchgeführt. Zusätzlich wird die ssdp Suche jetzt so oft wiederholt, bis ein Device gefunden wurde.

                              /****************************
                              ### HEOS Script for ioBroker
                              
                              #### Funktion
                              
                              Das Script stellt zwei JS Klassen zur Steuerung der Denon HEOS Geräte zur Verfügung. Die class Heos dient dabei dem Erkennen und Steuern der HEOS Geräte. Die class HeosPlayer dient der Interpretation der Antworten der Heos Geräte.
                               
                              Das Script muss lediglich gestartet werden, es sucht dann im Netzwerk nach HEOS Playern und erzeugt in der aktuellen Javascript-Instanz einen Subeintrag für die Favoriten und je einen Subeintrag für jeden gefundenen Player. Dort wiederum werden State-Variablen zur Aufnahme der Player-Daten angelegt.
                               
                              Das Script erzeugt auch on-Handler um auf Steuerungen über vis und andere Scripte an den State-Variablen reagieren zu können.
                               
                              Beim Beenden des Scripts werden alle Verbindungen und on-Handler geschlossen.
                              
                              Das Script ermöglicht die HEOS Player zu steuern, es soll aber nicht die HEOS App ersetzen. 
                               
                                
                              #### Wichtig!
                              
                              * Im Javascript-Adapter muss in der Instanz-Konfiguration "node-ssdp" mit angegeben werden! Alternativ kann die Bibliothek auch komplett über "npm install node-ssdp" installiert werden.
                              * Wenn mit Favoriten (Presets) gearbeitet werden soll, müssen die HEOS-Konto Logindaten in den beiden Konstanten HEOS_USERNAME (EmailAdr) und HEOS_PASSWORD hier im Script in der Konfiguration angegeben werden (so, wie in der HEOS App)!
                              * Eine Konfiguration von IP-Adressen und Player-IDs ist nicht notwendig!
                              * U.U. kann es notwendig sein das Script nach dem 1.Start zu beenden und nach 30 Sek erneut zu starten, da die Neuanlage der ioBroker States etwas Zeit benötigt. Warnungen sind dabei zu ignorieren. ;-)
                              
                              #### Updates
                               
                              ##### v2.0 13.03.2020
                              * das Script überprüft nun, ob alle notwendigen states für Heos und HeosPlayer im ioBroker korrekt erzeugt wurden und gibt eine Warnung mit Script-Neustartaufforderung aus, wenn die states nicht vorhanden sind. Dieses ist notwendig, da das Anlegen von states asynchron erfolgt und einige ms dauern kann
                              * (WICHTIG!) die states werden nicht mehr in der aktuellen Javascript-Instanz und auch nicht mehr unter der IP des Heos-Players gespeichert, sondern Javascript-Instanzunabhängig unter 0_userdata.0 und seiner Player-ID (pid), da diese sich nicht ändert. Die IP hingegen u.U. schon. Also statt "javascript.X.heos.192_168_2_43" nun als "0_userdata.0.heos.12345678". Wer bereits auf die alten Werte im VIS zugreift, muss dieses im VIS anpassen!  
                              * es wurden Befehle/states für die Gruppensteuerung der Heos-Player hinzugefügt. Beim Start werden die Gruppen ausgelesen und in den Player-states des Gruppenleiters (der erste genannte Player) gespeichert. Über dessen states kann die Gruppe gesteuert werden: 
                                  group_volume: dient der Steuerung der Gruppen-Lautstärke
                                  group_mute: dient der Steuerung der Gruppen-Mutes
                                  group_leader: ist true, wenn der Player der Gruppenleiter ist
                                  group_member: ist true, wenn der Player Mitglied einer Gruppe ist
                              * alle 60 Sek wird überprüft, ob noch alle HEOS-Player erreichbar sind bzw. neue/reconnectete hinzugekommen sind. Neue/reconnectete werden der Liste der HEOS-Player hinzugefügt, fehlende werden gestoppt. Da hierzu das HEOS-Netzwerk gefragt wird (get_players Meldung), kann es bis zu 5 Min dauern bis Änderungen erkannt werden. Der neue HEOS-Player-State "connected" wird entsprechend gesetzt
                              * jeder HEOS-Player hat einen neuen state "connected", dieser wird nach erfolgreichem Start auf true gesetzt, bei korrektem Beenden des Scripts auf false
                              * diverse Filter/Korrekturen aus dem Forum übernommen, vielen dank an withstu
                                
                                
                               #### class Heos
                               
                               Diese Basisklasse dient 
                              
                              * dem Erkennen von HEOS Geräten (Playern). Das Erkennen der HEOS Geräte findet via UPD unter Nutzung von node-ssdp statt. node-ssdp muss dazu im Javascript-Adapter in der Konfiguration mit angegeben werden!
                              * der Kommunikation über TelNet. Es wird genau eine TelNet Verbindung zu einem HEOS Gerät aufgebaut, dieses reicht die Sendungen und Antworten zentral weiter
                              * der Ermittlung von Favoriten (Presets). Für jeden Favorit wird ein ioBroker State heos.presets.n mit entsprechenden Sub-States erzeugt. Zur Nutzung der Presets ist ein SignIn notwendig, hierzu müssen HEOS_USERNAME und HEOS_PASSWORD gesetzt werden.
                              * dem Instanziieren der class HeosPlayer je HEOS Gerät. Je HEOS Gerät wird ein ioBroker-State mit der IP-Adresse des Players erzeugt, dieser State erhält diverse Sub-States
                                
                              ##### State-Variable: Heos
                               
                              Werden angelegt unter "0_userdata.0.heos."
                              
                              * connected: true wenn die Verbindung zu mindestens einem HEOS-Player besteht
                              * script_version: Version des Scripts, dient dazu Script-Updates zu erkennen
                              * last_error: Letzter Fehlertext
                              * command(cmd): Hierüber können der Heos-Klasse Befehle übergeben werden, für cmd gilt:
                                  "connect": Verbindung zu HEOS aufbauen bzw. erneut aufbauen (praktisch ein reset)
                                  "disconnect": Verbindung zu HEOS beenden
                                  "load_presets": lädt die Favoriten neu
                                
                                  "group/set_group?pid=<pid1>,<pid2>,...": setzen einer Gruppe, die pids sind die der Player wie sie unter den Objekten für jeden Player abgelegt sind. Bsp: "group/set_group?pid=12345678,12345679". 
                               
                                 "group/set_group?pid=<pid1>" : hebt die Gruppierung des Players mit der pid1 wieder auf. Bsp: "group/set_group?pid=12345678"
                                 "...": alle anderen cmd-Werte werden "as is" versucht an HEOS zu senden
                               
                                
                              * presets.<n>: Hierunter werden die Favoriten, Presets angelegt. Je Favorit ein Unterordner n=1 bis Anzahl. Darunter befinden sich dann die States, welche den Inhalt der Favoriten aufnehmen:
                               
                              * image_url (read): Bild des Favoriten 
                              * name (read): Name des Favoriten
                              * playable (read): Spielbar? true/false
                              * type (read): Typ des Favoriten (station, ...)
                               
                                  
                                 
                               #### class HeosPlayer 
                               
                               Diese Klasse dient
                              * der Steuerung genau eines HEOS Gerätes. Hierzu wird die zentrale Telnet Verbindung der class Heos genutzt.
                              * dem Erzeugen der ioBroker-States zum Speichern der Geräte-Werte
                              * der Auswertung von Antworten der HEOS Geräte und Zuweisung an die entsprechenden ioBroker-States
                               
                              ##### State-Variable
                              
                              Werden angelegt unter "0_userdata.0.heos.<pid>"
                              
                              * connected : true, wenn eine Verbindung besteht, sonst false
                              * command(cmd): Hierüber können dem Heos-Player Befehle übergeben werden. Diese werden im Klartext in die State-Variable geschrieben. Getrennt durch das | Zeichen können mehrere hintereinander eingetragen werden. Bsp: Setzen der Lautstärke auf 20 und Abspielen des 1.Favoriten "set_volume&level=20|play_preset&preset=1|set_play_state&state=play". Folgende cmd sind erlaubt:
                              
                                "set_volume&level=0|1|..|100" : Setzt die gewünschte Lautstärke 
                                "set_play_state&state=play|pause|stop" : Startet und stoppt die Wiedergabe
                                "set_play_mode&repeat=on_all|on_one|off&shuffle=on|off": Setzt Wiederholung und Zufallsweidergabe
                                "set_mute&state=on|off" : Stumm schalten oder nicht
                                "volume_down&step=1..10" : Lautstärke verringern um   
                                "volume_up&step=1..10"  : Lauststäre erhöhen um
                                "play_next"  : Nächsten Titel spielen
                                "play_previous" : Vorherigen Titel spielen
                                "play_preset&preset=1|2|..|n" : Favorit Nr n abspielen
                                "play_stream&url=url_path" : URL-Stream abspielen
                               
                              * cur_pos (read) : lfd. Position der Wiedergabe in [Sek]
                              * cur_pos_MMSS (read) : lfd. Position der Wiedergabe im Format MM:SS
                              * duration (read) : Länge des aktuellen Titels in [Sek]
                              * duration_MMSS (read) : Länge des aktuellen Titels im Format MM:SS
                              * ip (read): IP-Adresse des HEOS Players
                              * last_error (read): Text des letzten Fehlers, der vom Player gesendet wurde. Wird bei jedem neuen Befehl zurückgesetzt. Wenn man bspw. versucht eine Wiedergabe über Amazon o.ä. zu starten und es gibt aber keine Verbindung dahin, steht hier der Fehlertext drin
                              * model (read): Modell des HEOS Players
                              * mute (read write): Boolsche Variable, die anzeigt ob der Player gemutet (Stumm geschaltet) wurde. Hierüber kann auch ein mute gesetzt werden
                              * name (read): Name des HEOS Players
                              * now_playing_media_... (read) : Eine Gruppe von States, in welchen Infos über den aktuellen Titel gespeichert werden, wird automatisch aktualisiert. 
                              * pid (read): Player-ID des HEOS Players
                              * play_mode_repeat (read write) : Repeat-Modus der Wiedergabe. Mögliche Werte: on_all|on_one|off  
                              * play_mode_shuffle (read write) : Zufallswiedergabe true/false
                              * play_state (read write): Zustand des Players. Mögliche Werte play|pause|stop
                              * serial (read): Seriennummmer des HEOS Players
                              * volume (read write): Lautstärke im Bereich 0 - 100. 
                              * group_leader : Ist Gruppenleiter
                              * group_member : Ist Gruppenmitglied
                              * group_pid : Player pids in der Gruppe
                              * group_volume : Lautstärke wenn Gruppen-Leiter
                              * group_name : Name der Gruppe
                              * group_mute : Gruppe gemutet?
                              
                               
                              #### Weiterführende Links
                              
                              * HEOS CLI Protokoll: http://rn.dmglobal.com/euheos/HEOS_CLI_ProtocolSpecification.pdf
                              * http://forum.iobroker.net/viewtopic.php?f=30&t=5693&p=115554#p115554
                               
                               
                              (c) Uhula, MIT License, no warranty, use on your own risc
                               
                              */
                              
                              /****************************
                               * Konfiguration
                               ****************************/
                               
                              const HEOS_USERNAME = '';
                              const HEOS_PASSWORD = '';
                              
                              
                              /****************************
                               * ab hier nichts mehr ändern ;-) 
                               ****************************/
                              
                              var net = require('net');
                              
                              const stateDISCONNECTED = 0;
                              const stateSEARCHING = 1;
                              const stateCONNECTING = 2;
                              const stateCONNECTED = 3;
                              const SCRIPTVERSION = '2.0/2020-03-13';
                              
                              /********************
                               * class Heos
                               ********************/
                              class Heos  {
                              
                              logDebug(msg) { console.debug('[Heos] '+msg); }
                              log(msg) { console.log('[Heos] '+msg); }
                              logWarn(msg) { console.warn('[Heos] '+msg); }
                              logError(msg) { console.error('[Heos] '+msg); }
                              
                              constructor() {
                                  this.init();
                                  this.states = [
                                      { id:"command",        common:{name:"Kommando für HEOS Aufrufe"}                   },
                                      { id:"connected",      common:{name:"Verbunden?",                     write:false, def:false } },
                                      { id:"last_error",     common:{name:"Letzte Fehler",                  write:false} },
                                      { id:"script_version", common:{name:"Installierte Script-Version", write:false } }
                                  ];
                                  // beim 1.Start nur die States erzeugen
                                  if ( !this.existState("script_version") || (this.getState('script_version').val!=SCRIPTVERSION) ) {
                                      this.installed = false;
                                      this.logWarn('creating HEOS states for version '+SCRIPTVERSION+', please start script again');
                                      // Anlage der States 
                                      for (var s=0; s<this.states.length; s++) {
                                          this.setState( this.states[s].id );
                                      }
                                      this.setState('script_version', SCRIPTVERSION);
                                  }
                                  else {   
                                      this.installed = true;
                                      on({id: this.statePath+'command', change: "any"}, (obj) => {
                                              this.executeCommand( obj.state.val );
                                          });
                                  }
                              }
                              
                              
                              
                              // Initialisierung
                              init() {
                                  this.statePath = '0_userdata.0.heos.';
                                  this.players = [];
                                  this.heartbeatInterval = undefined;
                                  this.heartbeatTimeout = undefined;
                                  this.ssdpSearchInterval = undefined;
                                  this.getPlayersInterval = undefined;
                                  this.net_client = undefined;
                                  this.nodessdp_client = undefined;
                              
                                  this.ip='';
                                  this.msgs = [];
                                  this.lastResponse = '';
                                  this.state = stateDISCONNECTED;
                                  this.unfinishedResponses = '';
                                  this.ssdpSearchTargetName = 'urn:schemas-denon-com:device:ACT-Denon:1';
                              }
                              
                              
                              // über den $-Operator nachsehen, ob der state bereits vorhanden ist
                              // getState().notExists geht auch, erzeugt aber Warnmeldungen!
                              existState(id) {
                                  return ( $(this.statePath+id).length==0?false:true);
                              }
                              
                              // wrapper
                              getState(id) {
                                  return getState(this.statePath + id);
                              }
                              
                              // wie setState(), setzt aber den statePath davor und überpüft aber ob der state vorhanden ist und erzeugt ihn,
                              // wenn er noch nicht da ist
                              setState(id,value) {
                                  if ( !this.existState(id) ) this.createState(id, value, undefined);
                                  else setState( this.statePath + id, value);
                              }
                              
                              // wie createState, setzt aber noch den statePath davor und schaut im states-Array nach, ob dort common-Angaben
                              // vorhanden sind (wenn der common-Parameter leer ist)
                              createState(id, value, common) {
                                  if ( !this.existState(id) ) {
                                      if (common===undefined) {
                                          // id im states-Array suchen
                                          for (var i=0; i<this.states.length; i++) { 
                                              if (this.states[i].id==id) {
                                                  if (this.states[i].hasOwnProperty('common'))
                                                      common = this.states[i].common;
                                                 break;
                                              }   
                                          }
                                      }
                                      if ( (typeof value === 'undefined') && (common.hasOwnProperty('def'))) value = common.def;
                                      // unter "0_userdata.0"
                                      let obj = {};
                                      obj.type = 'state';
                                      obj.native = {};
                                      obj.common = common;
                                      setObject(this.statePath + id, obj, (err) => {
                                              if (err) {
                                                  this.log('cant write object for state "' + this.statePath + id + '": ' + err);
                                              } else { 
                                                  this.log('state "' + this.statePath + id + '" created');
                                              }
                                      });
                                    
                                      // value zeitversetzt setzen        
                                      setTimeout( setState, 3000, this.statePath + id, value );
                                  }
                              }
                              
                              /** Verbindung zum HEOS System herstellen
                               **/
                              connect() { 
                                  if ( !this.installed ) return;
                              
                                  try {
                                      this.log("searching for HEOS devices ...")
                                      this.setState( "connected", false );
                                      this.state = stateSEARCHING;
                                      const NodeSSDP = require('node-ssdp').Client;
                              	    this.nodessdp_client = new NodeSSDP();
                              	    this.nodessdp_client.explicitSocketBind = true;
                              	    this.nodessdp_client.on('response', (headers, statusCode, rinfo) => this.onNodeSSDPResponse(headers, statusCode, rinfo) );
                                      this.nodessdp_client.on('error', error => {	this.nodessdp_client.close(); this.logError(error); });
                                      this.nodessdp_client.search(this.ssdpSearchTargetName);
                                      this.ssdpSearchInterval = setInterval(() => {
                                          this.log("still searching for HEOS devices ...")
                                          this.nodessdp_client.search(this.ssdpSearchTargetName);
                                      }, 30000);
                                  } catch(err) { this.logError( 'connect: '+err.message ); } 
                                  
                              }
                              
                              
                              /** Alle Player stoppen und die TelNet Verbindung schließen 
                               **/
                              disconnect() {
                                  this.log('disconnecting from HEOS ...');
                                  unsubscribe(this.statePath.substr(0,this.statePath.length-1));
                              
                                  this.stopHeartbeat();
                                  this.stopPlayers();
                              
                                  if (typeof this.net_client!=='undefined') {
                                      this.registerChangeEvents( false );
                                      this.net_client.destroy();
                                      this.net_client.unref();
                                  }
                                  if (typeof this.nodessdp_client!=='undefined') {
                                      this.nodessdp_client.stop();
                                  }
                                  this.setState( "connected", false );
                                  this.log('disconnected from HEOS');
                              }
                              
                              reconnect(){
                                  this.log('reconnecting to HEOS ...');
                                  this.disconnect();
                                  this.init();
                                  this.connect();
                              }
                              
                              executeCommand(cmd) {
                              //l('command: '+cmd);
                                  switch (cmd) {
                                      case 'load_presets' :
                                          this.getMusicSources();
                                          break;
                                      case 'group/get_groups' :
                                          this.getGroups();
                                          break;
                                      case 'connect' :
                                          this.disconnect();
                                          this.init();
                                          this.connect();
                                          break;
                                      case 'disconnect' :
                                          this.disconnect();
                                          break;
                                      default:
                                          if (this.state == stateCONNECTED) {
                                              this.msgs.push( 'heos://'+cmd+'\n' );
                                              this.sendNextMsg();
                                          }
                                  
                                  }
                              }
                              /** es wurde mindestens ein Player erkannt, nun über dessen IP alle bekannten HEOS Player
                               *  durch senden von "player/get_players" ermitteln
                               */
                              onNodeSSDPResponse(headers, statusCode, rinfo) { try {
                                  // rinfo {"address":"192.168.2.225","family":"IPv4","port":53871,"size":430}
                                  if (typeof this.net_client=='undefined') {
                                      if(headers.ST !== this.ssdpSearchTargetName) { // korrektes SSDP
                                          this.logDebug('onNodeSSDPResponse: Getting wrong SSDP entry. Keep trying...');
                                      } else {
                                          if (this.ssdpSearchInterval!==undefined) {
                                              clearInterval(this.ssdpSearchInterval);
                                              this.ssdpSearchInterval = undefined;
                                          }
                              
                                          this.ip = rinfo.address;
                                          this.log('connecting to HEOS ('+this.ip+') ...');
                                          this.net_client = net.connect({host:this.ip, port:1255});
                                          this.net_client.setKeepAlive(true, 5000);
                                          this.net_client.setNoDelay(true);
                                          this.net_client.setTimeout(15000);
                              
                                          this.state = stateCONNECTING;
                              
                                          this.net_client.on('error',(error) => {
                                              this.logError(error);
                                              this.reconnect();
                                          }); 
                                  
                                          this.net_client.on('connect',  () => {
                                              this.setState( "connected", true );
                                              this.state = stateCONNECTED;
                                              this.log('connected to HEOS ('+this.ip+')');
                                              this.getPlayers();
                                              this.registerChangeEvents( true );
                                              this.signIn();
                                              this.getMusicSources();
                                              this.getGroups();
                                              this.startHeartbeat();
                                          });
                                      
                                          // Gegenseite hat die Verbindung geschlossen 
                                          this.net_client.on('end',  () => {              
                                              this.logWarn('HEOS closed the connection to '+this.ip);
                                              this.state = stateDISCONNECTED;
                                              this.reconnect();
                                          });
                              
                                          // timeout
                                          this.net_client.on('timeout',  () => {              
                                              this.logWarn('Timeout trying connect to '+this.ip);
                                              this.state = stateDISCONNECTED;
                                              this.reconnect();
                                          });
                              
                                          // Datenempfang
                                          this.net_client.on('data', (data) => this.onData(data)  );
                                      }
                                  }
                              } catch(err) { this.logError( 'onNodeSSDPResponse: '+err.message ); } }
                              
                              setLastError(err) {
                                  this.logWarn(err);
                                  /*
                                  var val = getState(this.statePath+'last_error').val;
                                  var lines = val.splitt('\n');
                                  if ( lines.length > 4)
                                      lines.pop();
                                  lines.unshift(err+'\n');
                              l(lines);       
                                  this.setState('last_error', lines.toString());
                                  */    
                              }
                              
                              
                              /** es liegen Antwort(en) vor
                               * 
                               * {"heos": {"command": "browse/browse", "result": "success", "message": "sid=1028&returned=9&count=9"}, 
                               *    "payload": [
                               *        {"container": "no", "mid": "s25529", "type": "station", "playable": "yes", "name": "NDR 1 Niedersachsen (Adult Hits)", "image_url": "http://cdn-profiles.tunein.com/s25529/images/logoq.png?t=154228"}, 
                               *        {"container": "no", "mid": "s56857", "type": "station", "playable": "yes", "name": "NDR 2 Niedersachsen 96.2 (Top 40 %26 Pop Music)", "image_url": "http://cdn-profiles.tunein.com/s56857/images/logoq.png?t=154228"}, 
                               *        {"container": "no", "mid": "s24885", "type": "station", "playable": "yes", "name": "NDR Info", "image_url": "http://cdn-profiles.tunein.com/s24885/images/logoq.png?t=1"}, {"container": "no", "mid": "s158432", "type": "station", "playable": "yes", "name": "Absolut relax (Easy Listening Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s158432q.png"}, 
                               *        {"container": "no", "mid": "catalog/stations/A316JYMKQTS45I/#chunk", "type": "station", "playable": "yes", "name": "Johannes Oerding", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/JohannesOerding._SX200_SY200_.jpg"}, 
                               *        {"container": "no", "mid": "catalog/stations/A1O1J39JGVQ9U1/#chunk", "type": "station", "playable": "yes", "name": "Passenger", "image_url": "https://images-na.ssl-images-amazon.com/images/I/71DsYkU4QaL._SY500_CR150,0,488,488_SX200_SY200_.jpg"}, 
                               *        {"container": "no", "mid": "catalog/stations/A1W7U8U71CGE50/#chunk"
                               **/
                              onData(data) {  try {
                                  data = data.toString();
                                  data=data.replace(/[\n\r]/g, '');    // Steuerzeichen "CR" entfernen   
                                  // es können auch mehrere Antworten vorhanden sein! {"heos": ... } {"heos": ... }
                                  // diese nun in einzelne Antworten zerlegen
                                  data = this.unfinishedResponses + data;
                                  this.unfinishedResponses = '';
                                  
                                  data=data.replace(/{"heos":/g, '|{"heos":');    
                                  var responses = data.split('|');
                                  responses.shift();
                                  for (var r=0; r<responses.length; r++ ) if(responses[r].trim().length > 0) {
                                      try {
                                          JSON.parse(responses[r]); // check ob korrektes JSON Array
                                          this.parseResponse(responses[r]);
                                      } catch(e) {
                                          this.logDebug('onData: invalid json (error: ' + e.message + '): ' + responses[r]);
                                          this.unfinishedResponses += responses[r];
                                      }
                                  }
                                  // wenn weitere Msg zum Senden vorhanden sind, die nächste senden
                                  if (this.msgs.length>0)
                                      this.sendNextMsg();
                              } catch(err) { this.logError( 'onData: '+err.message ); } }
                              
                              /** Antwort(en) verarbeiten. Sich wiederholende Antworten ignorieren
                               **/
                              parseResponse (response) { try {
                                  if (response == this.lastResponse || response.indexOf("command under process") > 0 )
                                      return
                                  this.lastResponse = response;        
                                  
                                  var jmsg;
                                  var i;
                                  var jdata = JSON.parse(response);
                                  if ( !jdata.hasOwnProperty('heos') || !jdata.heos.hasOwnProperty('command') || !jdata.heos.hasOwnProperty('message') ) 
                                      return;
                              
                                  // msg auswerten
                                  try {
                                      jmsg = '{"' + decodeURI(jdata.heos.message).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g,'":"').replace(/\s/g,'_') + '"}';
                                      jmsg = JSON.parse(jmsg);
                                  } catch(err) {
                                      jmsg = {};
                                  }
                              
                                  this.logDebug('parseResponse: '+response); 
                              
                                  // result ?
                                  var result = 'success';
                                  if (jdata.heos.hasOwnProperty('result') ) result = jdata.heos.result;
                                  if ( result!='success' ) { 
                                      this.setLastError('result='+result+', '+jmsg.text);
                                  }
                              
                                  // cmd auswerten
                                  var cmd = jdata.heos.command.split('/');
                                  var cmd_group = cmd[0];
                                  cmd = cmd[1];
                                  switch (cmd_group) {
                                      case 'system':
                                          switch(cmd) {
                                              case 'heart_beat':
                                                  this.resetHeartbeatTimeout();
                                                  break;
                                          }
                                          break;
                                      case 'event':
                                          switch (cmd) {
                                              case 'group_volume_changed' :
                                              // "heos": {"command": "event/group_volume_changed ","message": "gid='group_id'&level='vol_level'&mute='on_or_off'"}
                                              this.getGroups();
                                              break;
                                          }
                                          break;
                                      case 'player':
                                          switch (cmd) {
                                                  // {"heos": {"command": "player/get_players", "result": "success", "message": ""}, 
                                                  //  "payload": [{"name": "HEOS Bar", "pid": 1262037998, "model": "HEOS Bar", "version": "1.430.160", "ip": "192.168.2.225", "network": "wifi", "lineout": 0, "serial": "ADAG9170202780"}, 
                                                  //              {"name": "HEOS 1 rechts", "pid": -1746612370, "model": "HEOS 1", "version": "1.430.160", "ip": "192.168.2.201", "network": "wifi", "lineout": 0, "serial": "AMWG9170934429"}, 
                                                  //              {"name": "HEOS 1 links", "pid": 68572158, "model": "HEOS 1", "version": "1.430.160", "ip": "192.168.2.219", "network": "wifi", "lineout": 0, "serial": "AMWG9170934433"}
                                                  //             ]}
                                              case 'get_players' :
                                                  if (jdata.hasOwnProperty('payload')) {
                                                      if (jdata.payload.length != this.players.length) {
                                                          this.stopPlayers();
                                                          this.players = [];
                                                          for (i=0; i<jdata.payload.length; i++) {
                                                              var player = jdata.payload[i];
                                                              this.players.push(player);
                                                          }
                                                          this.startPlayers();
                                                      } 
                                                  }
                                                  break;
                                          }
                                          break;
                              
                                      // {"heos": {"command": "browse/get_music_sources", "result": "success", "message": ""}, 
                                      //  "payload": [{"name": "Amazon", "image_url": "https://production...png", "type": "music_service", "sid": 13}, 
                                      //              {"name": "TuneIn", "image_url": "https://production...png", "type": "music_service", "sid": 3}, 
                                      //              {"name": "Local Music", "image_url": "https://production...png", "type": "heos_server", "sid": 1024}, 
                                      //              {"name": "Playlists", "image_url": "https://production...png", "type": "heos_service", "sid": 1025}, 
                                      //              {"name": "History", "image_url": "https://production...png", "type": "heos_service", "sid": 1026}, 
                                      //              {"name": "AUX Input", "image_url": "https://production...png", "type": "heos_service", "sid": 1027}, 
                                      //              {"name": "Favorites", "image_url": "https://production...png", "type": "heos_service", "sid": 1028}]}
                                      case 'browse':
                                          switch (cmd) {
                                              case 'get_music_sources' :
                                                  if ( (jdata.hasOwnProperty('payload')) ) {
                                                      for (i=0; i<jdata.payload.length; i++) {
                                                          var source = jdata.payload[i];
                                                          if (source.name=='Favorites') {
                                                              this.browse(source.sid);
                                                          }
                                                      }
                                                  }
                              
                                                  break;
                                                  
                              	    // {"heos": {"command": "browse/browse", "result": "success", "message": "pid=1262037998&sid=1028&returned=5&count=5"}, 
                              	    //  "payload": [{"container": "no", "mid": "s17492", "type": "station", "playable": "yes", "name": "NDR 2 (Adult Contemporary Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s17492q.png"}, 
                              	    //              {"container": "no", "mid": "s158432", "type": "station", "playable": "yes", "name": "Absolut relax (Easy Listening Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s158432q.png"}, 
                              	    //              {"container": "no", "mid": "catalog/stations/A1W7U8U71CGE50/#chunk", "type": "station", "playable": "yes", "name": "Ed Sheeran", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/EdSheeran._SX200_SY200_.jpg"}, 
                              	    //              {"container": "no", "mid": "catalog/stations/A1O1J39JGVQ9U1/#chunk", "type": "station", "playable": "yes", "name": "Passenger", "image_url": "https://images-na.ssl-images-amazon.com/images/I/71DsYkU4QaL._SY500_CR150,0,488,488_SX200_SY200_.jpg"}, 
                              	    //              {"container": "no", "mid": "catalog/stations/A316JYMKQTS45I/#chunk", "type": "station", "playable": "yes", "name": "Johannes Oerding", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/JohannesOerding._SX200_SY200_.jpg"}], 
                              	    //  "options": [{"browse": [{"id": 20, "name": "Remove from HEOS Favorites"}]}]}                    
                                              case 'browse' :
                                                  if ( (jdata.hasOwnProperty('payload')) ) {
                                                      for (i=0; i<jdata.payload.length; i++) {
                                                          var preset = jdata.payload[i];
                                                          this.createState( 'presets.'+(i+1)+'.name', preset.name, {name: 'Favoritenname ' });
                                                          this.createState( 'presets.'+(i+1)+'.playable', (preset.playable=='yes'?true:false), {name: 'Favorit ist spielbar' });
                                                          this.createState( 'presets.'+(i+1)+'.type', preset.type, {name: 'Favorittyp' });
                                                          this.createState( 'presets.'+(i+1)+'.image_url', preset.image_url, {name: 'Favoritbild' });
                                                      }
                                                  }
                                                  break;
                                          }     
                                          break;
                              
                                      case 'group':
                              //l('group: '+response);
                                          switch (cmd) {
                                                // { "heos":{"command":"player/set_group","result":"success",
                                                //           "message": "gid='new group_id'&name='group_name'&pid='player_id_1, player_id_2,…,player_id_n'
                                                //          } 
                                                // }
                                              case 'set_group' :
                                                  this.setGroup( jmsg );
                                                  break;
                              
                                              // { "heos": {"command":"group/get_volume","result":"success","message": "gid='group_id'&level='vol_level'"}
                                              case 'get_volume':
                                                  if ( jmsg.hasOwnProperty('gid') ) {
                                                      if ( jmsg.hasOwnProperty('level') ) {
                                                          this.setState( jmsg.gid+'.group_volume', jmsg.level);
                                                      }
                                                  }
                                                  break;
                              
                                              // { "heos": {"command":"group/get_mute","result":"success","message": "gid='group_id'&state='on_or_off'"}
                                              case 'get_mute':
                                                  if ( jmsg.hasOwnProperty('gid') ) {
                                                      if ( jmsg.hasOwnProperty('state') ) {
                                                          this.setState( jmsg.gid+'.group_mute', (jmsg.state=='on' ? true : false));
                                                      }
                                                  }
                                                  break;
                              
                              
                                              // { "heos": { "command": "player/get_groups", "result": "success", "message": "" },
                                              //   "payload": [{"name":"'group name 1'", "gid": "group id 1'",
                                              //                "players":[{"name":"player name 1","pid":"'player id1'","role":"player role 1 (leader or member)'"},
                                              //                           {"name":"player name 2","pid":"'player id2'","role":"player role 2 (leader or member)'"} 
                                              //                          ]
                                              //               },
                                              //               {"name":"'group name 2'","gid":"group id 2'",
                                              //                "players":[{"name":"player name ... 
                                              case 'get_groups' :
                                                  // bisherige groups leeren
                                                  var objs = $(this.statePath+'*.group_name');
                                                  for (var o=0; o<objs.length; o++) setState(objs[o], 'no group');
                                                  objs = $(this.statePath+'*.group_leader');
                                                  for (var o=0; o<objs.length; o++) setState(objs[o], false);
                                                  objs = $(this.statePath+'*.group_member');
                                                  for (var o=0; o<objs.length; o++) setState(objs[o], false);
                                                  objs = $(this.statePath+'*.group_pid');
                                                  for (var o=0; o<objs.length; o++) setState(objs[o], '');
                              
                                                  // payload mit den groups auswerten
                                                  if ( (jdata.hasOwnProperty('payload')) ) {
                                                      for (i=0; i<jdata.payload.length; i++) {
                                                          var group = jdata.payload[i];
                                                          var players = group.players;
                                                          // Player IDs addieren. Hinweis: "leader" ist nicht immer der 1.Playereintrag
                                                          group.pid = "";
                                                          for (var p=0; p<players.length; p++) {
                                                              if ( players[p].role == 'leader' )
                                                                  group.pid = players[p].pid + (group.pid.length>0?",":"") + group.pid;
                                                              else
                                                                  group.pid = group.pid + (group.pid.length>0?",":"") + players[p].pid;
                                                          }
                                                          this.setGroup(group);
                                                      }
                                                  }
                                                  break;
                                                  
                                          }     
                                          break;
                              
                              
                                      case 'system':
                                          switch (cmd) {
                                              case 'sign_in' :
                                                  this.log('signed in: '+jdata.heos.result);
                                                  break;
                                          }     
                                          break;
                                  }
                              
                              
                                  // an die zugehörigen Player weiterleiten
                                  if ( jmsg.hasOwnProperty('pid') ) {
                                      for (i=0; i<this.players.length; i++)
                                          if (jmsg.pid==this.players[i].heosPlayer.pid) {
                                              this.players[i].heosPlayer.parseResponse (jdata, jmsg, cmd_group, cmd);
                                              break;
                                          }
                                  }
                              
                                  
                              } catch(err) { this.logError( 'parseResponse: '+err.message+'\n '+response ); } }
                              
                              
                              // sucht die zur pid passenden player-Insanz
                              sendCommandToPlayer(objID, cmd ) {
                                 // aus der objID die pid holen
                                 // objID = javascript.1.heos.394645376.command
                                 objID = objID.split('.');
                                 var pid = objID[3];
                                 for (var p=0; p<this.players.length; p++ ) 
                                     if (this.players[p].pid == pid ) {
                                         this.players[p].heosPlayer.sendCommand( cmd );
                                         break;
                                     }
                              } 
                              
                              // Für die gefundenen HEOS Player entsprechende class HeosPlayer Instanzen bilden 
                              startPlayers() { try {
                                 this.stopPlayers();
                                  let i=0;
                                  for (i=0; i<this.players.length; i++) {
                                     this.players[i].heosPlayer = new HeosPlayer( this, this.players[i]  );
                                  }
                              
                                  // alle 30 Sekunden auf neue Player untersuchen
                                  this.getPlayersInterval = setInterval(() => {
                                      this.getPlayers();
                                  }, 30000);
                              
                              /*
                                  // Events setzen
                                  for (i=0; i<this.players.length; i++) {
                                      let heosPlayer = this.players[i].heosPlayer;
                                      heosPlayer.onHandler = [];
                                      // on-Event für command
                                      heosPlayer.onHandler.push( on({id: heosPlayer.statePath+'command', change: "any"}, (obj) => {
                                          this.sendCommandToPlayer( obj.id, obj.state.val ) 
                                      }));
                              
                                      // on-Event für volume (nur wenn ack=false, also über vis)
                                      heosPlayer.onHandler.push( on({id: heosPlayer.statePath+'volume', change:'ne', ack:false }, (obj) => {
                                          this.sendCommandToPlayer( obj.id, 'set_volume&level='+obj.state.val );
                                      }));
                                      // on-Event für mute (nur wenn ack=false, also über vis)
                                      heosPlayer.onHandler.push( on({id: heosPlayer.statePath+'mute', change: 'ne', ack:false}, (obj) => {
                                          this.sendCommandToPlayer( obj.id, 'set_mute&state='+ (obj.state.val === true ? 'on' : 'off') );
                                      }));
                                      // on-Event für play_mode_shuffle (nur wenn ack=false, also über vis)
                                      heosPlayer.onHandler.push( on({id: heosPlayer.statePath+'play_mode_shuffle', change: 'ne', ack:false}, (obj) => {
                                          this.sendCommandToPlayer( obj.id, 'set_play_mode&shuffle='+ (obj.state.val === true ? 'on' : 'off') );
                                      }));
                                      // on-Event für play_state (nur wenn ack=false, also über vis)
                                      heosPlayer.onHandler.push( on({id: heosPlayer.statePath+'play_state', change: 'ne', ack:false}, (obj) => {
                                          this.sendCommandToPlayer( obj.id, 'set_play_state&state='+ obj.state.val );
                                      }));
                              
                                      // on-Event für group-volume (nur wenn ack=false, also über vis)
                                      heosPlayer.onHandler.push( on({id: heosPlayer.statePath+'group_volume', change:'ne', ack:false }, (obj) => {
                                          // "id":"javascript.1.heos.-1746612370.group_volume"
                                          var id = obj.id.split('.');
                                          this.executeCommand( 'group/set_volume?gid='+id[3]+'&level='+obj.state.val );
                                      }));
                              
                                      // on-Event für group-mute (nur wenn ack=false, also über vis)
                                      heosPlayer.onHandler.push( on({id: heosPlayer.statePath+'group_mute', change:'ne', ack:false }, (obj) => {
                                          // "id":"javascript.1.heos.-1746612370.group_volume"
                                          var id = obj.id.split('.');
                                          this.executeCommand( 'group/set_mute?gid='+id[3]+'&state='+(obj.state.val===true ? 'on':'off') );
                                             }));
                                  }    
                              */
                              } catch(err) { this.logError( 'startPlayers: '+err.message ); } }
                              
                              //
                              stopPlayers() { try {
                                  if (this.getPlayersInterval!==undefined) {
                                      clearInterval(this.getPlayersInterval);
                                      this.getPlayersInterval = undefined;
                                  }
                                  for (let i=0; i<this.players.length; i++) {
                                      let heosPlayer = this.players[i].heosPlayer;
                                      if (heosPlayer)
                                          heosPlayer.stopPlayer();
                                      // player leeren
                                      this.players[i].heosPlayer = undefined;
                                  }
                              } catch(err) { this.logError( 'stopPlayers: '+err.message ); } }
                              
                              // setzen der Werte einer Group
                              setGroup(group) {
                                  if ( group.hasOwnProperty('pid') ) {    
                                      // in den Playern den Groupstatus setzen
                                      var pids = group.pid.split(',');
                                        
                                      for (var i=0; i<pids.length; i++) {
                                          this.setState(pids[i]+'.group_name', (group.hasOwnProperty('name')?group.name:''));
                                          this.setState(pids[i]+'.group_pid', group.pid);
                                          this.setState(pids[i]+'.group_leader', (i==0) && (pids.length>1) );
                                          this.setState(pids[i]+'.group_member', (pids.length>1));
                                      }
                                          
                                      if ( group.hasOwnProperty('gid') ) {   
                                          // volume und mute dazu holen
                                          this.executeCommand("group/get_volume?gid="+group.gid);
                                          this.executeCommand("group/get_mute?gid="+group.gid);
                                      }
                                  }
                              
                              }
                              
                              getPlayers() {
                                  if (this.state == stateCONNECTED) {
                                      this.msgs.push( 'heos://player/get_players\n' );
                                      this.sendNextMsg();
                                  }
                              }
                              
                              registerChangeEvents( b ) {
                                  if (this.state == stateCONNECTED) {
                                      if (b) this.msgs.push( 'heos://system/register_for_change_events?enable=on' );
                                      else this.msgs.push( 'heos://system/register_for_change_events?enable=off' );
                                      this.sendNextMsg();
                                  }
                              }
                              
                              signIn() {
                                  if (this.state == stateCONNECTED) {
                                      // heos://system/sign_in?un=heos_username&pw=heos_password
                                      this.msgs.push( 'heos://system/sign_in?un='+HEOS_USERNAME+'&pw='+HEOS_PASSWORD );
                                      this.sendNextMsg();
                                  }
                              }
                              
                              getMusicSources() {
                                  if (this.state == stateCONNECTED) {
                                      // heos://browse/get_music_sources
                                      this.msgs.push( 'heos://browse/get_music_sources' );
                                      this.sendNextMsg();
                                  }
                                  
                              }
                              
                              getGroups() {
                                  if (this.state == stateCONNECTED) {
                                      // heos://group/get_groups
                                      this.msgs.push( 'heos://group/get_groups' );
                                      this.sendNextMsg();
                                  }
                                  
                              }
                              
                              startHeartbeat(){
                                  this.logDebug("start heartbeat interval");
                                  this.heartbeatInterval = setInterval(() => {
                                      this.logDebug("heartbeat")
                                      this.msgs.push( 'heos://system/heart_beat' );
                                      this.sendNextMsg();
                                      if (this.heartbeatTimeout==undefined) {
                                          this.heartbeatTimeout = setTimeout(() => {
                                              this.log("heartbeat timeout");
                                              this.reconnect();
                                          }, 60000);
                                      }
                                  }, 15000);
                              }
                              
                              resetHeartbeatTimeout(){
                                  this.logDebug("reset heartbeat timeout");
                                  if (this.heartbeatTimeout!==undefined) {
                                      clearTimeout(this.heartbeatTimeout);
                                      this.heartbeatTimeout = undefined;
                                  }
                              }
                              
                              stopHeartbeat(){
                                  this.logDebug("stop heartbeat interval");
                                  if (this.heartbeatInterval!==undefined) {
                                      clearInterval(this.heartbeatInterval);
                                      this.heartbeatInterval = undefined;
                                  }
                                  this.resetHeartbeatTimeout();
                              }
                              
                              browse(sid) {
                                  if (this.state == stateCONNECTED) {
                                      // heos://browse/browse?sid=source_id
                                      this.msgs.push( 'heos://browse/browse?sid='+sid );
                                      this.sendNextMsg();
                                  }
                                  
                              }
                              
                              
                              sendNextMsg () {
                                  if (this.msgs.length>0) {
                                      var msg = this.msgs.shift();
                                      this.sendMsg(msg);
                                  }
                              }
                              
                              // Nachricht an player senden
                              sendMsg (msg) {
                                  this.net_client.write(msg + "\n");
                                  this.logDebug("data sent: "+ msg);
                              }
                              
                              
                              } // end of class Heos
                              
                              /********************
                               * class HeosPlayer
                               ********************/
                              
                              class HeosPlayer  {
                              
                              logDebug(msg) { console.debug('[HeosPlayer '+this.pid+'] '+msg); }
                              log(msg) { console.log('[HeosPlayer '+this.pid+'] '+msg); }
                              logWarn(msg) { console.warn('[HeosPlayer '+this.pid+'] '+msg); }
                              logError(msg) { console.error('[HeosPlayer '+this.pid+'] '+msg); }
                              
                              constructor(heos, player) {
                                  this.heos = heos;
                                  this.ip = player.ip;
                                  this.name = player.name;
                                  this.pid = player.pid;
                                  this.model = player.model;
                                  this.serial = player.serial;
                                  this.onHandler = [];
                              
                              //    this.statePath = 'javascript.'+instance+'.heos.'+this.ip.replace(/\./g,'_')+".";
                                  this.statePath = '0_userdata.0.heos.'+this.pid+".";
                              
                                  this.log('creating HEOS player with pid '+this.pid+' ('+this.ip+')');
                              
                                  this.states = [
                                      { id:"connected",                   common:{name:"Verbunden?"} },
                                      { id:"command",                     common:{name:"Befehl an Heos Player senden"} },
                                      { id:"ip",                          common:{name:"IP-Adresse", write:false} },
                                      { id:"pid",                         common:{name:"Player-ID", write:false} },
                                      { id:"name",                        common:{name:"Name des Players", write:false} },
                                      { id:"model",                       common:{name:"Modell des Players", write:false} },
                                      { id:"serial",                      common:{name:"Seriennummer des Players", write:false} },
                                      { id:"last_error",                  common:{name:"Letzte Fehler", write:false} },
                                      { id:"volume",                      common:{name:"Aktuelle Lautstärke"} },
                                      { id:"mute",                        common:{name:"Mute aktiviert?"} },
                                      { id:"play_state",                  common:{name:"Aktueller Wiedergabezustand"} },
                                      { id:"play_mode_repeat",            common:{name:"Wiedergabewiederholung"} },
                                      { id:"play_mode_shuffle",           common:{name:"Zufällige Wiedergabe"} },
                                      { id:"now_playing_media_type",      common:{name:"Aktuelle Wiedergabemedium", write:false} },
                                      { id:"now_playing_media_song",      common:{name:"Aktuelles Lied", write:false} },
                                      { id:"now_playing_media_station",   common:{name:"Aktuelle Station", write:false} },
                                      { id:"now_playing_media_album",     common:{name:"Aktuelle Wiedergabemedium", write:false} },
                                      { id:"now_playing_media_artist",    common:{name:"Aktueller Artist", write:false} },
                                      { id:"now_playing_media_image_url", common:{name:"Aktuelles Coverbild", write:false} },
                                      { id:"now_playing_media_album_id",  common:{name:"Aktuelle Album-ID", write:false} },
                                      { id:"now_playing_media_mid",       common:{name:"Aktuelle mid", write:false} },
                                      { id:"now_playing_media_qid",       common:{name:"Aktuelle qid", write:false} },
                                      { id:"cur_pos",                     common:{name:"lfd. Position"} },
                                      { id:"duration",                    common:{name:"Dauer", write:false} },
                                      { id:"cur_pos_MMSS",                common:{name:"lfd. Position MM:SS"} } ,
                                      { id:"duration_MMSS",               common:{name:"Dauer MM:SS", write:false} },
                                      { id:"group_leader",                common:{name:"Ist Gruppenleiter", write:false} },
                                      { id:"group_member",                common:{name:"Ist Gruppenmitglied", write:false} },
                                      { id:"group_pid",                   common:{name:"PlayerIDs in der Gruppe", write:false} },
                                      { id:"group_volume",                common:{name:"Lautstärke wenn Gruppen-Leiter"}},
                                      { id:"group_name",                  common:{name:"Name der Gruppe", write:false} } ,
                                      { id:"group_mute",                  common:{name:"Gruppe gemutet?"} },
                                      { id:"script_version",              common:{name:"Installierte Script-Version", write:false} }
                                  ];
                                  // beim 1.Start die states erzeugen
                                  if ( !this.existState("script_version") || this.getState('script_version').val!=SCRIPTVERSION ) {
                                      this.installed = false;
                                      this.logWarn('creating HEOS player states for version '+SCRIPTVERSION+', please start script again');
                                      // states erzeugen
                                      for (var s=0; s<this.states.length; s++) {
                                          this.setState( this.states[s].id);
                                      }
                                      this.setState('script_version', SCRIPTVERSION);
                                  }
                                  else {
                                      // Initialisierung der States nach 5 Sek
                                      setTimeout(() => {
                                          this.setState('ip',this.ip);
                                          this.setState('name',this.name);
                                          this.setState('pid',this.pid);
                                          this.setState('model',this.model);
                                          this.setState('serial',this.serial);
                                          this.sendCommand('get_play_state|get_play_mode|get_now_playing_media|get_volume');
                                      }, 5000 );
                                      setTimeout(() => {
                                          this.startPlayer();
                                      }, 10000 );
                                      this.installed = true;
                                  }
                              }
                              
                              // über den $-Operator nachsehen, ob der state bereits vorhanden ist
                              // getState().notExists geht auch, erzeugt aber Warnmeldungen!
                              existState(id) {
                                  return ( $(this.statePath+id).length==0?false:true);
                              }
                              
                              // wrapper
                              getState(id) {
                                  return getState(this.statePath + id);
                              }
                              
                              // wie setState(), setzt aber den statePath davor und überpüft aber ob der state vorhanden ist und erzeugt ihn,
                              // wenn er noch nicht da ist
                              setState(id,value) {
                                  if ( !this.existState(id) ) this.createState(id, value, undefined);
                                  else setState( this.statePath + id, value);
                              }
                              
                              // wie createState, setzt aber noch den statePath davor und schaut im states-Array nach, ob dort common-Angaben
                              // vorhanden sind (wenn der common-Parameter leer ist)
                              createState(id, value, common) {
                                  if ( !this.existState(id) ) {
                                      if (common===undefined) {
                                          // id im states-Array suchen
                                          for (var i=0; i<this.states.length; i++) { 
                                              if (this.states[i].id==id) {
                                                  if (this.states[i].hasOwnProperty('common'))
                                                      common = this.states[i].common;
                                                 break;
                                              }   
                                          }
                                      }
                                      if ( (typeof value === 'undefined') && (common.hasOwnProperty('def'))) value = common.def;
                                      // unter "0_userdata.0"
                                      let obj = {};
                                      obj.type = 'state';
                                      obj.native = {};
                                      obj.common = common;
                                      setObject(this.statePath + id, obj, (err) => {
                                              if (err) {
                                                  this.log('cant write object for state "' + this.statePath + id + '": ' + err);
                                              } else { 
                                                  this.log('state "' + this.statePath + id + '" created');
                                              }
                                      });
                                    
                                      // value zeitversetzt setzen        
                                      setTimeout( setState, 3000, this.statePath + id, value );
                                  }
                              }
                              
                              
                              startPlayer() { try {
                                  this.log('starting HEOS player with pid '+this.pid+' ('+this.ip+')');
                                  this.onHandler = [];
                                  // on-Event für command
                                  this.onHandler.push( on({id: this.statePath+'command', change: "any"}, (obj) => {
                                      this.executeCommand( obj.id, obj.state.val ) 
                                  }));
                              
                                  // on-Event für volume (nur wenn ack=false, also über vis)
                                  this.onHandler.push( on({id: this.statePath+'volume', change:'ne', ack:false }, (obj) => {
                                      this.executeCommand( obj.id, 'set_volume&level='+obj.state.val );
                                  }));
                                  // on-Event für mute (nur wenn ack=false, also über vis)
                                  this.onHandler.push( on({id: this.statePath+'mute', change: 'ne', ack:false}, (obj) => {
                                      this.executeCommand( obj.id, 'set_mute&state='+ (obj.state.val === true ? 'on' : 'off') );
                                  }));
                                  // on-Event für play_mode_shuffle (nur wenn ack=false, also über vis)
                                  this.onHandler.push( on({id: this.statePath+'play_mode_shuffle', change: 'ne', ack:false}, (obj) => {
                                      this.executeCommand( obj.id, 'set_play_mode&shuffle='+ (obj.state.val === true ? 'on' : 'off') );
                                  }));
                                  // on-Event für play_state (nur wenn ack=false, also über vis)
                                  this.onHandler.push( on({id: this.statePath+'play_state', change: 'ne', ack:false}, (obj) => {
                                      this.executeCommand( obj.id, 'set_play_state&state='+ obj.state.val );
                                  }));
                              
                                  // on-Event für group-volume (nur wenn ack=false, also über vis)
                                  this.onHandler.push( on({id: this.statePath+'group_volume', change:'ne', ack:false }, (obj) => {
                                      // "id":"javascript.1.heos.-1746612370.group_volume"
                                      var id = obj.id.split('.');
                                      this.executeCommand( 'group/set_volume?gid='+id[3]+'&level='+obj.state.val );
                                  }));
                              
                                  // on-Event für group-mute (nur wenn ack=false, also über vis)
                                  this.onHandler.push( on({id: this.statePath+'group_mute', change:'ne', ack:false }, (obj) => {
                                      // "id":"javascript.1.heos.-1746612370.group_volume"
                                      var id = obj.id.split('.');
                                      this.heos.executeCommand( 'group/set_mute?gid='+id[3]+'&state='+(obj.state.val===true ? 'on':'off') );
                                  }));
                              
                                  this.setState('connected', true);
                              } catch(err) { this.logError( 'startPlayer: '+err.message ); } }
                              
                              // sucht die zur pid passenden player-Insanz
                              executeCommand(objID, cmd ) {
                                  // aus der objID die pid holen
                                  // objID = javascript.1.heos.394645376.command
                                  objID = objID.split('.');
                                  var pid = objID[3];
                                  if (this.pid == pid ) {
                                      this.sendCommand( cmd );
                                  }
                               } 
                               
                              
                              stopPlayer() { try {
                                  this.log('stopping HEOS player with pid '+this.pid+' ('+this.ip+')');
                              
                                  // events unsubcribe
                                  for (let i=0; i<this.onHandler.length; i++) {
                                      if (this.onHandler[i]!==undefined) unsubscribe(this.onHandler[i]);
                                  }
                                  this.onHandler = [];
                                  // connected zurücksetzen
                                  this.setState( "connected", false );
                              } catch(err) { this.logError( 'stopPlayer: '+err.message ); } }
                              
                              /** wandelt einen sek Wert in MM:SS Darstellung um
                               **/
                              toMMSS (s) {
                                  var sec_num = parseInt(s, 10); 
                                  var minutes = Math.floor(sec_num  / 60);
                                  var seconds = sec_num - (minutes * 60);
                                  if (seconds < 10) {seconds = "0"+seconds;}
                                  return minutes+':'+seconds;
                              }
                              
                              setLastError(err) { try {
                                  this.logWarn(err);
                                  let val = this.getState('last_error').val;
                                  let lines = val.splitt('\n');
                                  if ( lines.length > 4)
                                      lines.pop();
                                  lines.unshift(err+'\n');
                                  this.setState('last_error', lines.toString());
                              } catch(e) { this.logError( 'setLastError: '+e.message ); } }
                              
                              
                              /** Auswertung der empfangenen Daten
                               **/
                              parseResponse (jdata, jmsg, cmd_group, cmd) { try {
                                  switch (cmd_group) {
                                      case 'event':
                                          switch (cmd) {
                                              case 'player_playback_error' :
                                                  this.setLastError(jmsg.error.replace(/_/g,' '));
                                                  break;
                                              case 'player_state_changed' :
                                                  this.setState("play_state", jmsg.state);
                                                  break;
                                              case 'player_volume_changed' :
                                                  this.setState("volume", jmsg.level );
                                                  this.setState("mute", (jmsg.mute=='on' ? true : false) );
                                                  break;
                                              case 'player_repeat_mode_changed' :
                                                  this.setState("play_mode_shuffle", jmsg.shuffle );
                                                  break;
                                              case 'player_shuffle_mode_changed' :
                                                  this.setState("play_mode_repeat", jmsg.repeat );
                                                  break;
                                              case 'player_now_playing_changed' :
                                                  this.sendCommand('get_now_playing_media');
                                                  break;
                                              case 'player_now_playing_progress' :
                                                  this.setState("cur_pos", jmsg.cur_pos / 1000);
                                                  this.setState("cur_pos_MMSS", this.toMMSS(jmsg.cur_pos / 1000));
                                                  this.setState("duration", jmsg.duration / 1000);
                                                  this.setState("duration_MMSS", this.toMMSS(jmsg.duration / 1000));
                                                  break;
                                          }        
                                          break;
                                          
                                          
                                      case 'player':
                                          switch (cmd) {
                                              case 'set_volume' :
                                              case 'get_volume' :
                                                  if ( getState(this.statePath+"volume").val != jmsg.level)
                                                      this.setState("volume", jmsg.level);
                                                  break;
                                              case 'set_mute' :
                                              case 'get_mute' :
                                                  this.setState("mute", (jmsg.state=='on' ? true : false) );
                                                  break;
                                              case 'set_play_state' :
                                              case 'get_play_state' :
                                                  this.setState("play_state", jmsg.state);
                                                  break;
                                              case 'set_play_mode' :
                                              case 'get_play_mode' :
                                                  this.setState("play_mode_repeat", jmsg.repeat);
                                                  this.setState("play_mode_shuffle", (jmsg.shuffle=='on'?true:false) );
                                                  break;
                                              case 'get_now_playing_media' :
                                                  this.setState("now_playing_media_type", jdata.payload.type);
                                                  if (jdata.payload.hasOwnProperty('song'))
                                                      this.setState("now_playing_media_song", jdata.payload.song);
                                                  if (jdata.payload.hasOwnProperty('album'))
                                                      this.setState("now_playing_media_album", jdata.payload.album);
                                                  if (jdata.payload.hasOwnProperty('album_id'))
                                                      this.setState("now_playing_media_album_id", jdata.payload.album_id);
                                                  if (jdata.payload.hasOwnProperty('artist'))
                                                      this.setState("now_playing_media_artist", jdata.payload.artist);
                                                  if (jdata.payload.hasOwnProperty('image_url'))
                                                      this.setState("now_playing_media_image_url", jdata.payload.image_url);
                                                  if (jdata.payload.hasOwnProperty('mid'))
                                                      this.setState("now_playing_media_mid", jdata.payload.mid);
                                                  if (jdata.payload.hasOwnProperty('qid'))
                                                      this.setState("now_playing_media_qid", jdata.payload.qid);
                                                  if (jdata.payload.hasOwnProperty('station')) {
                                                      if (jdata.payload.type=='station') {
                                                          this.setState("now_playing_media_station", jdata.payload.station);
                                                      } else {
                                                          this.setState("now_playing_media_station", null);
                                                      }
                                                  }  
                                                  break;
                                          }
                                          break;
                                  } // switch
                              
                              
                              } catch(err) { this.logError( 'parseResponse: '+err.message ); } }
                               
                              /** cmd der Form "cmd&param"  werden zur msg heos+cmd+pid+&param aufbereitet
                                  cmd der Form "cmd?param"  werden zur msg heos+cmd+?param aufbereitet
                               **/
                              commandToMsg (cmd) {
                                  var param = cmd.split('&');
                                  cmd = param[0];
                                  if ( param.length > 1 ) param='&'+param[1]; else param=''; 
                                  var cmd_group = 'player';
                              
                                  switch (cmd) {
                                      case 'get_play_state':
                                      case 'get_play_mode':
                                      case 'get_now_playing_media':
                                      case 'get_volume':
                                      case 'play_next':
                                      case 'play_previous':
                                      case 'set_mute':       // &state=on|off        
                                      case 'set_volume':     // &level=1..100   
                                      case 'volume_down':    // &step=1..10   
                                      case 'volume_up':      // &step=1..10
                                      case 'set_play_state': // &state=play|pause|stop
                                      case 'set_play_mode':  // &repeat=on_all|on_one|off  shuffle=on|off
                                          break;
                              
                                      // browse            
                                      case 'play_preset':    // heos://browse/play_preset?pid=player_id&preset=preset_position
                                          cmd_group = 'browse';
                                          break;
                                      case 'play_stream':    // heos://browse/play_stream?pid=player_id&url=url_path
                                          cmd_group = 'browse';
                                          break;
                                          
                                  }        
                                  return 'heos://'+cmd_group+'/'+cmd+'?pid=' + this.pid + param;
                              }
                              
                              /** Nachricht (command) an player senden
                                  es sind auch mehrere commands, getrennt mit | erlaubt
                                  bsp: set_volume&level=20|play_preset&preset=1
                               **/
                              sendCommand (command) {
                                  if ( !this.installed ) return;
                              
                                  var cmds = command.split('|');
                                  for (var c=0; c<cmds.length; c++) {
                                      this.heos.msgs.push( this.commandToMsg(cmds[c]) );
                                  }
                                  this.heos.sendNextMsg();
                              }
                              
                              
                              } // end of HeosPlayer
                              
                              
                              /* -----
                                 Heos
                                 ----- */
                              // Heos Instanz erzeugen und verbinden   
                              var heos = new Heos( );
                              heos.connect();
                              
                              // wenn das Script beendet wird, dann auch die Heos Instanz beenden
                              onStop(function () { 
                                  heos.disconnect(); 
                              }, 0 );
                              
                              UhulaU Offline
                              UhulaU Offline
                              Uhula
                              schrieb am zuletzt editiert von
                              #101

                              @withstu Ja, dass der keep alive nicht funktioniert, habe ich auch schon festgestellt. Vermutlich haben die Player keinen entsprechenden Server implementiert. Den Heartbeat und die anderen Änderungen sehe ich mir an und übernehme sie, danke schön!

                              Uhula - Leise und Weise
                              Ex: ioBroker on Gigabyte NUC Proxmox

                              1 Antwort Letzte Antwort
                              0
                              • W Offline
                                W Offline
                                withstu
                                schrieb am zuletzt editiert von
                                #102

                                @Uhula Mein Script hat gerade mein ioBroker lahmgelegt und im Millisekundentakt die Geräte muted/unmuted. Ursache war, dass in den zwei setState Funktionen das ack fehlt:

                                // wie setState(), setzt aber den statePath davor und überpüft aber ob der state vorhanden ist und erzeugt ihn,
                                // wenn er noch nicht da ist
                                setState(id,value) {
                                    if ( !this.existState(id) ) this.createState(id, value, undefined);
                                    else setState( this.statePath + id, value, true);
                                }
                                

                                Btw. ich habe die Events sources_changed/players_changed/groups_changed/group_volume_changed angepasst/hinzugefügt. Dadurch kann ich mir das getPlayers alle 30 Sekunden sparen:

                                var jmsg;
                                var i;
                                var jdata = JSON.parse(response);
                                if ( !jdata.hasOwnProperty('heos') || !jdata.heos.hasOwnProperty('command')) 
                                   return;
                                [...]
                                        case 'event':
                                            switch (cmd) {
                                                case 'sources_changed':
                                                    this.getMusicSources();
                                                    break;
                                                case 'players_changed':
                                                    this.getPlayers();
                                                    break;
                                                case 'groups_changed' :
                                                    this.getGroups();
                                                    break;
                                                case 'group_volume_changed' :
                                                    // "heos": {"command": "event/group_volume_changed ","message": "gid='group_id'&level='vol_level'&mute='on_or_off'"}
                                                    if ( jmsg.hasOwnProperty('gid') ) {
                                                        if ( jmsg.hasOwnProperty('level') ) {
                                                            this.setState( jmsg.gid+'.group_volume', jmsg.level);
                                                        }
                                                        if ( jmsg.hasOwnProperty('mute') ) {
                                                            this.setState( jmsg.gid+'.group_mute', (jmsg.mute=='on' ? true : false));
                                                        }
                                                    }
                                                    break;
                                            }
                                
                                1 Antwort Letzte Antwort
                                0
                                • B Offline
                                  B Offline
                                  Brati
                                  schrieb am zuletzt editiert von Brati
                                  #103

                                  Kann es sein, dass HEOS gerade down ist? Script läuft nicht und in der App kann ich mich auch nicht einloggen...

                                  ###Edit###

                                  Der AVR brauchte ein Update, jetzt läuft wieder alles...

                                  CCU2 - 46 Geräte, ioBroker auf Intel NUC (DN2820FYKH) mit Tab als Frontend, Projekt Gartenhaus mit HM

                                  W 1 Antwort Letzte Antwort
                                  0
                                  • B Brati

                                    Kann es sein, dass HEOS gerade down ist? Script läuft nicht und in der App kann ich mich auch nicht einloggen...

                                    ###Edit###

                                    Der AVR brauchte ein Update, jetzt läuft wieder alles...

                                    W Offline
                                    W Offline
                                    withstu
                                    schrieb am zuletzt editiert von
                                    #104

                                    @Brati Ja ich habe grad auch wieder Probleme mich einzuloggen...

                                    @Uhula Ich habe in den letzten Tagen noch ein paar Bugs gefixt und das get_players Verhalten umgestellt. Jetzt werden einzelne Player disconnected und nicht mehr alle.

                                    /****************************
                                    ### HEOS Script for ioBroker
                                    
                                    #### Funktion
                                    
                                    Das Script stellt zwei JS Klassen zur Steuerung der Denon HEOS Geräte zur Verfügung. Die class Heos dient dabei dem Erkennen und Steuern der HEOS Geräte. Die class HeosPlayer dient der Interpretation der Antworten der Heos Geräte.
                                     
                                    Das Script muss lediglich gestartet werden, es sucht dann im Netzwerk nach HEOS Playern und erzeugt in der aktuellen Javascript-Instanz einen Subeintrag für die Favoriten und je einen Subeintrag für jeden gefundenen Player. Dort wiederum werden State-Variablen zur Aufnahme der Player-Daten angelegt.
                                     
                                    Das Script erzeugt auch on-Handler um auf Steuerungen über vis und andere Scripte an den State-Variablen reagieren zu können.
                                     
                                    Beim Beenden des Scripts werden alle Verbindungen und on-Handler geschlossen.
                                    
                                    Das Script ermöglicht die HEOS Player zu steuern, es soll aber nicht die HEOS App ersetzen. 
                                     
                                      
                                    #### Wichtig!
                                    
                                    * Im Javascript-Adapter muss in der Instanz-Konfiguration "node-ssdp" mit angegeben werden! Alternativ kann die Bibliothek auch komplett über "npm install node-ssdp" installiert werden.
                                    * Wenn mit Favoriten (Presets) gearbeitet werden soll, müssen die HEOS-Konto Logindaten in den beiden Konstanten HEOS_USERNAME (EmailAdr) und HEOS_PASSWORD hier im Script in der Konfiguration angegeben werden (so, wie in der HEOS App)!
                                    * Eine Konfiguration von IP-Adressen und Player-IDs ist nicht notwendig!
                                    * U.U. kann es notwendig sein das Script nach dem 1.Start zu beenden und nach 30 Sek erneut zu starten, da die Neuanlage der ioBroker States etwas Zeit benötigt. Warnungen sind dabei zu ignorieren. ;-)
                                    
                                    #### Updates
                                     
                                    ##### v2.0 13.03.2020
                                    * das Script überprüft nun, ob alle notwendigen states für Heos und HeosPlayer im ioBroker korrekt erzeugt wurden und gibt eine Warnung mit Script-Neustartaufforderung aus, wenn die states nicht vorhanden sind. Dieses ist notwendig, da das Anlegen von states asynchron erfolgt und einige ms dauern kann
                                    * (WICHTIG!) die states werden nicht mehr in der aktuellen Javascript-Instanz und auch nicht mehr unter der IP des Heos-Players gespeichert, sondern Javascript-Instanzunabhängig unter 0_userdata.0 und seiner Player-ID (pid), da diese sich nicht ändert. Die IP hingegen u.U. schon. Also statt "javascript.X.heos.192_168_2_43" nun als "0_userdata.0.heos.12345678". Wer bereits auf die alten Werte im VIS zugreift, muss dieses im VIS anpassen!  
                                    * es wurden Befehle/states für die Gruppensteuerung der Heos-Player hinzugefügt. Beim Start werden die Gruppen ausgelesen und in den Player-states des Gruppenleiters (der erste genannte Player) gespeichert. Über dessen states kann die Gruppe gesteuert werden: 
                                        group_volume: dient der Steuerung der Gruppen-Lautstärke
                                        group_mute: dient der Steuerung der Gruppen-Mutes
                                        group_leader: ist true, wenn der Player der Gruppenleiter ist
                                        group_member: ist true, wenn der Player Mitglied einer Gruppe ist
                                    * alle 60 Sek wird überprüft, ob noch alle HEOS-Player erreichbar sind bzw. neue/reconnectete hinzugekommen sind. Neue/reconnectete werden der Liste der HEOS-Player hinzugefügt, fehlende werden gestoppt. Da hierzu das HEOS-Netzwerk gefragt wird (get_players Meldung), kann es bis zu 5 Min dauern bis Änderungen erkannt werden. Der neue HEOS-Player-State "connected" wird entsprechend gesetzt
                                    * jeder HEOS-Player hat einen neuen state "connected", dieser wird nach erfolgreichem Start auf true gesetzt, bei korrektem Beenden des Scripts auf false
                                    * diverse Filter/Korrekturen aus dem Forum übernommen, vielen dank an withstu
                                      
                                      
                                     #### class Heos
                                     
                                     Diese Basisklasse dient 
                                    
                                    * dem Erkennen von HEOS Geräten (Playern). Das Erkennen der HEOS Geräte findet via UPD unter Nutzung von node-ssdp statt. node-ssdp muss dazu im Javascript-Adapter in der Konfiguration mit angegeben werden!
                                    * der Kommunikation über TelNet. Es wird genau eine TelNet Verbindung zu einem HEOS Gerät aufgebaut, dieses reicht die Sendungen und Antworten zentral weiter
                                    * der Ermittlung von Favoriten (Presets). Für jeden Favorit wird ein ioBroker State heos.presets.n mit entsprechenden Sub-States erzeugt. Zur Nutzung der Presets ist ein SignIn notwendig, hierzu müssen HEOS_USERNAME und HEOS_PASSWORD gesetzt werden.
                                    * dem Instanziieren der class HeosPlayer je HEOS Gerät. Je HEOS Gerät wird ein ioBroker-State mit der IP-Adresse des Players erzeugt, dieser State erhält diverse Sub-States
                                      
                                    ##### State-Variable: Heos
                                     
                                    Werden angelegt unter "0_userdata.0.heos."
                                    
                                    * connected: true wenn die Verbindung zu mindestens einem HEOS-Player besteht
                                    * script_version: Version des Scripts, dient dazu Script-Updates zu erkennen
                                    * last_error: Letzter Fehlertext
                                    * command(cmd): Hierüber können der Heos-Klasse Befehle übergeben werden, für cmd gilt:
                                        "connect": Verbindung zu HEOS aufbauen bzw. erneut aufbauen (praktisch ein reset)
                                        "disconnect": Verbindung zu HEOS beenden
                                        "load_presets": lädt die Favoriten neu
                                      
                                        "group/set_group?pid=<pid1>,<pid2>,...": setzen einer Gruppe, die pids sind die der Player wie sie unter den Objekten für jeden Player abgelegt sind. Bsp: "group/set_group?pid=12345678,12345679". 
                                     
                                       "group/set_group?pid=<pid1>" : hebt die Gruppierung des Players mit der pid1 wieder auf. Bsp: "group/set_group?pid=12345678"
                                       "...": alle anderen cmd-Werte werden "as is" versucht an HEOS zu senden
                                     
                                      
                                    * presets.<n>: Hierunter werden die Favoriten, Presets angelegt. Je Favorit ein Unterordner n=1 bis Anzahl. Darunter befinden sich dann die States, welche den Inhalt der Favoriten aufnehmen:
                                     
                                    * image_url (read): Bild des Favoriten 
                                    * name (read): Name des Favoriten
                                    * playable (read): Spielbar? true/false
                                    * type (read): Typ des Favoriten (station, ...)
                                     
                                        
                                       
                                     #### class HeosPlayer 
                                     
                                     Diese Klasse dient
                                    * der Steuerung genau eines HEOS Gerätes. Hierzu wird die zentrale Telnet Verbindung der class Heos genutzt.
                                    * dem Erzeugen der ioBroker-States zum Speichern der Geräte-Werte
                                    * der Auswertung von Antworten der HEOS Geräte und Zuweisung an die entsprechenden ioBroker-States
                                     
                                    ##### State-Variable
                                    
                                    Werden angelegt unter "0_userdata.0.heos.<pid>"
                                    
                                    * connected : true, wenn eine Verbindung besteht, sonst false
                                    * command(cmd): Hierüber können dem Heos-Player Befehle übergeben werden. Diese werden im Klartext in die State-Variable geschrieben. Getrennt durch das | Zeichen können mehrere hintereinander eingetragen werden. Bsp: Setzen der Lautstärke auf 20 und Abspielen des 1.Favoriten "set_volume&level=20|play_preset&preset=1|set_play_state&state=play". Folgende cmd sind erlaubt:
                                    
                                      "set_volume&level=0|1|..|100" : Setzt die gewünschte Lautstärke 
                                      "set_play_state&state=play|pause|stop" : Startet und stoppt die Wiedergabe
                                      "set_play_mode&repeat=on_all|on_one|off&shuffle=on|off": Setzt Wiederholung und Zufallsweidergabe
                                      "set_mute&state=on|off" : Stumm schalten oder nicht
                                      "volume_down&step=1..10" : Lautstärke verringern um   
                                      "volume_up&step=1..10"  : Lauststäre erhöhen um
                                      "play_next"  : Nächsten Titel spielen
                                      "play_previous" : Vorherigen Titel spielen
                                      "play_preset&preset=1|2|..|n" : Favorit Nr n abspielen
                                      "play_stream&url=url_path" : URL-Stream abspielen
                                     
                                    * cur_pos (read) : lfd. Position der Wiedergabe in [Sek]
                                    * cur_pos_MMSS (read) : lfd. Position der Wiedergabe im Format MM:SS
                                    * duration (read) : Länge des aktuellen Titels in [Sek]
                                    * duration_MMSS (read) : Länge des aktuellen Titels im Format MM:SS
                                    * ip (read): IP-Adresse des HEOS Players
                                    * last_error (read): Text des letzten Fehlers, der vom Player gesendet wurde. Wird bei jedem neuen Befehl zurückgesetzt. Wenn man bspw. versucht eine Wiedergabe über Amazon o.ä. zu starten und es gibt aber keine Verbindung dahin, steht hier der Fehlertext drin
                                    * model (read): Modell des HEOS Players
                                    * mute (read write): Boolsche Variable, die anzeigt ob der Player gemutet (Stumm geschaltet) wurde. Hierüber kann auch ein mute gesetzt werden
                                    * name (read): Name des HEOS Players
                                    * now_playing_media_... (read) : Eine Gruppe von States, in welchen Infos über den aktuellen Titel gespeichert werden, wird automatisch aktualisiert. 
                                    * pid (read): Player-ID des HEOS Players
                                    * play_mode_repeat (read write) : Repeat-Modus der Wiedergabe. Mögliche Werte: on_all|on_one|off  
                                    * play_mode_shuffle (read write) : Zufallswiedergabe true/false
                                    * play_state (read write): Zustand des Players. Mögliche Werte play|pause|stop
                                    * serial (read): Seriennummmer des HEOS Players
                                    * volume (read write): Lautstärke im Bereich 0 - 100. 
                                    * group_leader : Ist Gruppenleiter
                                    * group_member : Ist Gruppenmitglied
                                    * group_pid : Player pids in der Gruppe
                                    * group_volume : Lautstärke wenn Gruppen-Leiter
                                    * group_name : Name der Gruppe
                                    * group_mute : Gruppe gemutet?
                                    
                                     
                                    #### Weiterführende Links
                                    
                                    * HEOS CLI Protokoll: http://rn.dmglobal.com/euheos/HEOS_CLI_ProtocolSpecification.pdf
                                    * http://forum.iobroker.net/viewtopic.php?f=30&t=5693&p=115554#p115554
                                     
                                     
                                    (c) Uhula, MIT License, no warranty, use on your own risc
                                     
                                    */
                                    
                                    /****************************
                                     * Konfiguration
                                     ****************************/
                                    
                                    const HEOS_USERNAME = '';
                                    const HEOS_PASSWORD = '';
                                    
                                    const HEARTBEAT_INTERVAL = 15000;
                                    const HEARTBEAT_TIMEOUT = 60000;
                                    
                                    
                                    /****************************
                                     * ab hier nichts mehr ändern ;-) 
                                     ****************************/
                                    
                                    var net = require('net');
                                    
                                    const stateDISCONNECTED = 0;
                                    const stateSEARCHING = 1;
                                    const stateCONNECTING = 2;
                                    const stateCONNECTED = 3;
                                    const SCRIPTVERSION = '2.0/2020-03-21';
                                    
                                    /********************
                                     * class Heos
                                     ********************/
                                    class Heos {
                                    
                                        logDebug(msg) { console.debug('[Heos] ' + msg); }
                                        log(msg) { console.log('[Heos] ' + msg); }
                                        logWarn(msg) { console.warn('[Heos] ' + msg); }
                                        logError(msg) { console.error('[Heos] ' + msg); }
                                    
                                        constructor() {
                                            this.init();
                                            this.states = [
                                                { id: "command", common: { name: "Kommando für HEOS Aufrufe" } },
                                                { id: "connected", common: { name: "Verbunden?", write: false, def: false } },
                                                { id: "last_error", common: { name: "Letzte Fehler", write: false } },
                                                { id: "script_version", common: { name: "Installierte Script-Version", write: false } }
                                            ];
                                            // beim 1.Start nur die States erzeugen
                                            if (!this.existState("script_version") || (this.getState('script_version').val != SCRIPTVERSION)) {
                                                this.installed = false;
                                                this.logWarn('creating HEOS states for version ' + SCRIPTVERSION + ', please start script again');
                                                // Anlage der States 
                                                for (var s = 0; s < this.states.length; s++) {
                                                    this.setState(this.states[s].id);
                                                }
                                                this.setState('script_version', SCRIPTVERSION);
                                            }
                                            else {
                                                this.installed = true;
                                                on({ id: this.statePath + 'command', change: "any" }, (obj) => {
                                                    this.executeCommand(obj.state.val);
                                                });
                                            }
                                        }
                                    
                                    
                                    
                                        // Initialisierung
                                        init() {
                                            this.statePath = '0_userdata.0.heos.';
                                            this.players = {};
                                            this.heartbeatInterval = undefined;
                                            this.heartbeatTimeout = undefined;
                                            this.ssdpSearchInterval = undefined;
                                            this.net_client = undefined;
                                            this.nodessdp_client = undefined;
                                    
                                            this.ip = '';
                                            this.msgs = [];
                                            this.lastResponse = '';
                                            this.state = stateDISCONNECTED;
                                            this.unfinishedResponses = '';
                                            this.ssdpSearchTargetName = 'urn:schemas-denon-com:device:ACT-Denon:1';
                                        }
                                    
                                    
                                        // über den $-Operator nachsehen, ob der state bereits vorhanden ist
                                        // getState().notExists geht auch, erzeugt aber Warnmeldungen!
                                        existState(id) {
                                            return ($(this.statePath + id).length == 0 ? false : true);
                                        }
                                    
                                        // wrapper
                                        getState(id) {
                                            return getState(this.statePath + id);
                                        }
                                    
                                        // wie setState(), setzt aber den statePath davor und überpüft aber ob der state vorhanden ist und erzeugt ihn,
                                        // wenn er noch nicht da ist
                                        setState(id, value) {
                                            if (!this.existState(id)) this.createState(id, value, undefined);
                                            else setState(this.statePath + id, value, true);
                                        }
                                    
                                        // wie createState, setzt aber noch den statePath davor und schaut im states-Array nach, ob dort common-Angaben
                                        // vorhanden sind (wenn der common-Parameter leer ist)
                                        createState(id, value, common) {
                                            if (!this.existState(id)) {
                                                if (common === undefined) {
                                                    // id im states-Array suchen
                                                    for (var i = 0; i < this.states.length; i++) {
                                                        if (this.states[i].id == id) {
                                                            if (this.states[i].hasOwnProperty('common'))
                                                                common = this.states[i].common;
                                                            break;
                                                        }
                                                    }
                                                }
                                                if ((typeof value === 'undefined') && (common.hasOwnProperty('def'))) value = common.def;
                                                // unter "0_userdata.0"
                                                let obj = {};
                                                obj.type = 'state';
                                                obj.native = {};
                                                obj.common = common;
                                                setObject(this.statePath + id, obj, (err) => {
                                                    if (err) {
                                                        this.log('cant write object for state "' + this.statePath + id + '": ' + err);
                                                    } else {
                                                        this.log('state "' + this.statePath + id + '" created');
                                                    }
                                                });
                                    
                                                // value zeitversetzt setzen        
                                                setTimeout(setState, 3000, this.statePath + id, value);
                                            }
                                        }
                                    
                                        /** Verbindung zum HEOS System herstellen
                                         **/
                                        connect() {
                                            if (!this.installed) return;
                                    
                                            try {
                                                this.log("searching for HEOS devices ...")
                                                this.setState("connected", false);
                                                this.state = stateSEARCHING;
                                                const NodeSSDP = require('node-ssdp').Client;
                                                this.nodessdp_client = new NodeSSDP();
                                                this.nodessdp_client.explicitSocketBind = true;
                                                this.nodessdp_client.on('response', (headers, statusCode, rinfo) => this.onNodeSSDPResponse(headers, statusCode, rinfo));
                                                this.nodessdp_client.on('error', error => { this.nodessdp_client.close(); this.logError(error); });
                                                this.nodessdp_client.search(this.ssdpSearchTargetName);
                                                this.ssdpSearchInterval = setInterval(() => {
                                                    this.log("still searching for HEOS devices ...")
                                                    this.nodessdp_client.search(this.ssdpSearchTargetName);
                                                }, 30000);
                                            } catch (err) { this.logError('connect: ' + err.message); }
                                    
                                        }
                                    
                                        /** Alle Player stoppen und die TelNet Verbindung schließen 
                                         **/
                                        disconnect() {
                                            this.log('disconnecting from HEOS ...');
                                            unsubscribe(this.statePath.substr(0, this.statePath.length - 1));
                                    
                                            this.stopHeartbeat();
                                            this.stopPlayers();
                                    
                                            if (typeof this.net_client !== 'undefined') {
                                                this.registerChangeEvents(false);
                                                this.net_client.destroy();
                                                this.net_client.unref();
                                            }
                                            if (typeof this.nodessdp_client !== 'undefined') {
                                                this.nodessdp_client.stop();
                                            }
                                            this.setState("connected", false);
                                            this.state = stateDISCONNECTED;
                                            this.log('disconnected from HEOS');
                                        }
                                    
                                        reconnect() {
                                            if (this.state == stateCONNECTED) {
                                                this.log('reconnecting to HEOS ...');
                                                this.disconnect();
                                                setTimeout(() => {
                                                    this.init();
                                                    this.connect();
                                                }, 5000);
                                            }
                                        }
                                    
                                        executeCommand(cmd) {
                                            //l('command: '+cmd);
                                            switch (cmd) {
                                                case 'load_presets':
                                                    this.getMusicSources();
                                                    break;
                                                case 'group/get_groups':
                                                    this.getGroups();
                                                    break;
                                                case 'connect':
                                                    this.disconnect();
                                                    this.init();
                                                    this.connect();
                                                    break;
                                                case 'disconnect':
                                                    this.disconnect();
                                                    break;
                                                default:
                                                    if (this.state == stateCONNECTED) {
                                                        this.msgs.push('heos://' + cmd + '\n');
                                                        this.sendNextMsg();
                                                    }
                                    
                                            }
                                        }
                                        /** es wurde mindestens ein Player erkannt, nun über dessen IP alle bekannten HEOS Player
                                         *  durch senden von "player/get_players" ermitteln
                                         */
                                        onNodeSSDPResponse(headers, statusCode, rinfo) {
                                            try {
                                                // rinfo {"address":"192.168.2.225","family":"IPv4","port":53871,"size":430}
                                                if (typeof this.net_client == 'undefined') {
                                                    if (headers.ST !== this.ssdpSearchTargetName) { // korrektes SSDP
                                                        this.logDebug('onNodeSSDPResponse: Getting wrong SSDP entry. Keep trying...');
                                                    } else {
                                                        if (this.ssdpSearchInterval) {
                                                            clearInterval(this.ssdpSearchInterval);
                                                            this.ssdpSearchInterval = undefined;
                                                        }
                                    
                                                        this.ip = rinfo.address;
                                                        this.log('connecting to HEOS (' + this.ip + ') ...');
                                                        this.net_client = net.connect({ host: this.ip, port: 1255 });
                                                        this.net_client.setKeepAlive(true, 5000);
                                                        this.net_client.setNoDelay(true);
                                                        this.net_client.setTimeout(15000);
                                    
                                                        this.state = stateCONNECTING;
                                    
                                                        this.net_client.on('error', (error) => {
                                                            this.logError(error);
                                                            this.reconnect();
                                                        });
                                    
                                                        this.net_client.on('connect', () => {
                                                            this.setState("connected", true);
                                                            this.state = stateCONNECTED;
                                                            this.log('connected to HEOS (' + this.ip + ')');
                                                            this.getPlayers();
                                                            this.registerChangeEvents(true);
                                                            this.signIn();
                                                            this.getMusicSources();
                                                            this.getGroups();
                                                            this.startHeartbeat();
                                                        });
                                    
                                                        // Gegenseite hat die Verbindung geschlossen 
                                                        this.net_client.on('end', () => {
                                                            this.logWarn('HEOS closed the connection to ' + this.ip);
                                                            this.reconnect();
                                                        });
                                    
                                                        // timeout
                                                        this.net_client.on('timeout', () => {
                                                            this.logWarn('Timeout trying connect to ' + this.ip);
                                                            this.reconnect();
                                                        });
                                    
                                                        // Datenempfang
                                                        this.net_client.on('data', (data) => this.onData(data));
                                                    }
                                                }
                                            } catch (err) { this.logError('onNodeSSDPResponse: ' + err.message); }
                                        }
                                    
                                        setLastError(err) {
                                            this.logWarn(err);
                                            let val = this.getState('last_error').val + '';
                                            let lines = val.split('\n');
                                            if (lines.length > 4)
                                                lines.pop();
                                            lines.unshift(err + '\n');
                                            this.setState('last_error', lines.toString());
                                        }
                                    
                                    
                                        /** es liegen Antwort(en) vor
                                         * 
                                         * {"heos": {"command": "browse/browse", "result": "success", "message": "sid=1028&returned=9&count=9"}, 
                                         *    "payload": [
                                         *        {"container": "no", "mid": "s25529", "type": "station", "playable": "yes", "name": "NDR 1 Niedersachsen (Adult Hits)", "image_url": "http://cdn-profiles.tunein.com/s25529/images/logoq.png?t=154228"}, 
                                         *        {"container": "no", "mid": "s56857", "type": "station", "playable": "yes", "name": "NDR 2 Niedersachsen 96.2 (Top 40 %26 Pop Music)", "image_url": "http://cdn-profiles.tunein.com/s56857/images/logoq.png?t=154228"}, 
                                         *        {"container": "no", "mid": "s24885", "type": "station", "playable": "yes", "name": "NDR Info", "image_url": "http://cdn-profiles.tunein.com/s24885/images/logoq.png?t=1"}, {"container": "no", "mid": "s158432", "type": "station", "playable": "yes", "name": "Absolut relax (Easy Listening Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s158432q.png"}, 
                                         *        {"container": "no", "mid": "catalog/stations/A316JYMKQTS45I/#chunk", "type": "station", "playable": "yes", "name": "Johannes Oerding", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/JohannesOerding._SX200_SY200_.jpg"}, 
                                         *        {"container": "no", "mid": "catalog/stations/A1O1J39JGVQ9U1/#chunk", "type": "station", "playable": "yes", "name": "Passenger", "image_url": "https://images-na.ssl-images-amazon.com/images/I/71DsYkU4QaL._SY500_CR150,0,488,488_SX200_SY200_.jpg"}, 
                                         *        {"container": "no", "mid": "catalog/stations/A1W7U8U71CGE50/#chunk"
                                         **/
                                        onData(data) {
                                            try {
                                                data = data.toString();
                                                data = data.replace(/[\n\r]/g, '');    // Steuerzeichen "CR" entfernen   
                                                // es können auch mehrere Antworten vorhanden sein! {"heos": ... } {"heos": ... }
                                                // diese nun in einzelne Antworten zerlegen
                                                data = this.unfinishedResponses + data;
                                                this.unfinishedResponses = '';
                                    
                                                data = data.replace(/{"heos":/g, '|{"heos":');
                                                var responses = data.split('|');
                                                responses.shift();
                                                for (var r = 0; r < responses.length; r++) if (responses[r].trim().length > 0) {
                                                    try {
                                                        JSON.parse(responses[r]); // check ob korrektes JSON Array
                                                        this.parseResponse(responses[r]);
                                                    } catch (e) {
                                                        this.logDebug('onData: invalid json (error: ' + e.message + '): ' + responses[r]);
                                                        this.unfinishedResponses += responses[r];
                                                    }
                                                }
                                                // wenn weitere Msg zum Senden vorhanden sind, die nächste senden
                                                if (this.msgs.length > 0)
                                                    this.sendNextMsg();
                                            } catch (err) { this.logError('onData: ' + err.message); }
                                        }
                                    
                                        /** Antwort(en) verarbeiten. Sich wiederholende Antworten ignorieren
                                         **/
                                        parseResponse(response) {
                                            try {
                                                this.logDebug('parseResponse: ' + response);
                                    
                                                if (response == this.lastResponse || response.indexOf("command under process") > 0)
                                                    return
                                                this.lastResponse = response;
                                    
                                                var jmsg;
                                                var i;
                                                var jdata = JSON.parse(response);
                                                if (!jdata.hasOwnProperty('heos') || !jdata.heos.hasOwnProperty('command'))
                                                    return;
                                    
                                                // msg auswerten
                                                try {
                                                    jmsg = '{"' + decodeURI(jdata.heos.message).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"').replace(/\s/g, '_') + '"}';
                                                    jmsg = JSON.parse(jmsg);
                                                } catch (err) {
                                                    jmsg = {};
                                                }
                                    
                                                // result ?
                                                var result = 'success';
                                                if (jdata.heos.hasOwnProperty('result')) result = jdata.heos.result;
                                                if (result != 'success') {
                                                    this.setLastError('result=' + result + ', ' + jmsg.text);
                                                }
                                    
                                                // cmd auswerten
                                                var cmd = jdata.heos.command.split('/');
                                                var cmd_group = cmd[0];
                                                cmd = cmd[1];
                                                switch (cmd_group) {
                                                    case 'system':
                                                        switch (cmd) {
                                                            case 'heart_beat':
                                                                this.resetHeartbeatTimeout();
                                                                break;
                                                        }
                                                        break;
                                                    case 'event':
                                                        switch (cmd) {
                                                            case 'players_changed':
                                                                this.getPlayers();
                                                                break;
                                                            case 'groups_changed':
                                                                this.getGroups();
                                                                break;
                                                            case 'group_volume_changed':
                                                                // "heos": {"command": "event/group_volume_changed ","message": "gid='group_id'&level='vol_level'&mute='on_or_off'"}
                                                                if (jmsg.hasOwnProperty('gid')) {
                                                                    if (jmsg.hasOwnProperty('level')) {
                                                                        this.setState(jmsg.gid + '.group_volume', jmsg.level);
                                                                    }
                                                                    if (jmsg.hasOwnProperty('mute')) {
                                                                        this.setState(jmsg.gid + '.group_mute', (jmsg.mute == 'on' ? true : false));
                                                                    }
                                                                }
                                                                break;
                                                        }
                                                        break;
                                                    case 'player':
                                                        switch (cmd) {
                                                            // {"heos": {"command": "player/get_players", "result": "success", "message": ""}, 
                                                            //  "payload": [{"name": "HEOS Bar", "pid": 1262037998, "model": "HEOS Bar", "version": "1.430.160", "ip": "192.168.2.225", "network": "wifi", "lineout": 0, "serial": "ADAG9170202780"}, 
                                                            //              {"name": "HEOS 1 rechts", "pid": -1746612370, "model": "HEOS 1", "version": "1.430.160", "ip": "192.168.2.201", "network": "wifi", "lineout": 0, "serial": "AMWG9170934429"}, 
                                                            //              {"name": "HEOS 1 links", "pid": 68572158, "model": "HEOS 1", "version": "1.430.160", "ip": "192.168.2.219", "network": "wifi", "lineout": 0, "serial": "AMWG9170934433"}
                                                            //             ]}
                                                            case 'get_players':
                                                                if (jdata.hasOwnProperty('payload')) {
                                                                    this.startPlayers(jdata.payload);
                                                                }
                                                                break;
                                                        }
                                                        break;
                                    
                                                    // {"heos": {"command": "browse/get_music_sources", "result": "success", "message": ""}, 
                                                    //  "payload": [{"name": "Amazon", "image_url": "https://production...png", "type": "music_service", "sid": 13}, 
                                                    //              {"name": "TuneIn", "image_url": "https://production...png", "type": "music_service", "sid": 3}, 
                                                    //              {"name": "Local Music", "image_url": "https://production...png", "type": "heos_server", "sid": 1024}, 
                                                    //              {"name": "Playlists", "image_url": "https://production...png", "type": "heos_service", "sid": 1025}, 
                                                    //              {"name": "History", "image_url": "https://production...png", "type": "heos_service", "sid": 1026}, 
                                                    //              {"name": "AUX Input", "image_url": "https://production...png", "type": "heos_service", "sid": 1027}, 
                                                    //              {"name": "Favorites", "image_url": "https://production...png", "type": "heos_service", "sid": 1028}]}
                                                    case 'browse':
                                                        switch (cmd) {
                                                            case 'get_music_sources':
                                                                if ((jdata.hasOwnProperty('payload'))) {
                                                                    for (i = 0; i < jdata.payload.length; i++) {
                                                                        var source = jdata.payload[i];
                                                                        if (source.name == 'Favorites') {
                                                                            this.browse(source.sid);
                                                                        }
                                                                    }
                                                                }
                                    
                                                                break;
                                    
                                                            // {"heos": {"command": "browse/browse", "result": "success", "message": "pid=1262037998&sid=1028&returned=5&count=5"}, 
                                                            //  "payload": [{"container": "no", "mid": "s17492", "type": "station", "playable": "yes", "name": "NDR 2 (Adult Contemporary Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s17492q.png"}, 
                                                            //              {"container": "no", "mid": "s158432", "type": "station", "playable": "yes", "name": "Absolut relax (Easy Listening Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s158432q.png"}, 
                                                            //              {"container": "no", "mid": "catalog/stations/A1W7U8U71CGE50/#chunk", "type": "station", "playable": "yes", "name": "Ed Sheeran", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/EdSheeran._SX200_SY200_.jpg"}, 
                                                            //              {"container": "no", "mid": "catalog/stations/A1O1J39JGVQ9U1/#chunk", "type": "station", "playable": "yes", "name": "Passenger", "image_url": "https://images-na.ssl-images-amazon.com/images/I/71DsYkU4QaL._SY500_CR150,0,488,488_SX200_SY200_.jpg"}, 
                                                            //              {"container": "no", "mid": "catalog/stations/A316JYMKQTS45I/#chunk", "type": "station", "playable": "yes", "name": "Johannes Oerding", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/JohannesOerding._SX200_SY200_.jpg"}], 
                                                            //  "options": [{"browse": [{"id": 20, "name": "Remove from HEOS Favorites"}]}]}                    
                                                            case 'browse':
                                                                if ((jdata.hasOwnProperty('payload'))) {
                                                                    for (i = 0; i < jdata.payload.length; i++) {
                                                                        var preset = jdata.payload[i];
                                                                        this.createState('presets.' + (i + 1) + '.name', preset.name, { name: 'Favoritenname ' });
                                                                        this.createState('presets.' + (i + 1) + '.playable', (preset.playable == 'yes' ? true : false), { name: 'Favorit ist spielbar' });
                                                                        this.createState('presets.' + (i + 1) + '.type', preset.type, { name: 'Favorittyp' });
                                                                        this.createState('presets.' + (i + 1) + '.image_url', preset.image_url, { name: 'Favoritbild' });
                                                                    }
                                                                }
                                                                break;
                                                        }
                                                        break;
                                    
                                                    case 'group':
                                                        //l('group: '+response);
                                                        switch (cmd) {
                                                            // { "heos":{"command":"player/set_group","result":"success",
                                                            //           "message": "gid='new group_id'&name='group_name'&pid='player_id_1, player_id_2,…,player_id_n'
                                                            //          } 
                                                            // }
                                                            case 'set_group':
                                                                this.setGroup(jmsg);
                                                                break;
                                    
                                                            // { "heos": {"command":"group/get_volume","result":"success","message": "gid='group_id'&level='vol_level'"}
                                                            case 'get_volume':
                                                                if (jmsg.hasOwnProperty('gid')) {
                                                                    if (jmsg.hasOwnProperty('level')) {
                                                                        this.setState(jmsg.gid + '.group_volume', jmsg.level);
                                                                    }
                                                                }
                                                                break;
                                    
                                                            // { "heos": {"command":"group/get_mute","result":"success","message": "gid='group_id'&state='on_or_off'"}
                                                            case 'get_mute':
                                                                if (jmsg.hasOwnProperty('gid')) {
                                                                    if (jmsg.hasOwnProperty('state')) {
                                                                        this.setState(jmsg.gid + '.group_mute', (jmsg.state == 'on' ? true : false));
                                                                    }
                                                                }
                                                                break;
                                    
                                    
                                                            // { "heos": { "command": "player/get_groups", "result": "success", "message": "" },
                                                            //   "payload": [{"name":"'group name 1'", "gid": "group id 1'",
                                                            //                "players":[{"name":"player name 1","pid":"'player id1'","role":"player role 1 (leader or member)'"},
                                                            //                           {"name":"player name 2","pid":"'player id2'","role":"player role 2 (leader or member)'"} 
                                                            //                          ]
                                                            //               },
                                                            //               {"name":"'group name 2'","gid":"group id 2'",
                                                            //                "players":[{"name":"player name ... 
                                                            case 'get_groups':
                                                                // bisherige groups leeren
                                                                var objs = $(this.statePath + '*.group_name');
                                                                for (var o = 0; o < objs.length; o++) setState(objs[o], 'no group');
                                                                objs = $(this.statePath + '*.group_leader');
                                                                for (var o = 0; o < objs.length; o++) setState(objs[o], false);
                                                                objs = $(this.statePath + '*.group_member');
                                                                for (var o = 0; o < objs.length; o++) setState(objs[o], false);
                                                                objs = $(this.statePath + '*.group_pid');
                                                                for (var o = 0; o < objs.length; o++) setState(objs[o], '');
                                    
                                                                // payload mit den groups auswerten
                                                                if ((jdata.hasOwnProperty('payload'))) {
                                                                    for (i = 0; i < jdata.payload.length; i++) {
                                                                        var group = jdata.payload[i];
                                                                        var players = group.players;
                                                                        // Player IDs addieren. Hinweis: "leader" ist nicht immer der 1.Playereintrag
                                                                        group.pid = "";
                                                                        for (var p = 0; p < players.length; p++) {
                                                                            if (players[p].role == 'leader')
                                                                                group.pid = players[p].pid + (group.pid.length > 0 ? "," : "") + group.pid;
                                                                            else
                                                                                group.pid = group.pid + (group.pid.length > 0 ? "," : "") + players[p].pid;
                                                                        }
                                                                        this.setGroup(group);
                                                                    }
                                                                }
                                                                break;
                                    
                                                        }
                                                        break;
                                    
                                    
                                                    case 'system':
                                                        switch (cmd) {
                                                            case 'sign_in':
                                                                this.log('signed in: ' + jdata.heos.result);
                                                                break;
                                                        }
                                                        break;
                                                }
                                    
                                    
                                                // an die zugehörigen Player weiterleiten
                                                if (jmsg.hasOwnProperty('pid')) {
                                                    if(jmsg.pid in this.players){
                                                        this.players[jmsg.pid].parseResponse(jdata, jmsg, cmd_group, cmd);
                                                    }
                                                }
                                    
                                    
                                            } catch (err) { this.logError('parseResponse: ' + err.message + '\n ' + response); }
                                        }
                                    
                                    
                                        // sucht die zur pid passenden player-Insanz
                                        sendCommandToPlayer(objID, cmd) {
                                            // aus der objID die pid holen
                                            // objID = javascript.1.heos.394645376.command
                                            objID = objID.split('.');
                                            var pid = objID[3];
                                            if(pid in this.players){
                                                this.players[pid].sendCommand(cmd);
                                            }
                                        }
                                    
                                        stopPlayer(pid){
                                            try {
                                                let heosPlayer = this.players[pid];
                                                if (heosPlayer)
                                                    heosPlayer.stopPlayer();
                                                // player leeren
                                                delete this.players[pid];
                                            } catch (err) { this.logError('stopPlayer: ' + err.message); }
                                        }
                                    
                                        // Für die gefundenen HEOS Player entsprechende class HeosPlayer Instanzen bilden und nicht mehr verbundene Player stoppen
                                        startPlayers(payload) {
                                            try {
                                                var connectedPlayers = [];
                                                for (var i = 0; i < payload.length; i++) {
                                                    var player = payload[i];
                                                    var pid = player.pid + ''; //Convert to String
                                                    if(!(pid in this.players)){
                                                        this.players[pid] = new HeosPlayer(this, player);
                                                    }
                                                    connectedPlayers.push(pid);
                                                }
                                                //Remove disconnected players
                                                for(var pid in this.players) {
                                                    if(!connectedPlayers.includes(pid)){
                                                        this.stopPlayer(pid);
                                                    }
                                                }
                                            } catch (err) { this.logError('startPlayers: ' + err.message); }
                                        }
                                    
                                        //Alle Player stoppen
                                        stopPlayers() {
                                            this.logDebug("Try to stop players:" + JSON.stringify(Object.keys(this.players)));
                                            for (var pid in this.players) {
                                                this.stopPlayer(pid);
                                            }
                                        }
                                    
                                        // setzen der Werte einer Group
                                        setGroup(group) {
                                            if (group.hasOwnProperty('pid')) {
                                                // in den Playern den Groupstatus setzen
                                                var pids = group.pid.split(',');
                                    
                                                for (var i = 0; i < pids.length; i++) {
                                                    this.setState(pids[i] + '.group_name', (group.hasOwnProperty('name') ? group.name : ''));
                                                    this.setState(pids[i] + '.group_pid', group.pid);
                                                    this.setState(pids[i] + '.group_leader', (i == 0) && (pids.length > 1));
                                                    this.setState(pids[i] + '.group_member', (pids.length > 1));
                                                }
                                    
                                                if (group.hasOwnProperty('gid')) {
                                                    // volume und mute dazu holen
                                                    this.executeCommand("group/get_volume?gid=" + group.gid);
                                                    this.executeCommand("group/get_mute?gid=" + group.gid);
                                                }
                                            }
                                    
                                        }
                                    
                                        getPlayers() {
                                            if (this.state == stateCONNECTED) {
                                                this.msgs.push('heos://player/get_players\n');
                                                this.sendNextMsg();
                                            }
                                        }
                                    
                                        registerChangeEvents(b) {
                                            if (this.state == stateCONNECTED) {
                                                if (b) this.msgs.push('heos://system/register_for_change_events?enable=on');
                                                else this.msgs.push('heos://system/register_for_change_events?enable=off');
                                                this.sendNextMsg();
                                            }
                                        }
                                    
                                        signIn() {
                                            if (this.state == stateCONNECTED) {
                                                // heos://system/sign_in?un=heos_username&pw=heos_password
                                                this.msgs.push('heos://system/sign_in?un=' + HEOS_USERNAME + '&pw=' + HEOS_PASSWORD);
                                                this.sendNextMsg();
                                            }
                                        }
                                    
                                        getMusicSources() {
                                            if (this.state == stateCONNECTED) {
                                                // heos://browse/get_music_sources
                                                this.msgs.push('heos://browse/get_music_sources');
                                                this.sendNextMsg();
                                            }
                                    
                                        }
                                    
                                        getGroups() {
                                            if (this.state == stateCONNECTED) {
                                                // heos://group/get_groups
                                                this.msgs.push('heos://group/get_groups');
                                                this.sendNextMsg();
                                            }
                                    
                                        }
                                    
                                        startHeartbeat() {
                                            if (this.state == stateCONNECTED) {
                                                this.logDebug("start heartbeat interval");
                                                this.heartbeatInterval = setInterval(() => {
                                                    this.logDebug("heartbeat")
                                                    this.msgs.push('heos://system/heart_beat');
                                                    this.sendNextMsg();
                                                    if (typeof this.heartbeatTimeout == undefined) {
                                                        this.heartbeatTimeout = setTimeout(() => {
                                                            this.log("heartbeat timeout");
                                                            this.reboot();
                                                            setTimeout(() => {
                                                                this.reconnect();
                                                            }, 1000)
                                                        }, HEARTBEAT_TIMEOUT);
                                                    }
                                                }, HEARTBEAT_INTERVAL);
                                            }
                                        }
                                    
                                        resetHeartbeatTimeout() {
                                            this.logDebug("reset heartbeat timeout");
                                            if (this.heartbeatTimeout) {
                                                clearTimeout(this.heartbeatTimeout);
                                                this.heartbeatTimeout = undefined;
                                            }
                                        }
                                    
                                        stopHeartbeat() {
                                            this.logDebug("stop heartbeat interval");
                                            if (this.heartbeatInterval) {
                                                clearInterval(this.heartbeatInterval);
                                                this.heartbeatInterval = undefined;
                                            }
                                            this.resetHeartbeatTimeout();
                                        }
                                    
                                        reboot() {
                                            if (this.state == stateCONNECTED) {
                                                this.logDebug("reboot device");
                                                // heos://system/reboot
                                                this.msgs.push('heos://system/reboot');
                                                this.sendNextMsg();
                                            }
                                        }
                                    
                                        browse(sid) {
                                            if (this.state == stateCONNECTED) {
                                                // heos://browse/browse?sid=source_id
                                                this.msgs.push('heos://browse/browse?sid=' + sid);
                                                this.sendNextMsg();
                                            }
                                        }
                                    
                                    
                                        sendNextMsg() {
                                            if (this.msgs.length > 0) {
                                                var msg = this.msgs.shift();
                                                this.sendMsg(msg);
                                            }
                                        }
                                    
                                        // Nachricht an player senden
                                        sendMsg(msg) {
                                            this.net_client.write(msg + "\n");
                                            this.logDebug("data sent: " + msg);
                                        }
                                    
                                    
                                    } // end of class Heos
                                    
                                    /********************
                                     * class HeosPlayer
                                     ********************/
                                    
                                    class HeosPlayer {
                                    
                                        logDebug(msg) { console.debug('[HeosPlayer ' + this.pid + '] ' + msg); }
                                        log(msg) { console.log('[HeosPlayer ' + this.pid + '] ' + msg); }
                                        logWarn(msg) { console.warn('[HeosPlayer ' + this.pid + '] ' + msg); }
                                        logError(msg) { console.error('[HeosPlayer ' + this.pid + '] ' + msg); }
                                    
                                        constructor(heos, player) {
                                            this.heos = heos;
                                            this.ip = player.ip;
                                            this.name = player.name;
                                            this.pid = player.pid;
                                            this.model = player.model;
                                            this.serial = player.serial;
                                            this.onHandler = [];
                                    
                                            //    this.statePath = 'javascript.'+instance+'.heos.'+this.ip.replace(/\./g,'_')+".";
                                            this.statePath = '0_userdata.0.heos.' + this.pid + ".";
                                    
                                            this.log('creating HEOS player with pid ' + this.pid + ' (' + this.ip + ')');
                                    
                                            this.states = [
                                                { id: "connected", common: { name: "Verbunden?" } },
                                                { id: "command", common: { name: "Befehl an Heos Player senden" } },
                                                { id: "ip", common: { name: "IP-Adresse", write: false } },
                                                { id: "pid", common: { name: "Player-ID", write: false } },
                                                { id: "name", common: { name: "Name des Players", write: false } },
                                                { id: "model", common: { name: "Modell des Players", write: false } },
                                                { id: "serial", common: { name: "Seriennummer des Players", write: false } },
                                                { id: "last_error", common: { name: "Letzte Fehler", write: false } },
                                                { id: "volume", common: { name: "Aktuelle Lautstärke" } },
                                                { id: "mute", common: { name: "Mute aktiviert?" } },
                                                { id: "play_state", common: { name: "Aktueller Wiedergabezustand" } },
                                                { id: "play_mode_repeat", common: { name: "Wiedergabewiederholung" } },
                                                { id: "play_mode_shuffle", common: { name: "Zufällige Wiedergabe" } },
                                                { id: "now_playing_media_type", common: { name: "Aktuelle Wiedergabemedium", write: false } },
                                                { id: "now_playing_media_song", common: { name: "Aktuelles Lied", write: false } },
                                                { id: "now_playing_media_station", common: { name: "Aktuelle Station", write: false } },
                                                { id: "now_playing_media_album", common: { name: "Aktuelle Wiedergabemedium", write: false } },
                                                { id: "now_playing_media_artist", common: { name: "Aktueller Artist", write: false } },
                                                { id: "now_playing_media_image_url", common: { name: "Aktuelles Coverbild", write: false } },
                                                { id: "now_playing_media_album_id", common: { name: "Aktuelle Album-ID", write: false } },
                                                { id: "now_playing_media_mid", common: { name: "Aktuelle mid", write: false } },
                                                { id: "now_playing_media_qid", common: { name: "Aktuelle qid", write: false } },
                                                { id: "cur_pos", common: { name: "lfd. Position" } },
                                                { id: "duration", common: { name: "Dauer", write: false } },
                                                { id: "cur_pos_MMSS", common: { name: "lfd. Position MM:SS" } },
                                                { id: "duration_MMSS", common: { name: "Dauer MM:SS", write: false } },
                                                { id: "group_leader", common: { name: "Ist Gruppenleiter", write: false } },
                                                { id: "group_member", common: { name: "Ist Gruppenmitglied", write: false } },
                                                { id: "group_pid", common: { name: "PlayerIDs in der Gruppe", write: false } },
                                                { id: "group_volume", common: { name: "Lautstärke wenn Gruppen-Leiter" } },
                                                { id: "group_name", common: { name: "Name der Gruppe", write: false } },
                                                { id: "group_mute", common: { name: "Gruppe gemutet?" } },
                                                { id: "script_version", common: { name: "Installierte Script-Version", write: false } }
                                            ];
                                            // beim 1.Start die states erzeugen
                                            if (!this.existState("script_version") || this.getState('script_version').val != SCRIPTVERSION) {
                                                this.installed = false;
                                                this.logWarn('creating HEOS player states for version ' + SCRIPTVERSION + ', please start script again');
                                                // states erzeugen
                                                for (var s = 0; s < this.states.length; s++) {
                                                    this.setState(this.states[s].id, "");
                                                }
                                                this.setState('script_version', SCRIPTVERSION);
                                            }
                                            else {
                                                // Initialisierung der States nach 5 Sek
                                                setTimeout(() => {
                                                    this.setState('ip', this.ip);
                                                    this.setState('name', this.name);
                                                    this.setState('pid', this.pid);
                                                    this.setState('model', this.model);
                                                    this.setState('serial', this.serial);
                                                    this.sendCommand('get_play_state|get_play_mode|get_now_playing_media|get_volume');
                                                }, 5000);
                                                setTimeout(() => {
                                                    this.startPlayer();
                                                }, 10000);
                                                this.installed = true;
                                            }
                                        }
                                    
                                        // über den $-Operator nachsehen, ob der state bereits vorhanden ist
                                        // getState().notExists geht auch, erzeugt aber Warnmeldungen!
                                        existState(id) {
                                            return ($(this.statePath + id).length == 0 ? false : true);
                                        }
                                    
                                        // wrapper
                                        getState(id) {
                                            return getState(this.statePath + id);
                                        }
                                    
                                        // wie setState(), setzt aber den statePath davor und überpüft aber ob der state vorhanden ist und erzeugt ihn,
                                        // wenn er noch nicht da ist
                                        setState(id, value) {
                                            if (!this.existState(id)) this.createState(id, value, undefined);
                                            else setState(this.statePath + id, value, true);
                                        }
                                    
                                        // wie createState, setzt aber noch den statePath davor und schaut im states-Array nach, ob dort common-Angaben
                                        // vorhanden sind (wenn der common-Parameter leer ist)
                                        createState(id, value, common) {
                                            if (!this.existState(id)) {
                                                if (common === undefined) {
                                                    // id im states-Array suchen
                                                    for (var i = 0; i < this.states.length; i++) {
                                                        if (this.states[i].id == id) {
                                                            if (this.states[i].hasOwnProperty('common'))
                                                                common = this.states[i].common;
                                                            break;
                                                        }
                                                    }
                                                }
                                                if ((typeof value === 'undefined') && (common.hasOwnProperty('def'))) value = common.def;
                                                // unter "0_userdata.0"
                                                let obj = {};
                                                obj.type = 'state';
                                                obj.native = {};
                                                obj.common = common;
                                                setObject(this.statePath + id, obj, (err) => {
                                                    if (err) {
                                                        this.log('cant write object for state "' + this.statePath + id + '": ' + err);
                                                    } else {
                                                        this.log('state "' + this.statePath + id + '" created');
                                                    }
                                                });
                                    
                                                // value zeitversetzt setzen        
                                                setTimeout(setState, 3000, this.statePath + id, value);
                                            }
                                        }
                                    
                                    
                                        startPlayer() {
                                            try {
                                                this.log('starting HEOS player with pid ' + this.pid + ' (' + this.ip + ')');
                                                this.onHandler = [];
                                                // on-Event für command
                                                this.onHandler.push(on({ id: this.statePath + 'command', change: "any" }, (obj) => {
                                                    this.executeCommand(obj.id, obj.state.val)
                                                }));
                                    
                                                // on-Event für volume (nur wenn ack=false, also über vis)
                                                this.onHandler.push(on({ id: this.statePath + 'volume', change: 'ne', ack: false }, (obj) => {
                                                    this.executeCommand(obj.id, 'set_volume&level=' + obj.state.val);
                                                }));
                                                // on-Event für mute (nur wenn ack=false, also über vis)
                                                this.onHandler.push(on({ id: this.statePath + 'mute', change: 'ne', ack: false }, (obj) => {
                                                    this.executeCommand(obj.id, 'set_mute&state=' + (obj.state.val === true ? 'on' : 'off'));
                                                }));
                                                // on-Event für play_mode_shuffle (nur wenn ack=false, also über vis)
                                                this.onHandler.push(on({ id: this.statePath + 'play_mode_shuffle', change: 'ne', ack: false }, (obj) => {
                                                    this.executeCommand(obj.id, 'set_play_mode&shuffle=' + (obj.state.val === true ? 'on' : 'off'));
                                                }));
                                                // on-Event für play_state (nur wenn ack=false, also über vis)
                                                this.onHandler.push(on({ id: this.statePath + 'play_state', change: 'ne', ack: false }, (obj) => {
                                                    this.executeCommand(obj.id, 'set_play_state&state=' + obj.state.val);
                                                }));
                                    
                                                // on-Event für group-volume (nur wenn ack=false, also über vis)
                                                this.onHandler.push(on({ id: this.statePath + 'group_volume', change: 'ne', ack: false }, (obj) => {
                                                    // "id":"javascript.1.heos.-1746612370.group_volume"
                                                    var id = obj.id.split('.');
                                                    this.executeCommand('group/set_volume?gid=' + id[3] + '&level=' + obj.state.val);
                                                }));
                                    
                                                // on-Event für group-mute (nur wenn ack=false, also über vis)
                                                this.onHandler.push(on({ id: this.statePath + 'group_mute', change: 'ne', ack: false }, (obj) => {
                                                    // "id":"javascript.1.heos.-1746612370.group_volume"
                                                    var id = obj.id.split('.');
                                                    this.heos.executeCommand('group/set_mute?gid=' + id[3] + '&state=' + (obj.state.val === true ? 'on' : 'off'));
                                                }));
                                    
                                                this.setState('connected', true);
                                            } catch (err) { this.logError('startPlayer: ' + err.message); }
                                        }
                                    
                                        // sucht die zur pid passenden player-Insanz
                                        executeCommand(objID, cmd) {
                                            // aus der objID die pid holen
                                            // objID = javascript.1.heos.394645376.command
                                            objID = objID.split('.');
                                            var pid = objID[3];
                                            if (this.pid == pid) {
                                                this.sendCommand(cmd);
                                            }
                                        }
                                    
                                        cleanupNowPlaying(){
                                            for (var s = 0; s < this.states.length; s++) {
                                                if(this.states[s].id.startsWith("now_playing")){
                                                    this.setState(this.states[s].id, "");
                                                }
                                            }
                                        }
                                    
                                    
                                        stopPlayer() {
                                            try {
                                                this.log('stopping HEOS player with pid ' + this.pid + ' (' + this.ip + ')');
                                    
                                                // events unsubcribe
                                                for (let i = 0; i < this.onHandler.length; i++) {
                                                    if (this.onHandler[i] !== undefined) unsubscribe(this.onHandler[i]);
                                                }
                                                this.onHandler = [];
                                                //cleanup now playing
                                                this.cleanupNowPlaying();
                                                // reset last error
                                                this.setState("last_error", "");
                                                // connected zurücksetzen
                                                this.setState("connected", false);
                                            } catch (err) { this.logError('stopPlayer: ' + err.message); }
                                        }
                                    
                                        /** wandelt einen sek Wert in MM:SS Darstellung um
                                         **/
                                        toMMSS(s) {
                                            var sec_num = parseInt(s, 10);
                                            var minutes = Math.floor(sec_num / 60);
                                            var seconds = sec_num - (minutes * 60);
                                            if (seconds < 10) { seconds = "0" + seconds; }
                                            return minutes + ':' + seconds;
                                        }
                                    
                                        setLastError(err) {
                                            try {
                                                this.logWarn(err);
                                                let val = this.getState('last_error').val + '';
                                                let lines = val.split('\n');
                                                if (lines.length > 4)
                                                    lines.pop();
                                                lines.unshift(err + '\n');
                                                this.setState('last_error', lines.toString());
                                            } catch (e) { this.logError('setLastError: ' + e.message); }
                                        }
                                    
                                    
                                        /** Auswertung der empfangenen Daten
                                         **/
                                        parseResponse(jdata, jmsg, cmd_group, cmd) {
                                            try {
                                                switch (cmd_group) {
                                                    case 'event':
                                                        switch (cmd) {
                                                            case 'player_playback_error':
                                                                this.setLastError(jmsg.error.replace(/_/g, ' '));
                                                                break;
                                                            case 'player_state_changed':
                                                                this.setState("play_state", jmsg.state);
                                                                this.sendCommand('get_now_playing_media');
                                                                break;
                                                            case 'player_volume_changed':
                                                                this.setState("volume", jmsg.level);
                                                                this.setState("mute", (jmsg.mute == 'on' ? true : false));
                                                                break;
                                                            case 'player_repeat_mode_changed':
                                                                this.setState("play_mode_shuffle", jmsg.shuffle);
                                                                break;
                                                            case 'player_shuffle_mode_changed':
                                                                this.setState("play_mode_repeat", jmsg.repeat);
                                                                break;
                                                            case 'player_now_playing_changed':
                                                                this.sendCommand('get_now_playing_media');
                                                                break;
                                                            case 'player_now_playing_progress':
                                                                this.setState("cur_pos", jmsg.cur_pos / 1000);
                                                                this.setState("cur_pos_MMSS", this.toMMSS(jmsg.cur_pos / 1000));
                                                                this.setState("duration", jmsg.duration / 1000);
                                                                this.setState("duration_MMSS", this.toMMSS(jmsg.duration / 1000));
                                                                break;
                                                        }
                                                        break;
                                    
                                    
                                                    case 'player':
                                                        switch (cmd) {
                                                            case 'set_volume':
                                                            case 'get_volume':
                                                                if (getState(this.statePath + "volume").val != jmsg.level)
                                                                    this.setState("volume", jmsg.level);
                                                                break;
                                                            case 'set_mute':
                                                            case 'get_mute':
                                                                this.setState("mute", (jmsg.state == 'on' ? true : false));
                                                                break;
                                                            case 'set_play_state':
                                                            case 'get_play_state':
                                                                this.setState("play_state", jmsg.state);
                                                                break;
                                                            case 'set_play_mode':
                                                            case 'get_play_mode':
                                                                this.setState("play_mode_repeat", jmsg.repeat);
                                                                this.setState("play_mode_shuffle", (jmsg.shuffle == 'on' ? true : false));
                                                                break;
                                                            case 'get_now_playing_media':
                                                                this.setState("now_playing_media_type", jdata.payload.type);
                                                                if (jdata.payload.hasOwnProperty('song'))
                                                                    this.setState("now_playing_media_song", jdata.payload.song);
                                                                if (jdata.payload.hasOwnProperty('album'))
                                                                    this.setState("now_playing_media_album", jdata.payload.album);
                                                                if (jdata.payload.hasOwnProperty('album_id'))
                                                                    this.setState("now_playing_media_album_id", jdata.payload.album_id);
                                                                if (jdata.payload.hasOwnProperty('artist'))
                                                                    this.setState("now_playing_media_artist", jdata.payload.artist);
                                                                if (jdata.payload.hasOwnProperty('image_url'))
                                                                    this.setState("now_playing_media_image_url", jdata.payload.image_url);
                                                                if (jdata.payload.hasOwnProperty('mid'))
                                                                    this.setState("now_playing_media_mid", jdata.payload.mid);
                                                                if (jdata.payload.hasOwnProperty('qid'))
                                                                    this.setState("now_playing_media_qid", jdata.payload.qid);
                                                                if (jdata.payload.hasOwnProperty('type')) {
                                                                    if (jdata.payload.type == 'station') {
                                                                        this.setState("now_playing_media_station", jdata.payload.station);
                                                                    } else {
                                                                        this.setState("now_playing_media_station", "");
                                                                    }
                                                                }
                                                                break;
                                                        }
                                                        break;
                                                } // switch
                                    
                                    
                                            } catch (err) { this.logError('parseResponse: ' + err.message); }
                                        }
                                    
                                        /** cmd der Form "cmd&param"  werden zur msg heos+cmd+pid+&param aufbereitet
                                            cmd der Form "cmd?param"  werden zur msg heos+cmd+?param aufbereitet
                                         **/
                                        commandToMsg(cmd) {
                                            var param = cmd.split('&');
                                            cmd = param[0];
                                            if (param.length > 1) param = '&' + param[1]; else param = '';
                                            var cmd_group = 'player';
                                    
                                            switch (cmd) {
                                                case 'get_play_state':
                                                case 'get_play_mode':
                                                case 'get_now_playing_media':
                                                case 'get_volume':
                                                case 'play_next':
                                                case 'play_previous':
                                                case 'set_mute':       // &state=on|off        
                                                case 'set_volume':     // &level=1..100   
                                                case 'volume_down':    // &step=1..10   
                                                case 'volume_up':      // &step=1..10
                                                case 'set_play_state': // &state=play|pause|stop
                                                case 'set_play_mode':  // &repeat=on_all|on_one|off  shuffle=on|off
                                                    break;
                                    
                                                // browse            
                                                case 'play_preset':    // heos://browse/play_preset?pid=player_id&preset=preset_position
                                                    cmd_group = 'browse';
                                                    break;
                                                case 'play_stream':    // heos://browse/play_stream?pid=player_id&url=url_path
                                                    cmd_group = 'browse';
                                                    break;
                                    
                                            }
                                            return 'heos://' + cmd_group + '/' + cmd + '?pid=' + this.pid + param;
                                        }
                                    
                                        /** Nachricht (command) an player senden
                                            es sind auch mehrere commands, getrennt mit | erlaubt
                                            bsp: set_volume&level=20|play_preset&preset=1
                                         **/
                                        sendCommand(command) {
                                            if (!this.installed) return;
                                    
                                            var cmds = command.split('|');
                                            for (var c = 0; c < cmds.length; c++) {
                                                this.heos.msgs.push(this.commandToMsg(cmds[c]));
                                            }
                                            this.heos.sendNextMsg();
                                        }
                                    
                                    
                                    } // end of HeosPlayer
                                    
                                    
                                    /* -----
                                       Heos
                                       ----- */
                                    // Heos Instanz erzeugen und verbinden   
                                    var heos = new Heos();
                                    heos.connect();
                                    
                                    // wenn das Script beendet wird, dann auch die Heos Instanz beenden
                                    onStop(function () {
                                        heos.disconnect();
                                    }, 0);
                                    
                                    UhulaU 1 Antwort Letzte Antwort
                                    0
                                    • W withstu

                                      @Brati Ja ich habe grad auch wieder Probleme mich einzuloggen...

                                      @Uhula Ich habe in den letzten Tagen noch ein paar Bugs gefixt und das get_players Verhalten umgestellt. Jetzt werden einzelne Player disconnected und nicht mehr alle.

                                      /****************************
                                      ### HEOS Script for ioBroker
                                      
                                      #### Funktion
                                      
                                      Das Script stellt zwei JS Klassen zur Steuerung der Denon HEOS Geräte zur Verfügung. Die class Heos dient dabei dem Erkennen und Steuern der HEOS Geräte. Die class HeosPlayer dient der Interpretation der Antworten der Heos Geräte.
                                       
                                      Das Script muss lediglich gestartet werden, es sucht dann im Netzwerk nach HEOS Playern und erzeugt in der aktuellen Javascript-Instanz einen Subeintrag für die Favoriten und je einen Subeintrag für jeden gefundenen Player. Dort wiederum werden State-Variablen zur Aufnahme der Player-Daten angelegt.
                                       
                                      Das Script erzeugt auch on-Handler um auf Steuerungen über vis und andere Scripte an den State-Variablen reagieren zu können.
                                       
                                      Beim Beenden des Scripts werden alle Verbindungen und on-Handler geschlossen.
                                      
                                      Das Script ermöglicht die HEOS Player zu steuern, es soll aber nicht die HEOS App ersetzen. 
                                       
                                        
                                      #### Wichtig!
                                      
                                      * Im Javascript-Adapter muss in der Instanz-Konfiguration "node-ssdp" mit angegeben werden! Alternativ kann die Bibliothek auch komplett über "npm install node-ssdp" installiert werden.
                                      * Wenn mit Favoriten (Presets) gearbeitet werden soll, müssen die HEOS-Konto Logindaten in den beiden Konstanten HEOS_USERNAME (EmailAdr) und HEOS_PASSWORD hier im Script in der Konfiguration angegeben werden (so, wie in der HEOS App)!
                                      * Eine Konfiguration von IP-Adressen und Player-IDs ist nicht notwendig!
                                      * U.U. kann es notwendig sein das Script nach dem 1.Start zu beenden und nach 30 Sek erneut zu starten, da die Neuanlage der ioBroker States etwas Zeit benötigt. Warnungen sind dabei zu ignorieren. ;-)
                                      
                                      #### Updates
                                       
                                      ##### v2.0 13.03.2020
                                      * das Script überprüft nun, ob alle notwendigen states für Heos und HeosPlayer im ioBroker korrekt erzeugt wurden und gibt eine Warnung mit Script-Neustartaufforderung aus, wenn die states nicht vorhanden sind. Dieses ist notwendig, da das Anlegen von states asynchron erfolgt und einige ms dauern kann
                                      * (WICHTIG!) die states werden nicht mehr in der aktuellen Javascript-Instanz und auch nicht mehr unter der IP des Heos-Players gespeichert, sondern Javascript-Instanzunabhängig unter 0_userdata.0 und seiner Player-ID (pid), da diese sich nicht ändert. Die IP hingegen u.U. schon. Also statt "javascript.X.heos.192_168_2_43" nun als "0_userdata.0.heos.12345678". Wer bereits auf die alten Werte im VIS zugreift, muss dieses im VIS anpassen!  
                                      * es wurden Befehle/states für die Gruppensteuerung der Heos-Player hinzugefügt. Beim Start werden die Gruppen ausgelesen und in den Player-states des Gruppenleiters (der erste genannte Player) gespeichert. Über dessen states kann die Gruppe gesteuert werden: 
                                          group_volume: dient der Steuerung der Gruppen-Lautstärke
                                          group_mute: dient der Steuerung der Gruppen-Mutes
                                          group_leader: ist true, wenn der Player der Gruppenleiter ist
                                          group_member: ist true, wenn der Player Mitglied einer Gruppe ist
                                      * alle 60 Sek wird überprüft, ob noch alle HEOS-Player erreichbar sind bzw. neue/reconnectete hinzugekommen sind. Neue/reconnectete werden der Liste der HEOS-Player hinzugefügt, fehlende werden gestoppt. Da hierzu das HEOS-Netzwerk gefragt wird (get_players Meldung), kann es bis zu 5 Min dauern bis Änderungen erkannt werden. Der neue HEOS-Player-State "connected" wird entsprechend gesetzt
                                      * jeder HEOS-Player hat einen neuen state "connected", dieser wird nach erfolgreichem Start auf true gesetzt, bei korrektem Beenden des Scripts auf false
                                      * diverse Filter/Korrekturen aus dem Forum übernommen, vielen dank an withstu
                                        
                                        
                                       #### class Heos
                                       
                                       Diese Basisklasse dient 
                                      
                                      * dem Erkennen von HEOS Geräten (Playern). Das Erkennen der HEOS Geräte findet via UPD unter Nutzung von node-ssdp statt. node-ssdp muss dazu im Javascript-Adapter in der Konfiguration mit angegeben werden!
                                      * der Kommunikation über TelNet. Es wird genau eine TelNet Verbindung zu einem HEOS Gerät aufgebaut, dieses reicht die Sendungen und Antworten zentral weiter
                                      * der Ermittlung von Favoriten (Presets). Für jeden Favorit wird ein ioBroker State heos.presets.n mit entsprechenden Sub-States erzeugt. Zur Nutzung der Presets ist ein SignIn notwendig, hierzu müssen HEOS_USERNAME und HEOS_PASSWORD gesetzt werden.
                                      * dem Instanziieren der class HeosPlayer je HEOS Gerät. Je HEOS Gerät wird ein ioBroker-State mit der IP-Adresse des Players erzeugt, dieser State erhält diverse Sub-States
                                        
                                      ##### State-Variable: Heos
                                       
                                      Werden angelegt unter "0_userdata.0.heos."
                                      
                                      * connected: true wenn die Verbindung zu mindestens einem HEOS-Player besteht
                                      * script_version: Version des Scripts, dient dazu Script-Updates zu erkennen
                                      * last_error: Letzter Fehlertext
                                      * command(cmd): Hierüber können der Heos-Klasse Befehle übergeben werden, für cmd gilt:
                                          "connect": Verbindung zu HEOS aufbauen bzw. erneut aufbauen (praktisch ein reset)
                                          "disconnect": Verbindung zu HEOS beenden
                                          "load_presets": lädt die Favoriten neu
                                        
                                          "group/set_group?pid=<pid1>,<pid2>,...": setzen einer Gruppe, die pids sind die der Player wie sie unter den Objekten für jeden Player abgelegt sind. Bsp: "group/set_group?pid=12345678,12345679". 
                                       
                                         "group/set_group?pid=<pid1>" : hebt die Gruppierung des Players mit der pid1 wieder auf. Bsp: "group/set_group?pid=12345678"
                                         "...": alle anderen cmd-Werte werden "as is" versucht an HEOS zu senden
                                       
                                        
                                      * presets.<n>: Hierunter werden die Favoriten, Presets angelegt. Je Favorit ein Unterordner n=1 bis Anzahl. Darunter befinden sich dann die States, welche den Inhalt der Favoriten aufnehmen:
                                       
                                      * image_url (read): Bild des Favoriten 
                                      * name (read): Name des Favoriten
                                      * playable (read): Spielbar? true/false
                                      * type (read): Typ des Favoriten (station, ...)
                                       
                                          
                                         
                                       #### class HeosPlayer 
                                       
                                       Diese Klasse dient
                                      * der Steuerung genau eines HEOS Gerätes. Hierzu wird die zentrale Telnet Verbindung der class Heos genutzt.
                                      * dem Erzeugen der ioBroker-States zum Speichern der Geräte-Werte
                                      * der Auswertung von Antworten der HEOS Geräte und Zuweisung an die entsprechenden ioBroker-States
                                       
                                      ##### State-Variable
                                      
                                      Werden angelegt unter "0_userdata.0.heos.<pid>"
                                      
                                      * connected : true, wenn eine Verbindung besteht, sonst false
                                      * command(cmd): Hierüber können dem Heos-Player Befehle übergeben werden. Diese werden im Klartext in die State-Variable geschrieben. Getrennt durch das | Zeichen können mehrere hintereinander eingetragen werden. Bsp: Setzen der Lautstärke auf 20 und Abspielen des 1.Favoriten "set_volume&level=20|play_preset&preset=1|set_play_state&state=play". Folgende cmd sind erlaubt:
                                      
                                        "set_volume&level=0|1|..|100" : Setzt die gewünschte Lautstärke 
                                        "set_play_state&state=play|pause|stop" : Startet und stoppt die Wiedergabe
                                        "set_play_mode&repeat=on_all|on_one|off&shuffle=on|off": Setzt Wiederholung und Zufallsweidergabe
                                        "set_mute&state=on|off" : Stumm schalten oder nicht
                                        "volume_down&step=1..10" : Lautstärke verringern um   
                                        "volume_up&step=1..10"  : Lauststäre erhöhen um
                                        "play_next"  : Nächsten Titel spielen
                                        "play_previous" : Vorherigen Titel spielen
                                        "play_preset&preset=1|2|..|n" : Favorit Nr n abspielen
                                        "play_stream&url=url_path" : URL-Stream abspielen
                                       
                                      * cur_pos (read) : lfd. Position der Wiedergabe in [Sek]
                                      * cur_pos_MMSS (read) : lfd. Position der Wiedergabe im Format MM:SS
                                      * duration (read) : Länge des aktuellen Titels in [Sek]
                                      * duration_MMSS (read) : Länge des aktuellen Titels im Format MM:SS
                                      * ip (read): IP-Adresse des HEOS Players
                                      * last_error (read): Text des letzten Fehlers, der vom Player gesendet wurde. Wird bei jedem neuen Befehl zurückgesetzt. Wenn man bspw. versucht eine Wiedergabe über Amazon o.ä. zu starten und es gibt aber keine Verbindung dahin, steht hier der Fehlertext drin
                                      * model (read): Modell des HEOS Players
                                      * mute (read write): Boolsche Variable, die anzeigt ob der Player gemutet (Stumm geschaltet) wurde. Hierüber kann auch ein mute gesetzt werden
                                      * name (read): Name des HEOS Players
                                      * now_playing_media_... (read) : Eine Gruppe von States, in welchen Infos über den aktuellen Titel gespeichert werden, wird automatisch aktualisiert. 
                                      * pid (read): Player-ID des HEOS Players
                                      * play_mode_repeat (read write) : Repeat-Modus der Wiedergabe. Mögliche Werte: on_all|on_one|off  
                                      * play_mode_shuffle (read write) : Zufallswiedergabe true/false
                                      * play_state (read write): Zustand des Players. Mögliche Werte play|pause|stop
                                      * serial (read): Seriennummmer des HEOS Players
                                      * volume (read write): Lautstärke im Bereich 0 - 100. 
                                      * group_leader : Ist Gruppenleiter
                                      * group_member : Ist Gruppenmitglied
                                      * group_pid : Player pids in der Gruppe
                                      * group_volume : Lautstärke wenn Gruppen-Leiter
                                      * group_name : Name der Gruppe
                                      * group_mute : Gruppe gemutet?
                                      
                                       
                                      #### Weiterführende Links
                                      
                                      * HEOS CLI Protokoll: http://rn.dmglobal.com/euheos/HEOS_CLI_ProtocolSpecification.pdf
                                      * http://forum.iobroker.net/viewtopic.php?f=30&t=5693&p=115554#p115554
                                       
                                       
                                      (c) Uhula, MIT License, no warranty, use on your own risc
                                       
                                      */
                                      
                                      /****************************
                                       * Konfiguration
                                       ****************************/
                                      
                                      const HEOS_USERNAME = '';
                                      const HEOS_PASSWORD = '';
                                      
                                      const HEARTBEAT_INTERVAL = 15000;
                                      const HEARTBEAT_TIMEOUT = 60000;
                                      
                                      
                                      /****************************
                                       * ab hier nichts mehr ändern ;-) 
                                       ****************************/
                                      
                                      var net = require('net');
                                      
                                      const stateDISCONNECTED = 0;
                                      const stateSEARCHING = 1;
                                      const stateCONNECTING = 2;
                                      const stateCONNECTED = 3;
                                      const SCRIPTVERSION = '2.0/2020-03-21';
                                      
                                      /********************
                                       * class Heos
                                       ********************/
                                      class Heos {
                                      
                                          logDebug(msg) { console.debug('[Heos] ' + msg); }
                                          log(msg) { console.log('[Heos] ' + msg); }
                                          logWarn(msg) { console.warn('[Heos] ' + msg); }
                                          logError(msg) { console.error('[Heos] ' + msg); }
                                      
                                          constructor() {
                                              this.init();
                                              this.states = [
                                                  { id: "command", common: { name: "Kommando für HEOS Aufrufe" } },
                                                  { id: "connected", common: { name: "Verbunden?", write: false, def: false } },
                                                  { id: "last_error", common: { name: "Letzte Fehler", write: false } },
                                                  { id: "script_version", common: { name: "Installierte Script-Version", write: false } }
                                              ];
                                              // beim 1.Start nur die States erzeugen
                                              if (!this.existState("script_version") || (this.getState('script_version').val != SCRIPTVERSION)) {
                                                  this.installed = false;
                                                  this.logWarn('creating HEOS states for version ' + SCRIPTVERSION + ', please start script again');
                                                  // Anlage der States 
                                                  for (var s = 0; s < this.states.length; s++) {
                                                      this.setState(this.states[s].id);
                                                  }
                                                  this.setState('script_version', SCRIPTVERSION);
                                              }
                                              else {
                                                  this.installed = true;
                                                  on({ id: this.statePath + 'command', change: "any" }, (obj) => {
                                                      this.executeCommand(obj.state.val);
                                                  });
                                              }
                                          }
                                      
                                      
                                      
                                          // Initialisierung
                                          init() {
                                              this.statePath = '0_userdata.0.heos.';
                                              this.players = {};
                                              this.heartbeatInterval = undefined;
                                              this.heartbeatTimeout = undefined;
                                              this.ssdpSearchInterval = undefined;
                                              this.net_client = undefined;
                                              this.nodessdp_client = undefined;
                                      
                                              this.ip = '';
                                              this.msgs = [];
                                              this.lastResponse = '';
                                              this.state = stateDISCONNECTED;
                                              this.unfinishedResponses = '';
                                              this.ssdpSearchTargetName = 'urn:schemas-denon-com:device:ACT-Denon:1';
                                          }
                                      
                                      
                                          // über den $-Operator nachsehen, ob der state bereits vorhanden ist
                                          // getState().notExists geht auch, erzeugt aber Warnmeldungen!
                                          existState(id) {
                                              return ($(this.statePath + id).length == 0 ? false : true);
                                          }
                                      
                                          // wrapper
                                          getState(id) {
                                              return getState(this.statePath + id);
                                          }
                                      
                                          // wie setState(), setzt aber den statePath davor und überpüft aber ob der state vorhanden ist und erzeugt ihn,
                                          // wenn er noch nicht da ist
                                          setState(id, value) {
                                              if (!this.existState(id)) this.createState(id, value, undefined);
                                              else setState(this.statePath + id, value, true);
                                          }
                                      
                                          // wie createState, setzt aber noch den statePath davor und schaut im states-Array nach, ob dort common-Angaben
                                          // vorhanden sind (wenn der common-Parameter leer ist)
                                          createState(id, value, common) {
                                              if (!this.existState(id)) {
                                                  if (common === undefined) {
                                                      // id im states-Array suchen
                                                      for (var i = 0; i < this.states.length; i++) {
                                                          if (this.states[i].id == id) {
                                                              if (this.states[i].hasOwnProperty('common'))
                                                                  common = this.states[i].common;
                                                              break;
                                                          }
                                                      }
                                                  }
                                                  if ((typeof value === 'undefined') && (common.hasOwnProperty('def'))) value = common.def;
                                                  // unter "0_userdata.0"
                                                  let obj = {};
                                                  obj.type = 'state';
                                                  obj.native = {};
                                                  obj.common = common;
                                                  setObject(this.statePath + id, obj, (err) => {
                                                      if (err) {
                                                          this.log('cant write object for state "' + this.statePath + id + '": ' + err);
                                                      } else {
                                                          this.log('state "' + this.statePath + id + '" created');
                                                      }
                                                  });
                                      
                                                  // value zeitversetzt setzen        
                                                  setTimeout(setState, 3000, this.statePath + id, value);
                                              }
                                          }
                                      
                                          /** Verbindung zum HEOS System herstellen
                                           **/
                                          connect() {
                                              if (!this.installed) return;
                                      
                                              try {
                                                  this.log("searching for HEOS devices ...")
                                                  this.setState("connected", false);
                                                  this.state = stateSEARCHING;
                                                  const NodeSSDP = require('node-ssdp').Client;
                                                  this.nodessdp_client = new NodeSSDP();
                                                  this.nodessdp_client.explicitSocketBind = true;
                                                  this.nodessdp_client.on('response', (headers, statusCode, rinfo) => this.onNodeSSDPResponse(headers, statusCode, rinfo));
                                                  this.nodessdp_client.on('error', error => { this.nodessdp_client.close(); this.logError(error); });
                                                  this.nodessdp_client.search(this.ssdpSearchTargetName);
                                                  this.ssdpSearchInterval = setInterval(() => {
                                                      this.log("still searching for HEOS devices ...")
                                                      this.nodessdp_client.search(this.ssdpSearchTargetName);
                                                  }, 30000);
                                              } catch (err) { this.logError('connect: ' + err.message); }
                                      
                                          }
                                      
                                          /** Alle Player stoppen und die TelNet Verbindung schließen 
                                           **/
                                          disconnect() {
                                              this.log('disconnecting from HEOS ...');
                                              unsubscribe(this.statePath.substr(0, this.statePath.length - 1));
                                      
                                              this.stopHeartbeat();
                                              this.stopPlayers();
                                      
                                              if (typeof this.net_client !== 'undefined') {
                                                  this.registerChangeEvents(false);
                                                  this.net_client.destroy();
                                                  this.net_client.unref();
                                              }
                                              if (typeof this.nodessdp_client !== 'undefined') {
                                                  this.nodessdp_client.stop();
                                              }
                                              this.setState("connected", false);
                                              this.state = stateDISCONNECTED;
                                              this.log('disconnected from HEOS');
                                          }
                                      
                                          reconnect() {
                                              if (this.state == stateCONNECTED) {
                                                  this.log('reconnecting to HEOS ...');
                                                  this.disconnect();
                                                  setTimeout(() => {
                                                      this.init();
                                                      this.connect();
                                                  }, 5000);
                                              }
                                          }
                                      
                                          executeCommand(cmd) {
                                              //l('command: '+cmd);
                                              switch (cmd) {
                                                  case 'load_presets':
                                                      this.getMusicSources();
                                                      break;
                                                  case 'group/get_groups':
                                                      this.getGroups();
                                                      break;
                                                  case 'connect':
                                                      this.disconnect();
                                                      this.init();
                                                      this.connect();
                                                      break;
                                                  case 'disconnect':
                                                      this.disconnect();
                                                      break;
                                                  default:
                                                      if (this.state == stateCONNECTED) {
                                                          this.msgs.push('heos://' + cmd + '\n');
                                                          this.sendNextMsg();
                                                      }
                                      
                                              }
                                          }
                                          /** es wurde mindestens ein Player erkannt, nun über dessen IP alle bekannten HEOS Player
                                           *  durch senden von "player/get_players" ermitteln
                                           */
                                          onNodeSSDPResponse(headers, statusCode, rinfo) {
                                              try {
                                                  // rinfo {"address":"192.168.2.225","family":"IPv4","port":53871,"size":430}
                                                  if (typeof this.net_client == 'undefined') {
                                                      if (headers.ST !== this.ssdpSearchTargetName) { // korrektes SSDP
                                                          this.logDebug('onNodeSSDPResponse: Getting wrong SSDP entry. Keep trying...');
                                                      } else {
                                                          if (this.ssdpSearchInterval) {
                                                              clearInterval(this.ssdpSearchInterval);
                                                              this.ssdpSearchInterval = undefined;
                                                          }
                                      
                                                          this.ip = rinfo.address;
                                                          this.log('connecting to HEOS (' + this.ip + ') ...');
                                                          this.net_client = net.connect({ host: this.ip, port: 1255 });
                                                          this.net_client.setKeepAlive(true, 5000);
                                                          this.net_client.setNoDelay(true);
                                                          this.net_client.setTimeout(15000);
                                      
                                                          this.state = stateCONNECTING;
                                      
                                                          this.net_client.on('error', (error) => {
                                                              this.logError(error);
                                                              this.reconnect();
                                                          });
                                      
                                                          this.net_client.on('connect', () => {
                                                              this.setState("connected", true);
                                                              this.state = stateCONNECTED;
                                                              this.log('connected to HEOS (' + this.ip + ')');
                                                              this.getPlayers();
                                                              this.registerChangeEvents(true);
                                                              this.signIn();
                                                              this.getMusicSources();
                                                              this.getGroups();
                                                              this.startHeartbeat();
                                                          });
                                      
                                                          // Gegenseite hat die Verbindung geschlossen 
                                                          this.net_client.on('end', () => {
                                                              this.logWarn('HEOS closed the connection to ' + this.ip);
                                                              this.reconnect();
                                                          });
                                      
                                                          // timeout
                                                          this.net_client.on('timeout', () => {
                                                              this.logWarn('Timeout trying connect to ' + this.ip);
                                                              this.reconnect();
                                                          });
                                      
                                                          // Datenempfang
                                                          this.net_client.on('data', (data) => this.onData(data));
                                                      }
                                                  }
                                              } catch (err) { this.logError('onNodeSSDPResponse: ' + err.message); }
                                          }
                                      
                                          setLastError(err) {
                                              this.logWarn(err);
                                              let val = this.getState('last_error').val + '';
                                              let lines = val.split('\n');
                                              if (lines.length > 4)
                                                  lines.pop();
                                              lines.unshift(err + '\n');
                                              this.setState('last_error', lines.toString());
                                          }
                                      
                                      
                                          /** es liegen Antwort(en) vor
                                           * 
                                           * {"heos": {"command": "browse/browse", "result": "success", "message": "sid=1028&returned=9&count=9"}, 
                                           *    "payload": [
                                           *        {"container": "no", "mid": "s25529", "type": "station", "playable": "yes", "name": "NDR 1 Niedersachsen (Adult Hits)", "image_url": "http://cdn-profiles.tunein.com/s25529/images/logoq.png?t=154228"}, 
                                           *        {"container": "no", "mid": "s56857", "type": "station", "playable": "yes", "name": "NDR 2 Niedersachsen 96.2 (Top 40 %26 Pop Music)", "image_url": "http://cdn-profiles.tunein.com/s56857/images/logoq.png?t=154228"}, 
                                           *        {"container": "no", "mid": "s24885", "type": "station", "playable": "yes", "name": "NDR Info", "image_url": "http://cdn-profiles.tunein.com/s24885/images/logoq.png?t=1"}, {"container": "no", "mid": "s158432", "type": "station", "playable": "yes", "name": "Absolut relax (Easy Listening Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s158432q.png"}, 
                                           *        {"container": "no", "mid": "catalog/stations/A316JYMKQTS45I/#chunk", "type": "station", "playable": "yes", "name": "Johannes Oerding", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/JohannesOerding._SX200_SY200_.jpg"}, 
                                           *        {"container": "no", "mid": "catalog/stations/A1O1J39JGVQ9U1/#chunk", "type": "station", "playable": "yes", "name": "Passenger", "image_url": "https://images-na.ssl-images-amazon.com/images/I/71DsYkU4QaL._SY500_CR150,0,488,488_SX200_SY200_.jpg"}, 
                                           *        {"container": "no", "mid": "catalog/stations/A1W7U8U71CGE50/#chunk"
                                           **/
                                          onData(data) {
                                              try {
                                                  data = data.toString();
                                                  data = data.replace(/[\n\r]/g, '');    // Steuerzeichen "CR" entfernen   
                                                  // es können auch mehrere Antworten vorhanden sein! {"heos": ... } {"heos": ... }
                                                  // diese nun in einzelne Antworten zerlegen
                                                  data = this.unfinishedResponses + data;
                                                  this.unfinishedResponses = '';
                                      
                                                  data = data.replace(/{"heos":/g, '|{"heos":');
                                                  var responses = data.split('|');
                                                  responses.shift();
                                                  for (var r = 0; r < responses.length; r++) if (responses[r].trim().length > 0) {
                                                      try {
                                                          JSON.parse(responses[r]); // check ob korrektes JSON Array
                                                          this.parseResponse(responses[r]);
                                                      } catch (e) {
                                                          this.logDebug('onData: invalid json (error: ' + e.message + '): ' + responses[r]);
                                                          this.unfinishedResponses += responses[r];
                                                      }
                                                  }
                                                  // wenn weitere Msg zum Senden vorhanden sind, die nächste senden
                                                  if (this.msgs.length > 0)
                                                      this.sendNextMsg();
                                              } catch (err) { this.logError('onData: ' + err.message); }
                                          }
                                      
                                          /** Antwort(en) verarbeiten. Sich wiederholende Antworten ignorieren
                                           **/
                                          parseResponse(response) {
                                              try {
                                                  this.logDebug('parseResponse: ' + response);
                                      
                                                  if (response == this.lastResponse || response.indexOf("command under process") > 0)
                                                      return
                                                  this.lastResponse = response;
                                      
                                                  var jmsg;
                                                  var i;
                                                  var jdata = JSON.parse(response);
                                                  if (!jdata.hasOwnProperty('heos') || !jdata.heos.hasOwnProperty('command'))
                                                      return;
                                      
                                                  // msg auswerten
                                                  try {
                                                      jmsg = '{"' + decodeURI(jdata.heos.message).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"').replace(/\s/g, '_') + '"}';
                                                      jmsg = JSON.parse(jmsg);
                                                  } catch (err) {
                                                      jmsg = {};
                                                  }
                                      
                                                  // result ?
                                                  var result = 'success';
                                                  if (jdata.heos.hasOwnProperty('result')) result = jdata.heos.result;
                                                  if (result != 'success') {
                                                      this.setLastError('result=' + result + ', ' + jmsg.text);
                                                  }
                                      
                                                  // cmd auswerten
                                                  var cmd = jdata.heos.command.split('/');
                                                  var cmd_group = cmd[0];
                                                  cmd = cmd[1];
                                                  switch (cmd_group) {
                                                      case 'system':
                                                          switch (cmd) {
                                                              case 'heart_beat':
                                                                  this.resetHeartbeatTimeout();
                                                                  break;
                                                          }
                                                          break;
                                                      case 'event':
                                                          switch (cmd) {
                                                              case 'players_changed':
                                                                  this.getPlayers();
                                                                  break;
                                                              case 'groups_changed':
                                                                  this.getGroups();
                                                                  break;
                                                              case 'group_volume_changed':
                                                                  // "heos": {"command": "event/group_volume_changed ","message": "gid='group_id'&level='vol_level'&mute='on_or_off'"}
                                                                  if (jmsg.hasOwnProperty('gid')) {
                                                                      if (jmsg.hasOwnProperty('level')) {
                                                                          this.setState(jmsg.gid + '.group_volume', jmsg.level);
                                                                      }
                                                                      if (jmsg.hasOwnProperty('mute')) {
                                                                          this.setState(jmsg.gid + '.group_mute', (jmsg.mute == 'on' ? true : false));
                                                                      }
                                                                  }
                                                                  break;
                                                          }
                                                          break;
                                                      case 'player':
                                                          switch (cmd) {
                                                              // {"heos": {"command": "player/get_players", "result": "success", "message": ""}, 
                                                              //  "payload": [{"name": "HEOS Bar", "pid": 1262037998, "model": "HEOS Bar", "version": "1.430.160", "ip": "192.168.2.225", "network": "wifi", "lineout": 0, "serial": "ADAG9170202780"}, 
                                                              //              {"name": "HEOS 1 rechts", "pid": -1746612370, "model": "HEOS 1", "version": "1.430.160", "ip": "192.168.2.201", "network": "wifi", "lineout": 0, "serial": "AMWG9170934429"}, 
                                                              //              {"name": "HEOS 1 links", "pid": 68572158, "model": "HEOS 1", "version": "1.430.160", "ip": "192.168.2.219", "network": "wifi", "lineout": 0, "serial": "AMWG9170934433"}
                                                              //             ]}
                                                              case 'get_players':
                                                                  if (jdata.hasOwnProperty('payload')) {
                                                                      this.startPlayers(jdata.payload);
                                                                  }
                                                                  break;
                                                          }
                                                          break;
                                      
                                                      // {"heos": {"command": "browse/get_music_sources", "result": "success", "message": ""}, 
                                                      //  "payload": [{"name": "Amazon", "image_url": "https://production...png", "type": "music_service", "sid": 13}, 
                                                      //              {"name": "TuneIn", "image_url": "https://production...png", "type": "music_service", "sid": 3}, 
                                                      //              {"name": "Local Music", "image_url": "https://production...png", "type": "heos_server", "sid": 1024}, 
                                                      //              {"name": "Playlists", "image_url": "https://production...png", "type": "heos_service", "sid": 1025}, 
                                                      //              {"name": "History", "image_url": "https://production...png", "type": "heos_service", "sid": 1026}, 
                                                      //              {"name": "AUX Input", "image_url": "https://production...png", "type": "heos_service", "sid": 1027}, 
                                                      //              {"name": "Favorites", "image_url": "https://production...png", "type": "heos_service", "sid": 1028}]}
                                                      case 'browse':
                                                          switch (cmd) {
                                                              case 'get_music_sources':
                                                                  if ((jdata.hasOwnProperty('payload'))) {
                                                                      for (i = 0; i < jdata.payload.length; i++) {
                                                                          var source = jdata.payload[i];
                                                                          if (source.name == 'Favorites') {
                                                                              this.browse(source.sid);
                                                                          }
                                                                      }
                                                                  }
                                      
                                                                  break;
                                      
                                                              // {"heos": {"command": "browse/browse", "result": "success", "message": "pid=1262037998&sid=1028&returned=5&count=5"}, 
                                                              //  "payload": [{"container": "no", "mid": "s17492", "type": "station", "playable": "yes", "name": "NDR 2 (Adult Contemporary Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s17492q.png"}, 
                                                              //              {"container": "no", "mid": "s158432", "type": "station", "playable": "yes", "name": "Absolut relax (Easy Listening Music)", "image_url": "http://cdn-radiotime-logos.tunein.com/s158432q.png"}, 
                                                              //              {"container": "no", "mid": "catalog/stations/A1W7U8U71CGE50/#chunk", "type": "station", "playable": "yes", "name": "Ed Sheeran", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/EdSheeran._SX200_SY200_.jpg"}, 
                                                              //              {"container": "no", "mid": "catalog/stations/A1O1J39JGVQ9U1/#chunk", "type": "station", "playable": "yes", "name": "Passenger", "image_url": "https://images-na.ssl-images-amazon.com/images/I/71DsYkU4QaL._SY500_CR150,0,488,488_SX200_SY200_.jpg"}, 
                                                              //              {"container": "no", "mid": "catalog/stations/A316JYMKQTS45I/#chunk", "type": "station", "playable": "yes", "name": "Johannes Oerding", "image_url": "https://images-na.ssl-images-amazon.com/images/G/01/Gotham/DE_artist/JohannesOerding._SX200_SY200_.jpg"}], 
                                                              //  "options": [{"browse": [{"id": 20, "name": "Remove from HEOS Favorites"}]}]}                    
                                                              case 'browse':
                                                                  if ((jdata.hasOwnProperty('payload'))) {
                                                                      for (i = 0; i < jdata.payload.length; i++) {
                                                                          var preset = jdata.payload[i];
                                                                          this.createState('presets.' + (i + 1) + '.name', preset.name, { name: 'Favoritenname ' });
                                                                          this.createState('presets.' + (i + 1) + '.playable', (preset.playable == 'yes' ? true : false), { name: 'Favorit ist spielbar' });
                                                                          this.createState('presets.' + (i + 1) + '.type', preset.type, { name: 'Favorittyp' });
                                                                          this.createState('presets.' + (i + 1) + '.image_url', preset.image_url, { name: 'Favoritbild' });
                                                                      }
                                                                  }
                                                                  break;
                                                          }
                                                          break;
                                      
                                                      case 'group':
                                                          //l('group: '+response);
                                                          switch (cmd) {
                                                              // { "heos":{"command":"player/set_group","result":"success",
                                                              //           "message": "gid='new group_id'&name='group_name'&pid='player_id_1, player_id_2,…,player_id_n'
                                                              //          } 
                                                              // }
                                                              case 'set_group':
                                                                  this.setGroup(jmsg);
                                                                  break;
                                      
                                                              // { "heos": {"command":"group/get_volume","result":"success","message": "gid='group_id'&level='vol_level'"}
                                                              case 'get_volume':
                                                                  if (jmsg.hasOwnProperty('gid')) {
                                                                      if (jmsg.hasOwnProperty('level')) {
                                                                          this.setState(jmsg.gid + '.group_volume', jmsg.level);
                                                                      }
                                                                  }
                                                                  break;
                                      
                                                              // { "heos": {"command":"group/get_mute","result":"success","message": "gid='group_id'&state='on_or_off'"}
                                                              case 'get_mute':
                                                                  if (jmsg.hasOwnProperty('gid')) {
                                                                      if (jmsg.hasOwnProperty('state')) {
                                                                          this.setState(jmsg.gid + '.group_mute', (jmsg.state == 'on' ? true : false));
                                                                      }
                                                                  }
                                                                  break;
                                      
                                      
                                                              // { "heos": { "command": "player/get_groups", "result": "success", "message": "" },
                                                              //   "payload": [{"name":"'group name 1'", "gid": "group id 1'",
                                                              //                "players":[{"name":"player name 1","pid":"'player id1'","role":"player role 1 (leader or member)'"},
                                                              //                           {"name":"player name 2","pid":"'player id2'","role":"player role 2 (leader or member)'"} 
                                                              //                          ]
                                                              //               },
                                                              //               {"name":"'group name 2'","gid":"group id 2'",
                                                              //                "players":[{"name":"player name ... 
                                                              case 'get_groups':
                                                                  // bisherige groups leeren
                                                                  var objs = $(this.statePath + '*.group_name');
                                                                  for (var o = 0; o < objs.length; o++) setState(objs[o], 'no group');
                                                                  objs = $(this.statePath + '*.group_leader');
                                                                  for (var o = 0; o < objs.length; o++) setState(objs[o], false);
                                                                  objs = $(this.statePath + '*.group_member');
                                                                  for (var o = 0; o < objs.length; o++) setState(objs[o], false);
                                                                  objs = $(this.statePath + '*.group_pid');
                                                                  for (var o = 0; o < objs.length; o++) setState(objs[o], '');
                                      
                                                                  // payload mit den groups auswerten
                                                                  if ((jdata.hasOwnProperty('payload'))) {
                                                                      for (i = 0; i < jdata.payload.length; i++) {
                                                                          var group = jdata.payload[i];
                                                                          var players = group.players;
                                                                          // Player IDs addieren. Hinweis: "leader" ist nicht immer der 1.Playereintrag
                                                                          group.pid = "";
                                                                          for (var p = 0; p < players.length; p++) {
                                                                              if (players[p].role == 'leader')
                                                                                  group.pid = players[p].pid + (group.pid.length > 0 ? "," : "") + group.pid;
                                                                              else
                                                                                  group.pid = group.pid + (group.pid.length > 0 ? "," : "") + players[p].pid;
                                                                          }
                                                                          this.setGroup(group);
                                                                      }
                                                                  }
                                                                  break;
                                      
                                                          }
                                                          break;
                                      
                                      
                                                      case 'system':
                                                          switch (cmd) {
                                                              case 'sign_in':
                                                                  this.log('signed in: ' + jdata.heos.result);
                                                                  break;
                                                          }
                                                          break;
                                                  }
                                      
                                      
                                                  // an die zugehörigen Player weiterleiten
                                                  if (jmsg.hasOwnProperty('pid')) {
                                                      if(jmsg.pid in this.players){
                                                          this.players[jmsg.pid].parseResponse(jdata, jmsg, cmd_group, cmd);
                                                      }
                                                  }
                                      
                                      
                                              } catch (err) { this.logError('parseResponse: ' + err.message + '\n ' + response); }
                                          }
                                      
                                      
                                          // sucht die zur pid passenden player-Insanz
                                          sendCommandToPlayer(objID, cmd) {
                                              // aus der objID die pid holen
                                              // objID = javascript.1.heos.394645376.command
                                              objID = objID.split('.');
                                              var pid = objID[3];
                                              if(pid in this.players){
                                                  this.players[pid].sendCommand(cmd);
                                              }
                                          }
                                      
                                          stopPlayer(pid){
                                              try {
                                                  let heosPlayer = this.players[pid];
                                                  if (heosPlayer)
                                                      heosPlayer.stopPlayer();
                                                  // player leeren
                                                  delete this.players[pid];
                                              } catch (err) { this.logError('stopPlayer: ' + err.message); }
                                          }
                                      
                                          // Für die gefundenen HEOS Player entsprechende class HeosPlayer Instanzen bilden und nicht mehr verbundene Player stoppen
                                          startPlayers(payload) {
                                              try {
                                                  var connectedPlayers = [];
                                                  for (var i = 0; i < payload.length; i++) {
                                                      var player = payload[i];
                                                      var pid = player.pid + ''; //Convert to String
                                                      if(!(pid in this.players)){
                                                          this.players[pid] = new HeosPlayer(this, player);
                                                      }
                                                      connectedPlayers.push(pid);
                                                  }
                                                  //Remove disconnected players
                                                  for(var pid in this.players) {
                                                      if(!connectedPlayers.includes(pid)){
                                                          this.stopPlayer(pid);
                                                      }
                                                  }
                                              } catch (err) { this.logError('startPlayers: ' + err.message); }
                                          }
                                      
                                          //Alle Player stoppen
                                          stopPlayers() {
                                              this.logDebug("Try to stop players:" + JSON.stringify(Object.keys(this.players)));
                                              for (var pid in this.players) {
                                                  this.stopPlayer(pid);
                                              }
                                          }
                                      
                                          // setzen der Werte einer Group
                                          setGroup(group) {
                                              if (group.hasOwnProperty('pid')) {
                                                  // in den Playern den Groupstatus setzen
                                                  var pids = group.pid.split(',');
                                      
                                                  for (var i = 0; i < pids.length; i++) {
                                                      this.setState(pids[i] + '.group_name', (group.hasOwnProperty('name') ? group.name : ''));
                                                      this.setState(pids[i] + '.group_pid', group.pid);
                                                      this.setState(pids[i] + '.group_leader', (i == 0) && (pids.length > 1));
                                                      this.setState(pids[i] + '.group_member', (pids.length > 1));
                                                  }
                                      
                                                  if (group.hasOwnProperty('gid')) {
                                                      // volume und mute dazu holen
                                                      this.executeCommand("group/get_volume?gid=" + group.gid);
                                                      this.executeCommand("group/get_mute?gid=" + group.gid);
                                                  }
                                              }
                                      
                                          }
                                      
                                          getPlayers() {
                                              if (this.state == stateCONNECTED) {
                                                  this.msgs.push('heos://player/get_players\n');
                                                  this.sendNextMsg();
                                              }
                                          }
                                      
                                          registerChangeEvents(b) {
                                              if (this.state == stateCONNECTED) {
                                                  if (b) this.msgs.push('heos://system/register_for_change_events?enable=on');
                                                  else this.msgs.push('heos://system/register_for_change_events?enable=off');
                                                  this.sendNextMsg();
                                              }
                                          }
                                      
                                          signIn() {
                                              if (this.state == stateCONNECTED) {
                                                  // heos://system/sign_in?un=heos_username&pw=heos_password
                                                  this.msgs.push('heos://system/sign_in?un=' + HEOS_USERNAME + '&pw=' + HEOS_PASSWORD);
                                                  this.sendNextMsg();
                                              }
                                          }
                                      
                                          getMusicSources() {
                                              if (this.state == stateCONNECTED) {
                                                  // heos://browse/get_music_sources
                                                  this.msgs.push('heos://browse/get_music_sources');
                                                  this.sendNextMsg();
                                              }
                                      
                                          }
                                      
                                          getGroups() {
                                              if (this.state == stateCONNECTED) {
                                                  // heos://group/get_groups
                                                  this.msgs.push('heos://group/get_groups');
                                                  this.sendNextMsg();
                                              }
                                      
                                          }
                                      
                                          startHeartbeat() {
                                              if (this.state == stateCONNECTED) {
                                                  this.logDebug("start heartbeat interval");
                                                  this.heartbeatInterval = setInterval(() => {
                                                      this.logDebug("heartbeat")
                                                      this.msgs.push('heos://system/heart_beat');
                                                      this.sendNextMsg();
                                                      if (typeof this.heartbeatTimeout == undefined) {
                                                          this.heartbeatTimeout = setTimeout(() => {
                                                              this.log("heartbeat timeout");
                                                              this.reboot();
                                                              setTimeout(() => {
                                                                  this.reconnect();
                                                              }, 1000)
                                                          }, HEARTBEAT_TIMEOUT);
                                                      }
                                                  }, HEARTBEAT_INTERVAL);
                                              }
                                          }
                                      
                                          resetHeartbeatTimeout() {
                                              this.logDebug("reset heartbeat timeout");
                                              if (this.heartbeatTimeout) {
                                                  clearTimeout(this.heartbeatTimeout);
                                                  this.heartbeatTimeout = undefined;
                                              }
                                          }
                                      
                                          stopHeartbeat() {
                                              this.logDebug("stop heartbeat interval");
                                              if (this.heartbeatInterval) {
                                                  clearInterval(this.heartbeatInterval);
                                                  this.heartbeatInterval = undefined;
                                              }
                                              this.resetHeartbeatTimeout();
                                          }
                                      
                                          reboot() {
                                              if (this.state == stateCONNECTED) {
                                                  this.logDebug("reboot device");
                                                  // heos://system/reboot
                                                  this.msgs.push('heos://system/reboot');
                                                  this.sendNextMsg();
                                              }
                                          }
                                      
                                          browse(sid) {
                                              if (this.state == stateCONNECTED) {
                                                  // heos://browse/browse?sid=source_id
                                                  this.msgs.push('heos://browse/browse?sid=' + sid);
                                                  this.sendNextMsg();
                                              }
                                          }
                                      
                                      
                                          sendNextMsg() {
                                              if (this.msgs.length > 0) {
                                                  var msg = this.msgs.shift();
                                                  this.sendMsg(msg);
                                              }
                                          }
                                      
                                          // Nachricht an player senden
                                          sendMsg(msg) {
                                              this.net_client.write(msg + "\n");
                                              this.logDebug("data sent: " + msg);
                                          }
                                      
                                      
                                      } // end of class Heos
                                      
                                      /********************
                                       * class HeosPlayer
                                       ********************/
                                      
                                      class HeosPlayer {
                                      
                                          logDebug(msg) { console.debug('[HeosPlayer ' + this.pid + '] ' + msg); }
                                          log(msg) { console.log('[HeosPlayer ' + this.pid + '] ' + msg); }
                                          logWarn(msg) { console.warn('[HeosPlayer ' + this.pid + '] ' + msg); }
                                          logError(msg) { console.error('[HeosPlayer ' + this.pid + '] ' + msg); }
                                      
                                          constructor(heos, player) {
                                              this.heos = heos;
                                              this.ip = player.ip;
                                              this.name = player.name;
                                              this.pid = player.pid;
                                              this.model = player.model;
                                              this.serial = player.serial;
                                              this.onHandler = [];
                                      
                                              //    this.statePath = 'javascript.'+instance+'.heos.'+this.ip.replace(/\./g,'_')+".";
                                              this.statePath = '0_userdata.0.heos.' + this.pid + ".";
                                      
                                              this.log('creating HEOS player with pid ' + this.pid + ' (' + this.ip + ')');
                                      
                                              this.states = [
                                                  { id: "connected", common: { name: "Verbunden?" } },
                                                  { id: "command", common: { name: "Befehl an Heos Player senden" } },
                                                  { id: "ip", common: { name: "IP-Adresse", write: false } },
                                                  { id: "pid", common: { name: "Player-ID", write: false } },
                                                  { id: "name", common: { name: "Name des Players", write: false } },
                                                  { id: "model", common: { name: "Modell des Players", write: false } },
                                                  { id: "serial", common: { name: "Seriennummer des Players", write: false } },
                                                  { id: "last_error", common: { name: "Letzte Fehler", write: false } },
                                                  { id: "volume", common: { name: "Aktuelle Lautstärke" } },
                                                  { id: "mute", common: { name: "Mute aktiviert?" } },
                                                  { id: "play_state", common: { name: "Aktueller Wiedergabezustand" } },
                                                  { id: "play_mode_repeat", common: { name: "Wiedergabewiederholung" } },
                                                  { id: "play_mode_shuffle", common: { name: "Zufällige Wiedergabe" } },
                                                  { id: "now_playing_media_type", common: { name: "Aktuelle Wiedergabemedium", write: false } },
                                                  { id: "now_playing_media_song", common: { name: "Aktuelles Lied", write: false } },
                                                  { id: "now_playing_media_station", common: { name: "Aktuelle Station", write: false } },
                                                  { id: "now_playing_media_album", common: { name: "Aktuelle Wiedergabemedium", write: false } },
                                                  { id: "now_playing_media_artist", common: { name: "Aktueller Artist", write: false } },
                                                  { id: "now_playing_media_image_url", common: { name: "Aktuelles Coverbild", write: false } },
                                                  { id: "now_playing_media_album_id", common: { name: "Aktuelle Album-ID", write: false } },
                                                  { id: "now_playing_media_mid", common: { name: "Aktuelle mid", write: false } },
                                                  { id: "now_playing_media_qid", common: { name: "Aktuelle qid", write: false } },
                                                  { id: "cur_pos", common: { name: "lfd. Position" } },
                                                  { id: "duration", common: { name: "Dauer", write: false } },
                                                  { id: "cur_pos_MMSS", common: { name: "lfd. Position MM:SS" } },
                                                  { id: "duration_MMSS", common: { name: "Dauer MM:SS", write: false } },
                                                  { id: "group_leader", common: { name: "Ist Gruppenleiter", write: false } },
                                                  { id: "group_member", common: { name: "Ist Gruppenmitglied", write: false } },
                                                  { id: "group_pid", common: { name: "PlayerIDs in der Gruppe", write: false } },
                                                  { id: "group_volume", common: { name: "Lautstärke wenn Gruppen-Leiter" } },
                                                  { id: "group_name", common: { name: "Name der Gruppe", write: false } },
                                                  { id: "group_mute", common: { name: "Gruppe gemutet?" } },
                                                  { id: "script_version", common: { name: "Installierte Script-Version", write: false } }
                                              ];
                                              // beim 1.Start die states erzeugen
                                              if (!this.existState("script_version") || this.getState('script_version').val != SCRIPTVERSION) {
                                                  this.installed = false;
                                                  this.logWarn('creating HEOS player states for version ' + SCRIPTVERSION + ', please start script again');
                                                  // states erzeugen
                                                  for (var s = 0; s < this.states.length; s++) {
                                                      this.setState(this.states[s].id, "");
                                                  }
                                                  this.setState('script_version', SCRIPTVERSION);
                                              }
                                              else {
                                                  // Initialisierung der States nach 5 Sek
                                                  setTimeout(() => {
                                                      this.setState('ip', this.ip);
                                                      this.setState('name', this.name);
                                                      this.setState('pid', this.pid);
                                                      this.setState('model', this.model);
                                                      this.setState('serial', this.serial);
                                                      this.sendCommand('get_play_state|get_play_mode|get_now_playing_media|get_volume');
                                                  }, 5000);
                                                  setTimeout(() => {
                                                      this.startPlayer();
                                                  }, 10000);
                                                  this.installed = true;
                                              }
                                          }
                                      
                                          // über den $-Operator nachsehen, ob der state bereits vorhanden ist
                                          // getState().notExists geht auch, erzeugt aber Warnmeldungen!
                                          existState(id) {
                                              return ($(this.statePath + id).length == 0 ? false : true);
                                          }
                                      
                                          // wrapper
                                          getState(id) {
                                              return getState(this.statePath + id);
                                          }
                                      
                                          // wie setState(), setzt aber den statePath davor und überpüft aber ob der state vorhanden ist und erzeugt ihn,
                                          // wenn er noch nicht da ist
                                          setState(id, value) {
                                              if (!this.existState(id)) this.createState(id, value, undefined);
                                              else setState(this.statePath + id, value, true);
                                          }
                                      
                                          // wie createState, setzt aber noch den statePath davor und schaut im states-Array nach, ob dort common-Angaben
                                          // vorhanden sind (wenn der common-Parameter leer ist)
                                          createState(id, value, common) {
                                              if (!this.existState(id)) {
                                                  if (common === undefined) {
                                                      // id im states-Array suchen
                                                      for (var i = 0; i < this.states.length; i++) {
                                                          if (this.states[i].id == id) {
                                                              if (this.states[i].hasOwnProperty('common'))
                                                                  common = this.states[i].common;
                                                              break;
                                                          }
                                                      }
                                                  }
                                                  if ((typeof value === 'undefined') && (common.hasOwnProperty('def'))) value = common.def;
                                                  // unter "0_userdata.0"
                                                  let obj = {};
                                                  obj.type = 'state';
                                                  obj.native = {};
                                                  obj.common = common;
                                                  setObject(this.statePath + id, obj, (err) => {
                                                      if (err) {
                                                          this.log('cant write object for state "' + this.statePath + id + '": ' + err);
                                                      } else {
                                                          this.log('state "' + this.statePath + id + '" created');
                                                      }
                                                  });
                                      
                                                  // value zeitversetzt setzen        
                                                  setTimeout(setState, 3000, this.statePath + id, value);
                                              }
                                          }
                                      
                                      
                                          startPlayer() {
                                              try {
                                                  this.log('starting HEOS player with pid ' + this.pid + ' (' + this.ip + ')');
                                                  this.onHandler = [];
                                                  // on-Event für command
                                                  this.onHandler.push(on({ id: this.statePath + 'command', change: "any" }, (obj) => {
                                                      this.executeCommand(obj.id, obj.state.val)
                                                  }));
                                      
                                                  // on-Event für volume (nur wenn ack=false, also über vis)
                                                  this.onHandler.push(on({ id: this.statePath + 'volume', change: 'ne', ack: false }, (obj) => {
                                                      this.executeCommand(obj.id, 'set_volume&level=' + obj.state.val);
                                                  }));
                                                  // on-Event für mute (nur wenn ack=false, also über vis)
                                                  this.onHandler.push(on({ id: this.statePath + 'mute', change: 'ne', ack: false }, (obj) => {
                                                      this.executeCommand(obj.id, 'set_mute&state=' + (obj.state.val === true ? 'on' : 'off'));
                                                  }));
                                                  // on-Event für play_mode_shuffle (nur wenn ack=false, also über vis)
                                                  this.onHandler.push(on({ id: this.statePath + 'play_mode_shuffle', change: 'ne', ack: false }, (obj) => {
                                                      this.executeCommand(obj.id, 'set_play_mode&shuffle=' + (obj.state.val === true ? 'on' : 'off'));
                                                  }));
                                                  // on-Event für play_state (nur wenn ack=false, also über vis)
                                                  this.onHandler.push(on({ id: this.statePath + 'play_state', change: 'ne', ack: false }, (obj) => {
                                                      this.executeCommand(obj.id, 'set_play_state&state=' + obj.state.val);
                                                  }));
                                      
                                                  // on-Event für group-volume (nur wenn ack=false, also über vis)
                                                  this.onHandler.push(on({ id: this.statePath + 'group_volume', change: 'ne', ack: false }, (obj) => {
                                                      // "id":"javascript.1.heos.-1746612370.group_volume"
                                                      var id = obj.id.split('.');
                                                      this.executeCommand('group/set_volume?gid=' + id[3] + '&level=' + obj.state.val);
                                                  }));
                                      
                                                  // on-Event für group-mute (nur wenn ack=false, also über vis)
                                                  this.onHandler.push(on({ id: this.statePath + 'group_mute', change: 'ne', ack: false }, (obj) => {
                                                      // "id":"javascript.1.heos.-1746612370.group_volume"
                                                      var id = obj.id.split('.');
                                                      this.heos.executeCommand('group/set_mute?gid=' + id[3] + '&state=' + (obj.state.val === true ? 'on' : 'off'));
                                                  }));
                                      
                                                  this.setState('connected', true);
                                              } catch (err) { this.logError('startPlayer: ' + err.message); }
                                          }
                                      
                                          // sucht die zur pid passenden player-Insanz
                                          executeCommand(objID, cmd) {
                                              // aus der objID die pid holen
                                              // objID = javascript.1.heos.394645376.command
                                              objID = objID.split('.');
                                              var pid = objID[3];
                                              if (this.pid == pid) {
                                                  this.sendCommand(cmd);
                                              }
                                          }
                                      
                                          cleanupNowPlaying(){
                                              for (var s = 0; s < this.states.length; s++) {
                                                  if(this.states[s].id.startsWith("now_playing")){
                                                      this.setState(this.states[s].id, "");
                                                  }
                                              }
                                          }
                                      
                                      
                                          stopPlayer() {
                                              try {
                                                  this.log('stopping HEOS player with pid ' + this.pid + ' (' + this.ip + ')');
                                      
                                                  // events unsubcribe
                                                  for (let i = 0; i < this.onHandler.length; i++) {
                                                      if (this.onHandler[i] !== undefined) unsubscribe(this.onHandler[i]);
                                                  }
                                                  this.onHandler = [];
                                                  //cleanup now playing
                                                  this.cleanupNowPlaying();
                                                  // reset last error
                                                  this.setState("last_error", "");
                                                  // connected zurücksetzen
                                                  this.setState("connected", false);
                                              } catch (err) { this.logError('stopPlayer: ' + err.message); }
                                          }
                                      
                                          /** wandelt einen sek Wert in MM:SS Darstellung um
                                           **/
                                          toMMSS(s) {
                                              var sec_num = parseInt(s, 10);
                                              var minutes = Math.floor(sec_num / 60);
                                              var seconds = sec_num - (minutes * 60);
                                              if (seconds < 10) { seconds = "0" + seconds; }
                                              return minutes + ':' + seconds;
                                          }
                                      
                                          setLastError(err) {
                                              try {
                                                  this.logWarn(err);
                                                  let val = this.getState('last_error').val + '';
                                                  let lines = val.split('\n');
                                                  if (lines.length > 4)
                                                      lines.pop();
                                                  lines.unshift(err + '\n');
                                                  this.setState('last_error', lines.toString());
                                              } catch (e) { this.logError('setLastError: ' + e.message); }
                                          }
                                      
                                      
                                          /** Auswertung der empfangenen Daten
                                           **/
                                          parseResponse(jdata, jmsg, cmd_group, cmd) {
                                              try {
                                                  switch (cmd_group) {
                                                      case 'event':
                                                          switch (cmd) {
                                                              case 'player_playback_error':
                                                                  this.setLastError(jmsg.error.replace(/_/g, ' '));
                                                                  break;
                                                              case 'player_state_changed':
                                                                  this.setState("play_state", jmsg.state);
                                                                  this.sendCommand('get_now_playing_media');
                                                                  break;
                                                              case 'player_volume_changed':
                                                                  this.setState("volume", jmsg.level);
                                                                  this.setState("mute", (jmsg.mute == 'on' ? true : false));
                                                                  break;
                                                              case 'player_repeat_mode_changed':
                                                                  this.setState("play_mode_shuffle", jmsg.shuffle);
                                                                  break;
                                                              case 'player_shuffle_mode_changed':
                                                                  this.setState("play_mode_repeat", jmsg.repeat);
                                                                  break;
                                                              case 'player_now_playing_changed':
                                                                  this.sendCommand('get_now_playing_media');
                                                                  break;
                                                              case 'player_now_playing_progress':
                                                                  this.setState("cur_pos", jmsg.cur_pos / 1000);
                                                                  this.setState("cur_pos_MMSS", this.toMMSS(jmsg.cur_pos / 1000));
                                                                  this.setState("duration", jmsg.duration / 1000);
                                                                  this.setState("duration_MMSS", this.toMMSS(jmsg.duration / 1000));
                                                                  break;
                                                          }
                                                          break;
                                      
                                      
                                                      case 'player':
                                                          switch (cmd) {
                                                              case 'set_volume':
                                                              case 'get_volume':
                                                                  if (getState(this.statePath + "volume").val != jmsg.level)
                                                                      this.setState("volume", jmsg.level);
                                                                  break;
                                                              case 'set_mute':
                                                              case 'get_mute':
                                                                  this.setState("mute", (jmsg.state == 'on' ? true : false));
                                                                  break;
                                                              case 'set_play_state':
                                                              case 'get_play_state':
                                                                  this.setState("play_state", jmsg.state);
                                                                  break;
                                                              case 'set_play_mode':
                                                              case 'get_play_mode':
                                                                  this.setState("play_mode_repeat", jmsg.repeat);
                                                                  this.setState("play_mode_shuffle", (jmsg.shuffle == 'on' ? true : false));
                                                                  break;
                                                              case 'get_now_playing_media':
                                                                  this.setState("now_playing_media_type", jdata.payload.type);
                                                                  if (jdata.payload.hasOwnProperty('song'))
                                                                      this.setState("now_playing_media_song", jdata.payload.song);
                                                                  if (jdata.payload.hasOwnProperty('album'))
                                                                      this.setState("now_playing_media_album", jdata.payload.album);
                                                                  if (jdata.payload.hasOwnProperty('album_id'))
                                                                      this.setState("now_playing_media_album_id", jdata.payload.album_id);
                                                                  if (jdata.payload.hasOwnProperty('artist'))
                                                                      this.setState("now_playing_media_artist", jdata.payload.artist);
                                                                  if (jdata.payload.hasOwnProperty('image_url'))
                                                                      this.setState("now_playing_media_image_url", jdata.payload.image_url);
                                                                  if (jdata.payload.hasOwnProperty('mid'))
                                                                      this.setState("now_playing_media_mid", jdata.payload.mid);
                                                                  if (jdata.payload.hasOwnProperty('qid'))
                                                                      this.setState("now_playing_media_qid", jdata.payload.qid);
                                                                  if (jdata.payload.hasOwnProperty('type')) {
                                                                      if (jdata.payload.type == 'station') {
                                                                          this.setState("now_playing_media_station", jdata.payload.station);
                                                                      } else {
                                                                          this.setState("now_playing_media_station", "");
                                                                      }
                                                                  }
                                                                  break;
                                                          }
                                                          break;
                                                  } // switch
                                      
                                      
                                              } catch (err) { this.logError('parseResponse: ' + err.message); }
                                          }
                                      
                                          /** cmd der Form "cmd&param"  werden zur msg heos+cmd+pid+&param aufbereitet
                                              cmd der Form "cmd?param"  werden zur msg heos+cmd+?param aufbereitet
                                           **/
                                          commandToMsg(cmd) {
                                              var param = cmd.split('&');
                                              cmd = param[0];
                                              if (param.length > 1) param = '&' + param[1]; else param = '';
                                              var cmd_group = 'player';
                                      
                                              switch (cmd) {
                                                  case 'get_play_state':
                                                  case 'get_play_mode':
                                                  case 'get_now_playing_media':
                                                  case 'get_volume':
                                                  case 'play_next':
                                                  case 'play_previous':
                                                  case 'set_mute':       // &state=on|off        
                                                  case 'set_volume':     // &level=1..100   
                                                  case 'volume_down':    // &step=1..10   
                                                  case 'volume_up':      // &step=1..10
                                                  case 'set_play_state': // &state=play|pause|stop
                                                  case 'set_play_mode':  // &repeat=on_all|on_one|off  shuffle=on|off
                                                      break;
                                      
                                                  // browse            
                                                  case 'play_preset':    // heos://browse/play_preset?pid=player_id&preset=preset_position
                                                      cmd_group = 'browse';
                                                      break;
                                                  case 'play_stream':    // heos://browse/play_stream?pid=player_id&url=url_path
                                                      cmd_group = 'browse';
                                                      break;
                                      
                                              }
                                              return 'heos://' + cmd_group + '/' + cmd + '?pid=' + this.pid + param;
                                          }
                                      
                                          /** Nachricht (command) an player senden
                                              es sind auch mehrere commands, getrennt mit | erlaubt
                                              bsp: set_volume&level=20|play_preset&preset=1
                                           **/
                                          sendCommand(command) {
                                              if (!this.installed) return;
                                      
                                              var cmds = command.split('|');
                                              for (var c = 0; c < cmds.length; c++) {
                                                  this.heos.msgs.push(this.commandToMsg(cmds[c]));
                                              }
                                              this.heos.sendNextMsg();
                                          }
                                      
                                      
                                      } // end of HeosPlayer
                                      
                                      
                                      /* -----
                                         Heos
                                         ----- */
                                      // Heos Instanz erzeugen und verbinden   
                                      var heos = new Heos();
                                      heos.connect();
                                      
                                      // wenn das Script beendet wird, dann auch die Heos Instanz beenden
                                      onStop(function () {
                                          heos.disconnect();
                                      }, 0);
                                      
                                      UhulaU Offline
                                      UhulaU Offline
                                      Uhula
                                      schrieb am zuletzt editiert von
                                      #105

                                      @withstu sagte in [Vorlage] Denon HEOS Script:

                                      Ich habe in den letzten Tagen noch ein paar Bugs gefixt

                                      Super, wenn ich Zeit finde (ist ja bald Ostern), schau ich rein und übernehme es. Hinweis: Wenn du das Script hier als code postest, versieht es das Forum mit zufällligen {1} Einträgen; besser ist es das Script als Datei (z.B.: heos.js) hochzuladen.

                                      Uhula - Leise und Weise
                                      Ex: ioBroker on Gigabyte NUC Proxmox

                                      1 Antwort Letzte Antwort
                                      0
                                      • W withstu

                                        @chrisblu Ich habe für jeden Lautsprecher einen Ping Adapter angelegt. Sobald sich da was ändert und in den HEOS Objekten der Player noch nicht verbunden ist, starte ich das Script neu (bei mir sind die Player nicht dauerhaft verbunden). Übrigens ich hatte total vergessen, dass das connected Flag der Player im disconnect wieder auf false gesetzt wird.
                                        Mein Blockly Heos Script Starter sieht so aus (kann bestimmt auch eleganter gelöst werden, ich nutze jedoch iobroker erst seit kurzem):

                                        var DelayedUnlock;
                                        
                                        
                                        schedule("*/15 * * * * *", function () {
                                          if (getState("0_userdata.0.scriptData.HEOSScriptStarterRunning").val == true && ((new Date().getTime()) - getState("0_userdata.0.scriptData.HEOSScriptStarterRunning").lc) / 1000 > 300) {
                                            setState("0_userdata.0.scriptData.HEOSScriptStarterRunning"/*HEOSScriptStarterRunning*/, false);
                                          }
                                          if (getState("0_userdata.0.scriptData.HEOSScriptStarterRunning").val == false) {
                                            setState("0_userdata.0.scriptData.HEOSScriptStarterRunning"/*HEOSScriptStarterRunning*/, true);
                                            DelayedUnlock = false;
                                            if (getState("javascript.0.scriptEnabled.common.HEOS").val == true && getState("javascript.0.heos.connected").val == false) {
                                              console.log('HEOS not connected. Shutdown.');
                                              setState("javascript.0.scriptEnabled.common.HEOS"/*scriptEnabled.common.HEOS*/, false);
                                            } else if (getState("javascript.0.scriptEnabled.common.HEOS").val == true && (getState("javascript.0.heos.192_168_178_43.connected").val != getState("ping.0.server.192_168_178_43").val || getState("javascript.0.heos.192_168_178_44.connected").val != getState("ping.0.server.192_168_178_44").val || getState("javascript.0.heos.192_168_178_45.connected").val != getState("ping.0.server.192_168_178_45").val || getState("javascript.0.heos.192_168_178_46.connected").val != getState("ping.0.server.192_168_178_46").val)) {
                                              console.log('HEOS Player updated. Shutdown.');
                                              setState("javascript.0.scriptEnabled.common.HEOS"/*scriptEnabled.common.HEOS*/, false);
                                            } else {
                                              if (getState("ping.0.server.192_168_178_45").val == false && getState("ping.0.server.192_168_178_44").val == false && getState("ping.0.server.192_168_178_43").val == false && getState("ping.0.server.192_168_178_46").val == false) {
                                                if (getState("javascript.0.scriptEnabled.common.HEOS").val == true) {
                                                  console.log('No Players. Shutdown.');
                                                  setState("javascript.0.scriptEnabled.common.HEOS"/*scriptEnabled.common.HEOS*/, false);
                                                  DelayedUnlock = true;
                                                }
                                              } else {
                                                if (getState("javascript.0.scriptEnabled.common.HEOS").val == false) {
                                                  console.log('Players available. Startup.');
                                                  setState("javascript.0.scriptEnabled.common.HEOS"/*scriptEnabled.common.HEOS*/, true);
                                                  DelayedUnlock = true;
                                                }
                                              }
                                            }
                                            if (DelayedUnlock == true) {
                                              setStateDelayed("0_userdata.0.scriptData.HEOSScriptStarterRunning"/*HEOSScriptStarterRunning*/, false, 60000, false);
                                            } else {
                                              setState("0_userdata.0.scriptData.HEOSScriptStarterRunning"/*HEOSScriptStarterRunning*/, false);
                                            }
                                          }
                                        });
                                        

                                        Zu jedem Player gibt es dann noch ein Script, welches im Notfall das connected Flag wieder auf false setzt (und automatisch die Musik und Lautstärker steuert :-) ) :

                                        var DelayedUnlock, Volume, Preset;
                                        
                                        
                                        on({id: new RegExp('javascript\\.0\\.heos\\.192_168_178_45\\.mute' + "|" + 'javascript\\.0\\.heos\\.192_168_178_45\\.connected' + "|" + 'ping\\.0\\.server\\.192_168_178_45' + "|" + 'javascript\\.0\\.heos\\.connected'), change: "ne"}, function (obj) {
                                            if (getState("0_userdata.0.scriptData.BadezimmerAutoPlayRunning").val == false) {
                                            setState("0_userdata.0.scriptData.BadezimmerAutoPlayRunning"/*scriptData.BadezimmerAutoPlayRunning*/, true);
                                            DelayedUnlock = false;
                                            if (getState("javascript.0.heos.connected").val == false && getState("javascript.0.heos.192_168_178_45.connected").val == true && getState("ping.0.server.192_168_178_45").val == false) {
                                              setState("javascript.0.heos.192_168_178_45.connected"/*Verbunden?*/, false);
                                            }
                                            if (getState("javascript.0.heos.192_168_178_45.connected").val == true && getState("javascript.0.heos.192_168_178_45.mute").val == false && (getState("javascript.0.heos.192_168_178_45.play_state").val == 'stop' || getState("javascript.0.heos.192_168_178_45.play_state").val == 'pause')) {
                                              console.log('Starte Musik im Badezimmer.');
                                              if ((new Date().getHours()) >= 22 || (new Date().getHours()) <= 7) {
                                                Volume = getState("0_userdata.0.scriptData.NachtVolume").val;
                                              } else {
                                                Volume = getState("0_userdata.0.scriptData.TagVolume").val;
                                              }
                                              setState("javascript.0.heos.192_168_178_45.volume"/*Aktuelle Lautstärke*/, Volume);
                                              if (getState("javascript.0.heos.192_168_178_45.now_playing_media_album_id").val.indexOf('ios-ipod-library') + 1 > 0 || getState("javascript.0.heos.192_168_178_45.last_error").val.length > 0) {
                                                if ((new Date().getDay() === 0 ? 7 : new Date().getDay()) == 0) {
                                                  Preset = getState("0_userdata.0.scriptData.SonntagPreset").val;
                                                } else {
                                                  Preset = getState("0_userdata.0.scriptData.DefaultPreset").val;
                                                }
                                                setState("javascript.0.heos.192_168_178_45.command"/*Kommando für HEOS Aufrufe*/, ('play_preset&preset=' + String(Preset)));
                                              } else {
                                                setState("javascript.0.heos.192_168_178_45.play_state"/*Aktueller Wiedergabezustand*/, 'play');
                                              }
                                              DelayedUnlock = true;
                                            }
                                            if (DelayedUnlock == true) {
                                              setStateDelayed("0_userdata.0.scriptData.BadezimmerAutoPlayRunning"/*scriptData.BadezimmerAutoPlayRunning*/, false, 60000, false);
                                            } else {
                                              setState("0_userdata.0.scriptData.BadezimmerAutoPlayRunning"/*scriptData.BadezimmerAutoPlayRunning*/, false);
                                            }
                                          }
                                        });
                                        
                                        schedule("* * * * *", function () {
                                          if (getState("javascript.0.heos.192_168_178_45.connected").val == true) {
                                            if ((new Date().getHours()) >= 22 || (new Date().getHours()) <= 7) {
                                              if (getState("0_userdata.0.scriptData.BadezimmerAutoVolume").val != 'Nacht') {
                                                Volume = getState("0_userdata.0.scriptData.NachtVolume").val;
                                                if (getState("javascript.0.heos.192_168_178_45.volume").val != Volume) {
                                                  setState("javascript.0.heos.192_168_178_45.volume"/*Aktuelle Lautstärke*/, Volume);
                                                }
                                                setState("0_userdata.0.scriptData.BadezimmerAutoVolume"/*BadezimmerAutoVolume*/, 'Nacht');
                                              }
                                            } else {
                                              if (getState("0_userdata.0.scriptData.BadezimmerAutoVolume").val != 'Tag') {
                                                Volume = getState("0_userdata.0.scriptData.TagVolume").val;
                                                if (getState("javascript.0.heos.192_168_178_45.volume").val != Volume) {
                                                  setState("javascript.0.heos.192_168_178_45.volume"/*Aktuelle Lautstärke*/, Volume);
                                                }
                                                setState("0_userdata.0.scriptData.BadezimmerAutoVolume"/*BadezimmerAutoVolume*/, 'Tag');
                                              }
                                            }
                                          }
                                        });
                                        
                                        C Offline
                                        C Offline
                                        chrisblu
                                        schrieb am zuletzt editiert von chrisblu
                                        #106

                                        @withstu

                                        Ich habe für jeden Lautsprecher einen Ping Adapter angelegt. Sobald sich da was ändert und in den HEOS Objekten der Player noch nicht verbunden ist, starte ich das Script neu (bei mir sind die Player nicht dauerhaft verbunden).

                                        Ich komme gerade erst dazu mir das mit der Ping-Überprüfung anzuschauen. Ist das mit dem neuen Skript überhaupt noch erforderlich? Wahrscheinlich ja, weil bei mir sind immer wieder Boxen nicht erreichbar.
                                        Du schreibst, das ist ein Blockly-Skript, aber die Definition sieht eher aus wie ein Java-Script. Könntest Du mir das als Blockly exportieren, oder hast du das in Java umgewandelt.

                                        Vielen Dank auf jeden Fall für Eure Mühen, das Heos Skript läuft sonst sehr gut bei mir.

                                        Danke,
                                        Christian

                                        W 1 Antwort Letzte Antwort
                                        0
                                        • C chrisblu

                                          @withstu

                                          Ich habe für jeden Lautsprecher einen Ping Adapter angelegt. Sobald sich da was ändert und in den HEOS Objekten der Player noch nicht verbunden ist, starte ich das Script neu (bei mir sind die Player nicht dauerhaft verbunden).

                                          Ich komme gerade erst dazu mir das mit der Ping-Überprüfung anzuschauen. Ist das mit dem neuen Skript überhaupt noch erforderlich? Wahrscheinlich ja, weil bei mir sind immer wieder Boxen nicht erreichbar.
                                          Du schreibst, das ist ein Blockly-Skript, aber die Definition sieht eher aus wie ein Java-Script. Könntest Du mir das als Blockly exportieren, oder hast du das in Java umgewandelt.

                                          Vielen Dank auf jeden Fall für Eure Mühen, das Heos Skript läuft sonst sehr gut bei mir.

                                          Danke,
                                          Christian

                                          W Offline
                                          W Offline
                                          withstu
                                          schrieb am zuletzt editiert von
                                          #107

                                          @chrisblu Mit der zweiten Version ist der Ping Adapter nicht mehr notwendig und auch ein Neustart des Scripts ist nicht mehr erforderlich. Mein Script läuft mittlerweile seit über einer Woche stabil und erkennt automatisch Player die neu hinzukommen oder ausgeschaltet werden. In der nachfolgenden Version habe ich noch Fehler in dem Heartbeat Mechanismus gelöst. Das einzige was mich noch stört ist das Feld "last error", welches nach der Lösung des Fehlers nicht geleert wird. So wird der Fehler bei mir im vis noch angezeigt, obwohl er nicht mehr besteht. Mal schauen, ob mir in den nächsten Tagen noch was dazu einfällt: HEOS.json

                                          1 Antwort Letzte Antwort
                                          0
                                          Antworten
                                          • In einem neuen Thema antworten
                                          Anmelden zum Antworten
                                          • Älteste zuerst
                                          • Neuste zuerst
                                          • Meiste Stimmen


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          718

                                          Online

                                          32.4k

                                          Benutzer

                                          81.5k

                                          Themen

                                          1.3m

                                          Beiträge
                                          Community
                                          Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                                          ioBroker Community 2014-2025
                                          logo
                                          • Anmelden

                                          • Du hast noch kein Konto? Registrieren

                                          • Anmelden oder registrieren, um zu suchen
                                          • Erster Beitrag
                                            Letzter Beitrag
                                          0
                                          • Home
                                          • Aktuell
                                          • Tags
                                          • Ungelesen 0
                                          • Kategorien
                                          • Unreplied
                                          • Beliebt
                                          • GitHub
                                          • Docu
                                          • Hilfe