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

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

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. JavaScript
  5. Tabelle JSON -> HTML - Link Abspielen WAV Datei am Tablet

NEWS

  • Jahresrückblick 2025 – unser neuer Blogbeitrag ist online! ✨
    BluefoxB
    Bluefox
    17
    1
    2.4k

  • Neuer Blogbeitrag: Monatsrückblick - Dezember 2025 🎄
    BluefoxB
    Bluefox
    13
    1
    966

  • Weihnachtsangebot 2025! 🎄
    BluefoxB
    Bluefox
    25
    1
    2.2k

Tabelle JSON -> HTML - Link Abspielen WAV Datei am Tablet

Geplant Angeheftet Gesperrt Verschoben JavaScript
30 Beiträge 3 Kommentatoren 2.1k Aufrufe 2 Watching
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • L Offline
    L Offline
    Larson-Sei180LX
    schrieb am zuletzt editiert von
    #1

    Hallo an die wissenden Mitleser,

    ich bin in Sachen JS erst durch ioBroker gekommen und habe gerade ein Problem, bei dem ich einen Denkanstoß bzw. Hilfe eines Experten von Euch brauche.

    Folgender Ausgangssachverhalt:
    Ich habe ein JSON File, das mir erfolgreich eine Tabelle in VIS mit dem Widget Basic JSON Tablet aufschlüsselt. Es geht hierbei um eine Anrufliste aus der Fritzbox, die über die Daten des Adapter iobroker.fritzbox die Anrufliste ausliest. Auch habe ich es geschafft, dass eine WAV Datei des Fritzbox-ABs auf den PI in ein Verzeichnis extrahiert wird, sofern eine Nachricht vorhanden ist. Dessen Pfad wird dann zugehörig zum Eintrag genau in dieser Tabelle unter der Spaltenüberschrift "AudioFile" abgelegt.

    Ich würde jetzt gerne erreichen, dass man dieses Audiofile am Tablet "abhören" kann. Demnach dachte ich an eine Art Verlinkung des Pfades, so dass man in der Tabelle danach nur noch auf den Link klicken muss.

    Da das Format JSON wohl (zumindest mir nicht bekannt) nicht für einen Link geeignet ist, habe ich das JSON mittels eines Skriptes aus dem Forum hier in ein HTML umformatiert. Leider schaffe ich es nicht, in der Spalte AudioFile einen anklickbaren LINK daraus zu kreieren. Kann mir hierbei vielleicht jemand weiterhelfen?

    Hier das Grund-JSON File, wie es vorliegt:

    [
      {
        "index": "0",
        "calledNumber": "4996666666",
        "date": "15.05.23 22:25",
        "duration": "0:01",
        "callerName": "Hans Müller",
        "callerNumber": "097654321",
        "audioFile": "/opt/iobroker/node_modules/iobroker.fritzbox/tam/1505232225-097654321.wav"
      },
      {
        "index": "1",
        "calledNumber": "4996666666",
        "date": "15.05.23 22:36",
        "duration": "0:01",
        "callerName": "Hans Müller",
        "callerNumber": "097654321",
        "audioFile": "/opt/iobroker/node_modules/iobroker.fritzbox/tam/1505232236-097654321.wav"
      },
      {
        "index": "2",
        "calledNumber": "4996666666",
        "date": "15.05.23 22:52",
        "duration": "0:01",
        "callerName": "Hans Müller",
        "callerNumber": "097654321",
        "audioFile": "/opt/iobroker/node_modules/iobroker.fritzbox/tam/1505232252-097654321.wav"
      }
    ]
    

    Und hier das HTML-Formatierungsskript:

    /******************************************************************************************************
     * JSON-Datenpunkt in HTML umwandeln
     * --------------------------------------------------------------
     * Zweck:      Überwacht einen JSON-Datenpunkt und sobald geändert, wird JSON in HTML umgewandelt und
     *             in einem eigenen Datenpunkt ausgegeben
     * Publiziert: https://forum.iobroker.net/topic/32540/json-zu-html-und-in-datei-schreiben-ablegen
     * Autor:      Mic-M (Github) | Mic (ioBroker)
     * --------------------------------------------------------------------------------------
     * Change Log:
     *  0.0.1  Mic-M   * Initial release
     ******************************************************************************************************/
    
    /*********************************************************************************************
     * Einstellungen
     *********************************************************************************************/
    // JSON-Datenpunkt
    const g_jsonState = 'fritzbox.0.tam.messagesJSON';
    
    // Neuer Datenpunkt für HTML-Ausgabe
    const g_htmlState = 'javascript.0.html-tables.log-Info';
    
    // Spalte entfernen (für Log Parser Adapter 'ts' nehmen, da dieser autmatisch den timestamp hinzufügt),
    // sonst leer lassen
    const g_removeColumn = 'ts';
    
    
    /*********************************************************************************************
     * Ab hier nichts mehr ändern
     *********************************************************************************************/
    main();
    function main() {
    
        // Create state for HTML, if not yet existing
        createState(g_htmlState, {'name':'HTML Table', 'type':'string', 'read':true, 'write':false, 'role':'html', 'def':'' }, () => {
            // State created, so let's subscribe to the given JSON state
            on({id: g_jsonState, change:'ne'}, function(obj) {
                // JSON state changed            
                if(obj.state.val && obj.state.val.length > 10) {
                    // state is not empty
                    const jsonObject = JSON.parse(obj.state.val);
                    if(g_removeColumn) {
                        for (let lpEntry of jsonObject) {
                            delete lpEntry[g_removeColumn];
                        }
                    }
                    setState(g_htmlState, json2table(jsonObject, ''));
                }
            });
    
        });
    
    }
    
    
    
    /**
     * Convert JSON to HTML table
     * Source: https://travishorn.com/building-json2table-turn-json-into-an-html-table-a57cf642b84a
     * 
     * @param {object}  jsonObject    The JSON as object.
     * @param {string}  [classes]     Optional: You can apply one or multiple classes (space separated) to <table>.
     * @return {string}               The HTML result as string
     */
    function json2table(jsonObject, classes = '') {
        const cols = Object.keys(jsonObject[0]);
    
        let headerRow = '';
        let bodyRows = '';
    
        classes = classes || '';
    
        cols.map(function(col) {
            headerRow += '<th>' + capitalizeFirstLetter(col) + '</th>';
        });
    
        jsonObject.map(function(row) {
    
            bodyRows += '<tr>';
    
            cols.map(function(colName) {
                bodyRows += '<td>' + row[colName] + '</td>';
            })
    
            bodyRows += '</tr>';
    
        });
    
        const addClasses = (classes && classes.length > 1) ? ' class="' + classes + '"' : '';
        return '<table' + addClasses + '><thead><tr>' +
                headerRow +
                '</tr></thead><tbody>' +
                bodyRows +
                '</tbody></table>';
    
        function capitalizeFirstLetter(string) {
            return string.charAt(0).toUpperCase() + string.slice(1);
        }
    
    }
    
    

    Das Umformeln der Tabelle klappt prima. Wie es danach weitergeht, weiss ich noch nicht. Am liebsten wäre mir, dass er dann das File abspielt und habe eben die Hoffnung, dass das funktioniert, wenn man auf den LINK klickt. Vielleicht hat auch noch jemand eine bessere Idee, bin für alle Ratschläge offen. Mein JS Wissen ist leider nicht weit.

    Vielen lieben Dank für Eure Bemühungen

    Andy

    liv-in-skyL OliverIOO 2 Antworten Letzte Antwort
    0
    • L Larson-Sei180LX

      Hallo an die wissenden Mitleser,

      ich bin in Sachen JS erst durch ioBroker gekommen und habe gerade ein Problem, bei dem ich einen Denkanstoß bzw. Hilfe eines Experten von Euch brauche.

      Folgender Ausgangssachverhalt:
      Ich habe ein JSON File, das mir erfolgreich eine Tabelle in VIS mit dem Widget Basic JSON Tablet aufschlüsselt. Es geht hierbei um eine Anrufliste aus der Fritzbox, die über die Daten des Adapter iobroker.fritzbox die Anrufliste ausliest. Auch habe ich es geschafft, dass eine WAV Datei des Fritzbox-ABs auf den PI in ein Verzeichnis extrahiert wird, sofern eine Nachricht vorhanden ist. Dessen Pfad wird dann zugehörig zum Eintrag genau in dieser Tabelle unter der Spaltenüberschrift "AudioFile" abgelegt.

      Ich würde jetzt gerne erreichen, dass man dieses Audiofile am Tablet "abhören" kann. Demnach dachte ich an eine Art Verlinkung des Pfades, so dass man in der Tabelle danach nur noch auf den Link klicken muss.

      Da das Format JSON wohl (zumindest mir nicht bekannt) nicht für einen Link geeignet ist, habe ich das JSON mittels eines Skriptes aus dem Forum hier in ein HTML umformatiert. Leider schaffe ich es nicht, in der Spalte AudioFile einen anklickbaren LINK daraus zu kreieren. Kann mir hierbei vielleicht jemand weiterhelfen?

      Hier das Grund-JSON File, wie es vorliegt:

      [
        {
          "index": "0",
          "calledNumber": "4996666666",
          "date": "15.05.23 22:25",
          "duration": "0:01",
          "callerName": "Hans Müller",
          "callerNumber": "097654321",
          "audioFile": "/opt/iobroker/node_modules/iobroker.fritzbox/tam/1505232225-097654321.wav"
        },
        {
          "index": "1",
          "calledNumber": "4996666666",
          "date": "15.05.23 22:36",
          "duration": "0:01",
          "callerName": "Hans Müller",
          "callerNumber": "097654321",
          "audioFile": "/opt/iobroker/node_modules/iobroker.fritzbox/tam/1505232236-097654321.wav"
        },
        {
          "index": "2",
          "calledNumber": "4996666666",
          "date": "15.05.23 22:52",
          "duration": "0:01",
          "callerName": "Hans Müller",
          "callerNumber": "097654321",
          "audioFile": "/opt/iobroker/node_modules/iobroker.fritzbox/tam/1505232252-097654321.wav"
        }
      ]
      

      Und hier das HTML-Formatierungsskript:

      /******************************************************************************************************
       * JSON-Datenpunkt in HTML umwandeln
       * --------------------------------------------------------------
       * Zweck:      Überwacht einen JSON-Datenpunkt und sobald geändert, wird JSON in HTML umgewandelt und
       *             in einem eigenen Datenpunkt ausgegeben
       * Publiziert: https://forum.iobroker.net/topic/32540/json-zu-html-und-in-datei-schreiben-ablegen
       * Autor:      Mic-M (Github) | Mic (ioBroker)
       * --------------------------------------------------------------------------------------
       * Change Log:
       *  0.0.1  Mic-M   * Initial release
       ******************************************************************************************************/
      
      /*********************************************************************************************
       * Einstellungen
       *********************************************************************************************/
      // JSON-Datenpunkt
      const g_jsonState = 'fritzbox.0.tam.messagesJSON';
      
      // Neuer Datenpunkt für HTML-Ausgabe
      const g_htmlState = 'javascript.0.html-tables.log-Info';
      
      // Spalte entfernen (für Log Parser Adapter 'ts' nehmen, da dieser autmatisch den timestamp hinzufügt),
      // sonst leer lassen
      const g_removeColumn = 'ts';
      
      
      /*********************************************************************************************
       * Ab hier nichts mehr ändern
       *********************************************************************************************/
      main();
      function main() {
      
          // Create state for HTML, if not yet existing
          createState(g_htmlState, {'name':'HTML Table', 'type':'string', 'read':true, 'write':false, 'role':'html', 'def':'' }, () => {
              // State created, so let's subscribe to the given JSON state
              on({id: g_jsonState, change:'ne'}, function(obj) {
                  // JSON state changed            
                  if(obj.state.val && obj.state.val.length > 10) {
                      // state is not empty
                      const jsonObject = JSON.parse(obj.state.val);
                      if(g_removeColumn) {
                          for (let lpEntry of jsonObject) {
                              delete lpEntry[g_removeColumn];
                          }
                      }
                      setState(g_htmlState, json2table(jsonObject, ''));
                  }
              });
      
          });
      
      }
      
      
      
      /**
       * Convert JSON to HTML table
       * Source: https://travishorn.com/building-json2table-turn-json-into-an-html-table-a57cf642b84a
       * 
       * @param {object}  jsonObject    The JSON as object.
       * @param {string}  [classes]     Optional: You can apply one or multiple classes (space separated) to <table>.
       * @return {string}               The HTML result as string
       */
      function json2table(jsonObject, classes = '') {
          const cols = Object.keys(jsonObject[0]);
      
          let headerRow = '';
          let bodyRows = '';
      
          classes = classes || '';
      
          cols.map(function(col) {
              headerRow += '<th>' + capitalizeFirstLetter(col) + '</th>';
          });
      
          jsonObject.map(function(row) {
      
              bodyRows += '<tr>';
      
              cols.map(function(colName) {
                  bodyRows += '<td>' + row[colName] + '</td>';
              })
      
              bodyRows += '</tr>';
      
          });
      
          const addClasses = (classes && classes.length > 1) ? ' class="' + classes + '"' : '';
          return '<table' + addClasses + '><thead><tr>' +
                  headerRow +
                  '</tr></thead><tbody>' +
                  bodyRows +
                  '</tbody></table>';
      
          function capitalizeFirstLetter(string) {
              return string.charAt(0).toUpperCase() + string.slice(1);
          }
      
      }
      
      

      Das Umformeln der Tabelle klappt prima. Wie es danach weitergeht, weiss ich noch nicht. Am liebsten wäre mir, dass er dann das File abspielt und habe eben die Hoffnung, dass das funktioniert, wenn man auf den LINK klickt. Vielleicht hat auch noch jemand eine bessere Idee, bin für alle Ratschläge offen. Mein JS Wissen ist leider nicht weit.

      Vielen lieben Dank für Eure Bemühungen

      Andy

      liv-in-skyL Offline
      liv-in-skyL Offline
      liv-in-sky
      schrieb am zuletzt editiert von
      #2

      @larson-sei180lx du musst das wav file in den ordner /opt/iobroker/iobroker-data/files/vis.0... haben, um es über die url ansprechen zu können

      z.b 192.168.178.59:8082/vis.0/armin/img/audio/1505232252-097654321.wav

      nach einem gelösten Thread wäre es sinnvoll dies in der Überschrift des ersten Posts einzutragen [gelöst]-... Bitte benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat. Forum-Tools: PicPick https://picpick.app/en/download/ und ScreenToGif https://www.screentogif.com/downloads.html

      L 1 Antwort Letzte Antwort
      0
      • liv-in-skyL liv-in-sky

        @larson-sei180lx du musst das wav file in den ordner /opt/iobroker/iobroker-data/files/vis.0... haben, um es über die url ansprechen zu können

        z.b 192.168.178.59:8082/vis.0/armin/img/audio/1505232252-097654321.wav

        L Offline
        L Offline
        Larson-Sei180LX
        schrieb am zuletzt editiert von
        #3

        @liv-in-sky
        Danke für den Hinweis. Aber wie funktioniert das, einen LINK dort zu hinterlegen?

        liv-in-skyL 2 Antworten Letzte Antwort
        0
        • L Larson-Sei180LX

          @liv-in-sky
          Danke für den Hinweis. Aber wie funktioniert das, einen LINK dort zu hinterlegen?

          liv-in-skyL Offline
          liv-in-skyL Offline
          liv-in-sky
          schrieb am zuletzt editiert von
          #4

          @larson-sei180lx

          die datei muss mit writefile() function über ein script dahin kopiert werden - dann wird dieses file erst sichtbar

          https://forum.iobroker.net/post/307268

          nach einem gelösten Thread wäre es sinnvoll dies in der Überschrift des ersten Posts einzutragen [gelöst]-... Bitte benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat. Forum-Tools: PicPick https://picpick.app/en/download/ und ScreenToGif https://www.screentogif.com/downloads.html

          L 1 Antwort Letzte Antwort
          0
          • liv-in-skyL liv-in-sky

            @larson-sei180lx

            die datei muss mit writefile() function über ein script dahin kopiert werden - dann wird dieses file erst sichtbar

            https://forum.iobroker.net/post/307268

            L Offline
            L Offline
            Larson-Sei180LX
            schrieb am zuletzt editiert von
            #5

            @liv-in-sky

            Ich habe hier das MAIN.JS File vom Fritzbox Adapter. Vl kannst Du mir das dort einpflegen ?

            /**
             *
             *      ioBroker fritzbox Adapter
             *
             *      (c) 2015 Michael Herwig <ruhr@digheim.de>
             *
             *      MIT License
             *
             *
             *    Callmonitor an der Fritzbox einschalten:
             *    Wenn man beim Telefon #96*5* eintippt, wird der TCP-Port 1012 geoffnet.
             *    Mit #96*4* wird dieser wieder geschlossen.
             *
             *    Format der Fritzbox-Meldungen:
             *
             *    Ausgehende Anrufe:            datum;CALL;      CallID;Nebenstelle;callingNumber;calledNumber;lineType;
             *    Eingehende Anrufe:            datum;RING;      CallID;callingNumber;calledNumber;lineTpe;
             *    Zustandegekommene Verbindung: datum;CONNECT;   CallID;Nebenstelle;connectedNumber;
             *    Ende der Verbindung:          datum;DISCONNECT;CallID;dauerInSekunden;
             *
             */
            
            // TODO Wie können auf der Adminseite die Werte vor Änderung (onChange) kontrolliert und ggf. markiert werden?
            // TODO Leistungsmerkmale: (2) Fritzbox Telefonbuch importieren und verarbeiten
            // TODO Anruferlisten persistent realisieren (nicht die erstellten Tabellen, sonder die die Arrays mit den Listeneinträgen (historyListAllHtml, historyListAllTxt, historyListAllJson, historyListMissedHtml)
            //      -> ggf. Redesign. Nicht verschiedene Listen, sondern eine JSON und die Funktionen so ändern, dass makeList auf diese eine JSON aufbaut
            
            "use strict";   // verlangt saubereren Code (nach ECMAScript 5) - EMPFEHLUNG:  beim Programmieren rein, im fertigen Programm raus
            
            // require() ist eine Funktion von node.js, um weitere Module nachzuladen
            
            var utils = require('@iobroker/adapter-core'); // Get common adapter utils
            var xml2js = require('xml2js'); // node-Modul um xml Strukturen in JSON umzuwandeln. Eine Beschreibung: http://www.flyacts.com/blog/nodejs-xml-daten-verarbeiten/
                                            // Bei der Entwicklung ins iobroker.frittbox Verzeichnis installieren. Als Admin, d.h. beim Mac mit sudo:
                                            // sudo npm install xml2js --save
                                            // mit dem --save wird es ins packet.json geschrieben
                                            // vorbereitet, um das Telefonbuch der Fritzbox zu verarbeiten (Export im XML Format)
            var net =    require('net');    // node-Modul für die tcp/ip Kommunikation
                                            // ist schon in node.js enthalten
                                            // Beschreibung: https://nodejs.org/api/net.html
            var tr = require("tr-064");     // node-Modul für die Kommunikation via TR-064 mit der FritzBox
                                            // Beschreibung zu TR-064: http://avm.de/service/schnittstellen/
            
            var https = require("https");
            var request = require("request");
            
            const { existsSync, writeFile, mkdirSync, readdir, unlink, createWriteStream } = require('fs');
            const path = require('path');
            const { url } = require('inspector');
            
            var adapter = utils.Adapter('fritzbox');
            
            var call = [];
            
            // Werte werden über die Adminseite gesetzt
            var // adapter.config.fritzboxAddress // ip-Adresse der Fritzbox
                configCC                    = "",   // eigene Landesvorwahl ohne führende 0
                configAC                    = "",   // eigene Ortsvorwahl ohne führende 0
                configUnknownNumber         = "",   // "### ? ###"wie soll eine unbekannte/unterdrückte externe Rufnummer angezeigt werden
                configNumberLength          = 15,   // (df=15) max. Stellen externer Rufnummern, die in den Tabellen HTML und TXT angezeigt werden
                configExternalLink          = true, // externe Rufnummer als wählbaren Link (tel:)
                configShowHeadline          = true, // Anruferliste mit überschrift?
                showHistoryAllTableTxt      = true,
                showHistoryAllTableHTML     = true,
                showHistoryAllTableJSON     = true,
                showCallmonitor             = true,
                showMissedTableHTML         = true,
                showMissedTableJSON         = true;
            
            // restlichen Config Werte
            var ipCallgen                   = "172.16.130.122",     // ip-Adresse des Callgenerators, wenn vorhanden
                configDeltaTimeOKSec        = 5,                    // Abweichung in Sekunden zwischen der Systemzeit von ioBroker und der Meldungszeit aus der Fritzbox, die noch als OK gilt
                configInit                  = true,                 // true = alle Werte werden zurückgesetzt, false = Werte aus den ioBroker Objekten werden eingelesen
                configHistoryAllLines       = 12,                   // Anzahl der Einträge in der Anruferliste gesamt
                configHistoryMissedLines    = 10;                   // Anzahl der Einträge in der Anruferliste verpasste Anrufe
            
            
            
            var ringLastNumber             = "",
                ringLastNumberTel          = "",
                ringLastMissedNumber       = "",
                ringLastMissedNumberTel    = "",
            //    ringActualNumber           = "",
            //    ring                       = false,
                callLastNumber             = "",
                callLastNumberTel          = "",
            //    connectNumber              = "",
                missedCount                = 0,
                historyListAllHtml         = [],
                historyListAllTxt          = [],
                historyListAllJson         = [],
                historyListMissedHtml      = [];
            
            
            var objMissedCount = 0;
            
            var onlyOne         = false; // Flag, ob main() schon einmal durchgeführt wurde
            
            var headlineDate            = "Tag    Zeit  ",  // mit utf-8 nbsp
                headlineExtnumber       = "ext. Rufnr.",
                headlineDirection       = "g<>k",
                headlineExtension       = "Nbst",
                headlineOwnnumber       = "Eigenes Amt",
                headlineLine            = "Ltg.",
                headlineDuration        = "  Dauer",        // mit utf-8 nbsp
                nbsp                    = " ",              // utf-8 nbsp
                headlineTableAllTxt     = "",
                headlineTableAllHTML    = "",
                headlineTableMissedHTML = "";
            
            
            
            // Liste der aktiven RINGs, CALLs, CONNECTs
            var listRing            = [],
                listCall            = [],
                listConnect         = [],
                listAll             = [];
            
            // für den 1-Sekundentimer für den Callmonitor
            var intervalRunningCall = null;
            
            // für die TR-064-Abfragen
            var intervalTR046 = null;
            var wlanState = null;
            
            
            
            adapter.on('message', function (obj) {
            //    if (obj) processMessage(obj);
            //    processMessages();
                adapter.log.debug('adapter.on-message: << MESSAGE >>');
            });
            
            // hier startet der Adapter
            adapter.on('ready', function () {
                adapter.log.debug('adapter.on-ready: << READY >>');
                main();
            });
            
            // hier wird der Adapter beendet
            adapter.on('unload', function () {
                adapter.log.debug('adapter.on-unload: << UNLOAD >>');
                clearRealtimeVars();
            
                if (intervalRunningCall) {
                    clearInterval(intervalRunningCall);
                    intervalRunningCall = null;
                }
            
                if (intervalTR046) {
                    clearInterval(intervalTR046);
                    intervalTR046 = null;
                }
            });
            
            
            // is called if a subscribed state changes
            adapter.on('stateChange', function (id, state) {
                // adapter.log.debug('adapter.on-stateChange: << stateChange >>');
                if (!state) {
                    return;
                }
                if (id === adapter.namespace + ".calls.missedCount") {
                    // New value of
                    // adapter.log.debug("state.val: " + state.val);
                    if (state.val == 0 || state.val == "0 ") {
                        adapter.setState('calls.missedDateReset',         dateNow(),          true);
                        adapter.log.debug("missed calls: counter zurückgesetzt " + dateNow());
                    }
                }
                else if (id === adapter.namespace + ".wlan.enabled" && !state.ack) {
                    adapter.log.debug(id + "=" + state.val);
                    if (state.val != wlanState && intervalTR046 && adapter.config.enableWlan) {
                        adapter.log.info("Changing WLAN to " + state.val);
                        setWlanEnabled(adapter.config.fritzboxAddress, adapter.config.fritzboxUser, adapter.config.fritzboxPassword, state.val);
                    }
                }
            });
            
            
            
            function fritzboxDateEpoch(date) {
                date = date || "";
                var year    = "20" + date.substring(6, 8), // Achtung: ab dem Jahr 2100 erzeugt die Funktion ein falsches Datum ;-)
                    month   = parseInt(date.substring(3, 5)) - 1,
                    day     = date.substring(0, 2),
                    hour    = date.substring(9, 11),
                    minute  = date.substring(12, 14),
                    second  = date.substring(15, 17),
                    time    = new Date(year, month, day, hour, minute, second);
                return Date.parse(time); // fritzbox message date in epoch
            }
            
            function dateEpochNow() {
                var now = new Date();
                now = Date.parse(now);  // aktuelle Zeit in epoch
                return now;
            }
            
            function dateNow() {
                var date     = new Date(dateEpochNow()),
                    year     = date.getFullYear(),
                    month    = date.getMonth() + 1,
                    day      = date.getDate(),
                    hour     = date.getHours(),
                    minute   = date.getMinutes(),
                    second   = date.getSeconds();
                if (month.toString().length == 1) {
                    month  = '0' + month;
                }
                if (day.toString().length == 1) {
                    day = '0' + day;
                }
                if (hour.toString().length == 1) {
                    hour = '0' + hour;
                }
                if (minute.toString().length == 1) {
                    minute = '0' + minute;
                }
                if (second.toString().length == 1) {
                    second = '0' + second;
                }
                return day + "." + month + "." + year + " " + hour + ":" + minute;
            }
            
            
            function fill(n, str) {  // liefere Anzahl n nbsp in utf-8, wenn str nicht angegeben oder n-mal str
                var space = "";
                for (var i = 0; i < n; ++i) {
                    space += ((!str) ? " " : str); // &nbsp; als utf-8 Code (Mac: alt+Leerzeichen) TODO: wie kann das nbsp-Leerzeichen in Wbstorm sichtbar gemacht werden
                }
                return space;
            }
            
            function dynamicSort(property) {
                var sortOrder = 1;
                if (property[0] === "-") {
                    sortOrder = -1;
                    property = property.substr(1);
                }
                return function (a,b) {
                    var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
                    return result * sortOrder;
                }
            }
            
            
            function numberFormat(number,length,align) {
            // bringt die Rufnummer auf die konfigurierte Länge (zu kurz: mit utf-8 Non-breaking-space auffüllen // zu lang: kürzen und als letze "Ziffer" ein x ("x" nur, wenn es sich um Zahlen handelt).
                var onlyNumbers = /\D+/g;
                if (!number.match(onlyNumbers)) { // wenn der String numbers nur Ziffern enthält und diese in der Anzahl zu lang sind, kürzen und die letzte Ziffer gegen ein "x" ersetzen
                    if (number.length > length) {
                        number = number.substring(0, length - 1) + "x";
                    }
                }
                number = number.substring(0,length); // sollte die externen numbern insgesamt länger sein, werden diese auf die max. gewünschte Länge gekürzt
                if (align !== "r") {
                    number = number + fill(length - number.length); // richtet linksbündig aus und füllt nbsp; auf
                } else {
                    number = fill(length - number.length) + number; // wenn allign = "r" wird rechtsbündig ausgerichtet
                }
                var i = 0;
                while (number.search(" ") >= 0) {
                number = number.replace(" ", " "); // alle normalen Leerzeichen gegen utf-8 non breaking spaces ersetzen
                    if (!i) adapter.log.debug('Leerzeichen gegen utf-8 non breaking space ersetzt: <'+ number + ">");
                    i++;
                    if (i > 40) {
                        adapter.log.warn("function numberFormat: zu langer Sting: " + number);
                        break;
                    }
                }
                return number;
            }
            
            
            function e164(extrnr) {
            // wandelt eine externe Rufnummer in das e164 Format, z.B. rufnummer 4711321 wird zu +492114711321 (in Abhängigkeit des konfigurierten CC und AC)
                extrnr = extrnr.toString().replace(/\D+/g, ""); // nur Ziffern behalten, Rest kürzen
                if (extrnr === configUnknownNumber) return extrnr;
                if (extrnr.length > 0) {
                    var i;
                    var extrnr0 = 0;
                    for (i = 0; i < extrnr.length; i++) {
                        if (extrnr.substr(i, i + 1) != "0") {
                            break;
                        }
                        extrnr0++;
                    }
                    extrnr = extrnr.slice(extrnr0, extrnr.length);
            
                    if (extrnr0 == 0) {
                        extrnr = "+" + configCC + configAC + extrnr;
                    }
                    if (extrnr0 == 1) {
                        extrnr = "+" + configCC + extrnr;
                    }
                    if (extrnr0 == 2) {
                        extrnr = "+" + extrnr;
                    }
            
                    if (extrnr0 < 0 && extrnr0 > 2) {
                        adapter.log.error("fritzbox - ungültige externe Rufnummer, Anzahl führender Nullen prüfen");
                    }
                    validateE164(extrnr);
                }
                return extrnr;
            }
            
            function telLink(e164,formattedNumber) {
            // erstellt aus einer e164 Rufnummer einen tel: link. Wird als 2. Parameter eine formatierte Rufnummer übergeben, wird diese dargestellt.
                var link = "";
                if (!formattedNumber) {
                    link = '<a style="text-decoration: none;" href="tel:' + e164 + '">' + e164 + '</a>';
                } else {
                    link = '<a style="text-decoration: none;" href="tel:' + e164 + '">' + formattedNumber + '</a>';
                }
                return link;
            }
            
            
            function validateE164(e164) {
            // testet auf eine gültige internationale Rufnummer mit + am Anfang
                var regex = /^\+(?:[0-9] ?){6,14}[0-9]$/;
                if (regex.test(e164)) {
                    // Valid international phone number
                    return true;
                } else {
                    // Invalid international phone number
                    adapter.log.warn(e164 + " is not a vail int. phone number. Please check your configuration (Country + Area Code).");
                    return false;
                }
            }
            
            
            function fritzboxDateToTableDate(fritzboxDate) {
                var year = fritzboxDate.substring(6,8);
                var month = fritzboxDate.substring(3,5);
                var day = fritzboxDate.substring(0,2);
                var hour = fritzboxDate.substring(9,11);
                var minute = fritzboxDate.substring(12,14);
                var second = fritzboxDate.substring(15,17);
                // var time = hour + ":" + minute + ":" + second + " ";
                // var time = hour + ":" + minute + "  ";
                // var date = day  + "." + month  + ".20" + year + " ";
                // var date = day  + "." + month  + "." + year + " ";
                var date = day + "." + month + ". " + hour + ":" + minute + "  "; // space = non-breaking-space in utf-8
                return date;
            }
            
            
            function durationForm(duration) {
            // Dauer in Sekunden formatiert zu einem 7-stelligen String:
            // "      -" = 0 Sek.
            // "      5" = einstellige Sekunde
            // "     27" = zweistellige Sekunden
            // "   1:41" = einstellige Minuten
            // "  59:32" = zweistellige Minuten
            // "8:21:44" = mehr als eine Stunde, weniger als 10h
            // "  >10 h" = mehr als 10h
                if (duration === "") {
                    duration = fill(7);
                    return duration;
                }
                var durationMin = Math.floor(parseInt(duration) / 60 );
                var durationSek = parseInt(duration) % 60;
                var durationStd = Math.floor(durationMin  / 60);
                durationMin %= 60;
                if (durationStd < 1) {
                    if (durationMin < 1) {
                        duration = durationSek;
                    } else {
                        duration = durationMin + ":" + fill((2- durationSek.toString().length),"0") + durationSek;
                    }
                } else {
                    duration = durationStd + ":" + fill((2- durationMin.toString().length),"0") + durationMin + ":" + fill((2- durationSek.toString().length),0) + durationSek;
                }
                duration = duration.toString();
            
                if (duration == "0") {
                    duration = "-";
                }
            
                if (duration.length > 7) {
                    duration = "> 10h";
                }
                duration = fill(7 - duration.toString().length) + duration; // auf 7-Stellen auffüllen
                return duration;
            }
            
            
            function getEventsList(anruferliste) {
                var text = '';
                for (var i = 0; i < anruferliste.length; i++) {
                     text += (text ? '<br>\n' : '') + anruferliste[i];
            //        text += anruferliste[i] + (text ? '<br>\n' : '');
                }
                return text;
            }
            
            function makeList(list,line,headline,howManyLines,showHeadline) {
                var lines = 0;
                if (showHeadline === true) {
                    list.shift(); // erste Element im Array löschen (Kopfzeile)
                    list.unshift(headline, line); // (Kopfzeile und aktueller Eintrag an Stelle 1 und 2 hinzufügen)
                    lines = 1;
                } else {
                    list.unshift(line);
                }
            
                if (list.length > howManyLines + lines) {
                    list.length = howManyLines + lines;
                }
                return getEventsList(list);
            }
            
            
            function callmonitor(list) {
            // Liste aktueller Anrufe (RING, CONNECT und CALL)
                var txt = '';
                for (var i = 0; i < list.length; i++) {
                    txt += fritzboxDateToTableDate(call[list[i].id].dateStart) + " " + call[list[i].id].externalNumberForm;
                    if (call[list[i].id].type === "CONNECT") {
                        txt += durationForm(call[list[i].id].durationSecs2);
                    }
                    if (call[list[i].id].type === "RING") {
                        txt += durationForm(call[list[i].id].durationRingSecs);
                    }
                    if (i < (list.length - 1)) {
                        txt += (txt ? '<br>\n' : '');
                    }
                }
                return txt;
            }
            
            
            function callmonitorAll(list) {
                var txt = '';
                for (var i = 0; i < list.length; i++) {
                    var id = list[i].id;
                    var intNr = fill(4 - call[id].extensionLine.length) + call[id].extensionLine;
                    if (call[id].callSymbol == " -> ") {
                        intNr = '<span style="color:red  ">RING</span>';
                    }
                    txt += fritzboxDateToTableDate(call[id].dateStart) + " " + call[id].externalNumberForm;
                    txt += call[id].callSymbolColor + " ";
                    txt += intNr;
                    if (call[id].type === "CONNECT") {
                        txt += " " + '<span style="color:green">' + "<b>" + durationForm(call[id].durationSecs2) + "</b>" + '</span>';
                    }
                    if (call[id].type === "RING") {
                        txt += " " + '<span style="color:red  ">' + durationForm(call[id].durationRingSecs) + '</span>';
                    }
                    if (i < (list.length - 1)) {
                        txt += (txt ? '<br>\n' : '');
                    }
                }
                return txt;
            }
            
            function headlineMissedHTML() {
                var headlineHistoryMissedHTML =
                    "<b>" + headlineDate +
                    headlineExtnumber + nbsp +
                    headlineOwnnumber + "</b>";
                return headlineHistoryMissedHTML;
            }
            
            
            function headlineAllTxt() {
                // Überschrift formatieren
                headlineDate = numberFormat(headlineDate, 14);
                headlineExtnumber = numberFormat(headlineExtnumber, configNumberLength);
                headlineDirection = numberFormat(headlineDirection, 4);
                headlineExtension = numberFormat(headlineExtension, 5, "r");   // 5 Zeichen lang, rechtsbündig
                headlineOwnnumber = numberFormat(headlineOwnnumber, configNumberLength);
                headlineLine = numberFormat(headlineLine, 4);
                headlineDuration = numberFormat(headlineDuration, 7, "r");     // 7 Zeichen lang, rechtsbündig
            // Überschrift Anruferliste gesamt (txt)
                var headlineHistoryAllTxt =
                    headlineDate +
                    headlineExtnumber + nbsp +
                    headlineDirection +
                    headlineExtension + nbsp +
                    headlineOwnnumber + nbsp +
                    headlineLine + nbsp +
                    headlineDuration;
                return headlineHistoryAllTxt;
            }
            
            function headlineAllHTML() {
            // Überschrift Anruferliste gesamt (html)
                var headlineHistoryAllHTML = "<b>" + headlineAllTxt() + "</b>";
            
                return headlineHistoryAllHTML;
            }
            
            
            function clearRealtimeVars() {
                 // da die Daten nach Start des Adapters oder neuer IP-Verbindung zur Fritzbox nicht konsitent sind, werden die Callmonitore gelöscht
            
                // Callmonitore löschen
                if (showCallmonitor) {
                    adapter.setState('callmonitor.ring', "", true);
                    adapter.setState('callmonitor.call', "", true);
                    adapter.setState('callmonitor.connect', "", true);
                    adapter.setState('callmonitor.all', "", true);
                }
                //Realtime Daten löschen
                adapter.setState('calls.ring',                               false ,    true);
                adapter.setState('calls.ringActualNumber',                   "" ,       true);
                adapter.setState('calls.ringActualNumbers',                  "" ,       true);
                adapter.setState('calls.connectNumber',                      "",        true);
                adapter.setState('calls.connectNumbers',                     "" ,       true);
                adapter.setState('calls.counterActualCalls.ringCount',       0 ,        true);
                adapter.setState('calls.counterActualCalls.callCount',       0 ,        true);
                adapter.setState('calls.counterActualCalls.connectCount',    0 ,        true);
                adapter.setState('calls.counterActualCalls.allActiveCount',  0 ,        true);
            
                // Liste der aktiven RINGs, CALLs, CONNECTs
                listRing            = [];
                listCall            = [];
                listConnect         = [];
                listAll             = [];
            
            }
            
            
            
            
            
            
            function adapterSetOnChange (object, value) {
                // ioBroker Objekte schreiben aber nur, wenn der Wert geändert wurde
                adapter.getState(object, function (err, state) {
                    if (!err && state) {
                        if (state.val !== value || !state.ack) {
                            adapter.setState(object, value , true);
                        }
                    }
                });
            }
            
            
            function adapterSetOnUndefined (object, value) {
                adapter.getState(object, function (err, state) {
                    if (!err && state) {
                        var newValue = state.val || value;
                        if (state.val == newValue) return; // vorhandene Werte nicht noch einmal schreiben
                        adapter.setState(object, newValue, true);
            //            adapter.log.info("Objekt: " + object + " Wert: " + state.val + " -> " + newValue + "  INIT: " +value);
                    } else {
                        adapter.setState(object, value, true);
                    }
                });
            }
            
            
            
            function initVars() {
            // alte Zustände übernehmen oder bei Erstinitalisierung sinnvolle Werte
                adapterSetOnUndefined("calls.missedCount", 0);
                adapterSetOnUndefined("calls.missedDateReset", dateNow());
            
                adapterSetOnUndefined("calls.ringLastNumber", "");
                adapterSetOnUndefined("calls.ringLastMissedNumber", "");
                adapterSetOnUndefined("calls.callLastNumber", "");
            
                adapterSetOnUndefined("telLinks.ringLastNumberTel", "");
                adapterSetOnUndefined("telLinks.ringLastMissedNumberTel", "");
                adapterSetOnUndefined("telLinks.callLastNumberTel", "");
            
                adapterSetOnUndefined("system.deltaTime", 0);
                adapterSetOnUndefined("system.deltaTimeOK", true);
            
                // config aus der Adapter Admin-Webseite übernehmen
                configCC                    = adapter.config.cc;    // "49",            // eigene Landesvorwahl ohne führende 0
                configAC                    = adapter.config.ac;    // "211",           // eigene Ortsvorwahl ohne führende 0
                configUnknownNumber         = numberFormat(adapter.config.unknownNumber, adapter.config.unknownNumber.length); // Leerzeichen gegen utf-8 nbsp ersetzen
                configNumberLength          = adapter.config.numberLength;
                if (configNumberLength < 4) {
                    adapter.log.warn("Rufnummernlänge zu klein gewählt, geändert von " + configNumberLength + " auf 4");
                    configNumberLength = 4;
                }
                if (configNumberLength > 30) {
                    adapter.log.warn("Rufnummernlänge zu groß gewählt, geändert von " + configNumberLength + " auf 30");
                    configNumberLength = 30;
                }
            
                configExternalLink          = adapter.config.externalLink;
                configShowHeadline          = adapter.config.showHeadline;
            
                showHistoryAllTableTxt      = adapter.config.showHistoryAllTableTxt;
                showHistoryAllTableHTML     = adapter.config.showHistoryAllTableHTML;
                showHistoryAllTableJSON     = adapter.config.showHistoryAllTableJSON;
                showCallmonitor             = adapter.config.showCallmonitor;
                showMissedTableHTML         = adapter.config.showMissedTableHTML;
                showMissedTableJSON         = adapter.config.showMissedTableJSON;
            
                // vorhandene Werte aus den ioBroker Objekte des Fritzbox Adapters rauslesen und in die globalen Variablen übernehmen
                adapter.getState('calls.missedCount', function (err, state) {
                    if (!err && state) {
                        objMissedCount = (state.val);
                        missedCount = (state.val);
                    }
                });
            
            
                if (adapter.config.showHeadline) {
                    headlineTableAllTxt = headlineAllTxt();
                    headlineTableAllHTML = headlineAllHTML();
                    headlineTableMissedHTML = headlineMissedHTML();
                } else {
                    headlineTableAllTxt = "";
                    headlineTableAllHTML = "";
                    headlineTableMissedHTML = "";
                }
            
            
                if (!showHistoryAllTableTxt) {
                    adapter.setState('history.allTableTxt',    "deactivated",          true);
                } else {
                    adapter.setState('history.allTableTxt',    headlineTableAllTxt,    true);
                }
                if (!showHistoryAllTableJSON) {
                    adapter.setState('history.allTableJSON',   "deactivated",          true);
                }
                if (!showHistoryAllTableHTML) {
                    adapter.setState('history.allTableHTML',   "deactivated",          true);
                } else {
                    adapter.setState('history.allTableHTML',   headlineTableAllHTML,   true);
                }
                if (!showMissedTableHTML) {
                    adapter.setState('history.missedTableHTML', "deactivated",         true);
                } else {
                    adapter.setState('history.missedTableHTML', headlineTableMissedHTML,   true);
                }
                if (!showMissedTableJSON)     adapter.setState('history.missedTableJSON',   "deactivated", true);
                if (!showCallmonitor) {
                    adapter.setState('callmonitor.connect', "deactivated", true);
                    adapter.setState('callmonitor.ring', "deactivated", true);
                    adapter.setState('callmonitor.call', "deactivated", true);
                    adapter.setState('callmonitor.all', "deactivated", true);
                } else {
                    adapter.setState('callmonitor.connect', "", true);
                    adapter.setState('callmonitor.ring', "", true);
                    adapter.setState('callmonitor.call', "", true);
                    adapter.setState('callmonitor.all', "", true);
                }
            
                adapter.setState('wlan.enabled', false, true);
            }
            
            
            
            
            
            function parseData(message) {
                adapter.log.info("data from " + adapter.config.fritzboxAddress + ": " + message);
                message = message.toString('utf8');
                adapter.setState('message', message, true);
            
                var obj = message.split(";"); 	// speichert die Felder der Meldung in ein Array mit dem Namen "obj"
                var id = obj[2];
                call[id] = call[id] || {};
            
                var ringCount           = 0,
                    callCount           = 0,
                    connectCount        = 0,
                    allActiveCount      = 0,
                    ring                = null,
                    ringActualNumber    = "";
            
                var ringActualNumbers   = [],
                    connectNumbers      = [],
                    connectNumber       = "";
            
                var cssGreen = '<span style="color:green">',
                    cssRed   = '<span style="color:red  ">',
                    cssBlack = '<span style="color:black">',
                    cssColor = cssGreen,
                    cssEnd   = "</span>";
            
            
            
                // ###########  Auswertung und Aufbau der Anruferinformationen aus der Fritzbox ###########
            
                // für alle Anruftypen identisch
                call[id].date            = obj[0];    // 01.07.15 12:34:56 - dd.mm.yy hh:mm:ss
                call[id].dateEpoch       = fritzboxDateEpoch(obj[0]); // fritzbox time
                call[id].dateEpochNow    = dateEpochNow();              // system time
                call[id].deltaTime       = (call[id].dateEpochNow  - call[id].dateEpoch) / 1000; // delta Time beetween system and fritzbox
                call[id].deltaTimeOK     = (call[id].deltaTime < configDeltaTimeOKSec);
                call[id].type            = obj[1];    // CALL (outgoing), RING (incoming), CONNECT (start), DISCONNECT (end)
                call[id].id              = obj[2];    // call identifier as integer
            
            
                // Outgoing call
                if (call[id].type == "CALL") {
                    call[id].extensionLine      = obj[3];    // used extension line
                    call[id].ownNumber          = obj[4];    // calling number - used own number
                    call[id].externalNumber     = obj[5];    // called number
                    call[id].lineType           = obj[6];    // line type
                    call[id].durationSecs       = null;      // duration of the connect in sec
                    call[id].durationForm       = null;      // duration of the connect in sec
                    call[id].durationSecs2      = "";        // duration of the connect in sec, count during connect
                    call[id].durationRingSecs   = "";        // duration ringing time in sec
                    call[id].connect            = false;
                    call[id].direction          = "out";
                    call[id].dateStartEpoch     = fritzboxDateEpoch(obj[0]);
                    call[id].dateConnEpoch      = null;
                    call[id].dateEndEpoch       = null;
                    call[id].dateStart          = obj[0];
                    call[id].dateConn           = null;
                    call[id].dateEnd            = null;
                    call[id].callSymbol         = " <- "; // utf-8 nbsp
                    call[id].callSymbolColor    = cssBlack + call[id].callSymbol + cssEnd;
                }
                else // Incoming (RING)
                if (call[id].type == "RING") {
                    call[id].extensionLine      = "";        // used extension line
                    call[id].ownNumber          = obj[4];    // called number - used own number
                    call[id].externalNumber     = obj[3];    // calling number
                    call[id].lineType           = obj[5];    // line type
                    call[id].durationSecs       = null;      // duration of the connect in sec
                    call[id].durationForm       = null;
                    call[id].durationSecs2      = "";        // duration of the connect in sec, count during connect
                    call[id].durationRingSecs   = 0;         // duration of ringing time in sec
                    call[id].connect            = false;
                    call[id].direction          = "in";
                    call[id].dateStartEpoch     = fritzboxDateEpoch(obj[0]);
                    call[id].dateConnEpoch      = null;
                    call[id].dateEndEpoch       = null;
                    call[id].dateStart          = obj[0];
                    call[id].dateConn           = null;
                    call[id].dateEnd            = null;
                    call[id].callSymbol         = " -> "; // utf-8 nbsp
                    call[id].callSymbolColor    = cssBlack + call[id].callSymbol + cssEnd;
                }
                else // Start of connect
                if (call[id].type == "CONNECT") {
                    call[id].extensionLine      = obj[3];    // used extension line
                    call[id].ownNumber          = call[id].ownNumber       || "????????";    // used own number
                    call[id].externalNumber     = obj[4];    // connected number
                    call[id].lineType           = call[id].lineType        || "????";        // line type
                    call[id].durationSecs       = null;      // duration of the connect in sec
                    call[id].durationForm       = null;
                    call[id].durationSecs2      = 0;         // duration of the connect in sec, count during connect
                    call[id].durationRingSecs   = call[id].durationRingSecs || "";         // duration of ringing time in sec
                    call[id].connect            = true;
                    call[id].direction          = call[id].direction       || "?";
                    call[id].dateStartEpoch     = call[id].dateStartEpoch  || null;
                    call[id].dateConnEpoch      = fritzboxDateEpoch(obj[0]);
                    call[id].dateEndEpoch       = null;
                    call[id].dateStart          = call[id].dateStart || obj[0];
                    call[id].dateConn           = obj[0];
                    call[id].dateEnd            = null;
                    if (call[id].direction == "in") {
                        call[id].callSymbol     = " ->>";
                    } else {
                        call[id].callSymbol     = "<<- "; // utf-8 nbsp
                    }
                    call[id].callSymbolColor    = cssGreen + "<b>" + call[id].callSymbol + "</b>" + cssEnd;
                }
                else // End of call
                if (call[id].type == "DISCONNECT") {
                    call[id].extensionLine      = call[id].extensionLine   || "";          // used extension line
                    call[id].ownNumber          = call[id].ownNumber       || "????????";    // used own number
                    call[id].externalNumber     = call[id].externalNumber  || "????????";    // connected number
                    call[id].lineType           = call[id].lineType        || "????";        // line type
                    call[id].durationSecs       = obj[3];                                    // call duration in seconds
                    call[id].durationForm       = durationForm(obj[3]);
                    call[id].durationSecs2      = call[id].durationSecs;      // duration of the connect in sec
                    call[id].durationRingSecs   = call[id].durationRingSecs || "";         // duration of ringing time in sec
                    //call[id].connect            = call[id].connect         || null;
                    call[id].direction          = call[id].direction       || "?";
                    call[id].dateStartEpoch     = call[id].dateStartEpoch  || fritzboxDateEpoch(obj[0]);
                    call[id].dateConnEpoch      = call[id].dateConnEpoch   || fritzboxDateEpoch(obj[0]);
                    call[id].dateEndEpoch       = fritzboxDateEpoch(obj[0]);
                    call[id].dateStart          = call[id].dateStart       || obj[0];
                    call[id].dateConn           = call[id].dateConn        || obj[0];
                    call[id].dateEnd            = obj[0];
                    if (call[id].connect === false) {
                        cssColor = cssRed;
                        if (call[id].direction == "in") {
                            call[id].callSymbol = " ->X";
                        } else {
                            call[id].callSymbol = "X<- "; // utf-8 nbsp
                        }
                    }
                    call[id].callSymbol = call[id].callSymbol || "????";
                    if (call[id].callSymbol === "????") cssColor = cssBlack;
                    call[id].callSymbolColor    = cssColor + "<b>" + call[id].callSymbol + "</b>" + cssEnd;
                }
                else {
                    adapter.log.error ("adapter fritzBox unknown event type " + call[id].type);
                    return;
                }
            
            
                // für alle Anruftypen identisch
                call[id].unknownNumber = false;
                if (call[id].externalNumber === "" || call[id].externalNumber === null || call[id].externalNumber === configUnknownNumber || call[id].externalNumber === "????????") {
                    if (call[id].externalNumber !== "????????") call[id].externalNumber         = configUnknownNumber;
                    call[id].unknownNumber          = true;
                }
                call[id].ownNumberForm              = numberFormat(call[id].ownNumber,configNumberLength);
                call[id].externalNumberForm         = numberFormat(call[id].externalNumber,configNumberLength);
                call[id].ownNumberE164              = e164(call[id].ownNumber);
                call[id].externalE164               = e164(call[id].externalNumber);
            
            
                if (call[id].unknownNumber) {
                    call[id].externalTelLink        = call[id].externalNumberForm;
                    call[id].externalTelLinkCenter  = call[id].externalNumber;
                } else {
                    call[id].externalTelLink        = telLink(call[id].externalE164,call[id].externalNumberForm);
                    call[id].externalTelLinkCenter  = telLink(call[id].externalE164,call[id].externalNumber);
                }
            
            /*
                adapter.log.debug("fritzBox event (date: " + call[id].date +
                    ", dateEpoch: "     + call[id].dateEpoch +
                    ", type: "          + call[id].type +
                    ", ID: "            + call[id].id +
                    ", exLine: "        + call[id].extensionLine +
                    ", extern: "        + call[id].externalNumber +
                    ", own: "           + call[id].ownNumber +
                    ", duration: "      + call[id].durationSecs
               );
            
                adapter.log.debug("fritzBox event (delta time: " + call[id].deltaTime +
                    ", delta time ok: "    + call[id].deltaTimeOK +
                    ", symbol: "           + call[id].callSymbol +
                    ", ext E164: "         + call[id].externalE164 +
                    ", ext Tel Link: "     + call[id].externalTelLink
                );
            */
            
            
                // Listen aktuelle Gesprächszustände nach jeder Fritzbox Message, neuaufbauen und damit aktualisieren)
                listRing            = [];
                listCall            = [];
                listConnect         = [];
                listAll             = [];
            
                // Schleife, Anzahl der Call IDs im Array: Zählt die jeweiligen Zustände, ein Array je Zustand
                for (var i = 0; i < call.length; i++){
                    if (call[i] != null) {
                        // Call Status = RING
                        if (call[i].type === "RING"){
                            listRing[ringCount]                         = listRing[ringCount] || {};
                            listRing[ringCount].id                      = call[i].id;
                            listRing[ringCount].dateStartEpoch          = call[i].dateStartEpoch;
                            ringCount++;
                         }
            
                        // Call Status = CALL
                        if (call[i].type === "CALL"){
                            listCall[callCount]                         = listCall[callCount] || {};
                            listCall[callCount].id                      = call[i].id;
                            listCall[callCount].dateStartEpoch          = call[i].dateStartEpoch;
                            callCount++;
                        }
            
                        // Call Status = CONNECT
                        if (call[i].type === "CONNECT"){
                            listConnect[connectCount]                   = listConnect[connectCount] || {};
                            listConnect[connectCount].id                = call[i].id;
                            listConnect[connectCount].dateStartEpoch    = call[i].dateStartEpoch;
                            connectCount++;
                        }
            
                        // Alle Status (RING, CALL, CONNECT)
                        if (call[i].type !== "DISCONNECT"){
                            listAll[allActiveCount]                     = listAll[allActiveCount] || {};
                            listAll[allActiveCount].id                  = call[i].id;
                            listAll[allActiveCount].dateStartEpoch      = call[i].dateStartEpoch;
                            allActiveCount++;
                        }
                    }
                }
            
            /*    adapter.log.debug("ringCount: " + ringCount +
                    ", callCount: "             + callCount+
                    ", connectCount: "          + connectCount +
                    ", allActiveCount: "        + allActiveCount
                );
            */
            
            // sortiert die ermittelten Listen nach der tatsächlichen Meldungszeit (Systemzeit)
                listRing.sort(dynamicSort("-dateStartEpoch"));      // Liste sortieren: jüngster Eintrag oben
                listCall.sort(dynamicSort("-dateStartEpoch"));      // Liste sortieren: jüngster Eintrag oben
                listConnect.sort(dynamicSort("-dateStartEpoch"));   // Liste sortieren: jüngster Eintrag oben
                listAll.sort(dynamicSort("-dateStartEpoch"));       // Liste sortieren: jüngster Eintrag oben
            
            
            // aktuellste Anrufernummer wird als aktueller und neuester Anrufer (Ring) gespeichert (es kann noch mehr aktive RINGs geben, diese werden in "ringActualNumbers" gespeichert)
                if (listRing[0] != null) {
                    ringActualNumber = call[listRing[0].id].externalNumber;
                    ring = true;
                }
            
            // kein aktiver Ring, ringAktuell löschen  && ring = false (kein Ring aktiv)
                if (ringCount < 1) {
                    ringActualNumber = "";
                    ring = false;
                }
            
                if (listConnect[0] != null) {
                    connectNumber = call[listConnect[0].id].externalNumber;
                }
                objMissedCount = missedCount; // den alten errechneten Wert der verpassten Anrufe sichern
                // aktuellen Wert Anzahl verpasste Anrufe (missedCount aus den ioBroker Objekten auslesen)
                adapter.getState('calls.missedCount', function (err, state) {
                    if (!err && state) {
                        missedCount = (state.val);
                    }
                });
            
            
            
                if (call[id].type == "DISCONNECT") {
                    if (call[id].direction == "in") {
                        ringLastNumber    = call[id].externalNumber;        // letzter Anrufer
                        adapter.setState('calls.ringLastNumber',                     ringLastNumber ,            true);
                        ringLastNumberTel = call[id].externalTelLinkCenter; // letzter Anrufer als wählbarer Link
                        adapter.setState('calls.telLinks.ringLastNumberTel',         ringLastNumberTel,          true);
            
                        if (!call[id].connect) { // letzter Anrufer, der verpasst wurde
                            ringLastMissedNumber = call[id].externalNumber;
                            adapter.setState('calls.ringLastMissedNumber',               ringLastMissedNumber ,      true);
                            ringLastMissedNumberTel = call[id].externalTelLinkCenter;
                            adapter.setState('calls.telLinks.ringLastMissedNumberTel',   ringLastMissedNumberTel ,   true);
                        }
                        // letzter verpasste Anruf wird hochgezählt, Zähler verpasste Anrufe (bis max. 999)
                        // ioBroker Datenpunkt kann beschrieben und über ioBrkoer zurückgesetzt werden
                        // das Datum, zu dem der Zähler auf 0 gesett wird, wird in calls.missedDateReset gespeichert.
                        if (!call[id].connect) {
                            ++missedCount;
                            if (missedCount > 999) missedCount = 999;
                        }
                    } else
                    if (call[id].direction == "out") {
                        callLastNumber = call[id].externalNumber;
                        adapter.setState('calls.callLastNumber',                     callLastNumber ,            true);
                        callLastNumberTel = call[id].externalTelLinkCenter;
                        adapter.setState('calls.telLinks.callLastNumberTel',         callLastNumberTel,          true);
                    } else {
                        adapter.log.warn ("Adapter starts during call. Some values are unknown.");
                    }
                } // END DISCONNECT
            
            
                // aktuelle Rufnummeren der gerade laufenden Anrufe (RING)
                for (var i = 0; i < listRing.length; i++){
                    ringActualNumbers.push(call[listRing[0].id].externalNumber);
                }
            
                // aktuelle Rufnummern der gerade laufenden Gespräche (CONNECT)
                for (var i = 0; i < listConnect.length; i++){
                    connectNumbers.push(call[listConnect[i].id].externalNumber);
                }
            
            
                // Daten, die bei jeder Meldung aktualisiert werden
                adapter.setState('system.deltaTime',                         call[id].deltaTime ,        true);
                adapter.setState('system.deltaTimeOK',                       call[id].deltaTimeOK ,      true);
                if (!call[id].deltaTimeOK) adapter.log.warn("delta time between system and fritzbox: " + call[id].deltaTime + " sec");
            
                //Auf Änderungen im ioBroker Objekt reagieren und die lokale Variable auch ändern.
                if (objMissedCount !== missedCount) { // Zähler nur schreiben, wenn er sich verändert hat (wg. Traffic Überwachung des Objekts auf Änderung)
                    adapter.setState('calls.missedCount',                        missedCount ,               true);
                }
                // adapter.log.debug ("objMissedCount: " + objMissedCount + "   missedCount: " + missedCount);
            
                //Realtime Daten
                adapterSetOnChange ("calls.ring" , ring);
                adapterSetOnChange ("calls.ringActualNumber" , ringActualNumber);
                adapterSetOnChange ("calls.ringActualNumbers" , ringActualNumbers.join());
                adapterSetOnChange ("calls.connectNumber" , connectNumber);
                adapterSetOnChange ("calls.connectNumbers" , connectNumbers.join());
                adapterSetOnChange ("calls.counterActualCalls.ringCount" , ringCount);
                adapterSetOnChange ("calls.counterActualCalls.callCount" , callCount);
                adapterSetOnChange ("calls.counterActualCalls.connectCount" , connectCount);
                adapterSetOnChange ("calls.counterActualCalls.allActiveCount" , allActiveCount);
            
                // History / Anruferlisten
                if (call[id].type == "DISCONNECT") {
            
                    // Datensatz formatieren
                    var extensionLine   = numberFormat(call[id].extensionLine, 5, "r");
                    var line            = numberFormat(call[id].lineType, 4);
                    // die externe Rufnummer, je nach Konfiguration als Link (tel:) oder Text
                    var externalNumber  = call[id].externalTelLink;
                    if (!configExternalLink) {
                        externalNumber  = call[id].externalNumberForm;
                    }
            
                    // Einzelne Datenzeile Anruferliste gesamt (txt)
                    var lineHistoryAllTxt =
                        fritzboxDateToTableDate(call[id].date) +
                        call[id].externalNumberForm + nbsp +
                        call[id].callSymbol +
                        extensionLine + nbsp +
                        call[id].ownNumberForm + nbsp +
                        call[id].lineType + nbsp +
                        call[id].durationForm;
            
                    // Einzelne Datenzeile Anruferliste gesamt (html)
                    var lineHistoryAllHtml =
                        fritzboxDateToTableDate(call[id].date) +
            //            call[id].externalTelLink + nbsp +
                        externalNumber + nbsp +
                        call[id].callSymbolColor +
                        extensionLine + nbsp +
                        call[id].ownNumberForm + nbsp +
                        call[id].lineType + nbsp +
                        call[id].durationForm;
            
                    // Datensatz html verpasste Anrufe erstellen && html & json schreiben
                    if (!call[id].connect) {
                        if (call[id].direction === "in") {
                            // Datensatz verpasste Anrufe
                            var lineHistoryMissedHtml =
                                fritzboxDateToTableDate(call[id].date) +
                                externalNumber + nbsp + call[id].ownNumberForm + nbsp;
                            // aktuelle Call-ID als JSON für verpasste Anrufe wegschreiben
                            adapter.setState('cdr.missedJSON',              JSON.stringify(call[id]),   true);
                            adapter.setState('cdr.missedHTML',              lineHistoryMissedHtml,      true);
                        }
                    }
            
                    if (showMissedTableHTML) {
                        // Anruferliste verpasste Anrufe erstellen
                        if (!call[id].connect) {
                            if (call[id].direction === "in") {
                                var historyListMissedHtmlStr = makeList(historyListMissedHtml, lineHistoryMissedHtml, headlineTableMissedHTML, configHistoryMissedLines, configShowHeadline);
                                adapter.setState('history.missedTableHTML', historyListMissedHtmlStr, true);
                            }
                        }
                    }
            
            
                    if (showHistoryAllTableHTML) {
                        // Tabelle html erstellen
                        var historyListAllHtmlStr = makeList(historyListAllHtml,lineHistoryAllHtml,headlineTableAllHTML,configHistoryAllLines, configShowHeadline);
                        adapter.setState('history.allTableHTML',        historyListAllHtmlStr,              true);
                    }
            
                    if (showHistoryAllTableTxt) {
                        // Tabelle txt erstellen
                        var historyListAllTxtStr = makeList(historyListAllTxt, lineHistoryAllTxt, headlineTableAllTxt, configHistoryAllLines, configShowHeadline);
                        adapter.setState('history.allTableTxt', historyListAllTxtStr, true);
                    }
                    if (showHistoryAllTableJSON) {
                        var lineHistoryAllJson = {
                            "date" :            call[id].date,
                            "externalNumber" :  call[id].externalNumber,
                            "callSymbolColor" : call[id].callSymbolColor,
                            "extensionLine" :   call[id].extensionLine,
                            "ownNumber" :       call[id].ownNumber,
                            "lineType" :        call[id].lineType,
                            "durationForm" :    call[id].durationForm
                        };
                        // Anruferliste als JSON auf max Anzahl configHistoryAllLine beschränken
            //        historyListAllJson.unshift(call[id]);
                        historyListAllJson.unshift(lineHistoryAllJson);
                        if (historyListAllJson.length   > configHistoryAllLines) {
                            historyListAllJson.length   = configHistoryAllLines;
                        }
                        adapter.setState('history.allTableJSON',    JSON.stringify(historyListAllJson), true);
                    }
            
            //        adapter.log.debug("historyListAllJson Obj " +   JSON.stringify(historyListAllJson[0]) );  // erste Zeile der JSON ANruferliste im Log
            //        adapter.log.debug("history all JSON Items: " +  historyListAllJson.length );              // Anzahl der JSON Elemente in der Anruferliste
            
                    // aktuellen Datensatz des letzten Anrufs als JSON speicherern (für History und die weiteren Verarbeitung in Javascript, usw.)
                    adapter.setState('cdr.json',                    JSON.stringify(call[id]),   true);
                    adapter.setState('cdr.html',                    lineHistoryAllHtml,         true);
                    adapter.setState('cdr.txt',                     lineHistoryAllTxt,          true);
            
                    // try to get phonebook
                    if (call[id].connect && call[id].direction === "in" && adapter.config.enableTAM) {
                        getTAM(adapter.config.fritzboxAddress, adapter.config.fritzboxUser, adapter.config.fritzboxPassword);
                    }
            
                } // End DSICONNECT
            
            
                if (showCallmonitor) {
                    // alle drei Callmonitorausgaben mit Sekundenintervall werden auf einmal aktualisiert, auch, wenn ein Anrufzustand keinen aktiven Zähler hat
                    // wird aus Performancegründen eine Optimierung benötigt, muss es für CONNECT und RING eigenen Intervalle geben
                    // (weniger Traffic und Speicherplatz) # Hinweis: der Callmonitor kann auch in der Config (webadmin) deaktiviert werden
                    if (allActiveCount && !intervalRunningCall) {
                        // Do it every second
                        intervalRunningCall = setInterval(function () {
            
                            // adapter.log.debug("###### callmonitor aktiv ######");
            
                            for (var i = 0; i < listConnect.length; i++) {
                                call[listConnect[i].id].durationSecs2 = dateEpochNow() / 1000 - call[listConnect[i].id].dateEpochNow / 1000;
                            }
                            for (var i = 0; i < listRing.length; i++) {
                                call[listRing[i].id].durationRingSecs = dateEpochNow() / 1000 - call[listRing[i].id].dateEpochNow / 1000;
                            }
                            adapterSetOnChange ("callmonitor.connect" , callmonitor(listConnect));
                            adapterSetOnChange ("callmonitor.ring" , callmonitor(listRing));
                            adapterSetOnChange ("callmonitor.all" , callmonitorAll(listAll));
            //                adapter.setState('callmonitor.connect', callmonitor(listConnect), true);
            //                adapter.setState('callmonitor.ring', callmonitor(listRing), true);
            //                adapter.setState('callmonitor.all', callmonitorAll(listAll), true);
            
                        }, 1000);
                    } else if (!allActiveCount && intervalRunningCall) {
                        // Stop interval
                        clearInterval(intervalRunningCall);
                        intervalRunningCall = null;
                    }
            
                    adapterSetOnChange ("callmonitor.call" , callmonitor(listCall));
            //        adapter.setState('callmonitor.call', callmonitor(listCall), true);
            
                    // wenn der Interval beendet wird, müssen die letzet Anrufe im Callmonitor auch bereinigt werden
                    // die Callmonitorlisten werden zusätzlich zum Intervall mit jeder Fritzbox-Meldung aktualisiert
                    // nur im else-Zweig im Intervall ist dies nicht ausreichend, wg. der Asynchonität
                    adapterSetOnChange ("callmonitor.connect" , callmonitor(listConnect));
                    adapterSetOnChange ("callmonitor.ring" , callmonitor(listRing));
                    adapterSetOnChange ("callmonitor.all" , callmonitorAll(listAll));
                }
            
            
            
            }
            
            
            
            function handleWLANConfiguration(config) {
                //console.log(config);
                adapter.log.debug("WLAN: " + config['NewEnable']);
                wlanState = config['NewEnable'] == 1;
                adapterSetOnChange("wlan.enabled", wlanState);
            }
            
            
            
            function connectToTR064(host, user, password, callback) {
                var tr064 = new tr.TR064();
                tr064.initTR064Device(host, 49000, function (err, device) {
                    if (!err) {
                        device.startEncryptedCommunication(function (err, sslDev) {
                            if (!err) {
                                sslDev.login(user, password);
                                callback(sslDev);
                            }
                            else {
                                adapter.log.warn("TR-064 error: " + err);
                            }
                        });
                    }
                    else {
                        adapter.log.warn("TR-064 error: " + err);
                    }
                });
            }
            
            
            
            function getWlanConfig(host, user, password, callback) {
                connectToTR064(host, user, password, function (sslDev) {
                    var wlanConfig = sslDev.services["urn:dslforum-org:service:WLANConfiguration:1"];
                    adapter.log.debug("TR-064: calling GetInfo()");
                    wlanConfig.actions.GetInfo(function (err, result) {
                        if (!err) {
                            adapter.log.debug("TR-064: got result from GetInfo()");
                            callback(result);
                        }
                        else {
                            adapter.log.warn("TR-064 error: " + err);
                        }
                    });
                });
            }
            
            
            
            function setWlanEnabled(host, user, password, enabled) {
                connectToTR064(host, user, password, function (sslDev) {
                    var wlanConfig = sslDev.services["urn:dslforum-org:service:WLANConfiguration:1"];
                    adapter.log.debug("TR-064: calling SetEnable(" + enabled + ")");
                    wlanConfig.actions.SetEnable({ 'NewEnable': enabled ? 1 : 0 }, function (err, result) {
                        if (!err) {
                            adapter.log.debug("TR-064: got result from SetEnable()");
                        }
                        else {
                            adapter.log.warn("TR-064 error: " + err);
                        }
                    });
                });
            }
            
            function getTAM(host, user, password) {
                connectToTR064(host, user, password, function (sslDev) {
                    var tam = sslDev.services["urn:dslforum-org:service:X_AVM-DE_TAM:1"];
                    adapter.log.debug("TR-064: Calling GetMessageList()");
                    tam.actions.GetMessageList({NewIndex: 0}, function(err, ret) {
                        if (err) {
                            adapter.log.warn("TR-064: Error while calling GetMessageList(): " + err);
                        } else if (ret.NewURL && ret.NewURL.length > 0) {
                            var url = ret.NewURL;
                            adapter.log.debug("TR-064: Got TAM uri: " + url);
                            var baseUrl = url.substring(0, url.lastIndexOf('/'));
                            var sid = url.match(/sid=([\d\w]+)/)[1];
                            adapter.log.debug(`TR-064: sid=${sid}`);
            
                            var agentOptions;
            				var agent;
            
            				agentOptions = {
            				  rejectUnauthorized: false
            				};
            
            				agent = new https.Agent(agentOptions);
            
                            request({
            				  url: url
            				, method: 'GET'
            				, agent: agent
            				}, function (error, response, body) {
                                if (!error && response.statusCode == 200) {
                                    adapter.log.debug("TR-064: Got valid TAM content from, starting to parse ...");
                                    var parser = new xml2js.Parser();
                                    parser.parseString(body, function (err, result) {
                                        if (err) {
                                            adapter.log.warn("TR-064: Error while parsing TAM content: " + err);
                                        } else {
                                            adapter.log.debug("TR-064: Successfully parsed TAM content, analyzing result ...");
                                            var promises = [];
                                            var messages = [];
            
                                            mkdirSync('tam', { recursive: true });
            
                                            if(!result.Root.Message) {
                                                // No messahes
                                                promises.push(new Promise((resolve, reject) => resolve()));
                                            } else {
                                                for (var m = 0; m <= result.Root.Message.length; m++) {
                                                    var message = result.Root.Message[m];
                                                    if (typeof message != 'undefined') {
                                                        promises.push(new Promise((resolve, reject) => {
                                                            var msg = {
                                                                index: message.Index[0],
                                                                calledNumber: message.Called[0],
                                                                date: message.Date[0],
                                                                duration: message.Duration[0],
                                                                callerName: message.Name[0],
                                                                callerNumber: message.Number[0],
                                                                audioFile: ''
                                                            };
                                                            if (!message.Path || message.Path.length < 1) {
                                                                adapter.log.warn("TR-064: TAM message has no url");
                                                                resolve(msg);
                                                                return;
                                                            }
            
                                                            var callDate = message.Date[0].split('.').join("").split(':').join("").split(' ').join("");
                                                            var file = `tam/${callDate}-${message.Number[0]}.wav`
                                                            adapter.log.debug(`TR-064: TAM message file: ${file}`);
                                                            if (existsSync(file)) {
                                                                msg.audioFile = path.resolve(file);
                                                                resolve(msg);
                                                                return;
                                                            }
            
                                                            var downloadUrl = message.Path[0];
                                                            if (downloadUrl.startsWith('/')) {
                                                                downloadUrl = baseUrl + downloadUrl;
                                                            }
                                                            if (downloadUrl.indexOf('sid=')<0){
                                                                downloadUrl += `&sid=${sid}`;
                                                            }
                                                            adapter.log.debug(`TR-064: Download TAM audio file from ${downloadUrl}`);
            
                                                            const stream = createWriteStream(file);
                                                            request({url: downloadUrl, agent: agent})
                                                                .on('error', function(err) {
                                                                    unlink(file);
                                                                    adapter.log.warn(
                                                                        `TR-064: Error while downloading TAM audio file: ${err}`
                                                                    );
                                                                })
                                                                .pipe(stream)
                                                                .on('error', function(err) {
                                                                    unlink(file);
                                                                    adapter.log.warn(
                                                                        `TR-064: Error while writing TAM audio file: ${err}`
                                                                    );
                                                                })
                                                                .on('finish', function() {
                                                                    stream.close(function() {
                                                                        msg.audioFile = path.resolve(file);
                                                                        resolve(msg);
                                                                    });
                                                                });
            
                                                        }).then((result) => {
                                                            messages.push(result);
                                                        }));
                                                    }
                                                }
                                            }
            
                                            Promise.all(promises).then(function() {
                                                messages.sort((m1,m2) => m1.index > m2.index ? 1 : m1.index < m2.index ? -1 : 0);
            
                                                // cleanup old files
                                                readdir('tam', (err, files) => {
                                                    if (err) {
                                                        adapter.log.warn(
                                                            `TR-064: Error reading files from dir /tam: ${err}`
                                                        );
                                                    } else {
                                                        files.forEach(file => {
                                                            file = path.resolve("tam/" + file);
                                                            if (!messages.find(msg => msg.audioFile == file)) {
                                                                // old file
                                                                adapter.log.debug(
                                                                    `TR-064: Remove old tam audio file: ${file}`
                                                                );
                                                                unlink(file, function(err){
                                                                    if (err) {
                                                                        adapter.log.warn(
                                                                            `TR-064: Error deleting file ${file}: ${err}`
                                                                        );
                                                                    }
                                                                });
                                                            }
                                                        })
                                                    }
                                                })
            
                                                adapter.setState('tam.messagesJSON', JSON.stringify(messages), true);
                                                adapter.log.debug("TR-064: Successfully analyzed TAM results");
                                            });
                                        }
                                    });
                                } else {
            						adapter.log.warn(
            							`TR-064: Error while requesting TAM: ${error}`
            						);
            					}
                            });
                        }
                    });
                });
            }
            
            function getPhonebook(host, user, password) {
                connectToTR064(host, user, password, function (sslDev) {
                    var tel = sslDev.services["urn:dslforum-org:service:X_AVM-DE_OnTel:1"];
                    adapter.log.debug("TR-064: Calling GetPhonebook()");
                    tel.actions.GetPhonebook({ NewPhonebookID: '0' }, function (err, ret) {
                        if (err) {
                            adapter.log.warn("TR-064: Error while calling GetPhonebook(): " + err);
                        } else if (ret.NewPhonebookURL && ret.NewPhonebookURL.length > 0) {
                            var url = ret.NewPhonebookURL;
                            adapter.log.debug("TR-064: Got phonebook uri: " + url);
            
            				var agentOptions;
            				var agent;
            
            				agentOptions = {
            				  rejectUnauthorized: false
            				};
            
            				agent = new https.Agent(agentOptions);
            
                            request({
            				  url: url
            				, method: 'GET'
            				, agent: agent
            				}, function (error, response, body) {
                                if (!error && response.statusCode == 200) {
                                    adapter.log.debug("TR-064: Got valid phonebook content from, starting to parse ...");
                                    var parser = new xml2js.Parser();
                                    parser.parseString(body, function (err, result) {
                                        if (err) {
                                            adapter.log.warn("TR-064: Error while parsing phonebook content: " + err);
                                        } else {
                                            adapter.log.debug("TR-064: Successfully parsed phonebook content, analyzing result ...");
                                            var phonenumbers = []; // create an empty array for fetching all configured phone numbers from fritzbox
                                            var phonebook = result.phonebooks.phonebook[0];
                                            for (var c = 0; c <= phonebook.contact.length; c++) {
                                                var contact = phonebook.contact[c];
                                                if (typeof contact != 'undefined') {
                                                    var entryName = contact.person[0].realName[0];
                                                    for (var t = 0; t <= contact.telephony.length; t++) {
                                                        var telephony = contact.telephony[t];
                                                        if (typeof telephony != 'undefined') {
                                                            for (var n = 0; n <= telephony.number.length; n++) {
                                                                var number = telephony.number[n];
                                                                if (typeof number != 'undefined') {
                                                                    var entryNumber = number._;
                                                                    var entryType = number.$.type;
                                                                    if (entryNumber.startsWith('0') || entryNumber.startsWith('+')) {
                                                                        phonenumbers.push({
                                                                            key: entryNumber,
                                                                            value: { name: entryName, type: entryType }
                                                                        });
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            }
            
                                            adapter.setState('phonebook.tableJSON', JSON.stringify(phonenumbers), true);
                                            adapter.log.debug("TR-064: Successfully analyzed phonebook results");
                                        }
                                    });
                                } else {
            						adapter.log.warn(
            							`TR-064: Error while requesting phonebook: ${error}`
            						);
            					}
                            });
                        }
                    });
                });
            }
            
            function connectToFritzbox(host) {
                clearRealtimeVars(); // IP-Verbindung neu: Realtimedaten werden gelöscht, da ggf. nicht konsistent
                var socketBox = net.connect({port: 1012, host: host}, function() {
                    adapter.log.info("adapter connected to fritzbox: " + host);
                });
            
                var connecting = false;
                function restartConnection() {
                    if (socketBox) {
                        socketBox.end();
                        socketBox = null;
                    }
            
                    if (!connecting){
                        adapter.log.warn("restartConnection: " + host);
                        clearRealtimeVars(); // IP-Verbindung neu: Realtimedaten werden gelöscht, da ggf. nicht konsistent
                        connecting = setTimeout(function () {
                            connectToFritzbox(host);
                        }, 10000);
                    }
                }
            
                socketBox.on('error', restartConnection);
                socketBox.on('close', restartConnection);
                socketBox.on('end',   restartConnection);
            
                socketBox.on('data',  parseData);   // Daten wurden aus der Fritzbox empfangen und dann in der Funktion parseData verarbeitet
            
                if ((adapter.config.enableWlan || adapter.config.enablePhonebook || adapter.config.enableTAM)
                    && adapter.config.fritzboxUser && adapter.config.fritzboxPassword && adapter.config.fritzboxPassword.length) {
                    adapter.log.info("Trying to connect to TR-064: " + host + ":49000");
            
                    // try to get WLAN status and enable timer
                    if (adapter.config.enableWlan) {
                        getWlanConfig(host, adapter.config.fritzboxUser, adapter.config.fritzboxPassword, function (result) {
                            adapter.log.info("Successfully connected to TR-064");
                            handleWLANConfiguration(result);
                            intervalTR046 = setInterval(function () {
                                getWlanConfig(host, adapter.config.fritzboxUser, adapter.config.fritzboxPassword, function (result) {
                                    handleWLANConfiguration(result);
                                });
                            }, 10000);
                        });
                    }
            
                    // try to get phonebook
                    if (adapter.config.enablePhonebook) {
                        getPhonebook(host, adapter.config.fritzboxUser, adapter.config.fritzboxPassword);
                    }
            
                    // try to get tel answering machine
                    if (adapter.config.enableTAM) {
                        getTAM(host, adapter.config.fritzboxUser, adapter.config.fritzboxPassword);
                    }
                }
            }
            
            
            
            function main() {
                adapter.log.debug("< main >");
            
                initVars(); // ioBroker Objekte übernehmen wenn vorhanden, sonst init
            
                // Zustandsänderungen innerhalb der ioBroker fritzbox-Adapterobjekte überwachen
            //    adapter.subscribeForeignStates("node-red.0.fritzbox.*); // Beispiel um Datenpunkte anderer Adapter zu überwachen
                adapter.subscribeStates('calls.missedCount');
                adapter.subscribeStates('wlan.enabled');
            
                // TODO: IP-Prüfung bringt nichts, da auch Hostnamen / DNS erlaubt sind & eine Prüfung auf der Admin-Webseite ist sinnvoller
                var validIP = /^((25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])$/;
                if (!adapter.config.fritzboxAddress.match(validIP)) {
                    adapter.log.info("no valid ip-Adress: " + adapter.config.fritzboxAddress) + ". Is it a valid hostname?";
                }
            
                if (adapter.config.fritzboxAddress && adapter.config.fritzboxAddress.length) {
                    adapter.log.info("try to connect: " + adapter.config.fritzboxAddress);
                    connectToFritzbox(adapter.config.fritzboxAddress);      // fritzbox
                } else {
                    adapter.log.error("<< ip-Adresse der Fritzbox unbekannt >>");
                }
            
                /*
                // Zweitanmeldung für Entwicklung: Fritzbox Simulator / Callgenerator
                if (ipCallgen && ipCallgen.length) {
                    adapter.log.info("try to connect: " + ipCallgen);
                    connectToFritzbox(ipCallgen);   // callgen
                }
            */
                onlyOne = true; // Flag, um Aktionen nur einmal nach Programmstart auszuführen
            }
            
            
            
            

            Ich glaube in Zeile 1316 wird das irgendwie geschrieben, kann das sein?

            L 1 Antwort Letzte Antwort
            0
            • L Larson-Sei180LX

              @liv-in-sky

              Ich habe hier das MAIN.JS File vom Fritzbox Adapter. Vl kannst Du mir das dort einpflegen ?

              /**
               *
               *      ioBroker fritzbox Adapter
               *
               *      (c) 2015 Michael Herwig <ruhr@digheim.de>
               *
               *      MIT License
               *
               *
               *    Callmonitor an der Fritzbox einschalten:
               *    Wenn man beim Telefon #96*5* eintippt, wird der TCP-Port 1012 geoffnet.
               *    Mit #96*4* wird dieser wieder geschlossen.
               *
               *    Format der Fritzbox-Meldungen:
               *
               *    Ausgehende Anrufe:            datum;CALL;      CallID;Nebenstelle;callingNumber;calledNumber;lineType;
               *    Eingehende Anrufe:            datum;RING;      CallID;callingNumber;calledNumber;lineTpe;
               *    Zustandegekommene Verbindung: datum;CONNECT;   CallID;Nebenstelle;connectedNumber;
               *    Ende der Verbindung:          datum;DISCONNECT;CallID;dauerInSekunden;
               *
               */
              
              // TODO Wie können auf der Adminseite die Werte vor Änderung (onChange) kontrolliert und ggf. markiert werden?
              // TODO Leistungsmerkmale: (2) Fritzbox Telefonbuch importieren und verarbeiten
              // TODO Anruferlisten persistent realisieren (nicht die erstellten Tabellen, sonder die die Arrays mit den Listeneinträgen (historyListAllHtml, historyListAllTxt, historyListAllJson, historyListMissedHtml)
              //      -> ggf. Redesign. Nicht verschiedene Listen, sondern eine JSON und die Funktionen so ändern, dass makeList auf diese eine JSON aufbaut
              
              "use strict";   // verlangt saubereren Code (nach ECMAScript 5) - EMPFEHLUNG:  beim Programmieren rein, im fertigen Programm raus
              
              // require() ist eine Funktion von node.js, um weitere Module nachzuladen
              
              var utils = require('@iobroker/adapter-core'); // Get common adapter utils
              var xml2js = require('xml2js'); // node-Modul um xml Strukturen in JSON umzuwandeln. Eine Beschreibung: http://www.flyacts.com/blog/nodejs-xml-daten-verarbeiten/
                                              // Bei der Entwicklung ins iobroker.frittbox Verzeichnis installieren. Als Admin, d.h. beim Mac mit sudo:
                                              // sudo npm install xml2js --save
                                              // mit dem --save wird es ins packet.json geschrieben
                                              // vorbereitet, um das Telefonbuch der Fritzbox zu verarbeiten (Export im XML Format)
              var net =    require('net');    // node-Modul für die tcp/ip Kommunikation
                                              // ist schon in node.js enthalten
                                              // Beschreibung: https://nodejs.org/api/net.html
              var tr = require("tr-064");     // node-Modul für die Kommunikation via TR-064 mit der FritzBox
                                              // Beschreibung zu TR-064: http://avm.de/service/schnittstellen/
              
              var https = require("https");
              var request = require("request");
              
              const { existsSync, writeFile, mkdirSync, readdir, unlink, createWriteStream } = require('fs');
              const path = require('path');
              const { url } = require('inspector');
              
              var adapter = utils.Adapter('fritzbox');
              
              var call = [];
              
              // Werte werden über die Adminseite gesetzt
              var // adapter.config.fritzboxAddress // ip-Adresse der Fritzbox
                  configCC                    = "",   // eigene Landesvorwahl ohne führende 0
                  configAC                    = "",   // eigene Ortsvorwahl ohne führende 0
                  configUnknownNumber         = "",   // "### ? ###"wie soll eine unbekannte/unterdrückte externe Rufnummer angezeigt werden
                  configNumberLength          = 15,   // (df=15) max. Stellen externer Rufnummern, die in den Tabellen HTML und TXT angezeigt werden
                  configExternalLink          = true, // externe Rufnummer als wählbaren Link (tel:)
                  configShowHeadline          = true, // Anruferliste mit überschrift?
                  showHistoryAllTableTxt      = true,
                  showHistoryAllTableHTML     = true,
                  showHistoryAllTableJSON     = true,
                  showCallmonitor             = true,
                  showMissedTableHTML         = true,
                  showMissedTableJSON         = true;
              
              // restlichen Config Werte
              var ipCallgen                   = "172.16.130.122",     // ip-Adresse des Callgenerators, wenn vorhanden
                  configDeltaTimeOKSec        = 5,                    // Abweichung in Sekunden zwischen der Systemzeit von ioBroker und der Meldungszeit aus der Fritzbox, die noch als OK gilt
                  configInit                  = true,                 // true = alle Werte werden zurückgesetzt, false = Werte aus den ioBroker Objekten werden eingelesen
                  configHistoryAllLines       = 12,                   // Anzahl der Einträge in der Anruferliste gesamt
                  configHistoryMissedLines    = 10;                   // Anzahl der Einträge in der Anruferliste verpasste Anrufe
              
              
              
              var ringLastNumber             = "",
                  ringLastNumberTel          = "",
                  ringLastMissedNumber       = "",
                  ringLastMissedNumberTel    = "",
              //    ringActualNumber           = "",
              //    ring                       = false,
                  callLastNumber             = "",
                  callLastNumberTel          = "",
              //    connectNumber              = "",
                  missedCount                = 0,
                  historyListAllHtml         = [],
                  historyListAllTxt          = [],
                  historyListAllJson         = [],
                  historyListMissedHtml      = [];
              
              
              var objMissedCount = 0;
              
              var onlyOne         = false; // Flag, ob main() schon einmal durchgeführt wurde
              
              var headlineDate            = "Tag    Zeit  ",  // mit utf-8 nbsp
                  headlineExtnumber       = "ext. Rufnr.",
                  headlineDirection       = "g<>k",
                  headlineExtension       = "Nbst",
                  headlineOwnnumber       = "Eigenes Amt",
                  headlineLine            = "Ltg.",
                  headlineDuration        = "  Dauer",        // mit utf-8 nbsp
                  nbsp                    = " ",              // utf-8 nbsp
                  headlineTableAllTxt     = "",
                  headlineTableAllHTML    = "",
                  headlineTableMissedHTML = "";
              
              
              
              // Liste der aktiven RINGs, CALLs, CONNECTs
              var listRing            = [],
                  listCall            = [],
                  listConnect         = [],
                  listAll             = [];
              
              // für den 1-Sekundentimer für den Callmonitor
              var intervalRunningCall = null;
              
              // für die TR-064-Abfragen
              var intervalTR046 = null;
              var wlanState = null;
              
              
              
              adapter.on('message', function (obj) {
              //    if (obj) processMessage(obj);
              //    processMessages();
                  adapter.log.debug('adapter.on-message: << MESSAGE >>');
              });
              
              // hier startet der Adapter
              adapter.on('ready', function () {
                  adapter.log.debug('adapter.on-ready: << READY >>');
                  main();
              });
              
              // hier wird der Adapter beendet
              adapter.on('unload', function () {
                  adapter.log.debug('adapter.on-unload: << UNLOAD >>');
                  clearRealtimeVars();
              
                  if (intervalRunningCall) {
                      clearInterval(intervalRunningCall);
                      intervalRunningCall = null;
                  }
              
                  if (intervalTR046) {
                      clearInterval(intervalTR046);
                      intervalTR046 = null;
                  }
              });
              
              
              // is called if a subscribed state changes
              adapter.on('stateChange', function (id, state) {
                  // adapter.log.debug('adapter.on-stateChange: << stateChange >>');
                  if (!state) {
                      return;
                  }
                  if (id === adapter.namespace + ".calls.missedCount") {
                      // New value of
                      // adapter.log.debug("state.val: " + state.val);
                      if (state.val == 0 || state.val == "0 ") {
                          adapter.setState('calls.missedDateReset',         dateNow(),          true);
                          adapter.log.debug("missed calls: counter zurückgesetzt " + dateNow());
                      }
                  }
                  else if (id === adapter.namespace + ".wlan.enabled" && !state.ack) {
                      adapter.log.debug(id + "=" + state.val);
                      if (state.val != wlanState && intervalTR046 && adapter.config.enableWlan) {
                          adapter.log.info("Changing WLAN to " + state.val);
                          setWlanEnabled(adapter.config.fritzboxAddress, adapter.config.fritzboxUser, adapter.config.fritzboxPassword, state.val);
                      }
                  }
              });
              
              
              
              function fritzboxDateEpoch(date) {
                  date = date || "";
                  var year    = "20" + date.substring(6, 8), // Achtung: ab dem Jahr 2100 erzeugt die Funktion ein falsches Datum ;-)
                      month   = parseInt(date.substring(3, 5)) - 1,
                      day     = date.substring(0, 2),
                      hour    = date.substring(9, 11),
                      minute  = date.substring(12, 14),
                      second  = date.substring(15, 17),
                      time    = new Date(year, month, day, hour, minute, second);
                  return Date.parse(time); // fritzbox message date in epoch
              }
              
              function dateEpochNow() {
                  var now = new Date();
                  now = Date.parse(now);  // aktuelle Zeit in epoch
                  return now;
              }
              
              function dateNow() {
                  var date     = new Date(dateEpochNow()),
                      year     = date.getFullYear(),
                      month    = date.getMonth() + 1,
                      day      = date.getDate(),
                      hour     = date.getHours(),
                      minute   = date.getMinutes(),
                      second   = date.getSeconds();
                  if (month.toString().length == 1) {
                      month  = '0' + month;
                  }
                  if (day.toString().length == 1) {
                      day = '0' + day;
                  }
                  if (hour.toString().length == 1) {
                      hour = '0' + hour;
                  }
                  if (minute.toString().length == 1) {
                      minute = '0' + minute;
                  }
                  if (second.toString().length == 1) {
                      second = '0' + second;
                  }
                  return day + "." + month + "." + year + " " + hour + ":" + minute;
              }
              
              
              function fill(n, str) {  // liefere Anzahl n nbsp in utf-8, wenn str nicht angegeben oder n-mal str
                  var space = "";
                  for (var i = 0; i < n; ++i) {
                      space += ((!str) ? " " : str); // &nbsp; als utf-8 Code (Mac: alt+Leerzeichen) TODO: wie kann das nbsp-Leerzeichen in Wbstorm sichtbar gemacht werden
                  }
                  return space;
              }
              
              function dynamicSort(property) {
                  var sortOrder = 1;
                  if (property[0] === "-") {
                      sortOrder = -1;
                      property = property.substr(1);
                  }
                  return function (a,b) {
                      var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
                      return result * sortOrder;
                  }
              }
              
              
              function numberFormat(number,length,align) {
              // bringt die Rufnummer auf die konfigurierte Länge (zu kurz: mit utf-8 Non-breaking-space auffüllen // zu lang: kürzen und als letze "Ziffer" ein x ("x" nur, wenn es sich um Zahlen handelt).
                  var onlyNumbers = /\D+/g;
                  if (!number.match(onlyNumbers)) { // wenn der String numbers nur Ziffern enthält und diese in der Anzahl zu lang sind, kürzen und die letzte Ziffer gegen ein "x" ersetzen
                      if (number.length > length) {
                          number = number.substring(0, length - 1) + "x";
                      }
                  }
                  number = number.substring(0,length); // sollte die externen numbern insgesamt länger sein, werden diese auf die max. gewünschte Länge gekürzt
                  if (align !== "r") {
                      number = number + fill(length - number.length); // richtet linksbündig aus und füllt nbsp; auf
                  } else {
                      number = fill(length - number.length) + number; // wenn allign = "r" wird rechtsbündig ausgerichtet
                  }
                  var i = 0;
                  while (number.search(" ") >= 0) {
                  number = number.replace(" ", " "); // alle normalen Leerzeichen gegen utf-8 non breaking spaces ersetzen
                      if (!i) adapter.log.debug('Leerzeichen gegen utf-8 non breaking space ersetzt: <'+ number + ">");
                      i++;
                      if (i > 40) {
                          adapter.log.warn("function numberFormat: zu langer Sting: " + number);
                          break;
                      }
                  }
                  return number;
              }
              
              
              function e164(extrnr) {
              // wandelt eine externe Rufnummer in das e164 Format, z.B. rufnummer 4711321 wird zu +492114711321 (in Abhängigkeit des konfigurierten CC und AC)
                  extrnr = extrnr.toString().replace(/\D+/g, ""); // nur Ziffern behalten, Rest kürzen
                  if (extrnr === configUnknownNumber) return extrnr;
                  if (extrnr.length > 0) {
                      var i;
                      var extrnr0 = 0;
                      for (i = 0; i < extrnr.length; i++) {
                          if (extrnr.substr(i, i + 1) != "0") {
                              break;
                          }
                          extrnr0++;
                      }
                      extrnr = extrnr.slice(extrnr0, extrnr.length);
              
                      if (extrnr0 == 0) {
                          extrnr = "+" + configCC + configAC + extrnr;
                      }
                      if (extrnr0 == 1) {
                          extrnr = "+" + configCC + extrnr;
                      }
                      if (extrnr0 == 2) {
                          extrnr = "+" + extrnr;
                      }
              
                      if (extrnr0 < 0 && extrnr0 > 2) {
                          adapter.log.error("fritzbox - ungültige externe Rufnummer, Anzahl führender Nullen prüfen");
                      }
                      validateE164(extrnr);
                  }
                  return extrnr;
              }
              
              function telLink(e164,formattedNumber) {
              // erstellt aus einer e164 Rufnummer einen tel: link. Wird als 2. Parameter eine formatierte Rufnummer übergeben, wird diese dargestellt.
                  var link = "";
                  if (!formattedNumber) {
                      link = '<a style="text-decoration: none;" href="tel:' + e164 + '">' + e164 + '</a>';
                  } else {
                      link = '<a style="text-decoration: none;" href="tel:' + e164 + '">' + formattedNumber + '</a>';
                  }
                  return link;
              }
              
              
              function validateE164(e164) {
              // testet auf eine gültige internationale Rufnummer mit + am Anfang
                  var regex = /^\+(?:[0-9] ?){6,14}[0-9]$/;
                  if (regex.test(e164)) {
                      // Valid international phone number
                      return true;
                  } else {
                      // Invalid international phone number
                      adapter.log.warn(e164 + " is not a vail int. phone number. Please check your configuration (Country + Area Code).");
                      return false;
                  }
              }
              
              
              function fritzboxDateToTableDate(fritzboxDate) {
                  var year = fritzboxDate.substring(6,8);
                  var month = fritzboxDate.substring(3,5);
                  var day = fritzboxDate.substring(0,2);
                  var hour = fritzboxDate.substring(9,11);
                  var minute = fritzboxDate.substring(12,14);
                  var second = fritzboxDate.substring(15,17);
                  // var time = hour + ":" + minute + ":" + second + " ";
                  // var time = hour + ":" + minute + "  ";
                  // var date = day  + "." + month  + ".20" + year + " ";
                  // var date = day  + "." + month  + "." + year + " ";
                  var date = day + "." + month + ". " + hour + ":" + minute + "  "; // space = non-breaking-space in utf-8
                  return date;
              }
              
              
              function durationForm(duration) {
              // Dauer in Sekunden formatiert zu einem 7-stelligen String:
              // "      -" = 0 Sek.
              // "      5" = einstellige Sekunde
              // "     27" = zweistellige Sekunden
              // "   1:41" = einstellige Minuten
              // "  59:32" = zweistellige Minuten
              // "8:21:44" = mehr als eine Stunde, weniger als 10h
              // "  >10 h" = mehr als 10h
                  if (duration === "") {
                      duration = fill(7);
                      return duration;
                  }
                  var durationMin = Math.floor(parseInt(duration) / 60 );
                  var durationSek = parseInt(duration) % 60;
                  var durationStd = Math.floor(durationMin  / 60);
                  durationMin %= 60;
                  if (durationStd < 1) {
                      if (durationMin < 1) {
                          duration = durationSek;
                      } else {
                          duration = durationMin + ":" + fill((2- durationSek.toString().length),"0") + durationSek;
                      }
                  } else {
                      duration = durationStd + ":" + fill((2- durationMin.toString().length),"0") + durationMin + ":" + fill((2- durationSek.toString().length),0) + durationSek;
                  }
                  duration = duration.toString();
              
                  if (duration == "0") {
                      duration = "-";
                  }
              
                  if (duration.length > 7) {
                      duration = "> 10h";
                  }
                  duration = fill(7 - duration.toString().length) + duration; // auf 7-Stellen auffüllen
                  return duration;
              }
              
              
              function getEventsList(anruferliste) {
                  var text = '';
                  for (var i = 0; i < anruferliste.length; i++) {
                       text += (text ? '<br>\n' : '') + anruferliste[i];
              //        text += anruferliste[i] + (text ? '<br>\n' : '');
                  }
                  return text;
              }
              
              function makeList(list,line,headline,howManyLines,showHeadline) {
                  var lines = 0;
                  if (showHeadline === true) {
                      list.shift(); // erste Element im Array löschen (Kopfzeile)
                      list.unshift(headline, line); // (Kopfzeile und aktueller Eintrag an Stelle 1 und 2 hinzufügen)
                      lines = 1;
                  } else {
                      list.unshift(line);
                  }
              
                  if (list.length > howManyLines + lines) {
                      list.length = howManyLines + lines;
                  }
                  return getEventsList(list);
              }
              
              
              function callmonitor(list) {
              // Liste aktueller Anrufe (RING, CONNECT und CALL)
                  var txt = '';
                  for (var i = 0; i < list.length; i++) {
                      txt += fritzboxDateToTableDate(call[list[i].id].dateStart) + " " + call[list[i].id].externalNumberForm;
                      if (call[list[i].id].type === "CONNECT") {
                          txt += durationForm(call[list[i].id].durationSecs2);
                      }
                      if (call[list[i].id].type === "RING") {
                          txt += durationForm(call[list[i].id].durationRingSecs);
                      }
                      if (i < (list.length - 1)) {
                          txt += (txt ? '<br>\n' : '');
                      }
                  }
                  return txt;
              }
              
              
              function callmonitorAll(list) {
                  var txt = '';
                  for (var i = 0; i < list.length; i++) {
                      var id = list[i].id;
                      var intNr = fill(4 - call[id].extensionLine.length) + call[id].extensionLine;
                      if (call[id].callSymbol == " -> ") {
                          intNr = '<span style="color:red  ">RING</span>';
                      }
                      txt += fritzboxDateToTableDate(call[id].dateStart) + " " + call[id].externalNumberForm;
                      txt += call[id].callSymbolColor + " ";
                      txt += intNr;
                      if (call[id].type === "CONNECT") {
                          txt += " " + '<span style="color:green">' + "<b>" + durationForm(call[id].durationSecs2) + "</b>" + '</span>';
                      }
                      if (call[id].type === "RING") {
                          txt += " " + '<span style="color:red  ">' + durationForm(call[id].durationRingSecs) + '</span>';
                      }
                      if (i < (list.length - 1)) {
                          txt += (txt ? '<br>\n' : '');
                      }
                  }
                  return txt;
              }
              
              function headlineMissedHTML() {
                  var headlineHistoryMissedHTML =
                      "<b>" + headlineDate +
                      headlineExtnumber + nbsp +
                      headlineOwnnumber + "</b>";
                  return headlineHistoryMissedHTML;
              }
              
              
              function headlineAllTxt() {
                  // Überschrift formatieren
                  headlineDate = numberFormat(headlineDate, 14);
                  headlineExtnumber = numberFormat(headlineExtnumber, configNumberLength);
                  headlineDirection = numberFormat(headlineDirection, 4);
                  headlineExtension = numberFormat(headlineExtension, 5, "r");   // 5 Zeichen lang, rechtsbündig
                  headlineOwnnumber = numberFormat(headlineOwnnumber, configNumberLength);
                  headlineLine = numberFormat(headlineLine, 4);
                  headlineDuration = numberFormat(headlineDuration, 7, "r");     // 7 Zeichen lang, rechtsbündig
              // Überschrift Anruferliste gesamt (txt)
                  var headlineHistoryAllTxt =
                      headlineDate +
                      headlineExtnumber + nbsp +
                      headlineDirection +
                      headlineExtension + nbsp +
                      headlineOwnnumber + nbsp +
                      headlineLine + nbsp +
                      headlineDuration;
                  return headlineHistoryAllTxt;
              }
              
              function headlineAllHTML() {
              // Überschrift Anruferliste gesamt (html)
                  var headlineHistoryAllHTML = "<b>" + headlineAllTxt() + "</b>";
              
                  return headlineHistoryAllHTML;
              }
              
              
              function clearRealtimeVars() {
                   // da die Daten nach Start des Adapters oder neuer IP-Verbindung zur Fritzbox nicht konsitent sind, werden die Callmonitore gelöscht
              
                  // Callmonitore löschen
                  if (showCallmonitor) {
                      adapter.setState('callmonitor.ring', "", true);
                      adapter.setState('callmonitor.call', "", true);
                      adapter.setState('callmonitor.connect', "", true);
                      adapter.setState('callmonitor.all', "", true);
                  }
                  //Realtime Daten löschen
                  adapter.setState('calls.ring',                               false ,    true);
                  adapter.setState('calls.ringActualNumber',                   "" ,       true);
                  adapter.setState('calls.ringActualNumbers',                  "" ,       true);
                  adapter.setState('calls.connectNumber',                      "",        true);
                  adapter.setState('calls.connectNumbers',                     "" ,       true);
                  adapter.setState('calls.counterActualCalls.ringCount',       0 ,        true);
                  adapter.setState('calls.counterActualCalls.callCount',       0 ,        true);
                  adapter.setState('calls.counterActualCalls.connectCount',    0 ,        true);
                  adapter.setState('calls.counterActualCalls.allActiveCount',  0 ,        true);
              
                  // Liste der aktiven RINGs, CALLs, CONNECTs
                  listRing            = [];
                  listCall            = [];
                  listConnect         = [];
                  listAll             = [];
              
              }
              
              
              
              
              
              
              function adapterSetOnChange (object, value) {
                  // ioBroker Objekte schreiben aber nur, wenn der Wert geändert wurde
                  adapter.getState(object, function (err, state) {
                      if (!err && state) {
                          if (state.val !== value || !state.ack) {
                              adapter.setState(object, value , true);
                          }
                      }
                  });
              }
              
              
              function adapterSetOnUndefined (object, value) {
                  adapter.getState(object, function (err, state) {
                      if (!err && state) {
                          var newValue = state.val || value;
                          if (state.val == newValue) return; // vorhandene Werte nicht noch einmal schreiben
                          adapter.setState(object, newValue, true);
              //            adapter.log.info("Objekt: " + object + " Wert: " + state.val + " -> " + newValue + "  INIT: " +value);
                      } else {
                          adapter.setState(object, value, true);
                      }
                  });
              }
              
              
              
              function initVars() {
              // alte Zustände übernehmen oder bei Erstinitalisierung sinnvolle Werte
                  adapterSetOnUndefined("calls.missedCount", 0);
                  adapterSetOnUndefined("calls.missedDateReset", dateNow());
              
                  adapterSetOnUndefined("calls.ringLastNumber", "");
                  adapterSetOnUndefined("calls.ringLastMissedNumber", "");
                  adapterSetOnUndefined("calls.callLastNumber", "");
              
                  adapterSetOnUndefined("telLinks.ringLastNumberTel", "");
                  adapterSetOnUndefined("telLinks.ringLastMissedNumberTel", "");
                  adapterSetOnUndefined("telLinks.callLastNumberTel", "");
              
                  adapterSetOnUndefined("system.deltaTime", 0);
                  adapterSetOnUndefined("system.deltaTimeOK", true);
              
                  // config aus der Adapter Admin-Webseite übernehmen
                  configCC                    = adapter.config.cc;    // "49",            // eigene Landesvorwahl ohne führende 0
                  configAC                    = adapter.config.ac;    // "211",           // eigene Ortsvorwahl ohne führende 0
                  configUnknownNumber         = numberFormat(adapter.config.unknownNumber, adapter.config.unknownNumber.length); // Leerzeichen gegen utf-8 nbsp ersetzen
                  configNumberLength          = adapter.config.numberLength;
                  if (configNumberLength < 4) {
                      adapter.log.warn("Rufnummernlänge zu klein gewählt, geändert von " + configNumberLength + " auf 4");
                      configNumberLength = 4;
                  }
                  if (configNumberLength > 30) {
                      adapter.log.warn("Rufnummernlänge zu groß gewählt, geändert von " + configNumberLength + " auf 30");
                      configNumberLength = 30;
                  }
              
                  configExternalLink          = adapter.config.externalLink;
                  configShowHeadline          = adapter.config.showHeadline;
              
                  showHistoryAllTableTxt      = adapter.config.showHistoryAllTableTxt;
                  showHistoryAllTableHTML     = adapter.config.showHistoryAllTableHTML;
                  showHistoryAllTableJSON     = adapter.config.showHistoryAllTableJSON;
                  showCallmonitor             = adapter.config.showCallmonitor;
                  showMissedTableHTML         = adapter.config.showMissedTableHTML;
                  showMissedTableJSON         = adapter.config.showMissedTableJSON;
              
                  // vorhandene Werte aus den ioBroker Objekte des Fritzbox Adapters rauslesen und in die globalen Variablen übernehmen
                  adapter.getState('calls.missedCount', function (err, state) {
                      if (!err && state) {
                          objMissedCount = (state.val);
                          missedCount = (state.val);
                      }
                  });
              
              
                  if (adapter.config.showHeadline) {
                      headlineTableAllTxt = headlineAllTxt();
                      headlineTableAllHTML = headlineAllHTML();
                      headlineTableMissedHTML = headlineMissedHTML();
                  } else {
                      headlineTableAllTxt = "";
                      headlineTableAllHTML = "";
                      headlineTableMissedHTML = "";
                  }
              
              
                  if (!showHistoryAllTableTxt) {
                      adapter.setState('history.allTableTxt',    "deactivated",          true);
                  } else {
                      adapter.setState('history.allTableTxt',    headlineTableAllTxt,    true);
                  }
                  if (!showHistoryAllTableJSON) {
                      adapter.setState('history.allTableJSON',   "deactivated",          true);
                  }
                  if (!showHistoryAllTableHTML) {
                      adapter.setState('history.allTableHTML',   "deactivated",          true);
                  } else {
                      adapter.setState('history.allTableHTML',   headlineTableAllHTML,   true);
                  }
                  if (!showMissedTableHTML) {
                      adapter.setState('history.missedTableHTML', "deactivated",         true);
                  } else {
                      adapter.setState('history.missedTableHTML', headlineTableMissedHTML,   true);
                  }
                  if (!showMissedTableJSON)     adapter.setState('history.missedTableJSON',   "deactivated", true);
                  if (!showCallmonitor) {
                      adapter.setState('callmonitor.connect', "deactivated", true);
                      adapter.setState('callmonitor.ring', "deactivated", true);
                      adapter.setState('callmonitor.call', "deactivated", true);
                      adapter.setState('callmonitor.all', "deactivated", true);
                  } else {
                      adapter.setState('callmonitor.connect', "", true);
                      adapter.setState('callmonitor.ring', "", true);
                      adapter.setState('callmonitor.call', "", true);
                      adapter.setState('callmonitor.all', "", true);
                  }
              
                  adapter.setState('wlan.enabled', false, true);
              }
              
              
              
              
              
              function parseData(message) {
                  adapter.log.info("data from " + adapter.config.fritzboxAddress + ": " + message);
                  message = message.toString('utf8');
                  adapter.setState('message', message, true);
              
                  var obj = message.split(";"); 	// speichert die Felder der Meldung in ein Array mit dem Namen "obj"
                  var id = obj[2];
                  call[id] = call[id] || {};
              
                  var ringCount           = 0,
                      callCount           = 0,
                      connectCount        = 0,
                      allActiveCount      = 0,
                      ring                = null,
                      ringActualNumber    = "";
              
                  var ringActualNumbers   = [],
                      connectNumbers      = [],
                      connectNumber       = "";
              
                  var cssGreen = '<span style="color:green">',
                      cssRed   = '<span style="color:red  ">',
                      cssBlack = '<span style="color:black">',
                      cssColor = cssGreen,
                      cssEnd   = "</span>";
              
              
              
                  // ###########  Auswertung und Aufbau der Anruferinformationen aus der Fritzbox ###########
              
                  // für alle Anruftypen identisch
                  call[id].date            = obj[0];    // 01.07.15 12:34:56 - dd.mm.yy hh:mm:ss
                  call[id].dateEpoch       = fritzboxDateEpoch(obj[0]); // fritzbox time
                  call[id].dateEpochNow    = dateEpochNow();              // system time
                  call[id].deltaTime       = (call[id].dateEpochNow  - call[id].dateEpoch) / 1000; // delta Time beetween system and fritzbox
                  call[id].deltaTimeOK     = (call[id].deltaTime < configDeltaTimeOKSec);
                  call[id].type            = obj[1];    // CALL (outgoing), RING (incoming), CONNECT (start), DISCONNECT (end)
                  call[id].id              = obj[2];    // call identifier as integer
              
              
                  // Outgoing call
                  if (call[id].type == "CALL") {
                      call[id].extensionLine      = obj[3];    // used extension line
                      call[id].ownNumber          = obj[4];    // calling number - used own number
                      call[id].externalNumber     = obj[5];    // called number
                      call[id].lineType           = obj[6];    // line type
                      call[id].durationSecs       = null;      // duration of the connect in sec
                      call[id].durationForm       = null;      // duration of the connect in sec
                      call[id].durationSecs2      = "";        // duration of the connect in sec, count during connect
                      call[id].durationRingSecs   = "";        // duration ringing time in sec
                      call[id].connect            = false;
                      call[id].direction          = "out";
                      call[id].dateStartEpoch     = fritzboxDateEpoch(obj[0]);
                      call[id].dateConnEpoch      = null;
                      call[id].dateEndEpoch       = null;
                      call[id].dateStart          = obj[0];
                      call[id].dateConn           = null;
                      call[id].dateEnd            = null;
                      call[id].callSymbol         = " <- "; // utf-8 nbsp
                      call[id].callSymbolColor    = cssBlack + call[id].callSymbol + cssEnd;
                  }
                  else // Incoming (RING)
                  if (call[id].type == "RING") {
                      call[id].extensionLine      = "";        // used extension line
                      call[id].ownNumber          = obj[4];    // called number - used own number
                      call[id].externalNumber     = obj[3];    // calling number
                      call[id].lineType           = obj[5];    // line type
                      call[id].durationSecs       = null;      // duration of the connect in sec
                      call[id].durationForm       = null;
                      call[id].durationSecs2      = "";        // duration of the connect in sec, count during connect
                      call[id].durationRingSecs   = 0;         // duration of ringing time in sec
                      call[id].connect            = false;
                      call[id].direction          = "in";
                      call[id].dateStartEpoch     = fritzboxDateEpoch(obj[0]);
                      call[id].dateConnEpoch      = null;
                      call[id].dateEndEpoch       = null;
                      call[id].dateStart          = obj[0];
                      call[id].dateConn           = null;
                      call[id].dateEnd            = null;
                      call[id].callSymbol         = " -> "; // utf-8 nbsp
                      call[id].callSymbolColor    = cssBlack + call[id].callSymbol + cssEnd;
                  }
                  else // Start of connect
                  if (call[id].type == "CONNECT") {
                      call[id].extensionLine      = obj[3];    // used extension line
                      call[id].ownNumber          = call[id].ownNumber       || "????????";    // used own number
                      call[id].externalNumber     = obj[4];    // connected number
                      call[id].lineType           = call[id].lineType        || "????";        // line type
                      call[id].durationSecs       = null;      // duration of the connect in sec
                      call[id].durationForm       = null;
                      call[id].durationSecs2      = 0;         // duration of the connect in sec, count during connect
                      call[id].durationRingSecs   = call[id].durationRingSecs || "";         // duration of ringing time in sec
                      call[id].connect            = true;
                      call[id].direction          = call[id].direction       || "?";
                      call[id].dateStartEpoch     = call[id].dateStartEpoch  || null;
                      call[id].dateConnEpoch      = fritzboxDateEpoch(obj[0]);
                      call[id].dateEndEpoch       = null;
                      call[id].dateStart          = call[id].dateStart || obj[0];
                      call[id].dateConn           = obj[0];
                      call[id].dateEnd            = null;
                      if (call[id].direction == "in") {
                          call[id].callSymbol     = " ->>";
                      } else {
                          call[id].callSymbol     = "<<- "; // utf-8 nbsp
                      }
                      call[id].callSymbolColor    = cssGreen + "<b>" + call[id].callSymbol + "</b>" + cssEnd;
                  }
                  else // End of call
                  if (call[id].type == "DISCONNECT") {
                      call[id].extensionLine      = call[id].extensionLine   || "";          // used extension line
                      call[id].ownNumber          = call[id].ownNumber       || "????????";    // used own number
                      call[id].externalNumber     = call[id].externalNumber  || "????????";    // connected number
                      call[id].lineType           = call[id].lineType        || "????";        // line type
                      call[id].durationSecs       = obj[3];                                    // call duration in seconds
                      call[id].durationForm       = durationForm(obj[3]);
                      call[id].durationSecs2      = call[id].durationSecs;      // duration of the connect in sec
                      call[id].durationRingSecs   = call[id].durationRingSecs || "";         // duration of ringing time in sec
                      //call[id].connect            = call[id].connect         || null;
                      call[id].direction          = call[id].direction       || "?";
                      call[id].dateStartEpoch     = call[id].dateStartEpoch  || fritzboxDateEpoch(obj[0]);
                      call[id].dateConnEpoch      = call[id].dateConnEpoch   || fritzboxDateEpoch(obj[0]);
                      call[id].dateEndEpoch       = fritzboxDateEpoch(obj[0]);
                      call[id].dateStart          = call[id].dateStart       || obj[0];
                      call[id].dateConn           = call[id].dateConn        || obj[0];
                      call[id].dateEnd            = obj[0];
                      if (call[id].connect === false) {
                          cssColor = cssRed;
                          if (call[id].direction == "in") {
                              call[id].callSymbol = " ->X";
                          } else {
                              call[id].callSymbol = "X<- "; // utf-8 nbsp
                          }
                      }
                      call[id].callSymbol = call[id].callSymbol || "????";
                      if (call[id].callSymbol === "????") cssColor = cssBlack;
                      call[id].callSymbolColor    = cssColor + "<b>" + call[id].callSymbol + "</b>" + cssEnd;
                  }
                  else {
                      adapter.log.error ("adapter fritzBox unknown event type " + call[id].type);
                      return;
                  }
              
              
                  // für alle Anruftypen identisch
                  call[id].unknownNumber = false;
                  if (call[id].externalNumber === "" || call[id].externalNumber === null || call[id].externalNumber === configUnknownNumber || call[id].externalNumber === "????????") {
                      if (call[id].externalNumber !== "????????") call[id].externalNumber         = configUnknownNumber;
                      call[id].unknownNumber          = true;
                  }
                  call[id].ownNumberForm              = numberFormat(call[id].ownNumber,configNumberLength);
                  call[id].externalNumberForm         = numberFormat(call[id].externalNumber,configNumberLength);
                  call[id].ownNumberE164              = e164(call[id].ownNumber);
                  call[id].externalE164               = e164(call[id].externalNumber);
              
              
                  if (call[id].unknownNumber) {
                      call[id].externalTelLink        = call[id].externalNumberForm;
                      call[id].externalTelLinkCenter  = call[id].externalNumber;
                  } else {
                      call[id].externalTelLink        = telLink(call[id].externalE164,call[id].externalNumberForm);
                      call[id].externalTelLinkCenter  = telLink(call[id].externalE164,call[id].externalNumber);
                  }
              
              /*
                  adapter.log.debug("fritzBox event (date: " + call[id].date +
                      ", dateEpoch: "     + call[id].dateEpoch +
                      ", type: "          + call[id].type +
                      ", ID: "            + call[id].id +
                      ", exLine: "        + call[id].extensionLine +
                      ", extern: "        + call[id].externalNumber +
                      ", own: "           + call[id].ownNumber +
                      ", duration: "      + call[id].durationSecs
                 );
              
                  adapter.log.debug("fritzBox event (delta time: " + call[id].deltaTime +
                      ", delta time ok: "    + call[id].deltaTimeOK +
                      ", symbol: "           + call[id].callSymbol +
                      ", ext E164: "         + call[id].externalE164 +
                      ", ext Tel Link: "     + call[id].externalTelLink
                  );
              */
              
              
                  // Listen aktuelle Gesprächszustände nach jeder Fritzbox Message, neuaufbauen und damit aktualisieren)
                  listRing            = [];
                  listCall            = [];
                  listConnect         = [];
                  listAll             = [];
              
                  // Schleife, Anzahl der Call IDs im Array: Zählt die jeweiligen Zustände, ein Array je Zustand
                  for (var i = 0; i < call.length; i++){
                      if (call[i] != null) {
                          // Call Status = RING
                          if (call[i].type === "RING"){
                              listRing[ringCount]                         = listRing[ringCount] || {};
                              listRing[ringCount].id                      = call[i].id;
                              listRing[ringCount].dateStartEpoch          = call[i].dateStartEpoch;
                              ringCount++;
                           }
              
                          // Call Status = CALL
                          if (call[i].type === "CALL"){
                              listCall[callCount]                         = listCall[callCount] || {};
                              listCall[callCount].id                      = call[i].id;
                              listCall[callCount].dateStartEpoch          = call[i].dateStartEpoch;
                              callCount++;
                          }
              
                          // Call Status = CONNECT
                          if (call[i].type === "CONNECT"){
                              listConnect[connectCount]                   = listConnect[connectCount] || {};
                              listConnect[connectCount].id                = call[i].id;
                              listConnect[connectCount].dateStartEpoch    = call[i].dateStartEpoch;
                              connectCount++;
                          }
              
                          // Alle Status (RING, CALL, CONNECT)
                          if (call[i].type !== "DISCONNECT"){
                              listAll[allActiveCount]                     = listAll[allActiveCount] || {};
                              listAll[allActiveCount].id                  = call[i].id;
                              listAll[allActiveCount].dateStartEpoch      = call[i].dateStartEpoch;
                              allActiveCount++;
                          }
                      }
                  }
              
              /*    adapter.log.debug("ringCount: " + ringCount +
                      ", callCount: "             + callCount+
                      ", connectCount: "          + connectCount +
                      ", allActiveCount: "        + allActiveCount
                  );
              */
              
              // sortiert die ermittelten Listen nach der tatsächlichen Meldungszeit (Systemzeit)
                  listRing.sort(dynamicSort("-dateStartEpoch"));      // Liste sortieren: jüngster Eintrag oben
                  listCall.sort(dynamicSort("-dateStartEpoch"));      // Liste sortieren: jüngster Eintrag oben
                  listConnect.sort(dynamicSort("-dateStartEpoch"));   // Liste sortieren: jüngster Eintrag oben
                  listAll.sort(dynamicSort("-dateStartEpoch"));       // Liste sortieren: jüngster Eintrag oben
              
              
              // aktuellste Anrufernummer wird als aktueller und neuester Anrufer (Ring) gespeichert (es kann noch mehr aktive RINGs geben, diese werden in "ringActualNumbers" gespeichert)
                  if (listRing[0] != null) {
                      ringActualNumber = call[listRing[0].id].externalNumber;
                      ring = true;
                  }
              
              // kein aktiver Ring, ringAktuell löschen  && ring = false (kein Ring aktiv)
                  if (ringCount < 1) {
                      ringActualNumber = "";
                      ring = false;
                  }
              
                  if (listConnect[0] != null) {
                      connectNumber = call[listConnect[0].id].externalNumber;
                  }
                  objMissedCount = missedCount; // den alten errechneten Wert der verpassten Anrufe sichern
                  // aktuellen Wert Anzahl verpasste Anrufe (missedCount aus den ioBroker Objekten auslesen)
                  adapter.getState('calls.missedCount', function (err, state) {
                      if (!err && state) {
                          missedCount = (state.val);
                      }
                  });
              
              
              
                  if (call[id].type == "DISCONNECT") {
                      if (call[id].direction == "in") {
                          ringLastNumber    = call[id].externalNumber;        // letzter Anrufer
                          adapter.setState('calls.ringLastNumber',                     ringLastNumber ,            true);
                          ringLastNumberTel = call[id].externalTelLinkCenter; // letzter Anrufer als wählbarer Link
                          adapter.setState('calls.telLinks.ringLastNumberTel',         ringLastNumberTel,          true);
              
                          if (!call[id].connect) { // letzter Anrufer, der verpasst wurde
                              ringLastMissedNumber = call[id].externalNumber;
                              adapter.setState('calls.ringLastMissedNumber',               ringLastMissedNumber ,      true);
                              ringLastMissedNumberTel = call[id].externalTelLinkCenter;
                              adapter.setState('calls.telLinks.ringLastMissedNumberTel',   ringLastMissedNumberTel ,   true);
                          }
                          // letzter verpasste Anruf wird hochgezählt, Zähler verpasste Anrufe (bis max. 999)
                          // ioBroker Datenpunkt kann beschrieben und über ioBrkoer zurückgesetzt werden
                          // das Datum, zu dem der Zähler auf 0 gesett wird, wird in calls.missedDateReset gespeichert.
                          if (!call[id].connect) {
                              ++missedCount;
                              if (missedCount > 999) missedCount = 999;
                          }
                      } else
                      if (call[id].direction == "out") {
                          callLastNumber = call[id].externalNumber;
                          adapter.setState('calls.callLastNumber',                     callLastNumber ,            true);
                          callLastNumberTel = call[id].externalTelLinkCenter;
                          adapter.setState('calls.telLinks.callLastNumberTel',         callLastNumberTel,          true);
                      } else {
                          adapter.log.warn ("Adapter starts during call. Some values are unknown.");
                      }
                  } // END DISCONNECT
              
              
                  // aktuelle Rufnummeren der gerade laufenden Anrufe (RING)
                  for (var i = 0; i < listRing.length; i++){
                      ringActualNumbers.push(call[listRing[0].id].externalNumber);
                  }
              
                  // aktuelle Rufnummern der gerade laufenden Gespräche (CONNECT)
                  for (var i = 0; i < listConnect.length; i++){
                      connectNumbers.push(call[listConnect[i].id].externalNumber);
                  }
              
              
                  // Daten, die bei jeder Meldung aktualisiert werden
                  adapter.setState('system.deltaTime',                         call[id].deltaTime ,        true);
                  adapter.setState('system.deltaTimeOK',                       call[id].deltaTimeOK ,      true);
                  if (!call[id].deltaTimeOK) adapter.log.warn("delta time between system and fritzbox: " + call[id].deltaTime + " sec");
              
                  //Auf Änderungen im ioBroker Objekt reagieren und die lokale Variable auch ändern.
                  if (objMissedCount !== missedCount) { // Zähler nur schreiben, wenn er sich verändert hat (wg. Traffic Überwachung des Objekts auf Änderung)
                      adapter.setState('calls.missedCount',                        missedCount ,               true);
                  }
                  // adapter.log.debug ("objMissedCount: " + objMissedCount + "   missedCount: " + missedCount);
              
                  //Realtime Daten
                  adapterSetOnChange ("calls.ring" , ring);
                  adapterSetOnChange ("calls.ringActualNumber" , ringActualNumber);
                  adapterSetOnChange ("calls.ringActualNumbers" , ringActualNumbers.join());
                  adapterSetOnChange ("calls.connectNumber" , connectNumber);
                  adapterSetOnChange ("calls.connectNumbers" , connectNumbers.join());
                  adapterSetOnChange ("calls.counterActualCalls.ringCount" , ringCount);
                  adapterSetOnChange ("calls.counterActualCalls.callCount" , callCount);
                  adapterSetOnChange ("calls.counterActualCalls.connectCount" , connectCount);
                  adapterSetOnChange ("calls.counterActualCalls.allActiveCount" , allActiveCount);
              
                  // History / Anruferlisten
                  if (call[id].type == "DISCONNECT") {
              
                      // Datensatz formatieren
                      var extensionLine   = numberFormat(call[id].extensionLine, 5, "r");
                      var line            = numberFormat(call[id].lineType, 4);
                      // die externe Rufnummer, je nach Konfiguration als Link (tel:) oder Text
                      var externalNumber  = call[id].externalTelLink;
                      if (!configExternalLink) {
                          externalNumber  = call[id].externalNumberForm;
                      }
              
                      // Einzelne Datenzeile Anruferliste gesamt (txt)
                      var lineHistoryAllTxt =
                          fritzboxDateToTableDate(call[id].date) +
                          call[id].externalNumberForm + nbsp +
                          call[id].callSymbol +
                          extensionLine + nbsp +
                          call[id].ownNumberForm + nbsp +
                          call[id].lineType + nbsp +
                          call[id].durationForm;
              
                      // Einzelne Datenzeile Anruferliste gesamt (html)
                      var lineHistoryAllHtml =
                          fritzboxDateToTableDate(call[id].date) +
              //            call[id].externalTelLink + nbsp +
                          externalNumber + nbsp +
                          call[id].callSymbolColor +
                          extensionLine + nbsp +
                          call[id].ownNumberForm + nbsp +
                          call[id].lineType + nbsp +
                          call[id].durationForm;
              
                      // Datensatz html verpasste Anrufe erstellen && html & json schreiben
                      if (!call[id].connect) {
                          if (call[id].direction === "in") {
                              // Datensatz verpasste Anrufe
                              var lineHistoryMissedHtml =
                                  fritzboxDateToTableDate(call[id].date) +
                                  externalNumber + nbsp + call[id].ownNumberForm + nbsp;
                              // aktuelle Call-ID als JSON für verpasste Anrufe wegschreiben
                              adapter.setState('cdr.missedJSON',              JSON.stringify(call[id]),   true);
                              adapter.setState('cdr.missedHTML',              lineHistoryMissedHtml,      true);
                          }
                      }
              
                      if (showMissedTableHTML) {
                          // Anruferliste verpasste Anrufe erstellen
                          if (!call[id].connect) {
                              if (call[id].direction === "in") {
                                  var historyListMissedHtmlStr = makeList(historyListMissedHtml, lineHistoryMissedHtml, headlineTableMissedHTML, configHistoryMissedLines, configShowHeadline);
                                  adapter.setState('history.missedTableHTML', historyListMissedHtmlStr, true);
                              }
                          }
                      }
              
              
                      if (showHistoryAllTableHTML) {
                          // Tabelle html erstellen
                          var historyListAllHtmlStr = makeList(historyListAllHtml,lineHistoryAllHtml,headlineTableAllHTML,configHistoryAllLines, configShowHeadline);
                          adapter.setState('history.allTableHTML',        historyListAllHtmlStr,              true);
                      }
              
                      if (showHistoryAllTableTxt) {
                          // Tabelle txt erstellen
                          var historyListAllTxtStr = makeList(historyListAllTxt, lineHistoryAllTxt, headlineTableAllTxt, configHistoryAllLines, configShowHeadline);
                          adapter.setState('history.allTableTxt', historyListAllTxtStr, true);
                      }
                      if (showHistoryAllTableJSON) {
                          var lineHistoryAllJson = {
                              "date" :            call[id].date,
                              "externalNumber" :  call[id].externalNumber,
                              "callSymbolColor" : call[id].callSymbolColor,
                              "extensionLine" :   call[id].extensionLine,
                              "ownNumber" :       call[id].ownNumber,
                              "lineType" :        call[id].lineType,
                              "durationForm" :    call[id].durationForm
                          };
                          // Anruferliste als JSON auf max Anzahl configHistoryAllLine beschränken
              //        historyListAllJson.unshift(call[id]);
                          historyListAllJson.unshift(lineHistoryAllJson);
                          if (historyListAllJson.length   > configHistoryAllLines) {
                              historyListAllJson.length   = configHistoryAllLines;
                          }
                          adapter.setState('history.allTableJSON',    JSON.stringify(historyListAllJson), true);
                      }
              
              //        adapter.log.debug("historyListAllJson Obj " +   JSON.stringify(historyListAllJson[0]) );  // erste Zeile der JSON ANruferliste im Log
              //        adapter.log.debug("history all JSON Items: " +  historyListAllJson.length );              // Anzahl der JSON Elemente in der Anruferliste
              
                      // aktuellen Datensatz des letzten Anrufs als JSON speicherern (für History und die weiteren Verarbeitung in Javascript, usw.)
                      adapter.setState('cdr.json',                    JSON.stringify(call[id]),   true);
                      adapter.setState('cdr.html',                    lineHistoryAllHtml,         true);
                      adapter.setState('cdr.txt',                     lineHistoryAllTxt,          true);
              
                      // try to get phonebook
                      if (call[id].connect && call[id].direction === "in" && adapter.config.enableTAM) {
                          getTAM(adapter.config.fritzboxAddress, adapter.config.fritzboxUser, adapter.config.fritzboxPassword);
                      }
              
                  } // End DSICONNECT
              
              
                  if (showCallmonitor) {
                      // alle drei Callmonitorausgaben mit Sekundenintervall werden auf einmal aktualisiert, auch, wenn ein Anrufzustand keinen aktiven Zähler hat
                      // wird aus Performancegründen eine Optimierung benötigt, muss es für CONNECT und RING eigenen Intervalle geben
                      // (weniger Traffic und Speicherplatz) # Hinweis: der Callmonitor kann auch in der Config (webadmin) deaktiviert werden
                      if (allActiveCount && !intervalRunningCall) {
                          // Do it every second
                          intervalRunningCall = setInterval(function () {
              
                              // adapter.log.debug("###### callmonitor aktiv ######");
              
                              for (var i = 0; i < listConnect.length; i++) {
                                  call[listConnect[i].id].durationSecs2 = dateEpochNow() / 1000 - call[listConnect[i].id].dateEpochNow / 1000;
                              }
                              for (var i = 0; i < listRing.length; i++) {
                                  call[listRing[i].id].durationRingSecs = dateEpochNow() / 1000 - call[listRing[i].id].dateEpochNow / 1000;
                              }
                              adapterSetOnChange ("callmonitor.connect" , callmonitor(listConnect));
                              adapterSetOnChange ("callmonitor.ring" , callmonitor(listRing));
                              adapterSetOnChange ("callmonitor.all" , callmonitorAll(listAll));
              //                adapter.setState('callmonitor.connect', callmonitor(listConnect), true);
              //                adapter.setState('callmonitor.ring', callmonitor(listRing), true);
              //                adapter.setState('callmonitor.all', callmonitorAll(listAll), true);
              
                          }, 1000);
                      } else if (!allActiveCount && intervalRunningCall) {
                          // Stop interval
                          clearInterval(intervalRunningCall);
                          intervalRunningCall = null;
                      }
              
                      adapterSetOnChange ("callmonitor.call" , callmonitor(listCall));
              //        adapter.setState('callmonitor.call', callmonitor(listCall), true);
              
                      // wenn der Interval beendet wird, müssen die letzet Anrufe im Callmonitor auch bereinigt werden
                      // die Callmonitorlisten werden zusätzlich zum Intervall mit jeder Fritzbox-Meldung aktualisiert
                      // nur im else-Zweig im Intervall ist dies nicht ausreichend, wg. der Asynchonität
                      adapterSetOnChange ("callmonitor.connect" , callmonitor(listConnect));
                      adapterSetOnChange ("callmonitor.ring" , callmonitor(listRing));
                      adapterSetOnChange ("callmonitor.all" , callmonitorAll(listAll));
                  }
              
              
              
              }
              
              
              
              function handleWLANConfiguration(config) {
                  //console.log(config);
                  adapter.log.debug("WLAN: " + config['NewEnable']);
                  wlanState = config['NewEnable'] == 1;
                  adapterSetOnChange("wlan.enabled", wlanState);
              }
              
              
              
              function connectToTR064(host, user, password, callback) {
                  var tr064 = new tr.TR064();
                  tr064.initTR064Device(host, 49000, function (err, device) {
                      if (!err) {
                          device.startEncryptedCommunication(function (err, sslDev) {
                              if (!err) {
                                  sslDev.login(user, password);
                                  callback(sslDev);
                              }
                              else {
                                  adapter.log.warn("TR-064 error: " + err);
                              }
                          });
                      }
                      else {
                          adapter.log.warn("TR-064 error: " + err);
                      }
                  });
              }
              
              
              
              function getWlanConfig(host, user, password, callback) {
                  connectToTR064(host, user, password, function (sslDev) {
                      var wlanConfig = sslDev.services["urn:dslforum-org:service:WLANConfiguration:1"];
                      adapter.log.debug("TR-064: calling GetInfo()");
                      wlanConfig.actions.GetInfo(function (err, result) {
                          if (!err) {
                              adapter.log.debug("TR-064: got result from GetInfo()");
                              callback(result);
                          }
                          else {
                              adapter.log.warn("TR-064 error: " + err);
                          }
                      });
                  });
              }
              
              
              
              function setWlanEnabled(host, user, password, enabled) {
                  connectToTR064(host, user, password, function (sslDev) {
                      var wlanConfig = sslDev.services["urn:dslforum-org:service:WLANConfiguration:1"];
                      adapter.log.debug("TR-064: calling SetEnable(" + enabled + ")");
                      wlanConfig.actions.SetEnable({ 'NewEnable': enabled ? 1 : 0 }, function (err, result) {
                          if (!err) {
                              adapter.log.debug("TR-064: got result from SetEnable()");
                          }
                          else {
                              adapter.log.warn("TR-064 error: " + err);
                          }
                      });
                  });
              }
              
              function getTAM(host, user, password) {
                  connectToTR064(host, user, password, function (sslDev) {
                      var tam = sslDev.services["urn:dslforum-org:service:X_AVM-DE_TAM:1"];
                      adapter.log.debug("TR-064: Calling GetMessageList()");
                      tam.actions.GetMessageList({NewIndex: 0}, function(err, ret) {
                          if (err) {
                              adapter.log.warn("TR-064: Error while calling GetMessageList(): " + err);
                          } else if (ret.NewURL && ret.NewURL.length > 0) {
                              var url = ret.NewURL;
                              adapter.log.debug("TR-064: Got TAM uri: " + url);
                              var baseUrl = url.substring(0, url.lastIndexOf('/'));
                              var sid = url.match(/sid=([\d\w]+)/)[1];
                              adapter.log.debug(`TR-064: sid=${sid}`);
              
                              var agentOptions;
              				var agent;
              
              				agentOptions = {
              				  rejectUnauthorized: false
              				};
              
              				agent = new https.Agent(agentOptions);
              
                              request({
              				  url: url
              				, method: 'GET'
              				, agent: agent
              				}, function (error, response, body) {
                                  if (!error && response.statusCode == 200) {
                                      adapter.log.debug("TR-064: Got valid TAM content from, starting to parse ...");
                                      var parser = new xml2js.Parser();
                                      parser.parseString(body, function (err, result) {
                                          if (err) {
                                              adapter.log.warn("TR-064: Error while parsing TAM content: " + err);
                                          } else {
                                              adapter.log.debug("TR-064: Successfully parsed TAM content, analyzing result ...");
                                              var promises = [];
                                              var messages = [];
              
                                              mkdirSync('tam', { recursive: true });
              
                                              if(!result.Root.Message) {
                                                  // No messahes
                                                  promises.push(new Promise((resolve, reject) => resolve()));
                                              } else {
                                                  for (var m = 0; m <= result.Root.Message.length; m++) {
                                                      var message = result.Root.Message[m];
                                                      if (typeof message != 'undefined') {
                                                          promises.push(new Promise((resolve, reject) => {
                                                              var msg = {
                                                                  index: message.Index[0],
                                                                  calledNumber: message.Called[0],
                                                                  date: message.Date[0],
                                                                  duration: message.Duration[0],
                                                                  callerName: message.Name[0],
                                                                  callerNumber: message.Number[0],
                                                                  audioFile: ''
                                                              };
                                                              if (!message.Path || message.Path.length < 1) {
                                                                  adapter.log.warn("TR-064: TAM message has no url");
                                                                  resolve(msg);
                                                                  return;
                                                              }
              
                                                              var callDate = message.Date[0].split('.').join("").split(':').join("").split(' ').join("");
                                                              var file = `tam/${callDate}-${message.Number[0]}.wav`
                                                              adapter.log.debug(`TR-064: TAM message file: ${file}`);
                                                              if (existsSync(file)) {
                                                                  msg.audioFile = path.resolve(file);
                                                                  resolve(msg);
                                                                  return;
                                                              }
              
                                                              var downloadUrl = message.Path[0];
                                                              if (downloadUrl.startsWith('/')) {
                                                                  downloadUrl = baseUrl + downloadUrl;
                                                              }
                                                              if (downloadUrl.indexOf('sid=')<0){
                                                                  downloadUrl += `&sid=${sid}`;
                                                              }
                                                              adapter.log.debug(`TR-064: Download TAM audio file from ${downloadUrl}`);
              
                                                              const stream = createWriteStream(file);
                                                              request({url: downloadUrl, agent: agent})
                                                                  .on('error', function(err) {
                                                                      unlink(file);
                                                                      adapter.log.warn(
                                                                          `TR-064: Error while downloading TAM audio file: ${err}`
                                                                      );
                                                                  })
                                                                  .pipe(stream)
                                                                  .on('error', function(err) {
                                                                      unlink(file);
                                                                      adapter.log.warn(
                                                                          `TR-064: Error while writing TAM audio file: ${err}`
                                                                      );
                                                                  })
                                                                  .on('finish', function() {
                                                                      stream.close(function() {
                                                                          msg.audioFile = path.resolve(file);
                                                                          resolve(msg);
                                                                      });
                                                                  });
              
                                                          }).then((result) => {
                                                              messages.push(result);
                                                          }));
                                                      }
                                                  }
                                              }
              
                                              Promise.all(promises).then(function() {
                                                  messages.sort((m1,m2) => m1.index > m2.index ? 1 : m1.index < m2.index ? -1 : 0);
              
                                                  // cleanup old files
                                                  readdir('tam', (err, files) => {
                                                      if (err) {
                                                          adapter.log.warn(
                                                              `TR-064: Error reading files from dir /tam: ${err}`
                                                          );
                                                      } else {
                                                          files.forEach(file => {
                                                              file = path.resolve("tam/" + file);
                                                              if (!messages.find(msg => msg.audioFile == file)) {
                                                                  // old file
                                                                  adapter.log.debug(
                                                                      `TR-064: Remove old tam audio file: ${file}`
                                                                  );
                                                                  unlink(file, function(err){
                                                                      if (err) {
                                                                          adapter.log.warn(
                                                                              `TR-064: Error deleting file ${file}: ${err}`
                                                                          );
                                                                      }
                                                                  });
                                                              }
                                                          })
                                                      }
                                                  })
              
                                                  adapter.setState('tam.messagesJSON', JSON.stringify(messages), true);
                                                  adapter.log.debug("TR-064: Successfully analyzed TAM results");
                                              });
                                          }
                                      });
                                  } else {
              						adapter.log.warn(
              							`TR-064: Error while requesting TAM: ${error}`
              						);
              					}
                              });
                          }
                      });
                  });
              }
              
              function getPhonebook(host, user, password) {
                  connectToTR064(host, user, password, function (sslDev) {
                      var tel = sslDev.services["urn:dslforum-org:service:X_AVM-DE_OnTel:1"];
                      adapter.log.debug("TR-064: Calling GetPhonebook()");
                      tel.actions.GetPhonebook({ NewPhonebookID: '0' }, function (err, ret) {
                          if (err) {
                              adapter.log.warn("TR-064: Error while calling GetPhonebook(): " + err);
                          } else if (ret.NewPhonebookURL && ret.NewPhonebookURL.length > 0) {
                              var url = ret.NewPhonebookURL;
                              adapter.log.debug("TR-064: Got phonebook uri: " + url);
              
              				var agentOptions;
              				var agent;
              
              				agentOptions = {
              				  rejectUnauthorized: false
              				};
              
              				agent = new https.Agent(agentOptions);
              
                              request({
              				  url: url
              				, method: 'GET'
              				, agent: agent
              				}, function (error, response, body) {
                                  if (!error && response.statusCode == 200) {
                                      adapter.log.debug("TR-064: Got valid phonebook content from, starting to parse ...");
                                      var parser = new xml2js.Parser();
                                      parser.parseString(body, function (err, result) {
                                          if (err) {
                                              adapter.log.warn("TR-064: Error while parsing phonebook content: " + err);
                                          } else {
                                              adapter.log.debug("TR-064: Successfully parsed phonebook content, analyzing result ...");
                                              var phonenumbers = []; // create an empty array for fetching all configured phone numbers from fritzbox
                                              var phonebook = result.phonebooks.phonebook[0];
                                              for (var c = 0; c <= phonebook.contact.length; c++) {
                                                  var contact = phonebook.contact[c];
                                                  if (typeof contact != 'undefined') {
                                                      var entryName = contact.person[0].realName[0];
                                                      for (var t = 0; t <= contact.telephony.length; t++) {
                                                          var telephony = contact.telephony[t];
                                                          if (typeof telephony != 'undefined') {
                                                              for (var n = 0; n <= telephony.number.length; n++) {
                                                                  var number = telephony.number[n];
                                                                  if (typeof number != 'undefined') {
                                                                      var entryNumber = number._;
                                                                      var entryType = number.$.type;
                                                                      if (entryNumber.startsWith('0') || entryNumber.startsWith('+')) {
                                                                          phonenumbers.push({
                                                                              key: entryNumber,
                                                                              value: { name: entryName, type: entryType }
                                                                          });
                                                                      }
                                                                  }
                                                              }
                                                          }
                                                      }
                                                  }
                                              }
              
                                              adapter.setState('phonebook.tableJSON', JSON.stringify(phonenumbers), true);
                                              adapter.log.debug("TR-064: Successfully analyzed phonebook results");
                                          }
                                      });
                                  } else {
              						adapter.log.warn(
              							`TR-064: Error while requesting phonebook: ${error}`
              						);
              					}
                              });
                          }
                      });
                  });
              }
              
              function connectToFritzbox(host) {
                  clearRealtimeVars(); // IP-Verbindung neu: Realtimedaten werden gelöscht, da ggf. nicht konsistent
                  var socketBox = net.connect({port: 1012, host: host}, function() {
                      adapter.log.info("adapter connected to fritzbox: " + host);
                  });
              
                  var connecting = false;
                  function restartConnection() {
                      if (socketBox) {
                          socketBox.end();
                          socketBox = null;
                      }
              
                      if (!connecting){
                          adapter.log.warn("restartConnection: " + host);
                          clearRealtimeVars(); // IP-Verbindung neu: Realtimedaten werden gelöscht, da ggf. nicht konsistent
                          connecting = setTimeout(function () {
                              connectToFritzbox(host);
                          }, 10000);
                      }
                  }
              
                  socketBox.on('error', restartConnection);
                  socketBox.on('close', restartConnection);
                  socketBox.on('end',   restartConnection);
              
                  socketBox.on('data',  parseData);   // Daten wurden aus der Fritzbox empfangen und dann in der Funktion parseData verarbeitet
              
                  if ((adapter.config.enableWlan || adapter.config.enablePhonebook || adapter.config.enableTAM)
                      && adapter.config.fritzboxUser && adapter.config.fritzboxPassword && adapter.config.fritzboxPassword.length) {
                      adapter.log.info("Trying to connect to TR-064: " + host + ":49000");
              
                      // try to get WLAN status and enable timer
                      if (adapter.config.enableWlan) {
                          getWlanConfig(host, adapter.config.fritzboxUser, adapter.config.fritzboxPassword, function (result) {
                              adapter.log.info("Successfully connected to TR-064");
                              handleWLANConfiguration(result);
                              intervalTR046 = setInterval(function () {
                                  getWlanConfig(host, adapter.config.fritzboxUser, adapter.config.fritzboxPassword, function (result) {
                                      handleWLANConfiguration(result);
                                  });
                              }, 10000);
                          });
                      }
              
                      // try to get phonebook
                      if (adapter.config.enablePhonebook) {
                          getPhonebook(host, adapter.config.fritzboxUser, adapter.config.fritzboxPassword);
                      }
              
                      // try to get tel answering machine
                      if (adapter.config.enableTAM) {
                          getTAM(host, adapter.config.fritzboxUser, adapter.config.fritzboxPassword);
                      }
                  }
              }
              
              
              
              function main() {
                  adapter.log.debug("< main >");
              
                  initVars(); // ioBroker Objekte übernehmen wenn vorhanden, sonst init
              
                  // Zustandsänderungen innerhalb der ioBroker fritzbox-Adapterobjekte überwachen
              //    adapter.subscribeForeignStates("node-red.0.fritzbox.*); // Beispiel um Datenpunkte anderer Adapter zu überwachen
                  adapter.subscribeStates('calls.missedCount');
                  adapter.subscribeStates('wlan.enabled');
              
                  // TODO: IP-Prüfung bringt nichts, da auch Hostnamen / DNS erlaubt sind & eine Prüfung auf der Admin-Webseite ist sinnvoller
                  var validIP = /^((25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])$/;
                  if (!adapter.config.fritzboxAddress.match(validIP)) {
                      adapter.log.info("no valid ip-Adress: " + adapter.config.fritzboxAddress) + ". Is it a valid hostname?";
                  }
              
                  if (adapter.config.fritzboxAddress && adapter.config.fritzboxAddress.length) {
                      adapter.log.info("try to connect: " + adapter.config.fritzboxAddress);
                      connectToFritzbox(adapter.config.fritzboxAddress);      // fritzbox
                  } else {
                      adapter.log.error("<< ip-Adresse der Fritzbox unbekannt >>");
                  }
              
                  /*
                  // Zweitanmeldung für Entwicklung: Fritzbox Simulator / Callgenerator
                  if (ipCallgen && ipCallgen.length) {
                      adapter.log.info("try to connect: " + ipCallgen);
                      connectToFritzbox(ipCallgen);   // callgen
                  }
              */
                  onlyOne = true; // Flag, um Aktionen nur einmal nach Programmstart auszuführen
              }
              
              
              
              

              Ich glaube in Zeile 1316 wird das irgendwie geschrieben, kann das sein?

              L Offline
              L Offline
              Larson-Sei180LX
              schrieb am zuletzt editiert von
              #6

              @larson-sei180lx said in Tabelle JSON -> HTML - Link Abspielen WAV Datei am Tablet:

              @liv-in-sky

              Ich habe hier das MAIN.JS File vom Fritzbox Adapter. Vl kannst Du mir das dort einpflegen ?

              /**
               *
               *      ioBroker fritzbox Adapter
               *
               *      (c) 2015 Michael Herwig <ruhr@digheim.de>
               *
               *      MIT License
               *
               *
               *    Callmonitor an der Fritzbox einschalten:
               *    Wenn man beim Telefon #96*5* eintippt, wird der TCP-Port 1012 geoffnet.
               *    Mit #96*4* wird dieser wieder geschlossen.
               *
               *    Format der Fritzbox-Meldungen:
               *
               *    Ausgehende Anrufe:            datum;CALL;      CallID;Nebenstelle;callingNumber;calledNumber;lineType;
               *    Eingehende Anrufe:            datum;RING;      CallID;callingNumber;calledNumber;lineTpe;
               *    Zustandegekommene Verbindung: datum;CONNECT;   CallID;Nebenstelle;connectedNumber;
               *    Ende der Verbindung:          datum;DISCONNECT;CallID;dauerInSekunden;
               *
               */
              
              // TODO Wie können auf der Adminseite die Werte vor Änderung (onChange) kontrolliert und ggf. markiert werden?
              // TODO Leistungsmerkmale: (2) Fritzbox Telefonbuch importieren und verarbeiten
              // TODO Anruferlisten persistent realisieren (nicht die erstellten Tabellen, sonder die die Arrays mit den Listeneinträgen (historyListAllHtml, historyListAllTxt, historyListAllJson, historyListMissedHtml)
              //      -> ggf. Redesign. Nicht verschiedene Listen, sondern eine JSON und die Funktionen so ändern, dass makeList auf diese eine JSON aufbaut
              
              "use strict";   // verlangt saubereren Code (nach ECMAScript 5) - EMPFEHLUNG:  beim Programmieren rein, im fertigen Programm raus
              
              // require() ist eine Funktion von node.js, um weitere Module nachzuladen
              
              var utils = require('@iobroker/adapter-core'); // Get common adapter utils
              var xml2js = require('xml2js'); // node-Modul um xml Strukturen in JSON umzuwandeln. Eine Beschreibung: http://www.flyacts.com/blog/nodejs-xml-daten-verarbeiten/
                                              // Bei der Entwicklung ins iobroker.frittbox Verzeichnis installieren. Als Admin, d.h. beim Mac mit sudo:
                                              // sudo npm install xml2js --save
                                              // mit dem --save wird es ins packet.json geschrieben
                                              // vorbereitet, um das Telefonbuch der Fritzbox zu verarbeiten (Export im XML Format)
              var net =    require('net');    // node-Modul für die tcp/ip Kommunikation
                                              // ist schon in node.js enthalten
                                              // Beschreibung: https://nodejs.org/api/net.html
              var tr = require("tr-064");     // node-Modul für die Kommunikation via TR-064 mit der FritzBox
                                              // Beschreibung zu TR-064: http://avm.de/service/schnittstellen/
              
              var https = require("https");
              var request = require("request");
              
              const { existsSync, writeFile, mkdirSync, readdir, unlink, createWriteStream } = require('fs');
              const path = require('path');
              const { url } = require('inspector');
              
              var adapter = utils.Adapter('fritzbox');
              
              var call = [];
              
              // Werte werden über die Adminseite gesetzt
              var // adapter.config.fritzboxAddress // ip-Adresse der Fritzbox
                  configCC                    = "",   // eigene Landesvorwahl ohne führende 0
                  configAC                    = "",   // eigene Ortsvorwahl ohne führende 0
                  configUnknownNumber         = "",   // "### ? ###"wie soll eine unbekannte/unterdrückte externe Rufnummer angezeigt werden
                  configNumberLength          = 15,   // (df=15) max. Stellen externer Rufnummern, die in den Tabellen HTML und TXT angezeigt werden
                  configExternalLink          = true, // externe Rufnummer als wählbaren Link (tel:)
                  configShowHeadline          = true, // Anruferliste mit überschrift?
                  showHistoryAllTableTxt      = true,
                  showHistoryAllTableHTML     = true,
                  showHistoryAllTableJSON     = true,
                  showCallmonitor             = true,
                  showMissedTableHTML         = true,
                  showMissedTableJSON         = true;
              
              // restlichen Config Werte
              var ipCallgen                   = "172.16.130.122",     // ip-Adresse des Callgenerators, wenn vorhanden
                  configDeltaTimeOKSec        = 5,                    // Abweichung in Sekunden zwischen der Systemzeit von ioBroker und der Meldungszeit aus der Fritzbox, die noch als OK gilt
                  configInit                  = true,                 // true = alle Werte werden zurückgesetzt, false = Werte aus den ioBroker Objekten werden eingelesen
                  configHistoryAllLines       = 12,                   // Anzahl der Einträge in der Anruferliste gesamt
                  configHistoryMissedLines    = 10;                   // Anzahl der Einträge in der Anruferliste verpasste Anrufe
              
              
              
              var ringLastNumber             = "",
                  ringLastNumberTel          = "",
                  ringLastMissedNumber       = "",
                  ringLastMissedNumberTel    = "",
              //    ringActualNumber           = "",
              //    ring                       = false,
                  callLastNumber             = "",
                  callLastNumberTel          = "",
              //    connectNumber              = "",
                  missedCount                = 0,
                  historyListAllHtml         = [],
                  historyListAllTxt          = [],
                  historyListAllJson         = [],
                  historyListMissedHtml      = [];
              
              
              var objMissedCount = 0;
              
              var onlyOne         = false; // Flag, ob main() schon einmal durchgeführt wurde
              
              var headlineDate            = "Tag    Zeit  ",  // mit utf-8 nbsp
                  headlineExtnumber       = "ext. Rufnr.",
                  headlineDirection       = "g<>k",
                  headlineExtension       = "Nbst",
                  headlineOwnnumber       = "Eigenes Amt",
                  headlineLine            = "Ltg.",
                  headlineDuration        = "  Dauer",        // mit utf-8 nbsp
                  nbsp                    = " ",              // utf-8 nbsp
                  headlineTableAllTxt     = "",
                  headlineTableAllHTML    = "",
                  headlineTableMissedHTML = "";
              
              
              
              // Liste der aktiven RINGs, CALLs, CONNECTs
              var listRing            = [],
                  listCall            = [],
                  listConnect         = [],
                  listAll             = [];
              
              // für den 1-Sekundentimer für den Callmonitor
              var intervalRunningCall = null;
              
              // für die TR-064-Abfragen
              var intervalTR046 = null;
              var wlanState = null;
              
              
              
              adapter.on('message', function (obj) {
              //    if (obj) processMessage(obj);
              //    processMessages();
                  adapter.log.debug('adapter.on-message: << MESSAGE >>');
              });
              
              // hier startet der Adapter
              adapter.on('ready', function () {
                  adapter.log.debug('adapter.on-ready: << READY >>');
                  main();
              });
              
              // hier wird der Adapter beendet
              adapter.on('unload', function () {
                  adapter.log.debug('adapter.on-unload: << UNLOAD >>');
                  clearRealtimeVars();
              
                  if (intervalRunningCall) {
                      clearInterval(intervalRunningCall);
                      intervalRunningCall = null;
                  }
              
                  if (intervalTR046) {
                      clearInterval(intervalTR046);
                      intervalTR046 = null;
                  }
              });
              
              
              // is called if a subscribed state changes
              adapter.on('stateChange', function (id, state) {
                  // adapter.log.debug('adapter.on-stateChange: << stateChange >>');
                  if (!state) {
                      return;
                  }
                  if (id === adapter.namespace + ".calls.missedCount") {
                      // New value of
                      // adapter.log.debug("state.val: " + state.val);
                      if (state.val == 0 || state.val == "0 ") {
                          adapter.setState('calls.missedDateReset',         dateNow(),          true);
                          adapter.log.debug("missed calls: counter zurückgesetzt " + dateNow());
                      }
                  }
                  else if (id === adapter.namespace + ".wlan.enabled" && !state.ack) {
                      adapter.log.debug(id + "=" + state.val);
                      if (state.val != wlanState && intervalTR046 && adapter.config.enableWlan) {
                          adapter.log.info("Changing WLAN to " + state.val);
                          setWlanEnabled(adapter.config.fritzboxAddress, adapter.config.fritzboxUser, adapter.config.fritzboxPassword, state.val);
                      }
                  }
              });
              
              
              
              function fritzboxDateEpoch(date) {
                  date = date || "";
                  var year    = "20" + date.substring(6, 8), // Achtung: ab dem Jahr 2100 erzeugt die Funktion ein falsches Datum ;-)
                      month   = parseInt(date.substring(3, 5)) - 1,
                      day     = date.substring(0, 2),
                      hour    = date.substring(9, 11),
                      minute  = date.substring(12, 14),
                      second  = date.substring(15, 17),
                      time    = new Date(year, month, day, hour, minute, second);
                  return Date.parse(time); // fritzbox message date in epoch
              }
              
              function dateEpochNow() {
                  var now = new Date();
                  now = Date.parse(now);  // aktuelle Zeit in epoch
                  return now;
              }
              
              function dateNow() {
                  var date     = new Date(dateEpochNow()),
                      year     = date.getFullYear(),
                      month    = date.getMonth() + 1,
                      day      = date.getDate(),
                      hour     = date.getHours(),
                      minute   = date.getMinutes(),
                      second   = date.getSeconds();
                  if (month.toString().length == 1) {
                      month  = '0' + month;
                  }
                  if (day.toString().length == 1) {
                      day = '0' + day;
                  }
                  if (hour.toString().length == 1) {
                      hour = '0' + hour;
                  }
                  if (minute.toString().length == 1) {
                      minute = '0' + minute;
                  }
                  if (second.toString().length == 1) {
                      second = '0' + second;
                  }
                  return day + "." + month + "." + year + " " + hour + ":" + minute;
              }
              
              
              function fill(n, str) {  // liefere Anzahl n nbsp in utf-8, wenn str nicht angegeben oder n-mal str
                  var space = "";
                  for (var i = 0; i < n; ++i) {
                      space += ((!str) ? " " : str); // &nbsp; als utf-8 Code (Mac: alt+Leerzeichen) TODO: wie kann das nbsp-Leerzeichen in Wbstorm sichtbar gemacht werden
                  }
                  return space;
              }
              
              function dynamicSort(property) {
                  var sortOrder = 1;
                  if (property[0] === "-") {
                      sortOrder = -1;
                      property = property.substr(1);
                  }
                  return function (a,b) {
                      var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
                      return result * sortOrder;
                  }
              }
              
              
              function numberFormat(number,length,align) {
              // bringt die Rufnummer auf die konfigurierte Länge (zu kurz: mit utf-8 Non-breaking-space auffüllen // zu lang: kürzen und als letze "Ziffer" ein x ("x" nur, wenn es sich um Zahlen handelt).
                  var onlyNumbers = /\D+/g;
                  if (!number.match(onlyNumbers)) { // wenn der String numbers nur Ziffern enthält und diese in der Anzahl zu lang sind, kürzen und die letzte Ziffer gegen ein "x" ersetzen
                      if (number.length > length) {
                          number = number.substring(0, length - 1) + "x";
                      }
                  }
                  number = number.substring(0,length); // sollte die externen numbern insgesamt länger sein, werden diese auf die max. gewünschte Länge gekürzt
                  if (align !== "r") {
                      number = number + fill(length - number.length); // richtet linksbündig aus und füllt nbsp; auf
                  } else {
                      number = fill(length - number.length) + number; // wenn allign = "r" wird rechtsbündig ausgerichtet
                  }
                  var i = 0;
                  while (number.search(" ") >= 0) {
                  number = number.replace(" ", " "); // alle normalen Leerzeichen gegen utf-8 non breaking spaces ersetzen
                      if (!i) adapter.log.debug('Leerzeichen gegen utf-8 non breaking space ersetzt: <'+ number + ">");
                      i++;
                      if (i > 40) {
                          adapter.log.warn("function numberFormat: zu langer Sting: " + number);
                          break;
                      }
                  }
                  return number;
              }
              
              
              function e164(extrnr) {
              // wandelt eine externe Rufnummer in das e164 Format, z.B. rufnummer 4711321 wird zu +492114711321 (in Abhängigkeit des konfigurierten CC und AC)
                  extrnr = extrnr.toString().replace(/\D+/g, ""); // nur Ziffern behalten, Rest kürzen
                  if (extrnr === configUnknownNumber) return extrnr;
                  if (extrnr.length > 0) {
                      var i;
                      var extrnr0 = 0;
                      for (i = 0; i < extrnr.length; i++) {
                          if (extrnr.substr(i, i + 1) != "0") {
                              break;
                          }
                          extrnr0++;
                      }
                      extrnr = extrnr.slice(extrnr0, extrnr.length);
              
                      if (extrnr0 == 0) {
                          extrnr = "+" + configCC + configAC + extrnr;
                      }
                      if (extrnr0 == 1) {
                          extrnr = "+" + configCC + extrnr;
                      }
                      if (extrnr0 == 2) {
                          extrnr = "+" + extrnr;
                      }
              
                      if (extrnr0 < 0 && extrnr0 > 2) {
                          adapter.log.error("fritzbox - ungültige externe Rufnummer, Anzahl führender Nullen prüfen");
                      }
                      validateE164(extrnr);
                  }
                  return extrnr;
              }
              
              function telLink(e164,formattedNumber) {
              // erstellt aus einer e164 Rufnummer einen tel: link. Wird als 2. Parameter eine formatierte Rufnummer übergeben, wird diese dargestellt.
                  var link = "";
                  if (!formattedNumber) {
                      link = '<a style="text-decoration: none;" href="tel:' + e164 + '">' + e164 + '</a>';
                  } else {
                      link = '<a style="text-decoration: none;" href="tel:' + e164 + '">' + formattedNumber + '</a>';
                  }
                  return link;
              }
              
              
              function validateE164(e164) {
              // testet auf eine gültige internationale Rufnummer mit + am Anfang
                  var regex = /^\+(?:[0-9] ?){6,14}[0-9]$/;
                  if (regex.test(e164)) {
                      // Valid international phone number
                      return true;
                  } else {
                      // Invalid international phone number
                      adapter.log.warn(e164 + " is not a vail int. phone number. Please check your configuration (Country + Area Code).");
                      return false;
                  }
              }
              
              
              function fritzboxDateToTableDate(fritzboxDate) {
                  var year = fritzboxDate.substring(6,8);
                  var month = fritzboxDate.substring(3,5);
                  var day = fritzboxDate.substring(0,2);
                  var hour = fritzboxDate.substring(9,11);
                  var minute = fritzboxDate.substring(12,14);
                  var second = fritzboxDate.substring(15,17);
                  // var time = hour + ":" + minute + ":" + second + " ";
                  // var time = hour + ":" + minute + "  ";
                  // var date = day  + "." + month  + ".20" + year + " ";
                  // var date = day  + "." + month  + "." + year + " ";
                  var date = day + "." + month + ". " + hour + ":" + minute + "  "; // space = non-breaking-space in utf-8
                  return date;
              }
              
              
              function durationForm(duration) {
              // Dauer in Sekunden formatiert zu einem 7-stelligen String:
              // "      -" = 0 Sek.
              // "      5" = einstellige Sekunde
              // "     27" = zweistellige Sekunden
              // "   1:41" = einstellige Minuten
              // "  59:32" = zweistellige Minuten
              // "8:21:44" = mehr als eine Stunde, weniger als 10h
              // "  >10 h" = mehr als 10h
                  if (duration === "") {
                      duration = fill(7);
                      return duration;
                  }
                  var durationMin = Math.floor(parseInt(duration) / 60 );
                  var durationSek = parseInt(duration) % 60;
                  var durationStd = Math.floor(durationMin  / 60);
                  durationMin %= 60;
                  if (durationStd < 1) {
                      if (durationMin < 1) {
                          duration = durationSek;
                      } else {
                          duration = durationMin + ":" + fill((2- durationSek.toString().length),"0") + durationSek;
                      }
                  } else {
                      duration = durationStd + ":" + fill((2- durationMin.toString().length),"0") + durationMin + ":" + fill((2- durationSek.toString().length),0) + durationSek;
                  }
                  duration = duration.toString();
              
                  if (duration == "0") {
                      duration = "-";
                  }
              
                  if (duration.length > 7) {
                      duration = "> 10h";
                  }
                  duration = fill(7 - duration.toString().length) + duration; // auf 7-Stellen auffüllen
                  return duration;
              }
              
              
              function getEventsList(anruferliste) {
                  var text = '';
                  for (var i = 0; i < anruferliste.length; i++) {
                       text += (text ? '<br>\n' : '') + anruferliste[i];
              //        text += anruferliste[i] + (text ? '<br>\n' : '');
                  }
                  return text;
              }
              
              function makeList(list,line,headline,howManyLines,showHeadline) {
                  var lines = 0;
                  if (showHeadline === true) {
                      list.shift(); // erste Element im Array löschen (Kopfzeile)
                      list.unshift(headline, line); // (Kopfzeile und aktueller Eintrag an Stelle 1 und 2 hinzufügen)
                      lines = 1;
                  } else {
                      list.unshift(line);
                  }
              
                  if (list.length > howManyLines + lines) {
                      list.length = howManyLines + lines;
                  }
                  return getEventsList(list);
              }
              
              
              function callmonitor(list) {
              // Liste aktueller Anrufe (RING, CONNECT und CALL)
                  var txt = '';
                  for (var i = 0; i < list.length; i++) {
                      txt += fritzboxDateToTableDate(call[list[i].id].dateStart) + " " + call[list[i].id].externalNumberForm;
                      if (call[list[i].id].type === "CONNECT") {
                          txt += durationForm(call[list[i].id].durationSecs2);
                      }
                      if (call[list[i].id].type === "RING") {
                          txt += durationForm(call[list[i].id].durationRingSecs);
                      }
                      if (i < (list.length - 1)) {
                          txt += (txt ? '<br>\n' : '');
                      }
                  }
                  return txt;
              }
              
              
              function callmonitorAll(list) {
                  var txt = '';
                  for (var i = 0; i < list.length; i++) {
                      var id = list[i].id;
                      var intNr = fill(4 - call[id].extensionLine.length) + call[id].extensionLine;
                      if (call[id].callSymbol == " -> ") {
                          intNr = '<span style="color:red  ">RING</span>';
                      }
                      txt += fritzboxDateToTableDate(call[id].dateStart) + " " + call[id].externalNumberForm;
                      txt += call[id].callSymbolColor + " ";
                      txt += intNr;
                      if (call[id].type === "CONNECT") {
                          txt += " " + '<span style="color:green">' + "<b>" + durationForm(call[id].durationSecs2) + "</b>" + '</span>';
                      }
                      if (call[id].type === "RING") {
                          txt += " " + '<span style="color:red  ">' + durationForm(call[id].durationRingSecs) + '</span>';
                      }
                      if (i < (list.length - 1)) {
                          txt += (txt ? '<br>\n' : '');
                      }
                  }
                  return txt;
              }
              
              function headlineMissedHTML() {
                  var headlineHistoryMissedHTML =
                      "<b>" + headlineDate +
                      headlineExtnumber + nbsp +
                      headlineOwnnumber + "</b>";
                  return headlineHistoryMissedHTML;
              }
              
              
              function headlineAllTxt() {
                  // Überschrift formatieren
                  headlineDate = numberFormat(headlineDate, 14);
                  headlineExtnumber = numberFormat(headlineExtnumber, configNumberLength);
                  headlineDirection = numberFormat(headlineDirection, 4);
                  headlineExtension = numberFormat(headlineExtension, 5, "r");   // 5 Zeichen lang, rechtsbündig
                  headlineOwnnumber = numberFormat(headlineOwnnumber, configNumberLength);
                  headlineLine = numberFormat(headlineLine, 4);
                  headlineDuration = numberFormat(headlineDuration, 7, "r");     // 7 Zeichen lang, rechtsbündig
              // Überschrift Anruferliste gesamt (txt)
                  var headlineHistoryAllTxt =
                      headlineDate +
                      headlineExtnumber + nbsp +
                      headlineDirection +
                      headlineExtension + nbsp +
                      headlineOwnnumber + nbsp +
                      headlineLine + nbsp +
                      headlineDuration;
                  return headlineHistoryAllTxt;
              }
              
              function headlineAllHTML() {
              // Überschrift Anruferliste gesamt (html)
                  var headlineHistoryAllHTML = "<b>" + headlineAllTxt() + "</b>";
              
                  return headlineHistoryAllHTML;
              }
              
              
              function clearRealtimeVars() {
                   // da die Daten nach Start des Adapters oder neuer IP-Verbindung zur Fritzbox nicht konsitent sind, werden die Callmonitore gelöscht
              
                  // Callmonitore löschen
                  if (showCallmonitor) {
                      adapter.setState('callmonitor.ring', "", true);
                      adapter.setState('callmonitor.call', "", true);
                      adapter.setState('callmonitor.connect', "", true);
                      adapter.setState('callmonitor.all', "", true);
                  }
                  //Realtime Daten löschen
                  adapter.setState('calls.ring',                               false ,    true);
                  adapter.setState('calls.ringActualNumber',                   "" ,       true);
                  adapter.setState('calls.ringActualNumbers',                  "" ,       true);
                  adapter.setState('calls.connectNumber',                      "",        true);
                  adapter.setState('calls.connectNumbers',                     "" ,       true);
                  adapter.setState('calls.counterActualCalls.ringCount',       0 ,        true);
                  adapter.setState('calls.counterActualCalls.callCount',       0 ,        true);
                  adapter.setState('calls.counterActualCalls.connectCount',    0 ,        true);
                  adapter.setState('calls.counterActualCalls.allActiveCount',  0 ,        true);
              
                  // Liste der aktiven RINGs, CALLs, CONNECTs
                  listRing            = [];
                  listCall            = [];
                  listConnect         = [];
                  listAll             = [];
              
              }
              
              
              
              
              
              
              function adapterSetOnChange (object, value) {
                  // ioBroker Objekte schreiben aber nur, wenn der Wert geändert wurde
                  adapter.getState(object, function (err, state) {
                      if (!err && state) {
                          if (state.val !== value || !state.ack) {
                              adapter.setState(object, value , true);
                          }
                      }
                  });
              }
              
              
              function adapterSetOnUndefined (object, value) {
                  adapter.getState(object, function (err, state) {
                      if (!err && state) {
                          var newValue = state.val || value;
                          if (state.val == newValue) return; // vorhandene Werte nicht noch einmal schreiben
                          adapter.setState(object, newValue, true);
              //            adapter.log.info("Objekt: " + object + " Wert: " + state.val + " -> " + newValue + "  INIT: " +value);
                      } else {
                          adapter.setState(object, value, true);
                      }
                  });
              }
              
              
              
              function initVars() {
              // alte Zustände übernehmen oder bei Erstinitalisierung sinnvolle Werte
                  adapterSetOnUndefined("calls.missedCount", 0);
                  adapterSetOnUndefined("calls.missedDateReset", dateNow());
              
                  adapterSetOnUndefined("calls.ringLastNumber", "");
                  adapterSetOnUndefined("calls.ringLastMissedNumber", "");
                  adapterSetOnUndefined("calls.callLastNumber", "");
              
                  adapterSetOnUndefined("telLinks.ringLastNumberTel", "");
                  adapterSetOnUndefined("telLinks.ringLastMissedNumberTel", "");
                  adapterSetOnUndefined("telLinks.callLastNumberTel", "");
              
                  adapterSetOnUndefined("system.deltaTime", 0);
                  adapterSetOnUndefined("system.deltaTimeOK", true);
              
                  // config aus der Adapter Admin-Webseite übernehmen
                  configCC                    = adapter.config.cc;    // "49",            // eigene Landesvorwahl ohne führende 0
                  configAC                    = adapter.config.ac;    // "211",           // eigene Ortsvorwahl ohne führende 0
                  configUnknownNumber         = numberFormat(adapter.config.unknownNumber, adapter.config.unknownNumber.length); // Leerzeichen gegen utf-8 nbsp ersetzen
                  configNumberLength          = adapter.config.numberLength;
                  if (configNumberLength < 4) {
                      adapter.log.warn("Rufnummernlänge zu klein gewählt, geändert von " + configNumberLength + " auf 4");
                      configNumberLength = 4;
                  }
                  if (configNumberLength > 30) {
                      adapter.log.warn("Rufnummernlänge zu groß gewählt, geändert von " + configNumberLength + " auf 30");
                      configNumberLength = 30;
                  }
              
                  configExternalLink          = adapter.config.externalLink;
                  configShowHeadline          = adapter.config.showHeadline;
              
                  showHistoryAllTableTxt      = adapter.config.showHistoryAllTableTxt;
                  showHistoryAllTableHTML     = adapter.config.showHistoryAllTableHTML;
                  showHistoryAllTableJSON     = adapter.config.showHistoryAllTableJSON;
                  showCallmonitor             = adapter.config.showCallmonitor;
                  showMissedTableHTML         = adapter.config.showMissedTableHTML;
                  showMissedTableJSON         = adapter.config.showMissedTableJSON;
              
                  // vorhandene Werte aus den ioBroker Objekte des Fritzbox Adapters rauslesen und in die globalen Variablen übernehmen
                  adapter.getState('calls.missedCount', function (err, state) {
                      if (!err && state) {
                          objMissedCount = (state.val);
                          missedCount = (state.val);
                      }
                  });
              
              
                  if (adapter.config.showHeadline) {
                      headlineTableAllTxt = headlineAllTxt();
                      headlineTableAllHTML = headlineAllHTML();
                      headlineTableMissedHTML = headlineMissedHTML();
                  } else {
                      headlineTableAllTxt = "";
                      headlineTableAllHTML = "";
                      headlineTableMissedHTML = "";
                  }
              
              
                  if (!showHistoryAllTableTxt) {
                      adapter.setState('history.allTableTxt',    "deactivated",          true);
                  } else {
                      adapter.setState('history.allTableTxt',    headlineTableAllTxt,    true);
                  }
                  if (!showHistoryAllTableJSON) {
                      adapter.setState('history.allTableJSON',   "deactivated",          true);
                  }
                  if (!showHistoryAllTableHTML) {
                      adapter.setState('history.allTableHTML',   "deactivated",          true);
                  } else {
                      adapter.setState('history.allTableHTML',   headlineTableAllHTML,   true);
                  }
                  if (!showMissedTableHTML) {
                      adapter.setState('history.missedTableHTML', "deactivated",         true);
                  } else {
                      adapter.setState('history.missedTableHTML', headlineTableMissedHTML,   true);
                  }
                  if (!showMissedTableJSON)     adapter.setState('history.missedTableJSON',   "deactivated", true);
                  if (!showCallmonitor) {
                      adapter.setState('callmonitor.connect', "deactivated", true);
                      adapter.setState('callmonitor.ring', "deactivated", true);
                      adapter.setState('callmonitor.call', "deactivated", true);
                      adapter.setState('callmonitor.all', "deactivated", true);
                  } else {
                      adapter.setState('callmonitor.connect', "", true);
                      adapter.setState('callmonitor.ring', "", true);
                      adapter.setState('callmonitor.call', "", true);
                      adapter.setState('callmonitor.all', "", true);
                  }
              
                  adapter.setState('wlan.enabled', false, true);
              }
              
              
              
              
              
              function parseData(message) {
                  adapter.log.info("data from " + adapter.config.fritzboxAddress + ": " + message);
                  message = message.toString('utf8');
                  adapter.setState('message', message, true);
              
                  var obj = message.split(";"); 	// speichert die Felder der Meldung in ein Array mit dem Namen "obj"
                  var id = obj[2];
                  call[id] = call[id] || {};
              
                  var ringCount           = 0,
                      callCount           = 0,
                      connectCount        = 0,
                      allActiveCount      = 0,
                      ring                = null,
                      ringActualNumber    = "";
              
                  var ringActualNumbers   = [],
                      connectNumbers      = [],
                      connectNumber       = "";
              
                  var cssGreen = '<span style="color:green">',
                      cssRed   = '<span style="color:red  ">',
                      cssBlack = '<span style="color:black">',
                      cssColor = cssGreen,
                      cssEnd   = "</span>";
              
              
              
                  // ###########  Auswertung und Aufbau der Anruferinformationen aus der Fritzbox ###########
              
                  // für alle Anruftypen identisch
                  call[id].date            = obj[0];    // 01.07.15 12:34:56 - dd.mm.yy hh:mm:ss
                  call[id].dateEpoch       = fritzboxDateEpoch(obj[0]); // fritzbox time
                  call[id].dateEpochNow    = dateEpochNow();              // system time
                  call[id].deltaTime       = (call[id].dateEpochNow  - call[id].dateEpoch) / 1000; // delta Time beetween system and fritzbox
                  call[id].deltaTimeOK     = (call[id].deltaTime < configDeltaTimeOKSec);
                  call[id].type            = obj[1];    // CALL (outgoing), RING (incoming), CONNECT (start), DISCONNECT (end)
                  call[id].id              = obj[2];    // call identifier as integer
              
              
                  // Outgoing call
                  if (call[id].type == "CALL") {
                      call[id].extensionLine      = obj[3];    // used extension line
                      call[id].ownNumber          = obj[4];    // calling number - used own number
                      call[id].externalNumber     = obj[5];    // called number
                      call[id].lineType           = obj[6];    // line type
                      call[id].durationSecs       = null;      // duration of the connect in sec
                      call[id].durationForm       = null;      // duration of the connect in sec
                      call[id].durationSecs2      = "";        // duration of the connect in sec, count during connect
                      call[id].durationRingSecs   = "";        // duration ringing time in sec
                      call[id].connect            = false;
                      call[id].direction          = "out";
                      call[id].dateStartEpoch     = fritzboxDateEpoch(obj[0]);
                      call[id].dateConnEpoch      = null;
                      call[id].dateEndEpoch       = null;
                      call[id].dateStart          = obj[0];
                      call[id].dateConn           = null;
                      call[id].dateEnd            = null;
                      call[id].callSymbol         = " <- "; // utf-8 nbsp
                      call[id].callSymbolColor    = cssBlack + call[id].callSymbol + cssEnd;
                  }
                  else // Incoming (RING)
                  if (call[id].type == "RING") {
                      call[id].extensionLine      = "";        // used extension line
                      call[id].ownNumber          = obj[4];    // called number - used own number
                      call[id].externalNumber     = obj[3];    // calling number
                      call[id].lineType           = obj[5];    // line type
                      call[id].durationSecs       = null;      // duration of the connect in sec
                      call[id].durationForm       = null;
                      call[id].durationSecs2      = "";        // duration of the connect in sec, count during connect
                      call[id].durationRingSecs   = 0;         // duration of ringing time in sec
                      call[id].connect            = false;
                      call[id].direction          = "in";
                      call[id].dateStartEpoch     = fritzboxDateEpoch(obj[0]);
                      call[id].dateConnEpoch      = null;
                      call[id].dateEndEpoch       = null;
                      call[id].dateStart          = obj[0];
                      call[id].dateConn           = null;
                      call[id].dateEnd            = null;
                      call[id].callSymbol         = " -> "; // utf-8 nbsp
                      call[id].callSymbolColor    = cssBlack + call[id].callSymbol + cssEnd;
                  }
                  else // Start of connect
                  if (call[id].type == "CONNECT") {
                      call[id].extensionLine      = obj[3];    // used extension line
                      call[id].ownNumber          = call[id].ownNumber       || "????????";    // used own number
                      call[id].externalNumber     = obj[4];    // connected number
                      call[id].lineType           = call[id].lineType        || "????";        // line type
                      call[id].durationSecs       = null;      // duration of the connect in sec
                      call[id].durationForm       = null;
                      call[id].durationSecs2      = 0;         // duration of the connect in sec, count during connect
                      call[id].durationRingSecs   = call[id].durationRingSecs || "";         // duration of ringing time in sec
                      call[id].connect            = true;
                      call[id].direction          = call[id].direction       || "?";
                      call[id].dateStartEpoch     = call[id].dateStartEpoch  || null;
                      call[id].dateConnEpoch      = fritzboxDateEpoch(obj[0]);
                      call[id].dateEndEpoch       = null;
                      call[id].dateStart          = call[id].dateStart || obj[0];
                      call[id].dateConn           = obj[0];
                      call[id].dateEnd            = null;
                      if (call[id].direction == "in") {
                          call[id].callSymbol     = " ->>";
                      } else {
                          call[id].callSymbol     = "<<- "; // utf-8 nbsp
                      }
                      call[id].callSymbolColor    = cssGreen + "<b>" + call[id].callSymbol + "</b>" + cssEnd;
                  }
                  else // End of call
                  if (call[id].type == "DISCONNECT") {
                      call[id].extensionLine      = call[id].extensionLine   || "";          // used extension line
                      call[id].ownNumber          = call[id].ownNumber       || "????????";    // used own number
                      call[id].externalNumber     = call[id].externalNumber  || "????????";    // connected number
                      call[id].lineType           = call[id].lineType        || "????";        // line type
                      call[id].durationSecs       = obj[3];                                    // call duration in seconds
                      call[id].durationForm       = durationForm(obj[3]);
                      call[id].durationSecs2      = call[id].durationSecs;      // duration of the connect in sec
                      call[id].durationRingSecs   = call[id].durationRingSecs || "";         // duration of ringing time in sec
                      //call[id].connect            = call[id].connect         || null;
                      call[id].direction          = call[id].direction       || "?";
                      call[id].dateStartEpoch     = call[id].dateStartEpoch  || fritzboxDateEpoch(obj[0]);
                      call[id].dateConnEpoch      = call[id].dateConnEpoch   || fritzboxDateEpoch(obj[0]);
                      call[id].dateEndEpoch       = fritzboxDateEpoch(obj[0]);
                      call[id].dateStart          = call[id].dateStart       || obj[0];
                      call[id].dateConn           = call[id].dateConn        || obj[0];
                      call[id].dateEnd            = obj[0];
                      if (call[id].connect === false) {
                          cssColor = cssRed;
                          if (call[id].direction == "in") {
                              call[id].callSymbol = " ->X";
                          } else {
                              call[id].callSymbol = "X<- "; // utf-8 nbsp
                          }
                      }
                      call[id].callSymbol = call[id].callSymbol || "????";
                      if (call[id].callSymbol === "????") cssColor = cssBlack;
                      call[id].callSymbolColor    = cssColor + "<b>" + call[id].callSymbol + "</b>" + cssEnd;
                  }
                  else {
                      adapter.log.error ("adapter fritzBox unknown event type " + call[id].type);
                      return;
                  }
              
              
                  // für alle Anruftypen identisch
                  call[id].unknownNumber = false;
                  if (call[id].externalNumber === "" || call[id].externalNumber === null || call[id].externalNumber === configUnknownNumber || call[id].externalNumber === "????????") {
                      if (call[id].externalNumber !== "????????") call[id].externalNumber         = configUnknownNumber;
                      call[id].unknownNumber          = true;
                  }
                  call[id].ownNumberForm              = numberFormat(call[id].ownNumber,configNumberLength);
                  call[id].externalNumberForm         = numberFormat(call[id].externalNumber,configNumberLength);
                  call[id].ownNumberE164              = e164(call[id].ownNumber);
                  call[id].externalE164               = e164(call[id].externalNumber);
              
              
                  if (call[id].unknownNumber) {
                      call[id].externalTelLink        = call[id].externalNumberForm;
                      call[id].externalTelLinkCenter  = call[id].externalNumber;
                  } else {
                      call[id].externalTelLink        = telLink(call[id].externalE164,call[id].externalNumberForm);
                      call[id].externalTelLinkCenter  = telLink(call[id].externalE164,call[id].externalNumber);
                  }
              
              /*
                  adapter.log.debug("fritzBox event (date: " + call[id].date +
                      ", dateEpoch: "     + call[id].dateEpoch +
                      ", type: "          + call[id].type +
                      ", ID: "            + call[id].id +
                      ", exLine: "        + call[id].extensionLine +
                      ", extern: "        + call[id].externalNumber +
                      ", own: "           + call[id].ownNumber +
                      ", duration: "      + call[id].durationSecs
                 );
              
                  adapter.log.debug("fritzBox event (delta time: " + call[id].deltaTime +
                      ", delta time ok: "    + call[id].deltaTimeOK +
                      ", symbol: "           + call[id].callSymbol +
                      ", ext E164: "         + call[id].externalE164 +
                      ", ext Tel Link: "     + call[id].externalTelLink
                  );
              */
              
              
                  // Listen aktuelle Gesprächszustände nach jeder Fritzbox Message, neuaufbauen und damit aktualisieren)
                  listRing            = [];
                  listCall            = [];
                  listConnect         = [];
                  listAll             = [];
              
                  // Schleife, Anzahl der Call IDs im Array: Zählt die jeweiligen Zustände, ein Array je Zustand
                  for (var i = 0; i < call.length; i++){
                      if (call[i] != null) {
                          // Call Status = RING
                          if (call[i].type === "RING"){
                              listRing[ringCount]                         = listRing[ringCount] || {};
                              listRing[ringCount].id                      = call[i].id;
                              listRing[ringCount].dateStartEpoch          = call[i].dateStartEpoch;
                              ringCount++;
                           }
              
                          // Call Status = CALL
                          if (call[i].type === "CALL"){
                              listCall[callCount]                         = listCall[callCount] || {};
                              listCall[callCount].id                      = call[i].id;
                              listCall[callCount].dateStartEpoch          = call[i].dateStartEpoch;
                              callCount++;
                          }
              
                          // Call Status = CONNECT
                          if (call[i].type === "CONNECT"){
                              listConnect[connectCount]                   = listConnect[connectCount] || {};
                              listConnect[connectCount].id                = call[i].id;
                              listConnect[connectCount].dateStartEpoch    = call[i].dateStartEpoch;
                              connectCount++;
                          }
              
                          // Alle Status (RING, CALL, CONNECT)
                          if (call[i].type !== "DISCONNECT"){
                              listAll[allActiveCount]                     = listAll[allActiveCount] || {};
                              listAll[allActiveCount].id                  = call[i].id;
                              listAll[allActiveCount].dateStartEpoch      = call[i].dateStartEpoch;
                              allActiveCount++;
                          }
                      }
                  }
              
              /*    adapter.log.debug("ringCount: " + ringCount +
                      ", callCount: "             + callCount+
                      ", connectCount: "          + connectCount +
                      ", allActiveCount: "        + allActiveCount
                  );
              */
              
              // sortiert die ermittelten Listen nach der tatsächlichen Meldungszeit (Systemzeit)
                  listRing.sort(dynamicSort("-dateStartEpoch"));      // Liste sortieren: jüngster Eintrag oben
                  listCall.sort(dynamicSort("-dateStartEpoch"));      // Liste sortieren: jüngster Eintrag oben
                  listConnect.sort(dynamicSort("-dateStartEpoch"));   // Liste sortieren: jüngster Eintrag oben
                  listAll.sort(dynamicSort("-dateStartEpoch"));       // Liste sortieren: jüngster Eintrag oben
              
              
              // aktuellste Anrufernummer wird als aktueller und neuester Anrufer (Ring) gespeichert (es kann noch mehr aktive RINGs geben, diese werden in "ringActualNumbers" gespeichert)
                  if (listRing[0] != null) {
                      ringActualNumber = call[listRing[0].id].externalNumber;
                      ring = true;
                  }
              
              // kein aktiver Ring, ringAktuell löschen  && ring = false (kein Ring aktiv)
                  if (ringCount < 1) {
                      ringActualNumber = "";
                      ring = false;
                  }
              
                  if (listConnect[0] != null) {
                      connectNumber = call[listConnect[0].id].externalNumber;
                  }
                  objMissedCount = missedCount; // den alten errechneten Wert der verpassten Anrufe sichern
                  // aktuellen Wert Anzahl verpasste Anrufe (missedCount aus den ioBroker Objekten auslesen)
                  adapter.getState('calls.missedCount', function (err, state) {
                      if (!err && state) {
                          missedCount = (state.val);
                      }
                  });
              
              
              
                  if (call[id].type == "DISCONNECT") {
                      if (call[id].direction == "in") {
                          ringLastNumber    = call[id].externalNumber;        // letzter Anrufer
                          adapter.setState('calls.ringLastNumber',                     ringLastNumber ,            true);
                          ringLastNumberTel = call[id].externalTelLinkCenter; // letzter Anrufer als wählbarer Link
                          adapter.setState('calls.telLinks.ringLastNumberTel',         ringLastNumberTel,          true);
              
                          if (!call[id].connect) { // letzter Anrufer, der verpasst wurde
                              ringLastMissedNumber = call[id].externalNumber;
                              adapter.setState('calls.ringLastMissedNumber',               ringLastMissedNumber ,      true);
                              ringLastMissedNumberTel = call[id].externalTelLinkCenter;
                              adapter.setState('calls.telLinks.ringLastMissedNumberTel',   ringLastMissedNumberTel ,   true);
                          }
                          // letzter verpasste Anruf wird hochgezählt, Zähler verpasste Anrufe (bis max. 999)
                          // ioBroker Datenpunkt kann beschrieben und über ioBrkoer zurückgesetzt werden
                          // das Datum, zu dem der Zähler auf 0 gesett wird, wird in calls.missedDateReset gespeichert.
                          if (!call[id].connect) {
                              ++missedCount;
                              if (missedCount > 999) missedCount = 999;
                          }
                      } else
                      if (call[id].direction == "out") {
                          callLastNumber = call[id].externalNumber;
                          adapter.setState('calls.callLastNumber',                     callLastNumber ,            true);
                          callLastNumberTel = call[id].externalTelLinkCenter;
                          adapter.setState('calls.telLinks.callLastNumberTel',         callLastNumberTel,          true);
                      } else {
                          adapter.log.warn ("Adapter starts during call. Some values are unknown.");
                      }
                  } // END DISCONNECT
              
              
                  // aktuelle Rufnummeren der gerade laufenden Anrufe (RING)
                  for (var i = 0; i < listRing.length; i++){
                      ringActualNumbers.push(call[listRing[0].id].externalNumber);
                  }
              
                  // aktuelle Rufnummern der gerade laufenden Gespräche (CONNECT)
                  for (var i = 0; i < listConnect.length; i++){
                      connectNumbers.push(call[listConnect[i].id].externalNumber);
                  }
              
              
                  // Daten, die bei jeder Meldung aktualisiert werden
                  adapter.setState('system.deltaTime',                         call[id].deltaTime ,        true);
                  adapter.setState('system.deltaTimeOK',                       call[id].deltaTimeOK ,      true);
                  if (!call[id].deltaTimeOK) adapter.log.warn("delta time between system and fritzbox: " + call[id].deltaTime + " sec");
              
                  //Auf Änderungen im ioBroker Objekt reagieren und die lokale Variable auch ändern.
                  if (objMissedCount !== missedCount) { // Zähler nur schreiben, wenn er sich verändert hat (wg. Traffic Überwachung des Objekts auf Änderung)
                      adapter.setState('calls.missedCount',                        missedCount ,               true);
                  }
                  // adapter.log.debug ("objMissedCount: " + objMissedCount + "   missedCount: " + missedCount);
              
                  //Realtime Daten
                  adapterSetOnChange ("calls.ring" , ring);
                  adapterSetOnChange ("calls.ringActualNumber" , ringActualNumber);
                  adapterSetOnChange ("calls.ringActualNumbers" , ringActualNumbers.join());
                  adapterSetOnChange ("calls.connectNumber" , connectNumber);
                  adapterSetOnChange ("calls.connectNumbers" , connectNumbers.join());
                  adapterSetOnChange ("calls.counterActualCalls.ringCount" , ringCount);
                  adapterSetOnChange ("calls.counterActualCalls.callCount" , callCount);
                  adapterSetOnChange ("calls.counterActualCalls.connectCount" , connectCount);
                  adapterSetOnChange ("calls.counterActualCalls.allActiveCount" , allActiveCount);
              
                  // History / Anruferlisten
                  if (call[id].type == "DISCONNECT") {
              
                      // Datensatz formatieren
                      var extensionLine   = numberFormat(call[id].extensionLine, 5, "r");
                      var line            = numberFormat(call[id].lineType, 4);
                      // die externe Rufnummer, je nach Konfiguration als Link (tel:) oder Text
                      var externalNumber  = call[id].externalTelLink;
                      if (!configExternalLink) {
                          externalNumber  = call[id].externalNumberForm;
                      }
              
                      // Einzelne Datenzeile Anruferliste gesamt (txt)
                      var lineHistoryAllTxt =
                          fritzboxDateToTableDate(call[id].date) +
                          call[id].externalNumberForm + nbsp +
                          call[id].callSymbol +
                          extensionLine + nbsp +
                          call[id].ownNumberForm + nbsp +
                          call[id].lineType + nbsp +
                          call[id].durationForm;
              
                      // Einzelne Datenzeile Anruferliste gesamt (html)
                      var lineHistoryAllHtml =
                          fritzboxDateToTableDate(call[id].date) +
              //            call[id].externalTelLink + nbsp +
                          externalNumber + nbsp +
                          call[id].callSymbolColor +
                          extensionLine + nbsp +
                          call[id].ownNumberForm + nbsp +
                          call[id].lineType + nbsp +
                          call[id].durationForm;
              
                      // Datensatz html verpasste Anrufe erstellen && html & json schreiben
                      if (!call[id].connect) {
                          if (call[id].direction === "in") {
                              // Datensatz verpasste Anrufe
                              var lineHistoryMissedHtml =
                                  fritzboxDateToTableDate(call[id].date) +
                                  externalNumber + nbsp + call[id].ownNumberForm + nbsp;
                              // aktuelle Call-ID als JSON für verpasste Anrufe wegschreiben
                              adapter.setState('cdr.missedJSON',              JSON.stringify(call[id]),   true);
                              adapter.setState('cdr.missedHTML',              lineHistoryMissedHtml,      true);
                          }
                      }
              
                      if (showMissedTableHTML) {
                          // Anruferliste verpasste Anrufe erstellen
                          if (!call[id].connect) {
                              if (call[id].direction === "in") {
                                  var historyListMissedHtmlStr = makeList(historyListMissedHtml, lineHistoryMissedHtml, headlineTableMissedHTML, configHistoryMissedLines, configShowHeadline);
                                  adapter.setState('history.missedTableHTML', historyListMissedHtmlStr, true);
                              }
                          }
                      }
              
              
                      if (showHistoryAllTableHTML) {
                          // Tabelle html erstellen
                          var historyListAllHtmlStr = makeList(historyListAllHtml,lineHistoryAllHtml,headlineTableAllHTML,configHistoryAllLines, configShowHeadline);
                          adapter.setState('history.allTableHTML',        historyListAllHtmlStr,              true);
                      }
              
                      if (showHistoryAllTableTxt) {
                          // Tabelle txt erstellen
                          var historyListAllTxtStr = makeList(historyListAllTxt, lineHistoryAllTxt, headlineTableAllTxt, configHistoryAllLines, configShowHeadline);
                          adapter.setState('history.allTableTxt', historyListAllTxtStr, true);
                      }
                      if (showHistoryAllTableJSON) {
                          var lineHistoryAllJson = {
                              "date" :            call[id].date,
                              "externalNumber" :  call[id].externalNumber,
                              "callSymbolColor" : call[id].callSymbolColor,
                              "extensionLine" :   call[id].extensionLine,
                              "ownNumber" :       call[id].ownNumber,
                              "lineType" :        call[id].lineType,
                              "durationForm" :    call[id].durationForm
                          };
                          // Anruferliste als JSON auf max Anzahl configHistoryAllLine beschränken
              //        historyListAllJson.unshift(call[id]);
                          historyListAllJson.unshift(lineHistoryAllJson);
                          if (historyListAllJson.length   > configHistoryAllLines) {
                              historyListAllJson.length   = configHistoryAllLines;
                          }
                          adapter.setState('history.allTableJSON',    JSON.stringify(historyListAllJson), true);
                      }
              
              //        adapter.log.debug("historyListAllJson Obj " +   JSON.stringify(historyListAllJson[0]) );  // erste Zeile der JSON ANruferliste im Log
              //        adapter.log.debug("history all JSON Items: " +  historyListAllJson.length );              // Anzahl der JSON Elemente in der Anruferliste
              
                      // aktuellen Datensatz des letzten Anrufs als JSON speicherern (für History und die weiteren Verarbeitung in Javascript, usw.)
                      adapter.setState('cdr.json',                    JSON.stringify(call[id]),   true);
                      adapter.setState('cdr.html',                    lineHistoryAllHtml,         true);
                      adapter.setState('cdr.txt',                     lineHistoryAllTxt,          true);
              
                      // try to get phonebook
                      if (call[id].connect && call[id].direction === "in" && adapter.config.enableTAM) {
                          getTAM(adapter.config.fritzboxAddress, adapter.config.fritzboxUser, adapter.config.fritzboxPassword);
                      }
              
                  } // End DSICONNECT
              
              
                  if (showCallmonitor) {
                      // alle drei Callmonitorausgaben mit Sekundenintervall werden auf einmal aktualisiert, auch, wenn ein Anrufzustand keinen aktiven Zähler hat
                      // wird aus Performancegründen eine Optimierung benötigt, muss es für CONNECT und RING eigenen Intervalle geben
                      // (weniger Traffic und Speicherplatz) # Hinweis: der Callmonitor kann auch in der Config (webadmin) deaktiviert werden
                      if (allActiveCount && !intervalRunningCall) {
                          // Do it every second
                          intervalRunningCall = setInterval(function () {
              
                              // adapter.log.debug("###### callmonitor aktiv ######");
              
                              for (var i = 0; i < listConnect.length; i++) {
                                  call[listConnect[i].id].durationSecs2 = dateEpochNow() / 1000 - call[listConnect[i].id].dateEpochNow / 1000;
                              }
                              for (var i = 0; i < listRing.length; i++) {
                                  call[listRing[i].id].durationRingSecs = dateEpochNow() / 1000 - call[listRing[i].id].dateEpochNow / 1000;
                              }
                              adapterSetOnChange ("callmonitor.connect" , callmonitor(listConnect));
                              adapterSetOnChange ("callmonitor.ring" , callmonitor(listRing));
                              adapterSetOnChange ("callmonitor.all" , callmonitorAll(listAll));
              //                adapter.setState('callmonitor.connect', callmonitor(listConnect), true);
              //                adapter.setState('callmonitor.ring', callmonitor(listRing), true);
              //                adapter.setState('callmonitor.all', callmonitorAll(listAll), true);
              
                          }, 1000);
                      } else if (!allActiveCount && intervalRunningCall) {
                          // Stop interval
                          clearInterval(intervalRunningCall);
                          intervalRunningCall = null;
                      }
              
                      adapterSetOnChange ("callmonitor.call" , callmonitor(listCall));
              //        adapter.setState('callmonitor.call', callmonitor(listCall), true);
              
                      // wenn der Interval beendet wird, müssen die letzet Anrufe im Callmonitor auch bereinigt werden
                      // die Callmonitorlisten werden zusätzlich zum Intervall mit jeder Fritzbox-Meldung aktualisiert
                      // nur im else-Zweig im Intervall ist dies nicht ausreichend, wg. der Asynchonität
                      adapterSetOnChange ("callmonitor.connect" , callmonitor(listConnect));
                      adapterSetOnChange ("callmonitor.ring" , callmonitor(listRing));
                      adapterSetOnChange ("callmonitor.all" , callmonitorAll(listAll));
                  }
              
              
              
              }
              
              
              
              function handleWLANConfiguration(config) {
                  //console.log(config);
                  adapter.log.debug("WLAN: " + config['NewEnable']);
                  wlanState = config['NewEnable'] == 1;
                  adapterSetOnChange("wlan.enabled", wlanState);
              }
              
              
              
              function connectToTR064(host, user, password, callback) {
                  var tr064 = new tr.TR064();
                  tr064.initTR064Device(host, 49000, function (err, device) {
                      if (!err) {
                          device.startEncryptedCommunication(function (err, sslDev) {
                              if (!err) {
                                  sslDev.login(user, password);
                                  callback(sslDev);
                              }
                              else {
                                  adapter.log.warn("TR-064 error: " + err);
                              }
                          });
                      }
                      else {
                          adapter.log.warn("TR-064 error: " + err);
                      }
                  });
              }
              
              
              
              function getWlanConfig(host, user, password, callback) {
                  connectToTR064(host, user, password, function (sslDev) {
                      var wlanConfig = sslDev.services["urn:dslforum-org:service:WLANConfiguration:1"];
                      adapter.log.debug("TR-064: calling GetInfo()");
                      wlanConfig.actions.GetInfo(function (err, result) {
                          if (!err) {
                              adapter.log.debug("TR-064: got result from GetInfo()");
                              callback(result);
                          }
                          else {
                              adapter.log.warn("TR-064 error: " + err);
                          }
                      });
                  });
              }
              
              
              
              function setWlanEnabled(host, user, password, enabled) {
                  connectToTR064(host, user, password, function (sslDev) {
                      var wlanConfig = sslDev.services["urn:dslforum-org:service:WLANConfiguration:1"];
                      adapter.log.debug("TR-064: calling SetEnable(" + enabled + ")");
                      wlanConfig.actions.SetEnable({ 'NewEnable': enabled ? 1 : 0 }, function (err, result) {
                          if (!err) {
                              adapter.log.debug("TR-064: got result from SetEnable()");
                          }
                          else {
                              adapter.log.warn("TR-064 error: " + err);
                          }
                      });
                  });
              }
              
              function getTAM(host, user, password) {
                  connectToTR064(host, user, password, function (sslDev) {
                      var tam = sslDev.services["urn:dslforum-org:service:X_AVM-DE_TAM:1"];
                      adapter.log.debug("TR-064: Calling GetMessageList()");
                      tam.actions.GetMessageList({NewIndex: 0}, function(err, ret) {
                          if (err) {
                              adapter.log.warn("TR-064: Error while calling GetMessageList(): " + err);
                          } else if (ret.NewURL && ret.NewURL.length > 0) {
                              var url = ret.NewURL;
                              adapter.log.debug("TR-064: Got TAM uri: " + url);
                              var baseUrl = url.substring(0, url.lastIndexOf('/'));
                              var sid = url.match(/sid=([\d\w]+)/)[1];
                              adapter.log.debug(`TR-064: sid=${sid}`);
              
                              var agentOptions;
              				var agent;
              
              				agentOptions = {
              				  rejectUnauthorized: false
              				};
              
              				agent = new https.Agent(agentOptions);
              
                              request({
              				  url: url
              				, method: 'GET'
              				, agent: agent
              				}, function (error, response, body) {
                                  if (!error && response.statusCode == 200) {
                                      adapter.log.debug("TR-064: Got valid TAM content from, starting to parse ...");
                                      var parser = new xml2js.Parser();
                                      parser.parseString(body, function (err, result) {
                                          if (err) {
                                              adapter.log.warn("TR-064: Error while parsing TAM content: " + err);
                                          } else {
                                              adapter.log.debug("TR-064: Successfully parsed TAM content, analyzing result ...");
                                              var promises = [];
                                              var messages = [];
              
                                              mkdirSync('tam', { recursive: true });
              
                                              if(!result.Root.Message) {
                                                  // No messahes
                                                  promises.push(new Promise((resolve, reject) => resolve()));
                                              } else {
                                                  for (var m = 0; m <= result.Root.Message.length; m++) {
                                                      var message = result.Root.Message[m];
                                                      if (typeof message != 'undefined') {
                                                          promises.push(new Promise((resolve, reject) => {
                                                              var msg = {
                                                                  index: message.Index[0],
                                                                  calledNumber: message.Called[0],
                                                                  date: message.Date[0],
                                                                  duration: message.Duration[0],
                                                                  callerName: message.Name[0],
                                                                  callerNumber: message.Number[0],
                                                                  audioFile: ''
                                                              };
                                                              if (!message.Path || message.Path.length < 1) {
                                                                  adapter.log.warn("TR-064: TAM message has no url");
                                                                  resolve(msg);
                                                                  return;
                                                              }
              
                                                              var callDate = message.Date[0].split('.').join("").split(':').join("").split(' ').join("");
                                                              var file = `tam/${callDate}-${message.Number[0]}.wav`
                                                              adapter.log.debug(`TR-064: TAM message file: ${file}`);
                                                              if (existsSync(file)) {
                                                                  msg.audioFile = path.resolve(file);
                                                                  resolve(msg);
                                                                  return;
                                                              }
              
                                                              var downloadUrl = message.Path[0];
                                                              if (downloadUrl.startsWith('/')) {
                                                                  downloadUrl = baseUrl + downloadUrl;
                                                              }
                                                              if (downloadUrl.indexOf('sid=')<0){
                                                                  downloadUrl += `&sid=${sid}`;
                                                              }
                                                              adapter.log.debug(`TR-064: Download TAM audio file from ${downloadUrl}`);
              
                                                              const stream = createWriteStream(file);
                                                              request({url: downloadUrl, agent: agent})
                                                                  .on('error', function(err) {
                                                                      unlink(file);
                                                                      adapter.log.warn(
                                                                          `TR-064: Error while downloading TAM audio file: ${err}`
                                                                      );
                                                                  })
                                                                  .pipe(stream)
                                                                  .on('error', function(err) {
                                                                      unlink(file);
                                                                      adapter.log.warn(
                                                                          `TR-064: Error while writing TAM audio file: ${err}`
                                                                      );
                                                                  })
                                                                  .on('finish', function() {
                                                                      stream.close(function() {
                                                                          msg.audioFile = path.resolve(file);
                                                                          resolve(msg);
                                                                      });
                                                                  });
              
                                                          }).then((result) => {
                                                              messages.push(result);
                                                          }));
                                                      }
                                                  }
                                              }
              
                                              Promise.all(promises).then(function() {
                                                  messages.sort((m1,m2) => m1.index > m2.index ? 1 : m1.index < m2.index ? -1 : 0);
              
                                                  // cleanup old files
                                                  readdir('tam', (err, files) => {
                                                      if (err) {
                                                          adapter.log.warn(
                                                              `TR-064: Error reading files from dir /tam: ${err}`
                                                          );
                                                      } else {
                                                          files.forEach(file => {
                                                              file = path.resolve("tam/" + file);
                                                              if (!messages.find(msg => msg.audioFile == file)) {
                                                                  // old file
                                                                  adapter.log.debug(
                                                                      `TR-064: Remove old tam audio file: ${file}`
                                                                  );
                                                                  unlink(file, function(err){
                                                                      if (err) {
                                                                          adapter.log.warn(
                                                                              `TR-064: Error deleting file ${file}: ${err}`
                                                                          );
                                                                      }
                                                                  });
                                                              }
                                                          })
                                                      }
                                                  })
              
                                                  adapter.setState('tam.messagesJSON', JSON.stringify(messages), true);
                                                  adapter.log.debug("TR-064: Successfully analyzed TAM results");
                                              });
                                          }
                                      });
                                  } else {
              						adapter.log.warn(
              							`TR-064: Error while requesting TAM: ${error}`
              						);
              					}
                              });
                          }
                      });
                  });
              }
              
              function getPhonebook(host, user, password) {
                  connectToTR064(host, user, password, function (sslDev) {
                      var tel = sslDev.services["urn:dslforum-org:service:X_AVM-DE_OnTel:1"];
                      adapter.log.debug("TR-064: Calling GetPhonebook()");
                      tel.actions.GetPhonebook({ NewPhonebookID: '0' }, function (err, ret) {
                          if (err) {
                              adapter.log.warn("TR-064: Error while calling GetPhonebook(): " + err);
                          } else if (ret.NewPhonebookURL && ret.NewPhonebookURL.length > 0) {
                              var url = ret.NewPhonebookURL;
                              adapter.log.debug("TR-064: Got phonebook uri: " + url);
              
              				var agentOptions;
              				var agent;
              
              				agentOptions = {
              				  rejectUnauthorized: false
              				};
              
              				agent = new https.Agent(agentOptions);
              
                              request({
              				  url: url
              				, method: 'GET'
              				, agent: agent
              				}, function (error, response, body) {
                                  if (!error && response.statusCode == 200) {
                                      adapter.log.debug("TR-064: Got valid phonebook content from, starting to parse ...");
                                      var parser = new xml2js.Parser();
                                      parser.parseString(body, function (err, result) {
                                          if (err) {
                                              adapter.log.warn("TR-064: Error while parsing phonebook content: " + err);
                                          } else {
                                              adapter.log.debug("TR-064: Successfully parsed phonebook content, analyzing result ...");
                                              var phonenumbers = []; // create an empty array for fetching all configured phone numbers from fritzbox
                                              var phonebook = result.phonebooks.phonebook[0];
                                              for (var c = 0; c <= phonebook.contact.length; c++) {
                                                  var contact = phonebook.contact[c];
                                                  if (typeof contact != 'undefined') {
                                                      var entryName = contact.person[0].realName[0];
                                                      for (var t = 0; t <= contact.telephony.length; t++) {
                                                          var telephony = contact.telephony[t];
                                                          if (typeof telephony != 'undefined') {
                                                              for (var n = 0; n <= telephony.number.length; n++) {
                                                                  var number = telephony.number[n];
                                                                  if (typeof number != 'undefined') {
                                                                      var entryNumber = number._;
                                                                      var entryType = number.$.type;
                                                                      if (entryNumber.startsWith('0') || entryNumber.startsWith('+')) {
                                                                          phonenumbers.push({
                                                                              key: entryNumber,
                                                                              value: { name: entryName, type: entryType }
                                                                          });
                                                                      }
                                                                  }
                                                              }
                                                          }
                                                      }
                                                  }
                                              }
              
                                              adapter.setState('phonebook.tableJSON', JSON.stringify(phonenumbers), true);
                                              adapter.log.debug("TR-064: Successfully analyzed phonebook results");
                                          }
                                      });
                                  } else {
              						adapter.log.warn(
              							`TR-064: Error while requesting phonebook: ${error}`
              						);
              					}
                              });
                          }
                      });
                  });
              }
              
              function connectToFritzbox(host) {
                  clearRealtimeVars(); // IP-Verbindung neu: Realtimedaten werden gelöscht, da ggf. nicht konsistent
                  var socketBox = net.connect({port: 1012, host: host}, function() {
                      adapter.log.info("adapter connected to fritzbox: " + host);
                  });
              
                  var connecting = false;
                  function restartConnection() {
                      if (socketBox) {
                          socketBox.end();
                          socketBox = null;
                      }
              
                      if (!connecting){
                          adapter.log.warn("restartConnection: " + host);
                          clearRealtimeVars(); // IP-Verbindung neu: Realtimedaten werden gelöscht, da ggf. nicht konsistent
                          connecting = setTimeout(function () {
                              connectToFritzbox(host);
                          }, 10000);
                      }
                  }
              
                  socketBox.on('error', restartConnection);
                  socketBox.on('close', restartConnection);
                  socketBox.on('end',   restartConnection);
              
                  socketBox.on('data',  parseData);   // Daten wurden aus der Fritzbox empfangen und dann in der Funktion parseData verarbeitet
              
                  if ((adapter.config.enableWlan || adapter.config.enablePhonebook || adapter.config.enableTAM)
                      && adapter.config.fritzboxUser && adapter.config.fritzboxPassword && adapter.config.fritzboxPassword.length) {
                      adapter.log.info("Trying to connect to TR-064: " + host + ":49000");
              
                      // try to get WLAN status and enable timer
                      if (adapter.config.enableWlan) {
                          getWlanConfig(host, adapter.config.fritzboxUser, adapter.config.fritzboxPassword, function (result) {
                              adapter.log.info("Successfully connected to TR-064");
                              handleWLANConfiguration(result);
                              intervalTR046 = setInterval(function () {
                                  getWlanConfig(host, adapter.config.fritzboxUser, adapter.config.fritzboxPassword, function (result) {
                                      handleWLANConfiguration(result);
                                  });
                              }, 10000);
                          });
                      }
              
                      // try to get phonebook
                      if (adapter.config.enablePhonebook) {
                          getPhonebook(host, adapter.config.fritzboxUser, adapter.config.fritzboxPassword);
                      }
              
                      // try to get tel answering machine
                      if (adapter.config.enableTAM) {
                          getTAM(host, adapter.config.fritzboxUser, adapter.config.fritzboxPassword);
                      }
                  }
              }
              
              
              
              function main() {
                  adapter.log.debug("< main >");
              
                  initVars(); // ioBroker Objekte übernehmen wenn vorhanden, sonst init
              
                  // Zustandsänderungen innerhalb der ioBroker fritzbox-Adapterobjekte überwachen
              //    adapter.subscribeForeignStates("node-red.0.fritzbox.*); // Beispiel um Datenpunkte anderer Adapter zu überwachen
                  adapter.subscribeStates('calls.missedCount');
                  adapter.subscribeStates('wlan.enabled');
              
                  // TODO: IP-Prüfung bringt nichts, da auch Hostnamen / DNS erlaubt sind & eine Prüfung auf der Admin-Webseite ist sinnvoller
                  var validIP = /^((25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])$/;
                  if (!adapter.config.fritzboxAddress.match(validIP)) {
                      adapter.log.info("no valid ip-Adress: " + adapter.config.fritzboxAddress) + ". Is it a valid hostname?";
                  }
              
                  if (adapter.config.fritzboxAddress && adapter.config.fritzboxAddress.length) {
                      adapter.log.info("try to connect: " + adapter.config.fritzboxAddress);
                      connectToFritzbox(adapter.config.fritzboxAddress);      // fritzbox
                  } else {
                      adapter.log.error("<< ip-Adresse der Fritzbox unbekannt >>");
                  }
              
                  /*
                  // Zweitanmeldung für Entwicklung: Fritzbox Simulator / Callgenerator
                  if (ipCallgen && ipCallgen.length) {
                      adapter.log.info("try to connect: " + ipCallgen);
                      connectToFritzbox(ipCallgen);   // callgen
                  }
              */
                  onlyOne = true; // Flag, um Aktionen nur einmal nach Programmstart auszuführen
              }
              
              
              
              

              Ich glaube in Zeile 1316 wird das irgendwie geschrieben, kann das sein? Oder ehr Zeile 1268...

              1 Antwort Letzte Antwort
              0
              • L Larson-Sei180LX

                @liv-in-sky
                Danke für den Hinweis. Aber wie funktioniert das, einen LINK dort zu hinterlegen?

                liv-in-skyL Offline
                liv-in-skyL Offline
                liv-in-sky
                schrieb am zuletzt editiert von
                #7

                @larson-sei180lx

                bin mir nicht ganz sicher:

                du wirst evtl einen audioplayer in die vis einbauen müssen - ansonsten wird vielleicht die vis im browser verlassen, um das file aufzurufen

                vielleicht wäre eine lösung mit dem sayit adapter möglich https://github.com/ioBroker/ioBroker.sayit

                ich habe leider im momentan nicht die zeit etwas umzuprogrammieren, da es mir auch zu aufwendig erscheint, dass ganze nebenbei zu machen

                ps: deine zeilenangabe weißt darauf hin (wo "etwas" geschrieben wird), dass du den firefox beim kopieren des scriptes genutzt hast - der fügt teilweise leerzeichen ein - um scripte zu kopieren nimm lieber chrome

                nach einem gelösten Thread wäre es sinnvoll dies in der Überschrift des ersten Posts einzutragen [gelöst]-... Bitte benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat. Forum-Tools: PicPick https://picpick.app/en/download/ und ScreenToGif https://www.screentogif.com/downloads.html

                L 1 Antwort Letzte Antwort
                0
                • liv-in-skyL liv-in-sky

                  @larson-sei180lx

                  bin mir nicht ganz sicher:

                  du wirst evtl einen audioplayer in die vis einbauen müssen - ansonsten wird vielleicht die vis im browser verlassen, um das file aufzurufen

                  vielleicht wäre eine lösung mit dem sayit adapter möglich https://github.com/ioBroker/ioBroker.sayit

                  ich habe leider im momentan nicht die zeit etwas umzuprogrammieren, da es mir auch zu aufwendig erscheint, dass ganze nebenbei zu machen

                  ps: deine zeilenangabe weißt darauf hin (wo "etwas" geschrieben wird), dass du den firefox beim kopieren des scriptes genutzt hast - der fügt teilweise leerzeichen ein - um scripte zu kopieren nimm lieber chrome

                  L Offline
                  L Offline
                  Larson-Sei180LX
                  schrieb am zuletzt editiert von
                  #8

                  @liv-in-sky
                  Danke trotzdem, vl meldet sich ja noch jemand, da ich in JS wirklich blank bin.

                  1 Antwort Letzte Antwort
                  0
                  • L Larson-Sei180LX

                    Hallo an die wissenden Mitleser,

                    ich bin in Sachen JS erst durch ioBroker gekommen und habe gerade ein Problem, bei dem ich einen Denkanstoß bzw. Hilfe eines Experten von Euch brauche.

                    Folgender Ausgangssachverhalt:
                    Ich habe ein JSON File, das mir erfolgreich eine Tabelle in VIS mit dem Widget Basic JSON Tablet aufschlüsselt. Es geht hierbei um eine Anrufliste aus der Fritzbox, die über die Daten des Adapter iobroker.fritzbox die Anrufliste ausliest. Auch habe ich es geschafft, dass eine WAV Datei des Fritzbox-ABs auf den PI in ein Verzeichnis extrahiert wird, sofern eine Nachricht vorhanden ist. Dessen Pfad wird dann zugehörig zum Eintrag genau in dieser Tabelle unter der Spaltenüberschrift "AudioFile" abgelegt.

                    Ich würde jetzt gerne erreichen, dass man dieses Audiofile am Tablet "abhören" kann. Demnach dachte ich an eine Art Verlinkung des Pfades, so dass man in der Tabelle danach nur noch auf den Link klicken muss.

                    Da das Format JSON wohl (zumindest mir nicht bekannt) nicht für einen Link geeignet ist, habe ich das JSON mittels eines Skriptes aus dem Forum hier in ein HTML umformatiert. Leider schaffe ich es nicht, in der Spalte AudioFile einen anklickbaren LINK daraus zu kreieren. Kann mir hierbei vielleicht jemand weiterhelfen?

                    Hier das Grund-JSON File, wie es vorliegt:

                    [
                      {
                        "index": "0",
                        "calledNumber": "4996666666",
                        "date": "15.05.23 22:25",
                        "duration": "0:01",
                        "callerName": "Hans Müller",
                        "callerNumber": "097654321",
                        "audioFile": "/opt/iobroker/node_modules/iobroker.fritzbox/tam/1505232225-097654321.wav"
                      },
                      {
                        "index": "1",
                        "calledNumber": "4996666666",
                        "date": "15.05.23 22:36",
                        "duration": "0:01",
                        "callerName": "Hans Müller",
                        "callerNumber": "097654321",
                        "audioFile": "/opt/iobroker/node_modules/iobroker.fritzbox/tam/1505232236-097654321.wav"
                      },
                      {
                        "index": "2",
                        "calledNumber": "4996666666",
                        "date": "15.05.23 22:52",
                        "duration": "0:01",
                        "callerName": "Hans Müller",
                        "callerNumber": "097654321",
                        "audioFile": "/opt/iobroker/node_modules/iobroker.fritzbox/tam/1505232252-097654321.wav"
                      }
                    ]
                    

                    Und hier das HTML-Formatierungsskript:

                    /******************************************************************************************************
                     * JSON-Datenpunkt in HTML umwandeln
                     * --------------------------------------------------------------
                     * Zweck:      Überwacht einen JSON-Datenpunkt und sobald geändert, wird JSON in HTML umgewandelt und
                     *             in einem eigenen Datenpunkt ausgegeben
                     * Publiziert: https://forum.iobroker.net/topic/32540/json-zu-html-und-in-datei-schreiben-ablegen
                     * Autor:      Mic-M (Github) | Mic (ioBroker)
                     * --------------------------------------------------------------------------------------
                     * Change Log:
                     *  0.0.1  Mic-M   * Initial release
                     ******************************************************************************************************/
                    
                    /*********************************************************************************************
                     * Einstellungen
                     *********************************************************************************************/
                    // JSON-Datenpunkt
                    const g_jsonState = 'fritzbox.0.tam.messagesJSON';
                    
                    // Neuer Datenpunkt für HTML-Ausgabe
                    const g_htmlState = 'javascript.0.html-tables.log-Info';
                    
                    // Spalte entfernen (für Log Parser Adapter 'ts' nehmen, da dieser autmatisch den timestamp hinzufügt),
                    // sonst leer lassen
                    const g_removeColumn = 'ts';
                    
                    
                    /*********************************************************************************************
                     * Ab hier nichts mehr ändern
                     *********************************************************************************************/
                    main();
                    function main() {
                    
                        // Create state for HTML, if not yet existing
                        createState(g_htmlState, {'name':'HTML Table', 'type':'string', 'read':true, 'write':false, 'role':'html', 'def':'' }, () => {
                            // State created, so let's subscribe to the given JSON state
                            on({id: g_jsonState, change:'ne'}, function(obj) {
                                // JSON state changed            
                                if(obj.state.val && obj.state.val.length > 10) {
                                    // state is not empty
                                    const jsonObject = JSON.parse(obj.state.val);
                                    if(g_removeColumn) {
                                        for (let lpEntry of jsonObject) {
                                            delete lpEntry[g_removeColumn];
                                        }
                                    }
                                    setState(g_htmlState, json2table(jsonObject, ''));
                                }
                            });
                    
                        });
                    
                    }
                    
                    
                    
                    /**
                     * Convert JSON to HTML table
                     * Source: https://travishorn.com/building-json2table-turn-json-into-an-html-table-a57cf642b84a
                     * 
                     * @param {object}  jsonObject    The JSON as object.
                     * @param {string}  [classes]     Optional: You can apply one or multiple classes (space separated) to <table>.
                     * @return {string}               The HTML result as string
                     */
                    function json2table(jsonObject, classes = '') {
                        const cols = Object.keys(jsonObject[0]);
                    
                        let headerRow = '';
                        let bodyRows = '';
                    
                        classes = classes || '';
                    
                        cols.map(function(col) {
                            headerRow += '<th>' + capitalizeFirstLetter(col) + '</th>';
                        });
                    
                        jsonObject.map(function(row) {
                    
                            bodyRows += '<tr>';
                    
                            cols.map(function(colName) {
                                bodyRows += '<td>' + row[colName] + '</td>';
                            })
                    
                            bodyRows += '</tr>';
                    
                        });
                    
                        const addClasses = (classes && classes.length > 1) ? ' class="' + classes + '"' : '';
                        return '<table' + addClasses + '><thead><tr>' +
                                headerRow +
                                '</tr></thead><tbody>' +
                                bodyRows +
                                '</tbody></table>';
                    
                        function capitalizeFirstLetter(string) {
                            return string.charAt(0).toUpperCase() + string.slice(1);
                        }
                    
                    }
                    
                    

                    Das Umformeln der Tabelle klappt prima. Wie es danach weitergeht, weiss ich noch nicht. Am liebsten wäre mir, dass er dann das File abspielt und habe eben die Hoffnung, dass das funktioniert, wenn man auf den LINK klickt. Vielleicht hat auch noch jemand eine bessere Idee, bin für alle Ratschläge offen. Mein JS Wissen ist leider nicht weit.

                    Vielen lieben Dank für Eure Bemühungen

                    Andy

                    OliverIOO Offline
                    OliverIOO Offline
                    OliverIO
                    schrieb am zuletzt editiert von OliverIO
                    #9

                    @larson-sei180lx sagte in Tabelle JSON -> HTML - Link Abspielen WAV Datei am Tablet:

                    Da das Format JSON wohl (zumindest mir nicht bekannt) nicht für einen Link geeignet ist

                    du kannst als value-wert auch direkt html eintragen.
                    ich habe das soeben mit dem test-json mal ausprobiert

                    [{"Title": "first", "Value": "<a href=\"https://www.google.de\">abc</a>", "_Description": "Value1"}, {"Title": "second", "Value": 2, "_Description": "Value2"}]
                    

                    du musst nur darauf achten, das du die Anführungsstriche die innterhalb der value vorkommen mit einem backslash escapest

                    um dann eine mp3 abspielen zu lassen, kannst du dann die normalen anweisungen für html anwenden, indem du als value ein audio-tag einträgst.
                    https://www.w3schools.com/html/html5_audio.asp
                    als audio src trägst du den link zu deiner mp3 ein, so das der browser sie auch erreichen kann. dazu musst du die datei dem iobroker webserver bekannt machen.
                    https://www.smarthome-tricks.de/software-iobroker/bilder-in-iobroker-ueber-vis-im-netzwerk-verfuegbar-machen/#:~:text=Alternativ einfach auf das Feld,in der Datei- und Ordnerauswahl.

                    nicht irritieren lassen das es hier um bilder geht. das selbe gilt auch für sound-dateien. dem server ist es egal was für typ von datei da liegt

                    Meine Adapter und Widgets
                    TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
                    Links im Profil

                    L 2 Antworten Letzte Antwort
                    0
                    • OliverIOO OliverIO

                      @larson-sei180lx sagte in Tabelle JSON -> HTML - Link Abspielen WAV Datei am Tablet:

                      Da das Format JSON wohl (zumindest mir nicht bekannt) nicht für einen Link geeignet ist

                      du kannst als value-wert auch direkt html eintragen.
                      ich habe das soeben mit dem test-json mal ausprobiert

                      [{"Title": "first", "Value": "<a href=\"https://www.google.de\">abc</a>", "_Description": "Value1"}, {"Title": "second", "Value": 2, "_Description": "Value2"}]
                      

                      du musst nur darauf achten, das du die Anführungsstriche die innterhalb der value vorkommen mit einem backslash escapest

                      um dann eine mp3 abspielen zu lassen, kannst du dann die normalen anweisungen für html anwenden, indem du als value ein audio-tag einträgst.
                      https://www.w3schools.com/html/html5_audio.asp
                      als audio src trägst du den link zu deiner mp3 ein, so das der browser sie auch erreichen kann. dazu musst du die datei dem iobroker webserver bekannt machen.
                      https://www.smarthome-tricks.de/software-iobroker/bilder-in-iobroker-ueber-vis-im-netzwerk-verfuegbar-machen/#:~:text=Alternativ einfach auf das Feld,in der Datei- und Ordnerauswahl.

                      nicht irritieren lassen das es hier um bilder geht. das selbe gilt auch für sound-dateien. dem server ist es egal was für typ von datei da liegt

                      L Offline
                      L Offline
                      Larson-Sei180LX
                      schrieb am zuletzt editiert von
                      #10

                      @oliverio said in Tabelle JSON -> HTML - Link Abspielen WAV Datei am Tablet:

                      "<a href="https://www.google.de">abc</a>"

                      Ich habe mal versucht das MAIN.JS file anzupassen....

                      Meinst du das dann so?

                                                                    var msg = {
                                                                          index: message.Index[0],
                                                                          calledNumber: message.Called[0],
                                                                          date: message.Date[0],
                                                                          duration: message.Duration[0],
                                                                          callerName: message.Name[0],
                                                                          callerNumber: message.Number[0],
                                                                          audioFile: "<a href=\"''">Link zum File</a>"
                                                                      };
                      
                      1 Antwort Letzte Antwort
                      0
                      • OliverIOO OliverIO

                        @larson-sei180lx sagte in Tabelle JSON -> HTML - Link Abspielen WAV Datei am Tablet:

                        Da das Format JSON wohl (zumindest mir nicht bekannt) nicht für einen Link geeignet ist

                        du kannst als value-wert auch direkt html eintragen.
                        ich habe das soeben mit dem test-json mal ausprobiert

                        [{"Title": "first", "Value": "<a href=\"https://www.google.de\">abc</a>", "_Description": "Value1"}, {"Title": "second", "Value": 2, "_Description": "Value2"}]
                        

                        du musst nur darauf achten, das du die Anführungsstriche die innterhalb der value vorkommen mit einem backslash escapest

                        um dann eine mp3 abspielen zu lassen, kannst du dann die normalen anweisungen für html anwenden, indem du als value ein audio-tag einträgst.
                        https://www.w3schools.com/html/html5_audio.asp
                        als audio src trägst du den link zu deiner mp3 ein, so das der browser sie auch erreichen kann. dazu musst du die datei dem iobroker webserver bekannt machen.
                        https://www.smarthome-tricks.de/software-iobroker/bilder-in-iobroker-ueber-vis-im-netzwerk-verfuegbar-machen/#:~:text=Alternativ einfach auf das Feld,in der Datei- und Ordnerauswahl.

                        nicht irritieren lassen das es hier um bilder geht. das selbe gilt auch für sound-dateien. dem server ist es egal was für typ von datei da liegt

                        L Offline
                        L Offline
                        Larson-Sei180LX
                        schrieb am zuletzt editiert von
                        #11

                        @oliverio

                        Ich bin gerade echt bisschen überfordert... Kannst Du mir vl Unterstützung bei dem File geben, wie es am Ende aussehen muss ?

                        OliverIOO 2 Antworten Letzte Antwort
                        0
                        • L Larson-Sei180LX

                          @oliverio

                          Ich bin gerade echt bisschen überfordert... Kannst Du mir vl Unterstützung bei dem File geben, wie es am Ende aussehen muss ?

                          OliverIOO Offline
                          OliverIOO Offline
                          OliverIO
                          schrieb am zuletzt editiert von OliverIO
                          #12

                          @larson-sei180lx

                          ja, allerdings war der link tag nur ein beispiel, den du selber schnell ausprobieren kannst, das man da auch direkt html reinschreiben kann.
                          um audio im browser abspielen zu können musst du den audio-tag verwenden.

                          da ich jetzt nicht weiß wo deine dateien liegen, muss man evtl den folgenden code noch etwas anpassen, siehe kommentar.
                          das deine wav datei für den browser abrufbar ist, kannst du testen in dem du den link direkt in den browser einträgst, der muss dann unter audioFile in den audio tag eingetragen werden.
                          schau dir am besten mal die beispiele bei wwwschools an.

                          var datei = "file.wav" // entweder hier den dateinamen zuweisen oder deine variable weiter unten anpassen, es sollte aber nicht der komplette pfad drin stehen, sondern nur der pfad unterhalb von /vis.0/"
                          var audio = `
                          <audio controls>
                            <source src="/vis.0/`+datei+`" type="audio/wav">
                          Your browser does not support the audio element.
                          </audio>
                          `;
                          var msg = {
                                                                              index: message.Index[0],
                                                                              calledNumber: message.Called[0],
                                                                              date: message.Date[0],
                                                                              duration: message.Duration[0],
                                                                              callerName: message.Name[0],
                                                                              callerNumber: message.Number[0],
                                                                              audioFile: audio
                                                                          };
                          

                          damit es lesbarer ist, habe ich für den string backticks vertwendet. da muss man die anführungsstriche auch nicht escapen

                          mit den web developer tools kannst du schauen, wie das html dann genau im browser ankommt (F12 im edge, chrome und firefox)

                          Meine Adapter und Widgets
                          TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
                          Links im Profil

                          1 Antwort Letzte Antwort
                          0
                          • OliverIOO Offline
                            OliverIOO Offline
                            OliverIO
                            schrieb am zuletzt editiert von OliverIO
                            #13

                            @larson-sei180lx

                            hier mal noch eine Lösung, wie du die Dateien in den webserver hochgeladen bekommst

                            const chokidar = require('chokidar'); //bibliothek zur Überwachung von Verzeichnissen und Dateien. chokidar muss in den adapter einstellungen für javascript als zusätzliches npm modul eingetragen werden
                            const fs = require('fs');
                            const nodepath = require('node:path')
                            
                            const watchpath  = "/opt/iobroker/node_modules/iobroker.fritzbox/tam/"; //verzeichnisname der überwacht werden soll
                            const vispath    = "/anrufbeantworter/"; //verzeichnisname unter dem du dann die dateien im browser abrufen kannst 
                            //bspw dann 
                            // /opt/iobroker/node_modules/iobroker.fritzbox/tam/1505232225-097654321.wav
                            //kann als
                            // ipiobroker:8081/anrufbeantworter/1505232225-097654321.wav
                            // abgerufen werden. das ist dann auch der name, den du in dein json-table datenpunkt reinschreibst
                            
                            //code zur überwachung. vgl dokumentation von chokidar
                            //der code wird aufgerufen, falls im überwachten verzeichnis eine datei hinzugefügt wird.
                            const watcher = chokidar.watch(watchpath).on("add", async (path) => {
                              log(path); //hier wird in der console dann der pfad ausgegeben, welche datei hinzugefügt wurde
                              var data = fs.readFileSync(path); //lesen des dateiinhalts
                              writeFile('vis.0', vispath+nodepath.basename(path), data, function (error) { //hochladen des dateiinhalts und definition des neuen dateinamens
                                  console.log('file written'); //optionale ausgabe in die console
                              });
                            });
                            
                            //wichtig, damit die überwachung des verzeichnisses auch bei beenden des skripts wieder angehalten wird. ansonsten multiplizieren sich die ereignisse mit jedem neuen start des skripts
                            onStop (function(){
                                watcher.close()
                            });
                            
                            
                            

                            Meine Adapter und Widgets
                            TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
                            Links im Profil

                            1 Antwort Letzte Antwort
                            0
                            • L Larson-Sei180LX

                              @oliverio

                              Ich bin gerade echt bisschen überfordert... Kannst Du mir vl Unterstützung bei dem File geben, wie es am Ende aussehen muss ?

                              OliverIOO Offline
                              OliverIOO Offline
                              OliverIO
                              schrieb am zuletzt editiert von
                              #14

                              @larson-sei180lx

                              hier noch eine kleine erweiterung, das die dateien aus dem webspace wieder gelöscht werden, wenn sie im überwachten verzeichnis ebenfalls gelöscht werden

                              const chokidar = require('chokidar');
                              const fs = require('fs');
                              const nodepath = require('node:path')
                              
                              const watchpath  = "/opt/iobroker/node_modules/iobroker.fritzbox/tam/";
                              const vispath    = "/anrufbeantworter/";
                              
                              // One-liner for current directory
                              const watcher = chokidar.watch(watchpath).on("add", async (path) => {
                                log("add "+path);
                                var data = fs.readFileSync(path);
                                writeFile('vis.0', vispath+nodepath.basename(path), data, function (error) {
                                    console.log('file written');
                                });
                              }).on("unlink", async (path) => {
                                log("del "+path);
                                delFile("vis.0", vispath+nodepath.basename(path), function (error) {
                                    console.log('file deleted');
                                });
                              });
                              onStop (function(){
                                  watcher.close()
                              });
                              
                              

                              Meine Adapter und Widgets
                              TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
                              Links im Profil

                              L 1 Antwort Letzte Antwort
                              0
                              • OliverIOO OliverIO

                                @larson-sei180lx

                                hier noch eine kleine erweiterung, das die dateien aus dem webspace wieder gelöscht werden, wenn sie im überwachten verzeichnis ebenfalls gelöscht werden

                                const chokidar = require('chokidar');
                                const fs = require('fs');
                                const nodepath = require('node:path')
                                
                                const watchpath  = "/opt/iobroker/node_modules/iobroker.fritzbox/tam/";
                                const vispath    = "/anrufbeantworter/";
                                
                                // One-liner for current directory
                                const watcher = chokidar.watch(watchpath).on("add", async (path) => {
                                  log("add "+path);
                                  var data = fs.readFileSync(path);
                                  writeFile('vis.0', vispath+nodepath.basename(path), data, function (error) {
                                      console.log('file written');
                                  });
                                }).on("unlink", async (path) => {
                                  log("del "+path);
                                  delFile("vis.0", vispath+nodepath.basename(path), function (error) {
                                      console.log('file deleted');
                                  });
                                });
                                onStop (function(){
                                    watcher.close()
                                });
                                
                                
                                L Offline
                                L Offline
                                Larson-Sei180LX
                                schrieb am zuletzt editiert von
                                #15

                                @oliverio

                                Vielen lieben Dank. Ich glaube so langsam bekomme ich das hin. Habe gestern schon einiges mit deinen Informationen gespielt. Wenn ich "durch" bin poste ich hier das Ergebnis!

                                OliverIOO 1 Antwort Letzte Antwort
                                0
                                • L Larson-Sei180LX

                                  @oliverio

                                  Vielen lieben Dank. Ich glaube so langsam bekomme ich das hin. Habe gestern schon einiges mit deinen Informationen gespielt. Wenn ich "durch" bin poste ich hier das Ergebnis!

                                  OliverIOO Offline
                                  OliverIOO Offline
                                  OliverIO
                                  schrieb am zuletzt editiert von OliverIO
                                  #16

                                  @larson-sei180lx
                                  wenn du fragen hast, dann gerne.
                                  hab das alles nicht im detail ausprobiert.
                                  aber vom prinzip müsste es klappen

                                  bin mir sicher daran sind auch andere interessiert
                                  wäre wahrscheinlich auch eine idee das in den adapter als widget zu integrieren
                                  aber das muss der original Autor machen

                                  Meine Adapter und Widgets
                                  TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
                                  Links im Profil

                                  L 1 Antwort Letzte Antwort
                                  0
                                  • OliverIOO OliverIO

                                    @larson-sei180lx
                                    wenn du fragen hast, dann gerne.
                                    hab das alles nicht im detail ausprobiert.
                                    aber vom prinzip müsste es klappen

                                    bin mir sicher daran sind auch andere interessiert
                                    wäre wahrscheinlich auch eine idee das in den adapter als widget zu integrieren
                                    aber das muss der original Autor machen

                                    L Offline
                                    L Offline
                                    Larson-Sei180LX
                                    schrieb am zuletzt editiert von
                                    #17

                                    @oliverio

                                    Ich war echt kurz vor dem Ziel.... er synchronisiert mir auch schon die Dateien....aber !!!!
                                    Er löscht mir das File auch gleich wieder...

                                    Das liegt wahrscheinlich daran, dass ich diese HTML Tags geschrieben habe. Und dadurch, dass er hinterher auch alte Dateien bereinigt, findet er das passende File nicht.
                                    Kannst Du mir noch mal helfen ? Ich poste mal den Bereich, wo das alles im Main.JS passiert und was ich bisher angepasst habe.

                                    function getTAM(host, user, password) {
                                        connectToTR064(host, user, password, function (sslDev) {
                                            var tam = sslDev.services["urn:dslforum-org:service:X_AVM-DE_TAM:1"];
                                            adapter.log.debug("TR-064: Calling GetMessageList()");
                                            tam.actions.GetMessageList({NewIndex: 0}, function(err, ret) {
                                                if (err) {
                                                    adapter.log.warn("TR-064: Error while calling GetMessageList(): " + err);
                                                } else if (ret.NewURL && ret.NewURL.length > 0) {
                                                    var url = ret.NewURL;
                                                    adapter.log.debug("TR-064: Got TAM uri: " + url);
                                                    var baseUrl = url.substring(0, url.lastIndexOf('/'));
                                                    var sid = url.match(/sid=([\d\w]+)/)[1];
                                                    adapter.log.debug(`TR-064: sid=${sid}`);
                                    
                                                    var agentOptions;
                                    				var agent;
                                    
                                    				agentOptions = {
                                    				  rejectUnauthorized: false
                                    				};
                                    
                                    				agent = new https.Agent(agentOptions);
                                    
                                                    request({
                                    				  url: url
                                    				, method: 'GET'
                                    				, agent: agent
                                    				}, function (error, response, body) {
                                                        if (!error && response.statusCode == 200) {
                                                            adapter.log.debug("TR-064: Got valid TAM content from, starting to parse ...");
                                                            var parser = new xml2js.Parser();
                                                            parser.parseString(body, function (err, result) {
                                                                if (err) {
                                                                    adapter.log.warn("TR-064: Error while parsing TAM content: " + err);
                                                                } else {
                                                                    adapter.log.debug("TR-064: Successfully parsed TAM content, analyzing result ...");
                                                                    var promises = [];
                                                                    var messages = [];
                                    
                                                                    mkdirSync('tam', { recursive: true });
                                    
                                                                    if(!result.Root.Message) {
                                                                        // No messahes
                                                                        promises.push(new Promise((resolve, reject) => resolve()));
                                                                    } else {
                                                                        for (var m = 0; m <= result.Root.Message.length; m++) {
                                                                            var message = result.Root.Message[m];
                                                                            if (typeof message != 'undefined') {
                                                                                promises.push(new Promise((resolve, reject) => {
                                                                                    var msg = {
                                                                                        index: message.Index[0],
                                                                                        calledNumber: message.Called[0],
                                                                                        date: message.Date[0],
                                                                                        duration: message.Duration[0],
                                                                                        callerName: message.Name[0],
                                                                                        callerNumber: message.Number[0],
                                                                                        audioFile: ''
                                                                                      //  audioLink: ''
                                    						};
                                                                                    if (!message.Path || message.Path.length < 1) {
                                                                                        adapter.log.warn("TR-064: TAM message has no url");
                                                                                        resolve(msg);
                                                                                        return;
                                                                                    }
                                    
                                                                                    var callDate = message.Date[0].split('.').join("").split(':').join("").split(' ').join("");
                                                                                    var file = `tam/${callDate}-${message.Number[0]}.wav`
                                                                                    adapter.log.debug(`TR-064: TAM message file: ${file}`);
                                                                                    if (existsSync(file)) {
                                    						//	msg.audioFile = path.resolve(file);						   
                                    						        msg.audioFile =`<audio id=&quot;AudioIndex`+message.Index+`&quot; src=&quot;vis.0/Audio/`+file+`&quot></audio><div><button oncklick=&quot;document.getElementById('AudioIndex`+message.Index+`').play()&quot;>&blacktriangleright;</button></div>`;
                                    							resolve(msg);
                                                                                        return;
                                                                                    }
                                    
                                                                                    var downloadUrl = message.Path[0];
                                                                                    if (downloadUrl.startsWith('/')) {
                                                                                        downloadUrl = baseUrl + downloadUrl;
                                                                                    }
                                                                                    if (downloadUrl.indexOf('sid=')<0){
                                                                                        downloadUrl += `&sid=${sid}`;
                                                                                    }
                                                                                    adapter.log.debug(`TR-064: Download TAM audio file from ${downloadUrl}`);
                                    
                                                                                    const stream = createWriteStream(file);
                                                                                    request({url: downloadUrl, agent: agent})
                                                                                        .on('error', function(err) {
                                                                                            unlink(file);
                                                                                            adapter.log.warn(
                                                                                                `TR-064: Error while downloading TAM audio file: ${err}`
                                                                                            );
                                                                                        })
                                                                                        .pipe(stream)
                                                                                        .on('error', function(err) {
                                                                                            unlink(file);
                                                                                            adapter.log.warn(
                                                                                                `TR-064: Error while writing TAM audio file: ${err}`
                                                                                            );
                                                                                        })
                                                                                        .on('finish', function() {
                                                                                            stream.close(function() {  
                                    						//	msg.audioFile = path.resolve(file);
                                    						        msg.audioFile =`<audio id=&quot;AudioIndex`+message.Index+`&quot; src=&quot;vis.0/Audio/`+file+`&quot></audio><div><button oncklick=&quot;document.getElementById('AudioIndex`+message.Index+`').play()&quot;>&blacktriangleright;</button></div>`;
                                    							resolve(msg);		
                                                                                            });
                                                                                        });
                                    
                                                                                }).then((result) => {
                                                                                    messages.push(result);
                                                                                }));
                                                                            }
                                                                        }
                                                                    }
                                    
                                                                    Promise.all(promises).then(function() {
                                                                        messages.sort((m1,m2) => m1.index > m2.index ? 1 : m1.index < m2.index ? -1 : 0);
                                    
                                                                        // cleanup old files
                                                                        readdir('tam', (err, files) => {
                                                                            if (err) {
                                                                                adapter.log.warn(
                                                                                    `TR-064: Error reading files from dir /tam: ${err}`
                                                                                );
                                                                            } else {
                                                                                files.forEach(file => {
                                                                                    file = path.resolve("tam/" + file);
                                                                                    if (!messages.find(msg => msg.audioFile == file)) {
                                                                                        // old file
                                                                                        adapter.log.debug(
                                                                                            `TR-064: Remove old tam audio file: ${file}`
                                                                                        );
                                                                                        unlink(file, function(err){
                                                                                            if (err) {
                                                                                                adapter.log.warn(
                                                                                                    `TR-064: Error deleting file ${file}: ${err}`
                                                                                                );
                                                                                            }
                                                                                        });
                                                                                    }
                                                                                })
                                                                            }
                                                                        })
                                    
                                                                        adapter.setState('tam.messagesJSON', JSON.stringify(messages), true);
                                                                        adapter.log.debug("TR-064: Successfully analyzed TAM results");
                                                                    });
                                                                }
                                                            });
                                                        } else {
                                    						adapter.log.warn(
                                    							`TR-064: Error while requesting TAM: ${error}`
                                    						);
                                    					}
                                                    });
                                                }
                                            });
                                        });
                                    }
                                    

                                    Ich habe Zeile 70 mit 71 getauscht und 102 mit 103. Ich glaube, das mit dem Cleanup der gelöschten AB Files (nachdem man Sie abgehört hat ) erfolgt in 127. Dort ist aber msg.Audiofile nicht gleich File....

                                    verstehst du was ich laienhaft sagen will :)?

                                    L OliverIOO 2 Antworten Letzte Antwort
                                    0
                                    • L Larson-Sei180LX

                                      @oliverio

                                      Ich war echt kurz vor dem Ziel.... er synchronisiert mir auch schon die Dateien....aber !!!!
                                      Er löscht mir das File auch gleich wieder...

                                      Das liegt wahrscheinlich daran, dass ich diese HTML Tags geschrieben habe. Und dadurch, dass er hinterher auch alte Dateien bereinigt, findet er das passende File nicht.
                                      Kannst Du mir noch mal helfen ? Ich poste mal den Bereich, wo das alles im Main.JS passiert und was ich bisher angepasst habe.

                                      function getTAM(host, user, password) {
                                          connectToTR064(host, user, password, function (sslDev) {
                                              var tam = sslDev.services["urn:dslforum-org:service:X_AVM-DE_TAM:1"];
                                              adapter.log.debug("TR-064: Calling GetMessageList()");
                                              tam.actions.GetMessageList({NewIndex: 0}, function(err, ret) {
                                                  if (err) {
                                                      adapter.log.warn("TR-064: Error while calling GetMessageList(): " + err);
                                                  } else if (ret.NewURL && ret.NewURL.length > 0) {
                                                      var url = ret.NewURL;
                                                      adapter.log.debug("TR-064: Got TAM uri: " + url);
                                                      var baseUrl = url.substring(0, url.lastIndexOf('/'));
                                                      var sid = url.match(/sid=([\d\w]+)/)[1];
                                                      adapter.log.debug(`TR-064: sid=${sid}`);
                                      
                                                      var agentOptions;
                                      				var agent;
                                      
                                      				agentOptions = {
                                      				  rejectUnauthorized: false
                                      				};
                                      
                                      				agent = new https.Agent(agentOptions);
                                      
                                                      request({
                                      				  url: url
                                      				, method: 'GET'
                                      				, agent: agent
                                      				}, function (error, response, body) {
                                                          if (!error && response.statusCode == 200) {
                                                              adapter.log.debug("TR-064: Got valid TAM content from, starting to parse ...");
                                                              var parser = new xml2js.Parser();
                                                              parser.parseString(body, function (err, result) {
                                                                  if (err) {
                                                                      adapter.log.warn("TR-064: Error while parsing TAM content: " + err);
                                                                  } else {
                                                                      adapter.log.debug("TR-064: Successfully parsed TAM content, analyzing result ...");
                                                                      var promises = [];
                                                                      var messages = [];
                                      
                                                                      mkdirSync('tam', { recursive: true });
                                      
                                                                      if(!result.Root.Message) {
                                                                          // No messahes
                                                                          promises.push(new Promise((resolve, reject) => resolve()));
                                                                      } else {
                                                                          for (var m = 0; m <= result.Root.Message.length; m++) {
                                                                              var message = result.Root.Message[m];
                                                                              if (typeof message != 'undefined') {
                                                                                  promises.push(new Promise((resolve, reject) => {
                                                                                      var msg = {
                                                                                          index: message.Index[0],
                                                                                          calledNumber: message.Called[0],
                                                                                          date: message.Date[0],
                                                                                          duration: message.Duration[0],
                                                                                          callerName: message.Name[0],
                                                                                          callerNumber: message.Number[0],
                                                                                          audioFile: ''
                                                                                        //  audioLink: ''
                                      						};
                                                                                      if (!message.Path || message.Path.length < 1) {
                                                                                          adapter.log.warn("TR-064: TAM message has no url");
                                                                                          resolve(msg);
                                                                                          return;
                                                                                      }
                                      
                                                                                      var callDate = message.Date[0].split('.').join("").split(':').join("").split(' ').join("");
                                                                                      var file = `tam/${callDate}-${message.Number[0]}.wav`
                                                                                      adapter.log.debug(`TR-064: TAM message file: ${file}`);
                                                                                      if (existsSync(file)) {
                                      						//	msg.audioFile = path.resolve(file);						   
                                      						        msg.audioFile =`<audio id=&quot;AudioIndex`+message.Index+`&quot; src=&quot;vis.0/Audio/`+file+`&quot></audio><div><button oncklick=&quot;document.getElementById('AudioIndex`+message.Index+`').play()&quot;>&blacktriangleright;</button></div>`;
                                      							resolve(msg);
                                                                                          return;
                                                                                      }
                                      
                                                                                      var downloadUrl = message.Path[0];
                                                                                      if (downloadUrl.startsWith('/')) {
                                                                                          downloadUrl = baseUrl + downloadUrl;
                                                                                      }
                                                                                      if (downloadUrl.indexOf('sid=')<0){
                                                                                          downloadUrl += `&sid=${sid}`;
                                                                                      }
                                                                                      adapter.log.debug(`TR-064: Download TAM audio file from ${downloadUrl}`);
                                      
                                                                                      const stream = createWriteStream(file);
                                                                                      request({url: downloadUrl, agent: agent})
                                                                                          .on('error', function(err) {
                                                                                              unlink(file);
                                                                                              adapter.log.warn(
                                                                                                  `TR-064: Error while downloading TAM audio file: ${err}`
                                                                                              );
                                                                                          })
                                                                                          .pipe(stream)
                                                                                          .on('error', function(err) {
                                                                                              unlink(file);
                                                                                              adapter.log.warn(
                                                                                                  `TR-064: Error while writing TAM audio file: ${err}`
                                                                                              );
                                                                                          })
                                                                                          .on('finish', function() {
                                                                                              stream.close(function() {  
                                      						//	msg.audioFile = path.resolve(file);
                                      						        msg.audioFile =`<audio id=&quot;AudioIndex`+message.Index+`&quot; src=&quot;vis.0/Audio/`+file+`&quot></audio><div><button oncklick=&quot;document.getElementById('AudioIndex`+message.Index+`').play()&quot;>&blacktriangleright;</button></div>`;
                                      							resolve(msg);		
                                                                                              });
                                                                                          });
                                      
                                                                                  }).then((result) => {
                                                                                      messages.push(result);
                                                                                  }));
                                                                              }
                                                                          }
                                                                      }
                                      
                                                                      Promise.all(promises).then(function() {
                                                                          messages.sort((m1,m2) => m1.index > m2.index ? 1 : m1.index < m2.index ? -1 : 0);
                                      
                                                                          // cleanup old files
                                                                          readdir('tam', (err, files) => {
                                                                              if (err) {
                                                                                  adapter.log.warn(
                                                                                      `TR-064: Error reading files from dir /tam: ${err}`
                                                                                  );
                                                                              } else {
                                                                                  files.forEach(file => {
                                                                                      file = path.resolve("tam/" + file);
                                                                                      if (!messages.find(msg => msg.audioFile == file)) {
                                                                                          // old file
                                                                                          adapter.log.debug(
                                                                                              `TR-064: Remove old tam audio file: ${file}`
                                                                                          );
                                                                                          unlink(file, function(err){
                                                                                              if (err) {
                                                                                                  adapter.log.warn(
                                                                                                      `TR-064: Error deleting file ${file}: ${err}`
                                                                                                  );
                                                                                              }
                                                                                          });
                                                                                      }
                                                                                  })
                                                                              }
                                                                          })
                                      
                                                                          adapter.setState('tam.messagesJSON', JSON.stringify(messages), true);
                                                                          adapter.log.debug("TR-064: Successfully analyzed TAM results");
                                                                      });
                                                                  }
                                                              });
                                                          } else {
                                      						adapter.log.warn(
                                      							`TR-064: Error while requesting TAM: ${error}`
                                      						);
                                      					}
                                                      });
                                                  }
                                              });
                                          });
                                      }
                                      

                                      Ich habe Zeile 70 mit 71 getauscht und 102 mit 103. Ich glaube, das mit dem Cleanup der gelöschten AB Files (nachdem man Sie abgehört hat ) erfolgt in 127. Dort ist aber msg.Audiofile nicht gleich File....

                                      verstehst du was ich laienhaft sagen will :)?

                                      L Offline
                                      L Offline
                                      Larson-Sei180LX
                                      schrieb am zuletzt editiert von
                                      #18

                                      @larson-sei180lx
                                      Achja, für " musste ich das als Sonderzeichen HTML nehmen, da er mir sonst immer ein \ vor jedes" gesetzt hat.

                                      L 1 Antwort Letzte Antwort
                                      0
                                      • L Larson-Sei180LX

                                        @larson-sei180lx
                                        Achja, für " musste ich das als Sonderzeichen HTML nehmen, da er mir sonst immer ein \ vor jedes" gesetzt hat.

                                        L Offline
                                        L Offline
                                        Larson-Sei180LX
                                        schrieb am zuletzt editiert von
                                        #19

                                        Es gibt noch ein kleines Problem: Der Code, den du zum Hochladen nach VIS geschrieben hast, kopiert mir immer eine 0KB Datei. Ich vermute, dass der zu früh startet, bevor die Datei von der Fritzbox erstmal heruntergeladen ist. Das dauert oft ein bisschen habe ich gesehen.... Kann man da was verändern ?

                                        1 Antwort Letzte Antwort
                                        0
                                        • L Larson-Sei180LX

                                          @oliverio

                                          Ich war echt kurz vor dem Ziel.... er synchronisiert mir auch schon die Dateien....aber !!!!
                                          Er löscht mir das File auch gleich wieder...

                                          Das liegt wahrscheinlich daran, dass ich diese HTML Tags geschrieben habe. Und dadurch, dass er hinterher auch alte Dateien bereinigt, findet er das passende File nicht.
                                          Kannst Du mir noch mal helfen ? Ich poste mal den Bereich, wo das alles im Main.JS passiert und was ich bisher angepasst habe.

                                          function getTAM(host, user, password) {
                                              connectToTR064(host, user, password, function (sslDev) {
                                                  var tam = sslDev.services["urn:dslforum-org:service:X_AVM-DE_TAM:1"];
                                                  adapter.log.debug("TR-064: Calling GetMessageList()");
                                                  tam.actions.GetMessageList({NewIndex: 0}, function(err, ret) {
                                                      if (err) {
                                                          adapter.log.warn("TR-064: Error while calling GetMessageList(): " + err);
                                                      } else if (ret.NewURL && ret.NewURL.length > 0) {
                                                          var url = ret.NewURL;
                                                          adapter.log.debug("TR-064: Got TAM uri: " + url);
                                                          var baseUrl = url.substring(0, url.lastIndexOf('/'));
                                                          var sid = url.match(/sid=([\d\w]+)/)[1];
                                                          adapter.log.debug(`TR-064: sid=${sid}`);
                                          
                                                          var agentOptions;
                                          				var agent;
                                          
                                          				agentOptions = {
                                          				  rejectUnauthorized: false
                                          				};
                                          
                                          				agent = new https.Agent(agentOptions);
                                          
                                                          request({
                                          				  url: url
                                          				, method: 'GET'
                                          				, agent: agent
                                          				}, function (error, response, body) {
                                                              if (!error && response.statusCode == 200) {
                                                                  adapter.log.debug("TR-064: Got valid TAM content from, starting to parse ...");
                                                                  var parser = new xml2js.Parser();
                                                                  parser.parseString(body, function (err, result) {
                                                                      if (err) {
                                                                          adapter.log.warn("TR-064: Error while parsing TAM content: " + err);
                                                                      } else {
                                                                          adapter.log.debug("TR-064: Successfully parsed TAM content, analyzing result ...");
                                                                          var promises = [];
                                                                          var messages = [];
                                          
                                                                          mkdirSync('tam', { recursive: true });
                                          
                                                                          if(!result.Root.Message) {
                                                                              // No messahes
                                                                              promises.push(new Promise((resolve, reject) => resolve()));
                                                                          } else {
                                                                              for (var m = 0; m <= result.Root.Message.length; m++) {
                                                                                  var message = result.Root.Message[m];
                                                                                  if (typeof message != 'undefined') {
                                                                                      promises.push(new Promise((resolve, reject) => {
                                                                                          var msg = {
                                                                                              index: message.Index[0],
                                                                                              calledNumber: message.Called[0],
                                                                                              date: message.Date[0],
                                                                                              duration: message.Duration[0],
                                                                                              callerName: message.Name[0],
                                                                                              callerNumber: message.Number[0],
                                                                                              audioFile: ''
                                                                                            //  audioLink: ''
                                          						};
                                                                                          if (!message.Path || message.Path.length < 1) {
                                                                                              adapter.log.warn("TR-064: TAM message has no url");
                                                                                              resolve(msg);
                                                                                              return;
                                                                                          }
                                          
                                                                                          var callDate = message.Date[0].split('.').join("").split(':').join("").split(' ').join("");
                                                                                          var file = `tam/${callDate}-${message.Number[0]}.wav`
                                                                                          adapter.log.debug(`TR-064: TAM message file: ${file}`);
                                                                                          if (existsSync(file)) {
                                          						//	msg.audioFile = path.resolve(file);						   
                                          						        msg.audioFile =`<audio id=&quot;AudioIndex`+message.Index+`&quot; src=&quot;vis.0/Audio/`+file+`&quot></audio><div><button oncklick=&quot;document.getElementById('AudioIndex`+message.Index+`').play()&quot;>&blacktriangleright;</button></div>`;
                                          							resolve(msg);
                                                                                              return;
                                                                                          }
                                          
                                                                                          var downloadUrl = message.Path[0];
                                                                                          if (downloadUrl.startsWith('/')) {
                                                                                              downloadUrl = baseUrl + downloadUrl;
                                                                                          }
                                                                                          if (downloadUrl.indexOf('sid=')<0){
                                                                                              downloadUrl += `&sid=${sid}`;
                                                                                          }
                                                                                          adapter.log.debug(`TR-064: Download TAM audio file from ${downloadUrl}`);
                                          
                                                                                          const stream = createWriteStream(file);
                                                                                          request({url: downloadUrl, agent: agent})
                                                                                              .on('error', function(err) {
                                                                                                  unlink(file);
                                                                                                  adapter.log.warn(
                                                                                                      `TR-064: Error while downloading TAM audio file: ${err}`
                                                                                                  );
                                                                                              })
                                                                                              .pipe(stream)
                                                                                              .on('error', function(err) {
                                                                                                  unlink(file);
                                                                                                  adapter.log.warn(
                                                                                                      `TR-064: Error while writing TAM audio file: ${err}`
                                                                                                  );
                                                                                              })
                                                                                              .on('finish', function() {
                                                                                                  stream.close(function() {  
                                          						//	msg.audioFile = path.resolve(file);
                                          						        msg.audioFile =`<audio id=&quot;AudioIndex`+message.Index+`&quot; src=&quot;vis.0/Audio/`+file+`&quot></audio><div><button oncklick=&quot;document.getElementById('AudioIndex`+message.Index+`').play()&quot;>&blacktriangleright;</button></div>`;
                                          							resolve(msg);		
                                                                                                  });
                                                                                              });
                                          
                                                                                      }).then((result) => {
                                                                                          messages.push(result);
                                                                                      }));
                                                                                  }
                                                                              }
                                                                          }
                                          
                                                                          Promise.all(promises).then(function() {
                                                                              messages.sort((m1,m2) => m1.index > m2.index ? 1 : m1.index < m2.index ? -1 : 0);
                                          
                                                                              // cleanup old files
                                                                              readdir('tam', (err, files) => {
                                                                                  if (err) {
                                                                                      adapter.log.warn(
                                                                                          `TR-064: Error reading files from dir /tam: ${err}`
                                                                                      );
                                                                                  } else {
                                                                                      files.forEach(file => {
                                                                                          file = path.resolve("tam/" + file);
                                                                                          if (!messages.find(msg => msg.audioFile == file)) {
                                                                                              // old file
                                                                                              adapter.log.debug(
                                                                                                  `TR-064: Remove old tam audio file: ${file}`
                                                                                              );
                                                                                              unlink(file, function(err){
                                                                                                  if (err) {
                                                                                                      adapter.log.warn(
                                                                                                          `TR-064: Error deleting file ${file}: ${err}`
                                                                                                      );
                                                                                                  }
                                                                                              });
                                                                                          }
                                                                                      })
                                                                                  }
                                                                              })
                                          
                                                                              adapter.setState('tam.messagesJSON', JSON.stringify(messages), true);
                                                                              adapter.log.debug("TR-064: Successfully analyzed TAM results");
                                                                          });
                                                                      }
                                                                  });
                                                              } else {
                                          						adapter.log.warn(
                                          							`TR-064: Error while requesting TAM: ${error}`
                                          						);
                                          					}
                                                          });
                                                      }
                                                  });
                                              });
                                          }
                                          

                                          Ich habe Zeile 70 mit 71 getauscht und 102 mit 103. Ich glaube, das mit dem Cleanup der gelöschten AB Files (nachdem man Sie abgehört hat ) erfolgt in 127. Dort ist aber msg.Audiofile nicht gleich File....

                                          verstehst du was ich laienhaft sagen will :)?

                                          OliverIOO Offline
                                          OliverIOO Offline
                                          OliverIO
                                          schrieb am zuletzt editiert von
                                          #20

                                          @larson-sei180lx
                                          Also für das löschen in 132 gibt es gar keine Bedingungen.
                                          Daher löscht es einfach alle Dateien. Woran erkennst du das bereits angehört wurde?

                                          Das verwenden von &quot ist falsch. Muss morgen mal probieren warum.

                                          Das verfrühte hochladen könnte man vermeiden, das die Datei erst mal unter einer anderen Endung (bspw. .wav-upload ) angelegt wird und erst wenn es fertig ist dann auf .wav umbenannt wird. Das erzeugt dann bei chokidar verschiedene Ereignisse. Erst wenn die Datei auf .wav endet, wird dann hochgeladen. Aktuell horcht chokidar auf das Ereignis add, wahrscheinlich muss man es dann auf change ändern.
                                          Hier die Doku
                                          https://github.com/paulmillr/chokidar

                                          Meine Adapter und Widgets
                                          TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
                                          Links im Profil

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


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          727

                                          Online

                                          32.6k

                                          Benutzer

                                          82.2k

                                          Themen

                                          1.3m

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

                                          • Du hast noch kein Konto? Registrieren

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