Skip to content
  • Home
  • Recent
  • Tags
  • 0 Unread 0
  • Categories
  • Unreplied
  • Popular
  • 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

  • Default (No Skin)
  • No Skin
Collapse
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. HEOS Skript funktioniert nicht mehr

NEWS

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

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

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

HEOS Skript funktioniert nicht mehr

Scheduled Pinned Locked Moved Skripten / Logik
8 Posts 4 Posters 543 Views 4 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • J Away
    J Away
    jwedenig
    Most Active
    wrote on last edited by jwedenig
    #1

    Hi!
    Ich benutze seit Jahren dieses Skript für meinen Denon Receiver:

    /****************************
     * 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 = 'wedl@live.de';
    const HEOS_PASSWORD = 'xxxx';
    const DEBUG = false;
     
     
    /****************************
     * 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) { if (DEBUG) console.log('[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;
    }
     
    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(rinfo) );
    	    this.nodessdp_client.on('error', error => {	client.close(); this.logError(error); });
    	    const searchTargetName = 'urn:schemas-denon-com:device:ACT-Denon:1';
    	    this.nodessdp_client.search(searchTargetName);
        } catch(err) { this.logError( 'connect: '+err.message ); } 
        
    }
     
     
    /** Alle Player stoppen und die TelNet Verbindung schließen 
     **/
    disconnect() {
        this.log('disconnecting from HEOS ...');
        unsubscribe('javascript.1.heos');
     
        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.destroy();
        }
        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();
                }
        
        }
    }
    /** es wurde mindestens ein Player erkannt, nun über dessen IP alle bekannten HEOS Player
     *  durch senden von "player/get_players" ermitteln
     */
    onNodeSSDPResponse(rinfo) { try {
        // rinfo {"address":"192.168.2.225","family":"IPv4","port":53871,"size":430}
        if (typeof this.net_client=='undefined') {
            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)  );
        }
    } catch(err) { this.logError( 'onNodeSSDPResponse: '+err.message ); } }
     
     
     
    /** es liegen Antwort(en) vor
     **/
    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=data.replace(/{"heos":/g, '|{"heos":');    
        var responses = data.split('|');
        responses.shift();
        for (var r=0; r<responses.length; r++ ) {
            this.parseResponse(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 )
            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' ) { 
            setState(this.statePath+'last_error', jmsg.text);
            this.logWarn(jmsg.text);
        }
     
        // cmd auswerten
        var cmd = jdata.heos.command.split('/');
        var cmd_group = cmd[0];
        cmd = cmd[1];
     
        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();
                        }
                        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];
                                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();
     
        // Initialiserung 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 );
     
    }
     
    static version () { return "0.1"; }
     
    logDebug(msg) { if (DEBUG) console.log('[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:"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 {
        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 );
                        break;
                    case 'player_mute_changed' :
                        this.setState("mute", (jmsg.state=='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);
                        // type == station
                        if (jdata.payload.type=='station') {
                            this.setState("now_playing_media_song", jdata.payload.song);
                            this.setState("now_playing_media_station", jdata.payload.station);
                            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);
                            }
                        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 );
     
     
     
    
    

    Jetzt bekomme ich folgende Fehlermeldungen:

    25.2.2024, 08:42:59.184	[info ]: javascript.1 (1097078) Stop script script.js.Weldscripts.Javascript.Heos_neu
    25.2.2024, 08:42:59.187	[info ]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: [Heos] disconnecting from HEOS ...
    25.2.2024, 08:42:59.190	[info ]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: [Heos] disconnected from HEOS
    25.2.2024, 08:43:00.767	[info ]: javascript.1 (1097078) Start javascript script.js.Weldscripts.Javascript.Heos_neu
    25.2.2024, 08:43:00.798	[info ]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: [Heos] connecting to HEOS ...
    25.2.2024, 08:43:00.805	[error]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: Error: ENOENT: no such file or directory, open '/opt/iobroker/node_modules/iobroker.javascript/node_modules/node-ssdp/index.js'
    25.2.2024, 08:43:00.809	[error]: javascript.1 (1097078)     at Heos.connect (script.js.Weldscripts.Javascript.Heos_neu:246:26)
    25.2.2024, 08:43:00.810	[error]: javascript.1 (1097078)     at script.js.Weldscripts.Javascript.Heos_neu:831:6
    25.2.2024, 08:43:00.810	[error]: javascript.1 (1097078)     at script.js.Weldscripts.Javascript.Heos_neu:841:3
    25.2.2024, 08:43:00.812	[error]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: [Heos] connect: Cannot read properties of undefined (reading 'Client')
    25.2.2024, 08:43:00.813	[info ]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: registered 1 subscription, 0 schedules, 0 messages, 0 logs and 0 file subscriptions
    

    Kann mir wer da weiterhelfen?
    Ich habe nichts geändert, ausser den neuen Denon Adapter installiert!
    Dachte, dies ist der Fehler, aber ich bin wieder zurückgestiegen auf die alte Version, der Fehler bleibt aber

    BananaJoeB CodierknechtC 2 Replies Last reply
    0
    • J jwedenig

      Hi!
      Ich benutze seit Jahren dieses Skript für meinen Denon Receiver:

      /****************************
       * 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 = 'wedl@live.de';
      const HEOS_PASSWORD = 'xxxx';
      const DEBUG = false;
       
       
      /****************************
       * 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) { if (DEBUG) console.log('[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;
      }
       
      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(rinfo) );
      	    this.nodessdp_client.on('error', error => {	client.close(); this.logError(error); });
      	    const searchTargetName = 'urn:schemas-denon-com:device:ACT-Denon:1';
      	    this.nodessdp_client.search(searchTargetName);
          } catch(err) { this.logError( 'connect: '+err.message ); } 
          
      }
       
       
      /** Alle Player stoppen und die TelNet Verbindung schließen 
       **/
      disconnect() {
          this.log('disconnecting from HEOS ...');
          unsubscribe('javascript.1.heos');
       
          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.destroy();
          }
          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();
                  }
          
          }
      }
      /** es wurde mindestens ein Player erkannt, nun über dessen IP alle bekannten HEOS Player
       *  durch senden von "player/get_players" ermitteln
       */
      onNodeSSDPResponse(rinfo) { try {
          // rinfo {"address":"192.168.2.225","family":"IPv4","port":53871,"size":430}
          if (typeof this.net_client=='undefined') {
              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)  );
          }
      } catch(err) { this.logError( 'onNodeSSDPResponse: '+err.message ); } }
       
       
       
      /** es liegen Antwort(en) vor
       **/
      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=data.replace(/{"heos":/g, '|{"heos":');    
          var responses = data.split('|');
          responses.shift();
          for (var r=0; r<responses.length; r++ ) {
              this.parseResponse(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 )
              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' ) { 
              setState(this.statePath+'last_error', jmsg.text);
              this.logWarn(jmsg.text);
          }
       
          // cmd auswerten
          var cmd = jdata.heos.command.split('/');
          var cmd_group = cmd[0];
          cmd = cmd[1];
       
          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();
                          }
                          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];
                                  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();
       
          // Initialiserung 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 );
       
      }
       
      static version () { return "0.1"; }
       
      logDebug(msg) { if (DEBUG) console.log('[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:"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 {
          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 );
                          break;
                      case 'player_mute_changed' :
                          this.setState("mute", (jmsg.state=='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);
                          // type == station
                          if (jdata.payload.type=='station') {
                              this.setState("now_playing_media_song", jdata.payload.song);
                              this.setState("now_playing_media_station", jdata.payload.station);
                              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);
                              }
                          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 );
       
       
       
      
      

      Jetzt bekomme ich folgende Fehlermeldungen:

      25.2.2024, 08:42:59.184	[info ]: javascript.1 (1097078) Stop script script.js.Weldscripts.Javascript.Heos_neu
      25.2.2024, 08:42:59.187	[info ]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: [Heos] disconnecting from HEOS ...
      25.2.2024, 08:42:59.190	[info ]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: [Heos] disconnected from HEOS
      25.2.2024, 08:43:00.767	[info ]: javascript.1 (1097078) Start javascript script.js.Weldscripts.Javascript.Heos_neu
      25.2.2024, 08:43:00.798	[info ]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: [Heos] connecting to HEOS ...
      25.2.2024, 08:43:00.805	[error]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: Error: ENOENT: no such file or directory, open '/opt/iobroker/node_modules/iobroker.javascript/node_modules/node-ssdp/index.js'
      25.2.2024, 08:43:00.809	[error]: javascript.1 (1097078)     at Heos.connect (script.js.Weldscripts.Javascript.Heos_neu:246:26)
      25.2.2024, 08:43:00.810	[error]: javascript.1 (1097078)     at script.js.Weldscripts.Javascript.Heos_neu:831:6
      25.2.2024, 08:43:00.810	[error]: javascript.1 (1097078)     at script.js.Weldscripts.Javascript.Heos_neu:841:3
      25.2.2024, 08:43:00.812	[error]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: [Heos] connect: Cannot read properties of undefined (reading 'Client')
      25.2.2024, 08:43:00.813	[info ]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: registered 1 subscription, 0 schedules, 0 messages, 0 logs and 0 file subscriptions
      

      Kann mir wer da weiterhelfen?
      Ich habe nichts geändert, ausser den neuen Denon Adapter installiert!
      Dachte, dies ist der Fehler, aber ich bin wieder zurückgestiegen auf die alte Version, der Fehler bleibt aber

      BananaJoeB Online
      BananaJoeB Online
      BananaJoe
      Most Active
      wrote on last edited by
      #2

      @jwedenig sagte in HEOS Skript funktioniert nicht mehr:

      • (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.

      und genau die fehlt laut Fehlermeldung:

      25.2.2024, 08:43:00.805	[error]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: Error: ENOENT: no such file or directory, open '/opt/iobroker/node_modules/iobroker.javascript/node_modules/node-ssdp/index.js'
      

      ioBroker@Ubuntu 24.04 LTS (VMware) für: >260 Geräte, 5 Switche, 7 AP, 9 IP-Cam, 1 NAS 42TB, 1 ESXi 15TB, 4 Proxmox 1TB, 1 Hyper-V 48TB, 14 x Echo, 5x FireTV, 5 x Tablett/Handy VIS || >=160 Tasmota/Shelly || >=95 ZigBee || PV 8.1kW / Akku 14kWh || 2x USV 750W kaskadiert || Creality CR-10 SE 3D-Drucker

      J 1 Reply Last reply
      0
      • J jwedenig

        Hi!
        Ich benutze seit Jahren dieses Skript für meinen Denon Receiver:

        /****************************
         * 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 = 'wedl@live.de';
        const HEOS_PASSWORD = 'xxxx';
        const DEBUG = false;
         
         
        /****************************
         * 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) { if (DEBUG) console.log('[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;
        }
         
        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(rinfo) );
        	    this.nodessdp_client.on('error', error => {	client.close(); this.logError(error); });
        	    const searchTargetName = 'urn:schemas-denon-com:device:ACT-Denon:1';
        	    this.nodessdp_client.search(searchTargetName);
            } catch(err) { this.logError( 'connect: '+err.message ); } 
            
        }
         
         
        /** Alle Player stoppen und die TelNet Verbindung schließen 
         **/
        disconnect() {
            this.log('disconnecting from HEOS ...');
            unsubscribe('javascript.1.heos');
         
            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.destroy();
            }
            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();
                    }
            
            }
        }
        /** es wurde mindestens ein Player erkannt, nun über dessen IP alle bekannten HEOS Player
         *  durch senden von "player/get_players" ermitteln
         */
        onNodeSSDPResponse(rinfo) { try {
            // rinfo {"address":"192.168.2.225","family":"IPv4","port":53871,"size":430}
            if (typeof this.net_client=='undefined') {
                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)  );
            }
        } catch(err) { this.logError( 'onNodeSSDPResponse: '+err.message ); } }
         
         
         
        /** es liegen Antwort(en) vor
         **/
        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=data.replace(/{"heos":/g, '|{"heos":');    
            var responses = data.split('|');
            responses.shift();
            for (var r=0; r<responses.length; r++ ) {
                this.parseResponse(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 )
                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' ) { 
                setState(this.statePath+'last_error', jmsg.text);
                this.logWarn(jmsg.text);
            }
         
            // cmd auswerten
            var cmd = jdata.heos.command.split('/');
            var cmd_group = cmd[0];
            cmd = cmd[1];
         
            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();
                            }
                            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];
                                    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();
         
            // Initialiserung 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 );
         
        }
         
        static version () { return "0.1"; }
         
        logDebug(msg) { if (DEBUG) console.log('[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:"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 {
            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 );
                            break;
                        case 'player_mute_changed' :
                            this.setState("mute", (jmsg.state=='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);
                            // type == station
                            if (jdata.payload.type=='station') {
                                this.setState("now_playing_media_song", jdata.payload.song);
                                this.setState("now_playing_media_station", jdata.payload.station);
                                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);
                                }
                            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 );
         
         
         
        
        

        Jetzt bekomme ich folgende Fehlermeldungen:

        25.2.2024, 08:42:59.184	[info ]: javascript.1 (1097078) Stop script script.js.Weldscripts.Javascript.Heos_neu
        25.2.2024, 08:42:59.187	[info ]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: [Heos] disconnecting from HEOS ...
        25.2.2024, 08:42:59.190	[info ]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: [Heos] disconnected from HEOS
        25.2.2024, 08:43:00.767	[info ]: javascript.1 (1097078) Start javascript script.js.Weldscripts.Javascript.Heos_neu
        25.2.2024, 08:43:00.798	[info ]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: [Heos] connecting to HEOS ...
        25.2.2024, 08:43:00.805	[error]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: Error: ENOENT: no such file or directory, open '/opt/iobroker/node_modules/iobroker.javascript/node_modules/node-ssdp/index.js'
        25.2.2024, 08:43:00.809	[error]: javascript.1 (1097078)     at Heos.connect (script.js.Weldscripts.Javascript.Heos_neu:246:26)
        25.2.2024, 08:43:00.810	[error]: javascript.1 (1097078)     at script.js.Weldscripts.Javascript.Heos_neu:831:6
        25.2.2024, 08:43:00.810	[error]: javascript.1 (1097078)     at script.js.Weldscripts.Javascript.Heos_neu:841:3
        25.2.2024, 08:43:00.812	[error]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: [Heos] connect: Cannot read properties of undefined (reading 'Client')
        25.2.2024, 08:43:00.813	[info ]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: registered 1 subscription, 0 schedules, 0 messages, 0 logs and 0 file subscriptions
        

        Kann mir wer da weiterhelfen?
        Ich habe nichts geändert, ausser den neuen Denon Adapter installiert!
        Dachte, dies ist der Fehler, aber ich bin wieder zurückgestiegen auf die alte Version, der Fehler bleibt aber

        CodierknechtC Offline
        CodierknechtC Offline
        Codierknecht
        Developer Most Active
        wrote on last edited by
        #3

        @jwedenig
        Klär mich auf: Warum brauchts dieses Script?
        Ich steuere meinen Denon allein mit dem Adapter.

        "Any fool can write code that a computer can understand. Good programmers write code that humans can understand." (Martin Fowler, "Refactoring")

        Proxmox 9.1.1 LXC|8 GB|Core i7-6700
        HmIP|ZigBee|Tasmota|Unifi
        Zabbix Certified Specialist
        Konnte ich Dir helfen? Dann benutze bitte das Voting unten rechts im Beitrag

        J 1 Reply Last reply
        1
        • CodierknechtC Codierknecht

          @jwedenig
          Klär mich auf: Warum brauchts dieses Script?
          Ich steuere meinen Denon allein mit dem Adapter.

          J Away
          J Away
          jwedenig
          Most Active
          wrote on last edited by
          #4

          @codierknecht

          Hi!

          Eigentlich nur um Coverbild, Titel und Interpret auf VIS darzustellen!
          Ich habe mir mittlerweile den Heos Adapter installiert, ist halt für diese 3 Sachen ein Luxus einen eigenen Adapter laufen zu lassen

          Meister MopperM 1 Reply Last reply
          0
          • BananaJoeB BananaJoe

            @jwedenig sagte in HEOS Skript funktioniert nicht mehr:

            • (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.

            und genau die fehlt laut Fehlermeldung:

            25.2.2024, 08:43:00.805	[error]: javascript.1 (1097078) script.js.Weldscripts.Javascript.Heos_neu: Error: ENOENT: no such file or directory, open '/opt/iobroker/node_modules/iobroker.javascript/node_modules/node-ssdp/index.js'
            
            J Away
            J Away
            jwedenig
            Most Active
            wrote on last edited by
            #5

            @bananajoe
            danke, ich check mal

            1 Reply Last reply
            0
            • J jwedenig

              @codierknecht

              Hi!

              Eigentlich nur um Coverbild, Titel und Interpret auf VIS darzustellen!
              Ich habe mir mittlerweile den Heos Adapter installiert, ist halt für diese 3 Sachen ein Luxus einen eigenen Adapter laufen zu lassen

              Meister MopperM Away
              Meister MopperM Away
              Meister Mopper
              wrote on last edited by
              #6

              @jwedenig sagte in HEOS Skript funktioniert nicht mehr:

              ist halt für diese 3 Sachen ein Luxus einen eigenen Adapter laufen zu lassen

              Sind die 120M für dich zuviel?

              Proxmox und HA

              J 1 Reply Last reply
              0
              • Meister MopperM Meister Mopper

                @jwedenig sagte in HEOS Skript funktioniert nicht mehr:

                ist halt für diese 3 Sachen ein Luxus einen eigenen Adapter laufen zu lassen

                Sind die 120M für dich zuviel?

                J Away
                J Away
                jwedenig
                Most Active
                wrote on last edited by
                #7

                @meister-mopper
                eigentlich hab ich eh noch Platz, oder?
                Freier Festplattenspeicher: 78%, Gesamte RAM-Auslastung: 5628 Mb / Frei: 49% = 3830 Mb [Server: raspberrypi - 45 Prozesse]

                Meister MopperM 1 Reply Last reply
                0
                • J jwedenig

                  @meister-mopper
                  eigentlich hab ich eh noch Platz, oder?
                  Freier Festplattenspeicher: 78%, Gesamte RAM-Auslastung: 5628 Mb / Frei: 49% = 3830 Mb [Server: raspberrypi - 45 Prozesse]

                  Meister MopperM Away
                  Meister MopperM Away
                  Meister Mopper
                  wrote on last edited by
                  #8

                  @jwedenig sagte in HEOS Skript funktioniert nicht mehr:

                  eigentlich hab ich eh noch Platz, oder?

                  RAM ist dafür da, genutzt zu werden. Es frisst kein Gras. Sollte halt nicht knapp werden!

                  Proxmox und HA

                  1 Reply Last reply
                  1
                  Reply
                  • Reply as topic
                  Log in to reply
                  • Oldest to Newest
                  • Newest to Oldest
                  • Most Votes


                  Support us

                  ioBroker
                  Community Adapters
                  Donate

                  951

                  Online

                  32.4k

                  Users

                  81.5k

                  Topics

                  1.3m

                  Posts
                  Community
                  Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                  ioBroker Community 2014-2025
                  logo
                  • Login

                  • Don't have an account? Register

                  • Login or register to search.
                  • First post
                    Last post
                  0
                  • Home
                  • Recent
                  • Tags
                  • Unread 0
                  • Categories
                  • Unreplied
                  • Popular
                  • GitHub
                  • Docu
                  • Hilfe