Navigation

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

    NEWS

    • Neuer Blog: Fotos und Eindrücke aus Solingen

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

    • ioBroker goes Matter ... Matter Adapter in Stable

    [Script] MessageHandler: Nachrichten protokollieren +Widget

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

      Ich habe ein Skript "MessageHandler" , "MessageGlobal" und "MessageStateCreator" erstellt zur Protokollierung von Nachrichten/Ereignissen.

      Beispiel:

      2020-04-06 18_02_59-vis.png
      2020-04-06 17_31_14-vis.png

      Kernfunktionen:

      • Ermöglicht es Nachrichten aus Skripten auszulösen und zu entfernen. Dies kann über zwei Wege erfolgen:

        • Automatisches Erzeugen/Entfernen von Nachrichten über das Javascript MessageStateCreator.
          Es werden konfigurierte Datenpunkte überwacht und bei konfigurierten Bedingungen Nachrichten erzeugt oder auch entfernt. Die Textausgabe der Nachrichten kann auch konfiguriert werden und dynamisch erzeugt werden.

        • Aufruf Javascript-Funktionen postMessage(..) oder removeMessage(..)

      • Es können entweder alle Nachrichten eines Nachrichtentyps protokolliert werden oder immer nur die letzte eingetretende Nachricht.

      • Nachrichten werden nach Prioritäten visuell in VIS dargestellt. Es stehen zwei VIS-Ausgaben zur Verfügung:

        • einfache HTML-Tabelle (ohne Schnickschnack)
        • Material Design CSS 2.0 Card für Uhula.
      • Nachrichten können (optional) in VIS global quittiert werden.

      • Nachrichtendefinition: Nachrichten werden über eine Konfigurationsstruktur definiert und damit wesentliche Eigenschaften der Nachricht bestimmt, darunter:

        • Nachrichtenüberschrift
        • Nachrichtentext
        • Kritikalität (Information, Warnung, Alarm etc.) / Priorität
        • Icon für die VIS Ausgabe
        • Farbe des Icons

      Beispiele für Nachrichtenereignisse:

      • Alarmanlage ausgelöst!
      • Wasseralarm
      • Erinnerung Fenster lüften!
      • Erinnerung Fenster zu lange geöffnet!
      • Aktuell offene Fenster
      • Aktuell
      • Aktuell offene Türen
      • Lichter angeschaltet
      • Aktive Steckdosen
      • Post im Briefkasten mit Datum letzter Einwurf
      • Nächster Müllabfuhrtermin mit Information zur Tonne
      • Ausgabe Temperatur / Luftfeuchte
      • Corona-Statistiken
      • Termine des Tages
      • Termine morgen

      Nachrichten können damit als kompakte Darstellung des globalen Systemzustands in VIS verwendet werden.
      Die Idee ist es, alle relevanten Informationen auf "einen Blick" zu erkennen. Wichtige und kritische Ereignisse werden daher in der Liste zentral oben platziert. Unwichtigere Informationen eher unten.

      Installation

      1. Das Javascript "MessageGlobal" als globales Script installieren und starten.

      2. Den Javascript "MessageHandler" serverseitiges Script installieren und starten-5 Sek warten-stoppen-starten.
        Beim 1.Start werden die notwendigen States unter STATE_PATH = '0_userdata.0.messageHandler.'
        erzeugt. Erst beim 2.Start instanziiert das Script die Event-Handler und läuft dann.

      3. Das Javascript "MessageStateCreator" installieren und starten (optional)

      4. VIS-Ausgabe: Für das Material Design CSS von Uhula steht eine eigene Card in Form einer View zur Verfügung.
        Optional gibt es auch eine einfache HTML-Tabellenausgabe als View zur Verfügung.

      Konfiguration

      Zur Konfiguration sind zwei Schritte erforderlich:

      1. Die Grundkonfiguration erfolgt über die Festlegung von MESSAGE-IDs (Nachrichten-Ids)
        im Javascript "MessageHandler" (siehe Anleitung weiter unten). Optional kann in der Funktion MessageHandler|doInit() eine Anpassung der KONFIGURATION vorgenommen werden.

      2. Über das Javascript "MessageStateCreator" können Datenpunkte überwacht werden
        und Nachrichten automatisiert ausgelöst werden.
        Die Konfiguration erfolgt hierfür im Javascript "MessageStateCreator".
        Im Javascript selbst sind auch Beispiele enthalten, wie die Konfiguration durchgeführt wird.

      Definition of MESSAGE-IDs (Nachrichten-Ids)

      Nachrichten sind die Grundlage der Meldungen, die später aus Skripten ausgelöst werden.
      Eine Nachricht trägt eine eindeutige ID und Eigenschaften, die die Verarbeitung der Nachricht oder das Verhalten der Ausgabe steuern. Nachrichten werden über eine Konfigurationsstruktur definiert und damit wesentliche Eigenschaften der Nachricht bestimmt, darunter:

      • Nachrichtenüberschrift
      • Nachrichtentext
      • Kritikalität (Information, Warnung, Alarm etc.) / Priorität
      • Icon für die VIS Ausgabe
      • Farbe des Icons

      Die Konfiguration erfolgt über die Konstante MESSAGE_IDS im Skript MessageHandler:
      Spoiler Details zur Konfiguration von Nachrichten-IDs / msgIDs:

      const MESSAGE_IDS = {
      
              // Alarmanlage
              HOUSE_ALARM: {logType: 'LAST',  severity: 'ALARM',  msgHeader: "Alarm im Haus", msgText: "", quit: false, mdIcon: 'notification_important', mdIconColor: '', fontColor: '', backgroundColor: ''},
      
              // Wasseralarm
              WATER_ALARM: {logType: 'LAST',  severity: 'ALARM',  msgHeader: "Wasseralarm", msgText: "", quit: false, mdIcon: 'waves', mdIconColor: '', fontColor: '', backgroundColor: ''},
      
              // Offene Fenster
              WINDOW_ISOPEN_INFO: {logType: 'LAST',  severity: 'WARN',  msgHeader: "Fenster geöffnet", msgText: "Bitte Fenster schließen", quit: false, mdIcon: 'tab', mdIconColor: '', fontColor: '', backgroundColor: ''},
      
      
      

      Eine Nachrichten-ID hat die folgenden Eigenschaften:

      • Erster Teil (z.B. "HOUSE_ALARM"): Definition der eindeutigen MESSAGE-ID (Nachrichten-ID). Als Konvention sollte diese immer MSG_<ID> tragen.

      • logType: ALL = Protokollierung jeder Nachricht einzeln
        LAST = Protokollierung nur der letzten Nachricht (vorhergehende Nachrichten mit gleicher MESSAGE_ID werden gelöscht).
        Dies eignet sich beispielsweise für die Protokollierung des letzten Briefkasteneinwurfs, des letzten Anrufs etc.

      • severity: Einstufung der Nachricht in Zustände (Alarm, Fehler, Warnung, Information).
        Es sind folgende Zuordnungen möglich:
        ALARM = Alarm
        ERROR = Fehler
        WARN = Warnung
        INFO = Info

        Über die Severity können separate Vorgaben aller Eigenschaften einer Nachricht gemacht werden (siehe nächsten Abschnitt).
        Somit können Standardeinstellungen auf der Severity-Ebene definiert werden, die für jede Nachricht greifen,
        sofern Sie in der Nachricht nicht übersteuert werden.

      • priority: Priorität der Nachricht innerhalb aller anderen Nachrichten. Bestimmt die Sortierreihenfolge für die Ausgabe.
        Nachrichten gleicher Priorität werden nach Timestamp sortiert (neueste oben).
        In der Regel wird die Prorität über die Severitys gesteuert und nicht für jede Nachricht separat festgelegt.

      • msgHeader: Kopftext der Nachricht. Hier kann ein Standardtext definiert werden.

      • msgText: Text der Nachricht. im Nachrichtentext sind variable Parameter &1, &2 etc. möglich, die mit der Ausführung der Nachricht ersetzt werden.

      • quit: Die Eigenschaft bestimmt, ob die Nachricht in der VIS-Oberfläche für das Material Design Widget löschbar ist (true)

      • mdIcon: Material Design Icon-Name

      • mdIconColor: Material Design Farbcode für das Icon

      • fontColor: HTML-Farbcode für die Schriftfarbe der HTML-Ausgabe (aktuell nicht in der VIS-Ausgabe implementiert)

      • backgroundColor: HTML-Farbcode für die Hintergrundfarbe in der HTML-Ausgabe (aktuell nicht in der VIS-Ausgabe implementiert)

      Definition of MESSAGE_DEFAULTS_BY_SEVERITY (Standardeinstellungen von Nachrichten für SEVERITYs)

      Über die Severity können separate Vorgaben aller Eigenschaften einer Nachricht gemacht werden.
      Somit können Standardeinstellungen auf der Severity-Ebene definiert werden, die für jede Nachricht greifen,
      sofern Sie in der Nachrichten-Definition über die Konstante MESSAGE_IDS nicht übersteuert werden.

      Spoiler Konfiguration MESSAGE_DEFAULTS_BY_SEVERITY:

      Die Konfiguration erfolgt über die Konstante MESSAGE_DEFAULTS_BY_SEVERITY (Auszug aus dem Skript MessageHandler):

      const MESSAGE_DEFAULTS_BY_SEVERITY = {
      
          INFO: {logType: 'ALL',  severity: 'INFO',  priority: 1000, msgHeader: "", msgText: "", quit: false, mdIcon: 'info', mdIconColor: 'mdui-blue', fontColor: '', backgroundColor: 'mdui-blue-bg'},
          WARN: {logType: 'ALL',  severity: 'WARN',  priority: 2000, msgHeader: "", msgText: "", quit: false, mdIcon: 'warning', mdIconColor: 'mdui-amber', fontColor: '', backgroundColor: 'mdui-amber-bg'},
          ERROR: {logType: 'ALL',  severity: 'ERROR', priority: 3000, msgHeader: "", msgText: "", quit: false, mdIcon: 'error', mdIconColor: 'mdui-orange', fontColor: '', backgroundColor: 'mdui-orange-bg'},
          ALARM: {logType: 'ALL',  severity: 'ALARM', priority: 4000, msgHeader: "", msgText: "", quit: false, mdIcon: 'error', mdIconColor: 'mdui-red', fontColor: '', backgroundColor: 'mdui-red-bg'}
      };
      

      Es können prinzipiell die gleichen Eigenschaften (s.o.) gesteuert werden, wie für die Nachrichten-Definition selbst.

      Script MessageStateCreator: Automatisiertes Auslösen / Löschen von Nachrichten

      Über das Skript "MessageStateCreator" werden konfigurierte Datenpunkte überwacht und bei konfigurierten Bedingungen Nachrichten erzeugt oder auch entfernt. Die Textausgabe der Nachrichten kann auch konfiguriert werden und dynamisch erzeugt werden.

      Die Grundkonfiguration erfolgt über die Festlegung von MESSAGE-IDs (Nachrichten-Ids) im Javascript "MessageHandler".
      Anschließend kann im Skript "MessageStateCreator" in der Konstante MESSAGE_EVENTS die Konfiguration von zu überwachenden Datenpunkten erfolgen.

      Spoiler zur exemplarischen Konfiguration von Datenpunkten, die automatisch Nachrichten erzeugen:

         {
         	
         	// msgID: Eindeutige msgID, die im Javascript "MessageHandler" definiert ist.
         	//        Über die msgID erfolgt die Steuerung der 
         	//        Priorität, Loglevel (INFO, WARNING, ALARM, ERROR),
         	//        die Vorgabe des Icons, der Iconfarbe, ob eine Nachricht nur einmal geloggt wird uvm.
         	
             msgID: 'WINDOW_ISOPEN_INFO', 
         	
         	// Datenpunkte, die als Trigger überwacht werden (auf die bei Veränderung von Werten reagiert wird).
         	// Es kann ein Datenpunkt in der Notation '' angegeben werden, oder mehrere wie folgt im Beispiel in der Notation ['', '',...] 
         	
             triggerDP: ['javascript.0.FensterUeberwachung.RoomsWithOpenWindows', 'javascript.0.FensterUeberwachung.WindowsOpen'],
         	
      
         	// postMsg: Nachricht nur erzeugen, wenn ein vorgegebener Datenpunkt einer bestimmten Bedingung entspricht.
         	//          Im Beispiel müssen die Anzahl der geöffneten Fenster größer als 0 sein,
         	//          damit die Nachricht "Fenster geöffnet" ausgelöst wird.
         	//          
         	//       dp: Datenpunkt dessen Wert der Bedingung entsprichen muss
         	//       comp: Vergleichsoperator. Es sind folgende Operatoren erlaubt:
         	//             == gleich
         	//             != ungleich
         	//             >= größer gleich
         	//             <= kleiner gleich
         	//             >  größer
         	//             <  kleiner
         	//       val: Wert
         	//       Die Nachricht wird erzeugt, wenn die Bedingung "dp comp val" eintritt.
         	
             postMsgDP: {dp:'javascript.0.FensterUeberwachung.WindowsOpen', comp: '>', val:0}, 
         	
         	
         	// removeMsgDP: Nachricht entfernen, wenn ein vorgegebener Datenpunkt einer bestimmten Bedingung entspricht.
         	//          Im Beispiel wird die Nachricht "Fenster geöffnet" entfernt, 
         	//          wenn die Anzahl der geöffneten Fenster gleich 0 ist.
         	//
         	//       dp: Datenpunkt dessen Wert der Bedingung entsprichen muss
         	//       comp: Vergleichsoperator. Es sind folgende Operatoren erlaubt:
         	//             == gleich
         	//             != ungleich
         	//             >= größer gleich
         	//             <= kleiner gleich
         	//             >  größer
         	//             <  kleiner
         	//       val: Wert
         	//       Die Nachricht wird entfernt, wenn die Bedingung "dp comp val" eintritt.
         			
             removeMsgDP: {dp:'javascript.0.FensterUeberwachung.WindowsOpen', comp: '==', val:0}, // Nachricht enfernen, wenn die Bedingung eintritt
         	
         	// msgText_<Nr> : Diese Attribute bestimmen die Ausgabe des Nachrichtentextes.
         	// 
         	//                Es kann ein statischer Text ausgegeben werden durch das Attribut:
         	//                Beispiel: 
         	//                msgText_1: {text: 'Fenster ist geöffnet'},
         	//        
         	//                Der Wert eines Datenpunkts kann in die Fehlernachricht mit ausgegeben werden.
         	//                Beispel: 
         	//                msgText_2: {dp: 'javascript.0.FensterUeberwachung.RoomsWithOpenWindows'},
         	// 
         	//                Es können beliebig viele msgText_ Attribute (mit fortlaufender Nummer) 
         	//                eingefügt werden (msgText_1, msgText_2, msgText_3, usw.).
         	//                Der Nachrichtentext ergibt sich aus der Konkatenation aller einzelner Bausteine.
         	
             msgText_1: {text: ''},
             msgText_2: {dp: 'javascript.0.FensterUeberwachung.RoomsWithOpenWindows'},
         	
         	
         	// countEventsDP: Information wieviele Ereignisse für die Meldung eingetreten sind.
         	//                Dieses Element ist optional.
         	//                Die Anzahl wird über den vorgegebenen Datenpunkt ermittelt.
         	// 
             //                Beispiele: Für das Beispiel werden die Anzahl der offenen Fenster ausgegeben.
         	
             countEventsDP: 'javascript.0.FensterUeberwachung.WindowsOpen'
         },
         
      

      Javascript: Auslösen / Löschen von Nachrichten

      Über das globale Javascript "MessageFunctions" stehen zwei Methoden
      für das Erzeugen/löschen von Nachrichten zur Verfügung.


      Die Funktion postMessage dient dem Erzeugen von Nachrichten

      postMessage(msgID,  msgText='', countEvents=0, msgHeader='')
      

      Parameter:

      • msgID: eindeutige Nachrichten-ID (definiert in der Konstante MESSAGE_IDS unten)

      • msgText: Nachrichtentext (optional).
        Sofern der Nachrichtentext aus der Nachrichtendefinition stammen
        soll ist beim Funktionsaufruf undefined als Parameter vorzugeben.

      • countEvents: Information wieviele Ereignisse für die Meldung eingetreten sind.
        Beispiele:
        - Anzahl der offenen Fenster
        - Anzahl der angeschalteten Lichter
        - Anzahl der Termine des Tages

      Beispiele für das Auslösen von Nachrichten:

      postMessage("HOUSE_ALARM", "Bewegung im Haus"); // Alarm: Bewegung im Haus
      postMessage("OPEN_WINDOW_INFO", "Badezimmer");  // Fenster geöffnet im Badezimmer
      postMessage("WATER_ALARM", "Wasser im Kellerraum."); // Wasseralarm im Kellerraum
      postMessage("LIGHTS_ON_INFO", "Wohnzimmer, Flur, Küche", 5); // 5 Lichter im Flur, Wohnzimmer und Küche sind angeschaltet
      postMessage("DOOR_ISOPEN_INFO", "Haustür"); // Haustür ist geöffnet.
      

      Die Funktion removeMessage ermöglicht das gezielte entfernen von Nachrichten:

      removeMessage(msgID, msgText='')
      

      Dies ist nützlich, wenn ein Zustand zurückgenommen werden soll.

      Beispiel:

      removeMessage("DOOR_ISOPEN_INFO");
      

      States die durch das Skript angelegt werden


      Unter dem STATE_PATH (Default STATE_PATH ist '0_userdata.0.messageHandler.') werden die folgenden States erzeugt:
      version : Script-Version, wird verwendet um Script-Updates zu erkennen
      updatePressed : auf true setzen, wenn ein table/list update außerhalb des Intervals erfolgen soll

      • messages.table : enthält die table-HTML für ein basic-string (unescaped) Widget
      • messages.list : enthält die list-HTML für ein basic-string (unescaped) Widget
      • messages.count : Anzahl der Log-Zeilen (wenn das Log mit '/:ERROR:|:WARN:/' gefiltert ist, dann ist es die Anzahl der Fehler/Warnungen)
      • messages.filter : Filter, der auch die logCache angewendet wurde im .table/.list zu erzeugen (siehe Filter)
      • messages.lastUpdate : Timestamp des letzten Updates
      .

      Filter


      In den filter-States können sowohl strings (Bsp:'ERROR') als auch RegExp-Strings (Bsp:'/WARN|ERROR/')
      hinterlegt werden. RegExp-Strings werden an den einschließenden '/' erkannt. Über den ':' kann der Anfang
      eines Feldes mit in den Filter einbezogen werden.
      Beispiele:
      '/Fenster|Alarm/' (RegExp) zeigt alle Zeilen an, in denen 'Fenster' oder 'Alarm' in irgendeinem Feld vorkommen
      ':ERROR:' (string) zeigt alle Nachrichten an, mit Typ Fehler
      'Fenster' (string) zeigt alle Nachrichten an, in denen 'Fenster' in irgendeinem Feld vorkommt

      Ideen für die Zukunft / Work in Process

      • Nachrichten habe eine definierbare Zeit in der sie ablaufen (und damit automatisch entfernt werden)
      • Integration Push-Dienste (Telegram, Pushover, Email etc.) mit Nachrichtendefinition
        • Zyklisches Wiederholen von Nachrichten-Pushs (alle 20 Minuten bei Alarmen z.B.)

      Skripte

      Version 0.3

      1. "MessageGlobal" als globales Skript installieren und starten
        MessageGlobal.js

      2. "MessageHandler" als normales JS installieren und starten.
        MessageHandler.js

      3. "MessageStateCreator" als normales JS installieren und starten
        MessageStateCreator.js

      Views
      MDCSS v2 ListView
      cardMessages.view

      HTML-Tabelle
      cardMessages_html.view

      sigi234 1 Reply Last reply Reply Quote 2
      • sigi234
        sigi234 Forum Testing Most Active @Tirador last edited by

        @Tirador

        Das Skript ist sehr klein? Ist das Alles?

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

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

          sigi234 1 Reply Last reply Reply Quote 0
          • sigi234
            sigi234 Forum Testing Most Active @Tirador last edited by

            @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 1 Reply Last reply Reply Quote 0
            • T
              Tirador @sigi234 last edited by

              @sigi234 ja genau.

              sigi234 1 Reply Last reply Reply Quote 0
              • sigi234
                sigi234 Forum Testing Most Active @Tirador last edited by 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 1 Reply Last reply Reply Quote 0
                • T
                  Tirador @sigi234 last edited by Tirador

                  @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 Reply Last reply Reply Quote 1
                  • T
                    Tirador last edited by

                    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>
                    

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

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

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

                        @uwe12489 Hallo Uwe, vielen Dank! 🙂

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

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

                        Codezeilen:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                                    @Tirador
                                    Blöder Fehler von mir 😉 Danke

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

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

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

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

                                      Insgesamt bin ich schon sehr zufrieden damit. 🙂

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

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

                                      Ein Auszug aus meinen Beispielen (Spoiler):


                                      Briefkastensensor:

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

                                      Nachricht, wenn alle Fenster geschlossen sind:

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

                                      Corona-Statistiken:

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

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

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

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

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

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

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

                                        Support us

                                        ioBroker
                                        Community Adapters
                                        Donate

                                        813
                                        Online

                                        31.8k
                                        Users

                                        80.0k
                                        Topics

                                        1.3m
                                        Posts

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