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. [Script] MessageHandler: Nachrichten protokollieren +Widget

NEWS

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

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

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

[Script] MessageHandler: Nachrichten protokollieren +Widget

Geplant Angeheftet Gesperrt Verschoben JavaScript
javascripttemplatemonitoring
18 Beiträge 3 Kommentatoren 2.0k Aufrufe 10 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.
  • sigi234S sigi234

    @Tirador sagte in [Script] MessageHandler: Nachrichten protokollieren +Widget:

    @sigi234 Hallo Sigi 234, hast du dir nur das message.js angesehen? Die Hauptlogik ist in der Datei MessageHandler.js

    Aha, also einmal das Hauptskript und einmal Global.

    T Offline
    T Offline
    Tirador
    schrieb am zuletzt editiert von
    #5

    @sigi234 ja genau.

    sigi234S 1 Antwort Letzte Antwort
    0
    • T Tirador

      @sigi234 ja genau.

      sigi234S Online
      sigi234S Online
      sigi234
      Forum Testing Most Active
      schrieb am zuletzt editiert von sigi234
      #6

      @Tirador

      Ok, teste gerade.
      Also Skripte laufen ohne Fehler.
      Was muss wo der User konfigurieren?
      Die Skripte und die Views zum runterladen sind nicht im .txt Format? Warum?
      Ich weis mir ja zu helfen.

      Also ein Beispiel, ich möchte diese Message :

      postMessage("DOOR_ISOPEN_INFO", "Haustür"); // Haustür ist geöffnet.

      DP:
      hm-rpc.0.NEQ1818500.1.STATE

      Wo muss ich jetzt was ändern?

      Bitte benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
      Immer Daten sichern!

      T 1 Antwort Letzte Antwort
      0
      • sigi234S sigi234

        @Tirador

        Ok, teste gerade.
        Also Skripte laufen ohne Fehler.
        Was muss wo der User konfigurieren?
        Die Skripte und die Views zum runterladen sind nicht im .txt Format? Warum?
        Ich weis mir ja zu helfen.

        Also ein Beispiel, ich möchte diese Message :

        postMessage("DOOR_ISOPEN_INFO", "Haustür"); // Haustür ist geöffnet.

        DP:
        hm-rpc.0.NEQ1818500.1.STATE

        Wo muss ich jetzt was ändern?

        T Offline
        T Offline
        Tirador
        schrieb am zuletzt editiert von Tirador
        #7

        @sigi234 aktuell gibt es nur das Framework zur Erzeugung und Ausgabe der Nachrichten.
        D.h. du brauchst noch ein separates Skript, welches deinen Datenpunkt überwacht und dann die Javascript Methode postMessage aufruft.
        Das geht auch in blockly. Ein blockly Beispiel kann ich morgen posten.

        In vielen anderen Skripten kann man natürlich auch direkt die Nachrichten integrieren. Die Nachricht für die aktuell anwesenden Personen erzeuge ich direkt im presence tr64 Skript von mic.

        Ich arbeite aktuell noch an einem weiteren Skript, um in Abhängigkeit von einstellbaren überwachenden Datenpunkten die Nachrichten auszulösen. Damit wird das Problem dann vereinfacht und generalisiert.

        1 Antwort Letzte Antwort
        1
        • T Offline
          T Offline
          Tirador
          schrieb am zuletzt editiert von
          #8

          Ich habe jetzt ein einfaches Blockly als Beispiel exportiert, die den Einsatz der Funktion zeigen.

          Das Beispiel ist mein Briefkastensensor, der eine Nachricht auslöst, wenn jemand Post einwirft.

          2020-04-07 19_40_43-javascript - ioBroker.png

          Blockly-Skript

          <xml xmlns="http://www.w3.org/1999/xhtml">
           <variables>
             <variable type="" id="VnOcDmXSH:k492pUGuDR">message</variable>
           </variables>
           <block type="on_ext" id="htG$ftnD|+dec}BK7ONm" x="63" y="38">
             <mutation items="1"></mutation>
             <field name="CONDITION">any</field>
             <field name="ACK_CONDITION"></field>
             <value name="OID0">
               <shadow type="field_oid" id="[(0c+QMnycfw+9^U;$O7">
                 <field name="oid">deconz.0.Sensors.8.open</field>
               </shadow>
             </value>
             <statement name="STATEMENT">
               <block type="controls_if" id="4lD40^K_uOV~GUDpAw,!">
                 <value name="IF0">
                   <block type="get_value" id="B!WNR-Z|]i==DZ)2*Oe3">
                     <field name="ATTR">val</field>
                     <field name="OID">deconz.0.Sensors.8.open</field>
                   </block>
                 </value>
                 <statement name="DO0">
                   <block type="update" id="GZ)NF${Zz8,eYE3cFn(:">
                     <mutation delay_input="false"></mutation>
                     <field name="OID">0_userdata.0.newPost</field>
                     <field name="WITH_DELAY">FALSE</field>
                     <value name="VALUE">
                       <block type="logic_boolean" id="W~BRjB?r+!9CY#InDqq.">
                         <field name="BOOL">TRUE</field>
                       </block>
                     </value>
                     <next>
                       <block type="telegram" id="mefp#g_-kQE!h]dAzqt.">
                         <field name="INSTANCE"></field>
                         <field name="LOG">log</field>
                         <field name="SILENT">FALSE</field>
                         <field name="PARSEMODE">default</field>
                         <value name="MESSAGE">
                           <shadow type="text" id="w0:Xf;n0aS8spJdI4nDl">
                             <field name="TEXT">✉ Neue Post im Briefkasten!</field>
                           </shadow>
                         </value>
                         <next>
                           <block type="procedures_callcustomnoreturn" id="AAZj9j=w8=Tx*h=Rd?E7">
                             <mutation name="MessageHandler">
                               <arg name="message"></arg>
                             </mutation>
                             <value name="ARG0">
                               <block type="text" id="xNiuZ-=xz%d]#.9BS,tu">
                                 <field name="TEXT">✉ Neue Post im Briefkasten!</field>
                               </block>
                             </value>
                           </block>
                         </next>
                       </block>
                     </next>
                   </block>
                 </statement>
               </block>
             </statement>
           </block>
           <block type="procedures_defcustomnoreturn" id="/XN%MvZ2GnXAjVrqd=+Q" x="63" y="463">
             <mutation statements="false">
               <arg name="message" varid="VnOcDmXSH:k492pUGuDR"></arg>
             </mutation>
             <field name="NAME">MessageHandler</field>
             <field name="SCRIPT">cG9zdE1lc3NhZ2UoJ0xBU1RfUE9TVEVOVFJBQ0VfSU5GTycsIG1lc3NhZ2UpOw==</field>
             <comment pinned="false" h="80" w="160">Material Design Alert</comment>
           </block>
          </xml>
          

          uwe12489U 1 Antwort Letzte Antwort
          0
          • T Tirador

            Ich habe jetzt ein einfaches Blockly als Beispiel exportiert, die den Einsatz der Funktion zeigen.

            Das Beispiel ist mein Briefkastensensor, der eine Nachricht auslöst, wenn jemand Post einwirft.

            2020-04-07 19_40_43-javascript - ioBroker.png

            Blockly-Skript

            <xml xmlns="http://www.w3.org/1999/xhtml">
             <variables>
               <variable type="" id="VnOcDmXSH:k492pUGuDR">message</variable>
             </variables>
             <block type="on_ext" id="htG$ftnD|+dec}BK7ONm" x="63" y="38">
               <mutation items="1"></mutation>
               <field name="CONDITION">any</field>
               <field name="ACK_CONDITION"></field>
               <value name="OID0">
                 <shadow type="field_oid" id="[(0c+QMnycfw+9^U;$O7">
                   <field name="oid">deconz.0.Sensors.8.open</field>
                 </shadow>
               </value>
               <statement name="STATEMENT">
                 <block type="controls_if" id="4lD40^K_uOV~GUDpAw,!">
                   <value name="IF0">
                     <block type="get_value" id="B!WNR-Z|]i==DZ)2*Oe3">
                       <field name="ATTR">val</field>
                       <field name="OID">deconz.0.Sensors.8.open</field>
                     </block>
                   </value>
                   <statement name="DO0">
                     <block type="update" id="GZ)NF${Zz8,eYE3cFn(:">
                       <mutation delay_input="false"></mutation>
                       <field name="OID">0_userdata.0.newPost</field>
                       <field name="WITH_DELAY">FALSE</field>
                       <value name="VALUE">
                         <block type="logic_boolean" id="W~BRjB?r+!9CY#InDqq.">
                           <field name="BOOL">TRUE</field>
                         </block>
                       </value>
                       <next>
                         <block type="telegram" id="mefp#g_-kQE!h]dAzqt.">
                           <field name="INSTANCE"></field>
                           <field name="LOG">log</field>
                           <field name="SILENT">FALSE</field>
                           <field name="PARSEMODE">default</field>
                           <value name="MESSAGE">
                             <shadow type="text" id="w0:Xf;n0aS8spJdI4nDl">
                               <field name="TEXT">✉ Neue Post im Briefkasten!</field>
                             </shadow>
                           </value>
                           <next>
                             <block type="procedures_callcustomnoreturn" id="AAZj9j=w8=Tx*h=Rd?E7">
                               <mutation name="MessageHandler">
                                 <arg name="message"></arg>
                               </mutation>
                               <value name="ARG0">
                                 <block type="text" id="xNiuZ-=xz%d]#.9BS,tu">
                                   <field name="TEXT">✉ Neue Post im Briefkasten!</field>
                                 </block>
                               </value>
                             </block>
                           </next>
                         </block>
                       </next>
                     </block>
                   </statement>
                 </block>
               </statement>
             </block>
             <block type="procedures_defcustomnoreturn" id="/XN%MvZ2GnXAjVrqd=+Q" x="63" y="463">
               <mutation statements="false">
                 <arg name="message" varid="VnOcDmXSH:k492pUGuDR"></arg>
               </mutation>
               <field name="NAME">MessageHandler</field>
               <field name="SCRIPT">cG9zdE1lc3NhZ2UoJ0xBU1RfUE9TVEVOVFJBQ0VfSU5GTycsIG1lc3NhZ2UpOw==</field>
               <comment pinned="false" h="80" w="160">Material Design Alert</comment>
             </block>
            </xml>
            

            uwe12489U Offline
            uwe12489U Offline
            uwe12489
            schrieb am zuletzt editiert von
            #9

            @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 Antwort Letzte Antwort
            0
            • uwe12489U uwe12489

              @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 Offline
              T Offline
              Tirador
              schrieb am zuletzt editiert von
              #10

              @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.

              uwe12489U 1 Antwort Letzte Antwort
              0
              • T Tirador

                @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.

                uwe12489U Offline
                uwe12489U Offline
                uwe12489
                schrieb am zuletzt editiert von
                #11

                @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 Antwort Letzte Antwort
                0
                • uwe12489U uwe12489

                  @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 Offline
                  T Offline
                  Tirador
                  schrieb am zuletzt editiert von
                  #12

                  @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) );
                                 }
                             });
                         });
                     }
                  }
                  
                  

                  uwe12489U 1 Antwort Letzte Antwort
                  0
                  • T Tirador

                    @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) );
                                   }
                               });
                           });
                       }
                    }
                    
                    

                    uwe12489U Offline
                    uwe12489U Offline
                    uwe12489
                    schrieb am zuletzt editiert von
                    #13

                    @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...

                    uwe12489U 1 Antwort Letzte Antwort
                    0
                    • uwe12489U uwe12489

                      @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...

                      uwe12489U Offline
                      uwe12489U Offline
                      uwe12489
                      schrieb am zuletzt editiert von
                      #14

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

                      T 1 Antwort Letzte Antwort
                      0
                      • uwe12489U uwe12489

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

                        T Offline
                        T Offline
                        Tirador
                        schrieb am zuletzt editiert von
                        #15

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

                        uwe12489U 1 Antwort Letzte Antwort
                        0
                        • T Tirador

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

                          uwe12489U Offline
                          uwe12489U Offline
                          uwe12489
                          schrieb am zuletzt editiert von
                          #16

                          @Tirador
                          Blöder Fehler von mir ;-) Danke

                          1 Antwort Letzte Antwort
                          0
                          • T Offline
                            T Offline
                            Tirador
                            schrieb am zuletzt editiert von Tirador
                            #17

                            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 Antwort Letzte Antwort
                            0
                            • T Offline
                              T Offline
                              Tirador
                              schrieb am zuletzt editiert von Tirador
                              #18

                              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 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

                              918

                              Online

                              32.4k

                              Benutzer

                              81.5k

                              Themen

                              1.3m

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

                              • Du hast noch kein Konto? Registrieren

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