Navigation

    Logo
    • Register
    • Login
    • Search
    • Recent
    • Tags
    • Unread
    • Categories
    • Unreplied
    • Popular
    • GitHub
    • Docu
    • Hilfe
    1. Home
    2. Deutsch
    3. Skripten / Logik
    4. JavaScript
    5. [Script] MessageHandler: Nachrichten protokollieren +Widget

    NEWS

    • Neuer Blog: Fotos und Eindrücke aus Solingen

    • ioBroker@Smart Living Forum Solingen, 14.06. - Agenda added

    • ioBroker goes Matter ... Matter Adapter in Stable

    [Script] MessageHandler: Nachrichten protokollieren +Widget

    This topic has been deleted. Only users with topic management privileges can see it.
    • uwe12489
      uwe12489 @Tirador last edited by

      @Tirador
      Idee und Umsetzung gefallen mir sehr gut. Danke für die Arbeit. Leider habe ich beim Blockly-Beispiel nur Fehlermeldungen...
      Poste doch bitte mal andere Beispiele (Kalender, presence tr64, Stati, ...) für ein besseres Verständnis.

      T 1 Reply Last reply Reply Quote 0
      • T
        Tirador @uwe12489 last edited by

        @uwe12489 Hallo Uwe, vielen Dank! 🙂

        Ein weiteres Beispiel, wie man das Auslösen von Nachrichten in anderen Skripten integrieren kann am Beispiel des "Script: An- und Abwesenheitserkennung über TR-064-Community-Adapter" (Quelle: https://github.com/Mic-M/iobroker.presence-script-for-tr-064-community-adapter)

        Dort kann man die folgenden im Screenshot gelb markierten Codezeilen ergänzen, damit die Nachrichten ausgelöst werden:
        2020-04-09 09_11_50-javascript - ioBroker.png

        Codezeilen:

            // Send global Message of available Users!
            
            if(presentPersons.length > 0) {
                postMessage("PERSONS_AVAILABLE_INFO", presentPersons, counter);
        
            } else {
                removeMessage("PERSONS_AVAILABLE_INFO");
            }
        

        An einem zusätzlichen "generischen" Skript zur Auslösung von Nachrichten bin ich dran. Die Idee ist dort, dass ich Datenpunkte für die Überwachung und Ausgabe der Nachrichtentexte definieren kann. Damit werden die Messages dann automatisiert angelegt. Damit kann man sich Eingriffe in andere Skripte ersparen, was die Wartbarkeit vereinfacht. D.h. wenn Mic sein Skript aktualisiert, muss ich nicht ständig die Codezeilen wieder ergänzen bei mir.
        Einen Prototyp habe ich gestern begonnen, dass wird aber noch etwas dauern, bis ich da einen Stand habe der veröffentlicht werden kann.

        Bezüglich Blockly:
        Das Blockly ist nur ein Beispiel von mir. Da der Datenpunkt für den Briefkastensensor bei dir nicht vorhanden sein wird, kann es nicht "out of the box" funktionieren. Es soll ja nur aufzeigen, wie man die Javascript-Funktion "postMessage" auch in Blockly verwenden kann.
        Wenn Du Details zu den Fehlermeldungen postest kann ich dir gerne versuchen zu helfen beim Blockly. Vielleicht schreibst du mir eine private Nachricht, um dies zu ergründen.

        uwe12489 1 Reply Last reply Reply Quote 0
        • uwe12489
          uwe12489 @Tirador last edited by

          @Tirador
          Das ist ja ein super Service. Danke.
          Leider bringt dein Codeschnipsel im script presence-tr64 auch nur Fehler
          Bildschirmfoto 2020-04-09 um 10.37.02.png
          wo klemmt da die säge? unterschiedliche bezeichnungen?

          T 1 Reply Last reply Reply Quote 0
          • T
            Tirador @uwe12489 last edited by

            @uwe12489 Der Fehler in deinem Log deutet daraufhin, dass du den Codeschnipsel an der falschen Stelle eingefügt hast.

            Ich habe bisher Version 0.7 verwendet, jetzt aber das Script von Mic nochmal aktualisiert.

            Die Codezeilen haben sich leicht verschoben in der Version 1.1 von mic:

            2020-04-09 12_37_18-javascript - ioBroker.png

            Das vollständige Script mit den Codezeilen habe ich aber zusätzlich nochmal eingefügt:

            /*******************************************************************************
            * ---------------------------
            * Script: An- und Abwesenheitserkennung über TR-064-Adapter
            * ---------------------------
            * Autor: Mic (ioBroker-Forum) / Mic-M (Github)
            * ---------------------------
            * Das Script nutzt den TR-064-Adapter, der die WLAN-Verfügbarkeit von allen Geräten überwacht.
            *
            * Funktionen:
            *  - Ermittlung der anwesenden und abwesenden Personen
            *  - State 'anyonePresent': wird 'true' gesetzt, wenn 1 oder mehr Personen anwesend, und 'false', wenn keiner.
            *    Dies kann als Trigger genutzt werden, um zum Beispiel alles auszuschalten, wenn keiner mehr zu Hause.
            *  - Speichern von Kommen- und Gehen-Zeiten
            *  - Führen einer An- und Abwesenheitsliste als Json und HTML
            *  - optional: Datei-Log  für jede Aktualisierung der An- und Abwesenheit
            * ---------------------------
            * Ressourcen:
            *  - Script ist hier veröffentlicht: https://github.com/Mic-M/iobroker.presence-script-for-tr-064-community-adapter
            *  - Support ioBroker-Forum: https://forum.iobroker.net/topic/4538/anwesenheitscontrol-basierend-auf-tr64-adapter-script
            *  - Link zum TR-064-Adapter: https://github.com/iobroker-community-adapters/ioBroker.tr-064
            * ---------------------------
            * Change log:
            * 1.1 - Mic: * Change path of TR-064 adapter
            * 1.0 - Mic: * Code improvements and fix
            * 0.8 - Mic: + JSON: Add css class "trRecentDate" for highlighting the date of most recent action.
            *              If state is "anwesend", CSS class will be applied to value in column "Kommt"
            *              If state is "abwesend", CSS class will be applied to value in column "Geht"
            *            + JSON: Add css class "trStatusPresent" / "trStatusLeft" to status values
            * 0.7 - Mic: Change statepath from javascript.0 to 0_userdata.0
            * 0.6 - Mic: Neuer State 'persons.xxx.offsetEntryLeave', zeigt wie lange die Person an-/abwesend war.
            *               entweder nur in Stunden gerundet (z.B. 49), oder in Stunden:Minuten (z.B. 48:36).
            *               Siehe Erweiterte Einstellungen, OFFSET_HOURS_AND_MINS, hier im Script.
            * 0.5 - Mic: State 'presentPersonsString': Alphabetische Sortierung und Trennzeichen kann in den erweiterten Einstellungen
            *               des Scripts geändert werden (PRESENT_PERSONS_DELIMITER). Außerdem kann Text vergeben werden, wenn niemand
            *               zu Hause ist (PRESENT_PERSONS_NONE_TXT).
            * 0.4 - Mic: kleine Korrekturen
            * 0.3 - Mic:
            *        - Diverse Verbesserungen: Sourcecode, Logging, States neu gegliedert,
            *          besserer Scriptstart, Bereinigung, Abfangen von Fehlern, usw.
            *        - Neue Option FIX_ERROR für FritzBox und iPhone (siehe Beschreibung in Konfiguration)
            * 0.2 - NightWatcher: optimiert, nun unlimitierte Geräte möglich, HTML String Liste für das Material Design
            * 0.1 - Looxer01: Initiale Version 
            * ---------------------------
            * Credits: Vielen dank an den ursprünglichen Autor Looxer01, der am 01.01.2017 das Script veröffentlichte.
            * Ebenso danke an NightWatcher, der eine Aktualisierung am 31.10.2018 veröffentlichte.
            ******************************************************************************/
            
            
            /*******************************************************************************
            * Einstellungen
            ******************************************************************************/
            
            // Pfad, unter dem die States (Datenpunkte) in den Objekten angelegt werden.
            // Es wird die Anlage sowohl unterhalb '0_userdata.0' als auch 'javascript.x' unterstützt.
            const STATE_PATH = '0_userdata.0.Anwesenheit.Status';
            
            
            // Hier ist der State des TR-064-Adapters, unter dem die einzelnen Geräte geführt sind
            const STATEPATH_TR064_DEVICES = 'tr-064.0.devices.';
            
            //  Hier die zu überwachenden Geräte vom TR-064-Adapter eintragen.
            //  Es können beliebig viele Personen mit neuen Zeilen ergänzt werden.
            //  Links: Gerät aus Spalte "Name" vom TR-064-Adapter
            //  Rechts: Name des Besitzers, der angezeigt werden soll
            const DEVICES = {
                'HandyStefan': 'Stefan', 
                'HandyMichi': 'Michi', 
            };
            
            
            // Logging in Datei
            const LOGFLAG = false;   // Logging ein- oder ausschalten
            const LOGPATH_FS = "/opt/iobroker/iobroker-data/Anwesenheiten.csv";             // Pfad und Dateiname der Log-Datei
            
            
            // Falls eine Anwesenheitssimulation verknüpft werden soll dann hier TRUE eintragen, sowie
            // die Zeit in Sekunden nach Abwesenheit, die vergehen soll bis die Simulation aktiviert wird
            const SIMULATION_ACTIVE = false;
            const SIMULATION_DELAY = 600;
            
            
            // Erweiterter Log im ioBroker
            const LOG_INFO = true;    // Informationen loggen
            const LOG_DEBUG = false;   // Erweiterter Log für Debugging
            
            // Behebe FritzBox-Fehler (zumindest mit iOS): Wenn ein Gerät nicht mehr im WLAN ist, wird manchmal direkt 
            // auf "nicht anwesend" im Adapter gesetzt, dann ca. 15 Sekunden später wieder auf "anwesend", dann ca. 5-10 Minuten
            // später dauerhaft auf "nicht anwesend". Um dieses Verhalten zu umgehen, wird hier ein Delay eingebaut,
            // um nach x Sekunden (FIX_ERROR_DELAY) zu prüfen, ob lt. Adapter tatsächlich abwesend.
            // Siehe auch Github Issue: https://github.com/iobroker-community-adapters/ioBroker.tr-064-community/issues/55
            const FIX_ERROR = true;
            const FIX_ERROR_DELAY = 25;
            
            
            /*******************************************************************************
            * Erweiterte Einstellungen
            ******************************************************************************/
            
            /********
            * Option für Datenpunkt "presentPersonsString" (Zeigt die derzeit anwesenden Personen)
            ********/
            // Trennzeichen für 'presentPersonsString'. Dieses wird zwischen den einzelnen anwesenden Namen gesetzt.
            const PRESENT_PERSONS_DELIMITER = ', ';
            
            // Text in für 'presentPersonsString', falls niemand anwesend.
            const PRESENT_PERSONS_NONE_TXT = '';
            
            /********
            * Option für Datenpunkt "persons.xxx.offsetEntryLeave" (zeigt , wie lange die Person an- oder abwesend war.)
            ********/
            // Wenn true: Im Datenpunkt werden Stunden und Minuten angezeigt, z.B. 10:36 (bei 10 Stunden 36 Min.), oder 48:12 (bei 48 Std. 12 Min.)
            // Wenn false: Es werden nur Stunden gerundet angezeigt, z.B. 11 (bei 10 Stunden 36 Minuten) oder 48 (bei 48 Std. 12 Min.)
            const OFFSET_HOURS_AND_MINS = true;
            
            
            
            /**********************************************************************************************************
            ++++++++++++++++++++++++++++ Ab hier nichts mehr ändern / Stop editing here! ++++++++++++++++++++++++++++
            *********************************************************************************************************/
            
            /****************************************************************************************
            * Global variables and constants
            ****************************************************************************************/
            // Final state path
            const FINAL_STATE_LOCATION = validateStatePath(STATE_PATH, false);
            const FINAL_STATE_PATH = validateStatePath(STATE_PATH, true) + '.';
            
            
            /*******************************************************************************
            * Executed on every script start.
            *******************************************************************************/
            init();
            function init() {
            
               /**
                * First, validate some of the options
               */
               let passed = false;
               // Prüfen ob der jeweilige State im TR-064-Adapter existert    
               for (let lpDevice in DEVICES) {
                   if (getObject(STATEPATH_TR064_DEVICES + lpDevice)) {
                       passed = true;
                       if (LOG_DEBUG) log('Prüfung erfolgreich: state [' + STATEPATH_TR064_DEVICES + lpDevice + '] existiert.')
                   } else {
                       passed = false;
                       log('Das im Script angegebene Gerät [' + lpDevice + '] von ' + cl(DEVICES[lpDevice]) + ' existiert nicht in den TR-064-Community-Adapter-Objekten.', 'warn')
                       log('Prüfe, ob Gerät [' + lpDevice + '] in den TR-064-Adapteroptionen so angelegt ist und Gerätename 1:1 übereinstimmt mit diesem Script.', 'warn')
                   }
               }
            
               if (passed) {
            
                   // Create states.
                   createUserStates(FINAL_STATE_LOCATION, false, buildScriptStates(), function() {
            
                       // Now, states are created.
            
                       // Delete state, if SIMULATION_ACTIVE is false and if state exists. Just to clean up if it was true before and user changed it to false.
                       if(! SIMULATION_ACTIVE) {
                           if (isState(FINAL_STATE_PATH + 'presenceSimulationActive'), false) {
                               deleteState(FINAL_STATE_PATH + 'presenceSimulationActive');
                           }
                       }
            
                       // Execute main() to get initial status with TR-064 adapter
                       main(0);
            
                       // Schedule for each user
                       for (let lpDevice in DEVICES){
                           on({id: STATEPATH_TR064_DEVICES + lpDevice, change:'ne'}, function(obj) {
                               let deviceName = obj.id.split('.').pop();
                               if (obj.state.ack) {
                                   // Only continue if adapter presence state differs to the script state
                                   if( obj.state.val !== (getState(FINAL_STATE_PATH + 'persons.' + cl(DEVICES[deviceName]) + '.isPresent')).val) {
                                       if (LOG_DEBUG) log('Presence status of ' + cl(DEVICES[deviceName]) + ' actually changed');
                                       if (FIX_ERROR && !obj.state.val) { // if fix Fritzbox error is active and if device is no longer in WiFi per Adapter
                                           if (LOG_DEBUG) log('Fix Error being triggert, Person: ' + cl(DEVICES[deviceName]));
                                           setTimeout(function() {
                                               if (!getState(obj.id).val) {
                                                   // OK, so user is indeed no longer present
                                                   if (LOG_DEBUG) log ('Getriggert: Eine Person geht (FIX_ERROR Funktion erfolgreich)');
                                                   main(deviceName);
                                               }
                                           }, FIX_ERROR_DELAY * 1000);
                                       } else {
                                           if (LOG_DEBUG) log ('Getriggert: Eine Person kommt oder geht');
                                           main(deviceName);
                                       }
                                   }
                               }
                           });
                       }
            
                   });
                   
               } else {
                   log('Script wird nicht weiter ausgeführt aufgrund der ausgegebenen Fehler.', 'warn');
               }
            }
            
            /*******************************************************************************
            * Haupt-Skript
            *******************************************************************************/
            function main(userKey) {
            
               let currentDateTime = formatDate(new Date(), 'TT.MM.JJJJ SS:mm:ss');
               
               let presentPersons    = '';
               let isAnyonePresent   = false;
               let jsonArr           = [];
               let HTMLString        = "<table style='width:100%'><thead><tr><th style='text-align:left;'>Name</th><th style='text-align:left;'>Status</th><th style='text-align:left;'>Kommt</th><th style='text-align:left;'>Geht</th></tr></thead><tbody>";
               let counter = 0;
               let message = '';
               for (let lpDevice in DEVICES) {
                   if (LOG_DEBUG) log('Loop: Device ' + lpDevice);
                   
                   // Anwesenheitsstatus auslesen aus TR064
                   let isLoopUserPresent = getState(STATEPATH_TR064_DEVICES + lpDevice).val;
                   // Status setzen
                   setState(FINAL_STATE_PATH + 'persons.' + cl(DEVICES[lpDevice]) + '.isPresent', isLoopUserPresent);
            
                   // Get state times of last leave/entry
                   let lpTimeLastLeave  = getState(FINAL_STATE_PATH + 'persons.' + cl(DEVICES[lpDevice]) + '.timeLastLeave').val;
                   let lpTimeLastEntry = getState(FINAL_STATE_PATH + 'persons.' + cl(DEVICES[lpDevice]) + '.timeLastEntry').val;
                   
                   if (lpDevice === userKey) {
                       setState(FINAL_STATE_PATH + 'persons.' + cl(DEVICES[lpDevice]) + (isLoopUserPresent ? '.timeLastEntry': '.timeLastLeave'), currentDateTime);
                       setState(FINAL_STATE_PATH + 'persons.' + cl(DEVICES[lpDevice]) + '.timeMostRecent', currentDateTime);
                       if(isLoopUserPresent) {
                           lpTimeLastEntry = currentDateTime;
                       } else {
                           lpTimeLastLeave = currentDateTime;
                       }
                       if (LOGFLAG) writelog(cl(DEVICES[lpDevice]) + ";" + lpDevice + ";" + (isLoopUserPresent ? "Kommt": "Geht"));
                       message = cl(DEVICES[lpDevice]) + (isLoopUserPresent ? ' kommt':' geht');
                   }
            
                   // Set statuses
                   if (!isLoopUserPresent && !isAnyonePresent) {
                       isAnyonePresent = false;
                   }
                   if (isLoopUserPresent) {
                       counter += 1;
                       if (presentPersons === '') {
                           presentPersons = cl(DEVICES[lpDevice]);
                       } else {
                           presentPersons += '######' + cl(DEVICES[lpDevice]);
                       }
                       isAnyonePresent = true;
                   }
            
            
                   /**
                    * Calculate offset leave/entry and set states accordingly
                    */
                   let lpCurrentOffset = '';
                   let stateLeave = FINAL_STATE_PATH + 'persons.' + cl(DEVICES[lpDevice]) + '.timeLastLeave';
                   let stateEntry = FINAL_STATE_PATH + 'persons.' + cl(DEVICES[lpDevice]) + '.timeLastEntry';
                   if ( (!isEmpty(getState(stateLeave).val) && isLoopUserPresent) || (!isEmpty(getState(stateEntry).val) &&  !isLoopUserPresent ) ) {
            
                       // As the states are string format, we simply get the last change of the state, which is a date/time variable
                       let dtLeave = getState(stateLeave).lc; // '.lc' property gets us the date/time when the state changed last time
                       let dtEntry = getState(stateEntry).lc;
                       let offsetMs = Math.abs(dtLeave - dtEntry); // remove minus '-', so get absolute number
                       let intHoursFull = offsetMs / 1000 / 60 /60; // convert milliseconds into hours
                       let intHoursDecimal =  parseInt(intHoursFull.toString().substring(0, intHoursFull.toString().indexOf("."))); // not rounded
                       let offsetJustMins = Math.round ( (intHoursFull - Math.round(intHoursDecimal)) * 60); // gets us just the minutes, without the hours
                       let resultStrHoursOnly = Math.round(intHoursFull).toString();
                       let resultStrHoursSec = zeroPad(intHoursDecimal, 2) + ':' + zeroPad(offsetJustMins, 2)
                       if(LOG_DEBUG) log (cl(DEVICES[lpDevice]) + ' Offset hours only: ' + resultStrHoursOnly + ', Offset hours:seconds: ' + resultStrHoursSec);
                       let finalOffsetStr = (OFFSET_HOURS_AND_MINS) ? resultStrHoursSec : resultStrHoursOnly;
                       setState(FINAL_STATE_PATH + 'persons.' + cl(DEVICES[lpDevice]) + '.offsetEntryLeave', finalOffsetStr);
                       lpCurrentOffset = finalOffsetStr;
            
                   } else {
                       // nothing to calculate, so empty state
                       setState(FINAL_STATE_PATH + 'persons.' + cl(DEVICES[lpDevice]) + '.offsetEntryLeave', '');
                       lpCurrentOffset = '';
                   }
            
                   /**
                    * Generate JSON
                    */
                   let lpObjJ = {};
                   lpObjJ['Name']                  = cl(DEVICES[lpDevice]);
                   lpObjJ['Status']                = (isLoopUserPresent ? "<span class='trStatusPresent'>anwesend</span>" : "<span class='trStatusLeave'>abwesend</span>");
                   lpObjJ['Letzte Ankunft']        = ((isLoopUserPresent) ? "<span class='trRecentDate'>" : '') + lpTimeLastEntry + ((isLoopUserPresent) ? '</span>' : '');
                   lpObjJ['Letzte Abwesenheit']    = ((!isLoopUserPresent) ? "<span class='trRecentDate'>" : '') + lpTimeLastLeave + ((!isLoopUserPresent) ? '</span>' : '');
                   lpObjJ['Dauer']                 = lpCurrentOffset;
                   jsonArr.push(lpObjJ);
            
                   /**
                    * Generate HTML String
                    */
                   HTMLString+="<tr>";
                   HTMLString+="<td>"+cl(DEVICES[lpDevice])+"</td>"
                   HTMLString+="<td>"+(isLoopUserPresent ? '<div class="mdui-green-bg mdui-state mdui-card">anwesend</div>' : '<div class="mdui-red-bg mdui-state mdui-card">abwesend</div>')+"</td>"
                   HTMLString+="<td>"+lpTimeLastEntry+"</td>"
                   HTMLString+="<td>"+lpTimeLastLeave+"</td>"
                   HTMLString+="</tr>";
            
               } // for (let lpDevice in DEVICES) {
            
            
               // Prepare present persons string
               if (!isAnyonePresent) {
                   presentPersons = PRESENT_PERSONS_NONE_TXT;
               } else {
                   // sort present persons alphabetically and add delimiter from options, when converting back to string
                   let presPersArr = presentPersons.split('######');
                   presPersArr.sort(); 
                   presentPersons = presPersArr.join(PRESENT_PERSONS_DELIMITER);
               }
               
            
            
               // Log
               if (LOG_INFO && (message != '')) {
                   if (isAnyonePresent) {
                       log(message + ', damit ' + (counter <= 1 ? 'ist':'sind') + ' jetzt ' + counter + (counter <= 1 ? ' Person anwesend: ':' Personen anwesend: ') + presentPersons); 
                   } else {
                       log(message + ', damit ist jetzt niemand mehr anwesend.'); 
                   }
               }
            
               HTMLString += "</body></table>";  
               
               setState(FINAL_STATE_PATH + 'presentPersonsJson', JSON.stringify(jsonArr));
               setState(FINAL_STATE_PATH + 'presentPersonsHTML', HTMLString);
            
               setState(FINAL_STATE_PATH + 'anyonePresent', isAnyonePresent);
               setState(FINAL_STATE_PATH + 'allPresentPersonsCount', counter);
               setState(FINAL_STATE_PATH + 'presentPersonsString', presentPersons);
            
            
               if(presentPersons.length > 0) {
                   postMessage("PERSONS_AVAILABLE_INFO", presentPersons, counter);
            
               } else {
                   removeMessage("PERSONS_AVAILABLE_INFO");
               }
            
               // Anwesenheitssimulation ein-oder ausschalten
               if (SIMULATION_ACTIVE){
                   if (isAnyonePresent) {
                       setState(FINAL_STATE_PATH + 'presenceSimulationActive', false);    
                   } else {
                       if (! getState(FINAL_STATE_PATH + 'presenceSimulationActive').val) {
                           // Presence simulation is currently off, so we set flag to true
                           setStateDelayed(FINAL_STATE_PATH + 'presenceSimulationActive', true, SIMULATION_DELAY * 1000);
                           if (LOG_INFO) log('Presence Simulation flag will be activated in ' + SIMULATION_DELAY + ' seconds.');     
                       }
                   } 
               }
              
            }
            
            
            /*********************************
            * Schreibt einen Logeintrag in das Filesystem
            * @param {string}   string      Logeintrag
            *********************************/
            function writelog(string) {
               let fs = require('fs');
               let logdate = formatDate(new Date(),"TT.MM.JJJJ");
               let logtime = formatDate(new Date(),"SS:mm:ss");
            
               if (fs.existsSync(LOGPATH_FS)) {
                   fs.appendFileSync(LOGPATH_FS, logdate + ";" + logtime + ";" + string + "\n");       // Füge Zeile in Datei ein
               } else {     
                   if (LOG_DEBUG) log('Logfile [' + LOGPATH_FS + '] nicht vorhanden, wird daher neu angelegt.');
                   let headerLine = "Datum;Uhrzeit;Name;Gerät;Kommt-Geht";
                   fs.appendFileSync(LOGPATH_FS, headerLine + "\n");       // Füge Zeile in Datei ein
                   fs.appendFileSync(LOGPATH_FS, logdate + ";" + logtime + ";" + string + "\n");       // Füge Zeile in Datei ein
               }
            }
            
            /**
            * Prepare states we need to create
            * @return {object} Array of all states to be created with createUserStates()
            */
            function buildScriptStates() {
               let finalStates = [];
               for (const lpDevice in DEVICES) {
                   finalStates.push([FINAL_STATE_PATH + 'persons.' + cl(DEVICES[lpDevice]) + '.isPresent', {name: 'Is '+ cl(DEVICES[lpDevice]) + ' currently present?', type: 'boolean', read: true, write: false, def:false }]);
                   finalStates.push([FINAL_STATE_PATH + 'persons.' + cl(DEVICES[lpDevice]) + '.timeLastLeave', {name: 'Time of last LEAVE of  ' + cl(DEVICES[lpDevice]), type: 'string', read: true, write: false, def:'' }]);
                   finalStates.push([FINAL_STATE_PATH + 'persons.' + cl(DEVICES[lpDevice]) + '.timeLastEntry', {name: 'Time of last ENTRY of ' + cl(DEVICES[lpDevice]), type: 'string', read: true, write: false, def:'' }]);
                   finalStates.push([FINAL_STATE_PATH + 'persons.' + cl(DEVICES[lpDevice]) + '.timeMostRecent', {name: 'Time of most recent entry or leave of ' + cl(DEVICES[lpDevice]), type: 'string', read: true, write: false, def:'' }]);
                   finalStates.push([FINAL_STATE_PATH + 'persons.' + cl(DEVICES[lpDevice]) + '.offsetEntryLeave', {name:'Offset: Leave date/time - Entry date/time', type:'string', read:true, write:false, def:'' }]);
               }
               finalStates.push([FINAL_STATE_PATH + 'anyonePresent',          {name: 'Is any person present?', type: 'boolean', read: true, write: false, def: false }]);
               finalStates.push([FINAL_STATE_PATH + 'presentPersonsString',   {name: 'List of present persons: String', type: 'string', read: true, write: false, def: '' }]);
               finalStates.push([FINAL_STATE_PATH + 'presentPersonsJson',     {name: 'List of present persons: JSON', type: 'string', read: true, write: false, def: '' }]);
               finalStates.push([FINAL_STATE_PATH + 'presentPersonsHTML',     {name: 'List of present persons: HTML', type: 'string', read: true, write: false, def: '' }]);
               finalStates.push([FINAL_STATE_PATH + 'allPresentPersonsCount', {name:'Number of present persons', type: 'number', read: true, write: false, def: 0 }]);
               if (SIMULATION_ACTIVE) finalStates.push([FINAL_STATE_PATH + 'presenceSimulationActive', {name: 'Presense Simulation Status', type: 'boolean', read: true, write: false, def: false }]);
            
               return finalStates;
            }
            
            
            /**
            * Just keep letters, numbers, umlauts, '-' and '_'
            */
            function cl(strToClean) {
               return strToClean.replace(/[^a-zA-Z0-9ß-ü-_]/g,'');
            }
            
            /**
            * Checks if a a given state or part of state is existing.
            * This is a workaround, as getObject() or getState() throw warnings in the log.
            * Set strict to true if the state shall match exactly. If it is false, it will add a wildcard * to the end.
            * See: https://forum.iobroker.net/topic/11354/
            * @param {string}    strStatePath     Input string of state, like 'javas-cript.0.switches.Osram.Bedroom'
            * @param {boolean}   [strict=true]    Optional: Default is true. If true, it will work strict, if false, it will add a wildcard * to the end of the string
            * @return {boolean}                   true if state exists, false if not
            */
            function isState(strStatePath, strict) {
            
               if(strict === undefined) strict = true;
            
               let mSelector;
               if (strict) {
                   mSelector = $('state[id=' + strStatePath + '$]');
               } else {
                   mSelector = $('state[id=' + strStatePath + ']');
               }
               if (mSelector.length > 0) {
                   return true;
               } else {
                   return false;
               }
            }
            
            
            /**
            * Checks if Array or String is not undefined, null or empty.
            * Array or String containing just whitespaces or >'< or >"< is considered empty
            * @param inputVar - Input Array or String, Number, etc.
            * @return true if it is undefined/null/empty, false if it contains value(s)
            */
            function isEmpty(inputVar) {
               if (typeof inputVar !== 'undefined' && inputVar !== null) {
                   var strTemp = JSON.stringify(inputVar);
                   strTemp = strTemp.replace(/\s+/g, ''); // remove all whitespaces
                   strTemp = strTemp.replace(/\"+/g, "");  // remove all >"<
                   strTemp = strTemp.replace(/\'+/g, "");  // remove all >'<  
                   if (strTemp !== '') {
                       return false;            
                   } else {
                       return true;
                   }
               } else {
                   return true;
               }
            }
            
            /**
            * Fügt Vornullen zu einer Zahl hinzu, macht also z.B. aus 7 eine "007". 
            * zeroPad(5, 4);    // wird "0005"
            * zeroPad('5', 6);  // wird "000005"
            * zeroPad(1234, 2); // wird "1234" :)
            * @param  {string|number}  num     Zahl, die Vornull(en) bekommen soll
            * @param  {number}         places  Anzahl Stellen.
            * @return {string}         Zahl mit Vornullen wie gewünscht.
            */
            function zeroPad(num, places) {
               let zero = places - num.toString().length + 1;
               return Array(+(zero > 0 && zero)).join("0") + num;        
            
            
            } 
            
            
            
            /**
            * For a given state path, we extract the location '0_userdata.0' or 'javascript.0' or add '0_userdata.0', if missing.
            * @param {string}  path            Like: 'Computer.Control-PC', 'javascript.0.Computer.Control-PC', '0_userdata.0.Computer.Control-PC'
            * @param {boolean} returnFullPath  If true: full path like '0_userdata.0.Computer.Control-PC', if false: just location like '0_userdata.0' or 'javascript.0'
            * @return {string}                 Path
            */
            function validateStatePath(path, returnFullPath) {
               if (path.startsWith('.')) path = path.substr(1);    // Remove first dot
               if (path.endsWith('.'))   path = path.slice(0, -1); // Remove trailing dot
               if (path.length < 1) log('Provided state path is not valid / too short.', 'error')
               let match = path.match(/^((javascript\.([1-9][0-9]|[0-9])\.)|0_userdata\.0\.)/);
               let location = (match == null) ? '0_userdata.0' : match[0].slice(0, -1); // default is '0_userdata.0'.
               if(returnFullPath) {
                   return (path.indexOf(location) == 0) ? path : (location + '.' + path);
               } else {
                   return location;
               }
            }
            
            
            /**
            * Create states under 0_userdata.0 or javascript.x
            * Current Version:     https://github.com/Mic-M/iobroker.createUserStates
            * Support:             https://forum.iobroker.net/topic/26839/
            * Autor:               Mic (ioBroker) | Mic-M (github)
            * Version:             1.1 (26 January 2020)
            * Example:             see https://github.com/Mic-M/iobroker.createUserStates#beispiel
            * -----------------------------------------------
            * PLEASE NOTE: Per https://github.com/ioBroker/ioBroker.javascript/issues/474, the used function setObject() 
            *              executes the callback PRIOR to completing the state creation. Therefore, we use a setTimeout and counter. 
            * -----------------------------------------------
            * @param {string} where          Where to create the state: '0_userdata.0' or 'javascript.x'.
            * @param {boolean} force         Force state creation (overwrite), if state is existing.
            * @param {array} statesToCreate  State(s) to create. single array or array of arrays
            * @param {object} [callback]     Optional: a callback function -- This provided function will be executed after all states are created.
            */
            function createUserStates(where, force, statesToCreate, callback = undefined) {
            
               const WARN = false; // Only for 0_userdata.0: Throws warning in log, if state is already existing and force=false. Default is false, so no warning in log, if state exists.
               const LOG_DEBUG = false; // To debug this function, set to true
               // Per issue #474 (https://github.com/ioBroker/ioBroker.javascript/issues/474), the used function setObject() executes the callback 
               // before the state is actual created. Therefore, we use a setTimeout and counter as a workaround.
               const DELAY = 50; // Delay in milliseconds (ms). Increase this to 100, if it is not working.
            
               // Validate "where"
               if (where.endsWith('.')) where = where.slice(0, -1); // Remove trailing dot
               if ( (where.match(/^((javascript\.([1-9][0-9]|[0-9]))$|0_userdata\.0$)/) == null) ) {
                   log('This script does not support to create states under [' + where + ']', 'error');
                   return;
               }
            
               // Prepare "statesToCreate" since we also allow a single state to create
               if(!Array.isArray(statesToCreate[0])) statesToCreate = [statesToCreate]; // wrap into array, if just one array and not inside an array
            
               // Add "where" to STATES_TO_CREATE
               for (let i = 0; i < statesToCreate.length; i++) {
                   let lpPath = statesToCreate[i][0].replace(/\.*\./g, '.'); // replace all multiple dots like '..', '...' with a single '.'
                   lpPath = lpPath.replace(/^((javascript\.([1-9][0-9]|[0-9])\.)|0_userdata\.0\.)/,'') // remove any javascript.x. / 0_userdata.0. from beginning
                   lpPath = where + '.' + lpPath; // add where to beginning of string
                   statesToCreate[i][0] = lpPath;
               }
            
               if (where != '0_userdata.0') {
                   // Create States under javascript.x
                   let numStates = statesToCreate.length;
                   statesToCreate.forEach(function(loopParam) {
                       if (LOG_DEBUG) log('[Debug] Now we are creating new state [' + loopParam[0] + ']');
                       let loopInit = (loopParam[1]['def'] == undefined) ? null : loopParam[1]['def']; // mimic same behavior as createState if no init value is provided
                       createState(loopParam[0], loopInit, force, loopParam[1], function() {
                           numStates--;
                           if (numStates === 0) {
                               if (LOG_DEBUG) log('[Debug] All states processed.');
                               if (typeof callback === 'function') { // execute if a function was provided to parameter callback
                                   if (LOG_DEBUG) log('[Debug] Function to callback parameter was provided');
                                   return callback();
                               } else {
                                   return;
                               }
                           }
                       });
                   });
               } else {
                   // Create States under 0_userdata.0
                   let numStates = statesToCreate.length;
                   let counter = -1;
                   statesToCreate.forEach(function(loopParam) {
                       counter += 1;
                       if (LOG_DEBUG) log ('[Debug] Currently processing following state: [' + loopParam[0] + ']');
                       if( ($(loopParam[0]).length > 0) && (existsState(loopParam[0])) ) { // Workaround due to https://github.com/ioBroker/ioBroker.javascript/issues/478
                           // State is existing.
                           if (WARN && !force) log('State [' + loopParam[0] + '] is already existing and will no longer be created.', 'warn');
                           if (!WARN && LOG_DEBUG) log('[Debug] State [' + loopParam[0] + '] is already existing. Option force (=overwrite) is set to [' + force + '].');
                           if(!force) {
                               // State exists and shall not be overwritten since force=false
                               // So, we do not proceed.
                               numStates--;
                               if (numStates === 0) {
                                   if (LOG_DEBUG) log('[Debug] All states successfully processed!');
                                   if (typeof callback === 'function') { // execute if a function was provided to parameter callback
                                       if (LOG_DEBUG) log('[Debug] An optional callback function was provided, which we are going to execute now.');
                                       return callback();
                                   }
                               } else {
                                   // We need to go out and continue with next element in loop.
                                   return; // https://stackoverflow.com/questions/18452920/continue-in-cursor-foreach
                               }
                           } // if(!force)
                       }
            
                       // State is not existing or force = true, so we are continuing to create the state through setObject().
                       let obj = {};
                       obj.type = 'state';
                       obj.native = {};
                       obj.common = loopParam[1];
                       setObject(loopParam[0], obj, function (err) {
                           if (err) {
                               log('Cannot write object for state [' + loopParam[0] + ']: ' + err);
                           } else {
                               if (LOG_DEBUG) log('[Debug] Now we are creating new state [' + loopParam[0] + ']')
                               let init = null;
                               if(loopParam[1].def === undefined) {
                                   if(loopParam[1].type === 'number') init = 0;
                                   if(loopParam[1].type === 'boolean') init = false;
                                   if(loopParam[1].type === 'string') init = '';
                               } else {
                                   init = loopParam[1].def;
                               }
                               setTimeout(function() {
                                   setState(loopParam[0], init, true, function() {
                                       if (LOG_DEBUG) log('[Debug] setState durchgeführt: ' + loopParam[0]);
                                       numStates--;
                                       if (numStates === 0) {
                                           if (LOG_DEBUG) log('[Debug] All states processed.');
                                           if (typeof callback === 'function') { // execute if a function was provided to parameter callback
                                               if (LOG_DEBUG) log('[Debug] Function to callback parameter was provided');
                                               return callback();
                                           }
                                       }
                                   });
                               }, DELAY + (20 * counter) );
                           }
                       });
                   });
               }
            }
            
            

            Mic-M created this issue in iobroker-community-adapters/ioBroker.tr-064-community

            closed iOS: state devices.xxx.active changes false/true/false if person leaves #55

            Mic-M created this issue in ioBroker/ioBroker.javascript

            closed setObject() function: callback not working as intended. #474

            Mic-M created this issue in ioBroker/ioBroker.javascript

            closed setObject() function: callback not working as intended. #474

            Mic-M created this issue in ioBroker/ioBroker.javascript

            closed 0_userdata.0: existsState() vs. $-Selector $().length after state deletion #478

            uwe12489 1 Reply Last reply Reply Quote 0
            • uwe12489
              uwe12489 @Tirador last edited by

              @Tirador
              Das geht ja wirklich super schnell. Danke.
              Mit dem neuen Script kommt folgender Log:
              Bildschirmfoto 2020-04-09 um 15.55.40.png
              Er findet wohl presentpersons nicht...

              uwe12489 1 Reply Last reply Reply Quote 0
              • uwe12489
                uwe12489 @uwe12489 last edited by

                @uwe12489
                Ah, eine ähnliche Meldung kommt auch bei removeMessage
                Bildschirmfoto 2020-04-09 um 17.30.58.png

                T 1 Reply Last reply Reply Quote 0
                • T
                  Tirador @uwe12489 last edited by

                  @uwe12489 dann hast du das message.js Script nicht unter global installiert.

                  uwe12489 1 Reply Last reply Reply Quote 0
                  • uwe12489
                    uwe12489 @Tirador last edited by

                    @Tirador
                    Blöder Fehler von mir 😉 Danke

                    1 Reply Last reply Reply Quote 0
                    • T
                      Tirador last edited by Tirador

                      Ich hatte die Tage etwas Zeit an der Lösung weiterzuarbeiten.

                      Ich habe nun noch ein weiteres Skript hinzugefügt "MessageStateCreator".
                      Damit kann man konfigurabel Datenpunkte überwachen und auch nur bei bestimmten Bedingungen Nachrichten erzeugen oder auch entfernen. Die Textausgabe der Nachrichten kann auch konfiguriert werden und dynamisch erzeugt werden.
                      Damit ist es nun nicht mehr notwendig in anderen Skripten hand anzulegen, sofern die notwendigen Datenpunkte bereits zur Verfügung stehen.

                      Damit kann ich nun die folgenden Informationen automatisiert erzeugen:
                      2020-04-12 12_27_30-vis.png

                      Insgesamt bin ich schon sehr zufrieden damit. 🙂

                      Ich habe das initale Posting in diesem Thread um die neuen und aktualisierten Skripte ergänzt, sowie die Anleitung aktualisiert.

                      Details zur Konfiguration inkl. Beispiel ist im Skript MessageStateCreator vorhanden.

                      Ein Auszug aus meinen Beispielen (Spoiler):


                      Briefkastensensor:

                          // Letzter Briefkasteneinwurf
                          // Eine Nachricht wird nur ausgelöst, wenn der Sensor aktiviert wird
                          {
                              msgID: 'LAST_POSTENTRACE_INFO',
                              triggerDP: 'deconz.0.Sensors.8.open',
                              postMsgDP: {dp:'deconz.0.Sensors.8.open', comp: '==', val:true},
                              msgText_1: {text: ''},
                              countEventsDP: ''
                          }, 
                      

                      Nachricht, wenn alle Fenster geschlossen sind:

                          // Eigene Nachricht, wenn alle Fenster geschlossen sind (Nur INFO)
                          // Datenpunkte basieren auf Pitinis Fensterskript
                          // GITHUB: https://github.com/Pittini/iobroker-Batterienauswertung
                          // Forum IOBroker: https://forum.iobroker.net/topic/31676/vorlage-generische-batteriestands%C3%BCberwachung-vis-ausgabe
                          {
                              msgID: 'WINDOW_ISCLOSED_INFO', 
                              triggerDP: ['javascript.0.FensterUeberwachung.RoomsWithOpenWindows', 'javascript.0.FensterUeberwachung.WindowsOpen'],
                              postMsgDP: {dp:'javascript.0.FensterUeberwachung.WindowsOpen', comp: '==', val:0},
                              removeMsgDP: {dp:'javascript.0.FensterUeberwachung.WindowsOpen', comp: '>', val:0}, // Nachricht enfernen, wenn die Bedingung eintritt
                              msgText_1: {text: ''},
                              msgText_2: {dp: 'javascript.0.FensterUeberwachung.RoomsWithOpenWindows'},
                              countEventsDP: 'javascript.0.FensterUeberwachung.WindowsOpen'
                          },
                      

                      Corona-Statistiken:

                          // Corona-Statistics
                          {
                              msgID: 'CORONA_STATS_CASES', 
                              triggerDP: ['coronavirus-statistics.0.Germany.cases', 'coronavirus-statistics.0.Germany.deaths'],
                              postMsgDP: {dp:'coronavirus-statistics.0.Germany.cases'},
                              msgText_1: {text: '☣ Bestätigt: '},
                              msgText_2: {dp: 'coronavirus-statistics.0.Germany.cases'},
                              msgText_3: {text: '</br>♱ Tote: '},
                              msgText_4: {dp: 'coronavirus-statistics.0.Germany.deaths'},
                              countEvents: 'coronavirus-statistics.0.Germany.deaths'
                          },
                      
                      

                      1 Reply Last reply Reply Quote 0
                      • T
                        Tirador last edited by Tirador

                        Da ich durch umfangreiche Bearbeitung des initialen Postings das Layout so "zerstückelt" habe, ist leider dieser Thread unbrauchbar geworden. Das initiale Posting ist nicht mehr editierbar / nichtmal für die Admins.

                        Ich habe daher ein neues Posting angelegt und bitte alle dort weiterzumachen:

                        https://forum.iobroker.net/topic/32207/script-messagehandler-nachrichten-protokollieren-vis

                        @Foren-Admins:
                        Bitte diesen Thread schließen.

                        1 Reply Last reply Reply Quote 0
                        • First post
                          Last post

                        Support us

                        ioBroker
                        Community Adapters
                        Donate

                        800
                        Online

                        31.8k
                        Users

                        80.0k
                        Topics

                        1.3m
                        Posts

                        javascript monitoring template
                        3
                        18
                        1501
                        Loading More Posts
                        • Oldest to Newest
                        • Newest to Oldest
                        • Most Votes
                        Reply
                        • Reply as topic
                        Log in to reply
                        Community
                        Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
                        The ioBroker Community 2014-2023
                        logo