/*******************************************************************************
* MessageStateCreator
* ----------------------------------------------------
* Ermöglicht die Überwachung von Datenpunkten und das Auslösen von Nachrichten
* mit dem MessageHandler-Nachrichtensystem.
* ----------------------------------------------------
* Autor: Github-Name: St0Ma ioBroker-Forum-Name: Tirador
* Source: https://github.com/St0Ma/ioBroker-MessageHandler
* Support: https://forum.iobroker.net/topic/32207/script-messagehandler-nachrichten-protokollieren-vis
* ----------------------------------------------------
* Change Log:
* 0.6 - Minor fix DWD
* 0.5 - Erweiterung um Attribute Wartezeit delayTime und Wiederholungszeit repatTime
* Möglichkeit der Zahlenformatierung über Attribute decimals und format
* 0.4 - Subscriptions nur noch für jeden Datenpunkt einmal, Fehlerausgabe bei fehlerhaften Trigger-DP
* 0.3 - few code improvements
* 0.2 - Initial Release
* ----------------------------------------------------
* (c) 2020 by Tirador, MIT License, no warranty, use on your own risc
******************************************************************************
*******************************************************************************
* 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.
*******************************************************************************
* Basis-Konfiguration
*******************************************************************************
Optional kann in der Funktion MessageHandler|doInit() eine Anpassung der KONFIGURATION vorgenommen werdne.
Zur Konfiguration sind zwei Schritte erforderlich:
1. Die Grundkonfiguration erfolgt über die Festlegung von MESSAGE-IDs (Nachrichten-Ids)
im Javascript "MessageHandler".
2. Über das Javascript "MessageStateCreator" können Datenpunkte überwacht werden
und Nachrichten automatisiert ausgelöst werden. Die Konfiguration erfolgt hierfür im Javascript "MessageStateCreator".
/*******************************************************************************/
// -------------------------------------------------------------------------------------
// Konfiguration der zu überwachenden Datenpunkte mit Konfiguration der Nachrichten (MSG-IDs)
// -------------------------------------------------------------------------------------
/////////////////////
// Hier die einzelnen Nachrichten anlegen und einstellen.
// Die Konfiguration ist vor dem Skriptstart an die eigenenen Datenpunkte anzupassen!
// nicht benötigte/vorhandene Konfigurationen sind auszukommentieren / zu löschen
// Im ersten Beispiel ist alles im Detail beschrieben.
/////////////////////
const MESSAGE_EVENTS = [
// Anzahl geöffneter Fenster mit Räumen
// 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: 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'],
// 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.
//
// delayTime: Verzögerungszeit in Sekunden für die Auslösung der Nachricht.
// In der Logik wird initial geprüft, ob die Bedingung gilt und erneut nach der Delay-Zeit.
// Erst wenn nach der Delay-Zeit auch die Bedingung der Nachricht gilt, wird die Nachricht erzeugt.
//
// Am Beispiel des Fenstersensors: die Nachricht soll erst ausgelöst werden,
// wenn das Fenster länger als 60*20 Sekunden (d.h. 20 Minuten) offen ist.
// In der Praxis macht das Setzen des Delays nur für einzelne Sensoren Sinn
// (und keine States, die gemeinsame Zustände (z.B. Gruppen von Fenstern abbilden)!)
//
// repeatTime: Wiederholungszeit in Sekunden. Prüft im Intervall in Sekunden, ob die Bedingung der Nachricht gilt.
// Die Nachricht wird frühestens nach der ersten konkreten Auslösung wiederholt.
// Wenn eine Delay-Zeit vorgegeben ist, beginnt die Wiederholungszeit nach der Delay-Zeit
// (sofern die Bedingung der Nachricht weiterhin gilt).
// Sofern Zwischenzeitlich der Datenpunkt erneut getriggert wird, wird ein bereits ausgelöster Timer
// zur Wiederholung erneut gestartet.
postMsgDP: {dp:'javascript.0.FensterUeberwachung.WindowsOpen', comp: '>', val:0, delayTime:0, repeatTime: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 text:
// Beispiel:
// msgText_1: {text: 'Fenster ist geöffnet'},
//
// Der Wert eines Datenpunkts kann in die Fehlernachricht mit ausgegeben werden.
// Beispiel:
// msgText_2: {dp: 'javascript.0.FensterUeberwachung.RoomsWithOpenWindows'},
//
// In Datenpunkten mit Zahlen kann eine Aufbereitung der Zahl vorgenommen werden
// über die Attribute format und decimals:
// Beispiel:
// msgText_2: {dp: 'deconz.0.Sensors.18.temperature', format:'"##,#"', decimals:1},
//
// Die Attribute im einzelnen:
// format: '#.###,##' // Ausgabe mit 1000er Trennzeichen Punkt und Komma
// decimals: Ausabe mit vorgegebenen Nachkommastellen
//
// 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'
},
// 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'
},
// Nachrichten für länger geöffnete Fenster
{
msgID: 'WINDOW_ISLONGEROPEN_GARAGE',
triggerDP: ['javascript.0.FensterUeberwachung.Garage.IsOpen'],
postMsgDP: {dp:'javascript.0.FensterUeberwachung.Garage.IsOpen', comp: '==', val: true, delayTime: 9000, repeatTime:0},
removeMsgDP: {dp:'javascript.0.FensterUeberwachung.Garage.IsOpen', comp: '!=', val: true}, // Nachricht enfernen, wenn die Bedingung eintritt
msgText_1: {text: 'Fenster Garage länger als 15 Minuten geöffnet'},
countEventsDP: 'javascript.0.FensterUeberwachung.Garage.RoomOpenWindowCount'
},
// Nachrichten für länger geöffnete Fenster
{
msgID: 'WINDOW_ISLONGEROPEN_HAUS',
triggerDP: ['javascript.0.FensterUeberwachung.Haus.IsOpen'],
postMsgDP: {dp:'javascript.0.FensterUeberwachung.Haus.IsOpen', comp: '==', val: true, delayTime: 9000, repeatTime:0},
removeMsgDP: {dp:'javascript.0.FensterUeberwachung.Haus.IsOpen', comp: '!=', val: true}, // Nachricht enfernen, wenn die Bedingung eintritt
msgText_1: {text: 'Fenster Haus länger als 15 Minuten geöffnet'},
countEventsDP: 'javascript.0.FensterUeberwachung.Haus.RoomOpenWindowCount'
},
// 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: ''
},
// Anzahl anwesender Personen mit Personenangabe
// An- und Abwesenheitserkennung über TR-064-Community-Adapter (von Mic)
// (Quelle: https://github.com/Mic-M/iobroker.presence-script-for-tr-064-community-adapter)
{
msgID: 'PERSONS_AVAILABLE_INFO',
triggerDP: '0_userdata.0.Anwesenheit.Status.presentPersonsString',
postMsgDP: {dp:'0_userdata.0.Anwesenheit.Status.allPresentPersonsCount'},
msgText_1: {dp: '0_userdata.0.Anwesenheit.Status.presentPersonsString'},
countEventsDP: '0_userdata.0.Anwesenheit.Status.allPresentPersonsCount'
},
// Verpasste Anrufe (des Tages)
// Über TR-064-Community-Adapter
{
msgID: 'MISSED_CALLS',
triggerDP: 'tr-064.0.calllists.missed.count',
postMsgDP: {dp:'tr-064.0.calllists.missed.count'},
msgText_1: {text: 'Anzahl verpasster Anrufe: '},
msgText_2: {dp: 'tr-064.0.calllists.missed.count'},
countEventsDP: 'tr-064.0.calllists.missed.count'
} ,
// letzter Anruf (des Tages)
// Über TR-064-Community-Adapter
{
msgID: 'LAST_CALL',
triggerDP: 'tr-064.0.callmonitor.lastCall.callerName',
postMsgDP: {dp:'tr-064.0.callmonitor.lastCall.callerName'},
msgText_1: {text: 'Anrufer: '},
msgText_2: {dp: 'tr-064.0.callmonitor.lastCall.callerName'},
msgText_3: {text: '</br>Angerufen: '},
msgText_4: {dp: 'tr-064.0.callmonitor.lastCall.calleeName'},
countEventsDP: ''
} ,
// Status Alarmanlage
// über Skript von andreaskos
// https://forum.iobroker.net/topic/32885/umfassendes-alarmanlagen-skript/3
{
msgID: 'ALARMANLAGE_STATUS',
triggerDP: ['javascript.0.Alarmanlage.Output.StatusText', 'sonos.0.root.192_168_178_59.state'],
msgText_1: {text: 'Aktiv: '},
msgText_2: {dp: 'javascript.0.Alarmanlage.Output.StatusText'},
msgText_3: {text: '<br>'},
msgText_4: {text: 'Status: '},
msgText_5: {dp: 'javascript.0.Alarmanlage.Output.AlarmText'}
},
// SONOS_INFO
// über SONOS-Adapter
{
msgID: 'SONOS_INFO',
triggerDP: ['sonos.0.root.192_168_178_59.current_artist', 'sonos.0.root.192_168_178_59.state'],
msgText_1: {text: '<img src=\''},
msgText_2: {dp: 'sonos.0.root.192_168_178_59.current_cover'},
msgText_3: {text: '\' height=\'50%\' width=\'50%\'></img>'},
msgText_5: {text: '</br>Künstler: '},
msgText_6: {dp: 'sonos.0.root.192_168_178_59.current_artist'},
msgText_7: {text: '</br>Album: '},
msgText_8: {dp: 'sonos.0.root.192_168_178_59.current_album'}
},
// Corona-Statistics
// über Corona-Adapter
{
msgID: 'CORONA_STATS_CASES',
triggerDP: ['coronavirus-statistics.0.Germany.cases', 'coronavirus-statistics.0.Germany.deaths'],
postMsgDP: {dp:'coronavirus-statistics.0.Germany.cases', format:'"#.###"', decimals:0},
msgText_1: {text: '☣ Bestätigt: '},
msgText_2: {dp: 'coronavirus-statistics.0.Germany.cases', format:'"#.###"', decimals:0},
msgText_3: {text: '</br>♱ Tote: '},
msgText_4: {dp: 'coronavirus-statistics.0.Germany.deaths', format:'"#.###"', decimals:0},
countEvents: 'coronavirus-statistics.0.Germany.deaths'
},
// Temperatur-Information
// Außen und Innentemperatur über eigene Sensoren
{
msgID: 'TEMPERATURE_INFO',
triggerDP: ['deconz.0.Sensors.18.temperature', 'deconz.0.Sensors.3.temperature'],
postMsgDP: {dp:'deconz.0.Sensors.18.temperature'},
msgText_1: {text: '🌐 '},
msgText_2: {dp: 'deconz.0.Sensors.18.temperature', format:'"##,#"', decimals:1},
msgText_3: {text: ' °C'},
msgText_5: {text: ' 🏠 '},
msgText_6: {dp: 'deconz.0.Sensors.3.temperature', format:'"##,#"', decimals:1},
msgText_7: {text: ' °C'},
countEvents: ''
},
// Müllabholung - Nächster Mülltermin
// über Adapter trashschedule
{
msgID: 'NEXT_GARBAGE_INFO',
triggerDP: ['trashschedule.0.next.daysleft', 'trashschedule.0.next.types'],
postMsgDP: {dp:'trashschedule.0.next.daysleft'},
msgText_1: {text: ''},
msgText_2: {dp: 'trashschedule.0.next.types'},
msgText_3: {text: ' in '},
msgText_4: {dp: 'trashschedule.0.next.daysleft'},
msgText_5: {text: ' Tage(n)'},
countEvents: 'trashschedule.0.next.daysleft'
},
// Batterieüberwachung - Evtl. nächste Batterie zu wechseln
// GITHUB: https://github.com/Pittini/iobroker-Batterienauswertung
// Forum ioBroker: https://forum.iobroker.net/topic/31676/vorlage-generische-batteriestandsüberwachung-vis-ausgabe
{
msgID: 'BATTERIE_INFO',
triggerDP: 'javascript.0.BatterieUeberwachung.NextExpectedLowBatt',
postMsgDP: {dp:'javascript.0.BatterieUeberwachung.NextExpectedLowBatt', comp: '!=', val:''},
removeMsgDP: {dp:'javascript.0.BatterieUeberwachung.NextExpectedLowBatt', comp: '==', val:''},
msgText_1: {text: ''},
msgText_2: {dp: 'javascript.0.BatterieUeberwachung.NextExpectedLowBatt'},
countEventsDP: ''
},
// Gefrierschrank geöffnet
// über eigenen Sensor
{
msgID: 'FREEZER_DOOR_ISOPEN_INFO',
triggerDP: 'deconz.0.Sensors.56.open',
postMsgDP: {dp:'deconz.0.Sensors.56.open', comp: '==', val:true, delayTime: 60, repeatTime: 180},
removeMsgDP: {dp:'deconz.0.Sensors.56.open', comp: '==', val:false},
msgText_1: {text: ''},
},
// Kühlschrank geöffnet
// über eigenen Sensor
{
msgID: 'FRIDGE_DOOR_ISOPEN_INFO',
triggerDP: 'deconz.0.Sensors.57.open',
postMsgDP: {dp:'deconz.0.Sensors.57.open', comp: '==', val:true, delayTime: 30, repeatTime: 180},
removeMsgDP: {dp:'deconz.0.Sensors.57.open', comp: '==', val:false},
msgText_1: {text: ''},
},
// DWD Wetterwarnung
// Über DWD-Adapter, erfordert die Konfiguration von 3 Meldungen im Adapter
{
msgID: 'DWD_WARN',
triggerDP: 'dwd.0.warning.severity',
postMsgDP: {dp:'dwd.0.warning.severity', comp: '!=', val:0, delayTime: 10},
removeMsgDP: {dp:'dwd.0.warning.severity', comp: '==', val:0},
msgText_1: {dp: 'dwd.0.warning.text'},
msgText_2: {text: ' <br> '},
msgText_3: {dp: 'dwd.0.warning.description'},
msgText_4: {text: ' <br> '},
msgText_5: {dp: 'dwd.0.warning1.text'},
msgText_6: {text: ' <br> '},
msgText_7: {dp: 'dwd.0.warning1.description'},
msgText_8: {dp: 'dwd.0.warning.severity'},
countEventsDP: ''
},
// Wassersensor Werkzeugraum
// über eigenen Sensor
{
msgID: 'WATER_ALARM',
triggerDP: 'deconz.0.Sensors.21.water',
postMsgDP: {dp:'deconz.0.Sensors.21.water', comp: '==', val:true},
//removeMsgDP: {dp:'deconz.0.Sensors.21.water', comp: '==', val:false}, // Nachricht wird zur Sicherheit nicht entfernt, falls der Sensor toggelt!
msgText_1: {text: 'Wasseralarm im Werkzeugraum!'},
countEventsDP: ''
},
// Wassersensor Waschraum
// über eigenen Sensor
{
msgID: 'WATER_ALARM',
triggerDP: 'deconz.0.Sensors.34.water',
postMsgDP: {dp:'deconz.0.Sensors.34.water', comp: '==', val:true},
//removeMsgDP: {dp:'deconz.0.Sensors.34.water', comp: '==', val:false}, // Nachricht wird zur Sicherheit nicht entfernt, falls der Sensor toggelt!
msgText_1: {text: 'Wasseralarm im Waschraum!'},
countEventsDP: ''
},
// Wassersensor Küche
// über eigenen Sensor
{
msgID: 'WATER_ALARM',
triggerDP: 'deconz.0.Sensors.6.water',
postMsgDP: {dp:'deconz.0.Sensors.6.water', comp: '==', val:true},
//removeMsgDP: {dp:'deconz.0.Sensors.6.water', comp: '==', val:false}, // Nachricht wird zur Sicherheit nicht entfernt, falls der Sensor toggelt!
msgText_1: {text: 'Wasseralarm in der Küche!'},
countEventsDP: ''
},
// Wassersensor großer Kellerraum
// über eigenen Sensor
{
msgID: 'WATER_ALARM',
triggerDP: 'deconz.0.Sensors.7.water',
postMsgDP: {dp:'deconz.0.Sensors.7.water', comp: '==', val:true},
//removeMsgDP: {dp:'deconz.0.Sensors.7.water', comp: '==', val:false}, // Nachricht wird zur Sicherheit nicht entfernt, falls der Sensor toggelt!
msgText_1: {text: 'Wasseralarm im großen Kellerraum!'},
countEventsDP: ''
},
// Logitech Harmony
// Über Harmony-Adapter
{
msgID: 'HARMONY_INFO',
triggerDP: 'harmony.0.Harmonyhub.activities.currentActivity',
postMsgDP: {dp:'harmony.0.Harmonyhub.activities.currentActivity', comp: '!=', val:'PowerOff'},
removeMsgDP: {dp:'harmony.0.Harmonyhub.activities.currentActivity', comp: '==', val:'PowerOff'}, // Nachricht wird zur Sicherheit nicht entfernt, falls der Sensor toggelt!
msgText_1: {text: 'Aktivität: '},
msgText_2: {dp: 'harmony.0.Harmonyhub.activities.currentActivity'},
countEventsDP: ''
},
// Spritpreis-Info
// über tankerkoenig-Adapter
{
msgID: 'TANK_INFO',
triggerDP: ['tankerkoenig.0.stations.cheapest.e5.feed','tankerkoenig.0.stations.cheapest.diesel.feed'],
postMsgDP: {dp:'tankerkoenig.0.stations.cheapest.e5.feed', comp: '>', val:0},
removeMsgDP: {dp:'tankerkoenig.0.stations.cheapest.e5.feed', comp: '==', val:0},
msgText_1: {text: 'DIESEL: '},
msgText_2: {dp: 'tankerkoenig.0.stations.cheapest.diesel.name'},
msgText_3: {text: ': '},
msgText_4: {dp: 'tankerkoenig.0.stations.cheapest.diesel.feed', format:'"#.##"', decimals:2},
msgText_5: {text: ' €'},
msgText_6: {text: '</br>SUPER:'},
msgText_7: {dp: 'tankerkoenig.0.stations.cheapest.e5.name'},
msgText_8: {text: ': '},
msgText_9: {dp: 'tankerkoenig.0.stations.cheapest.e5.feed', format:'"#.##"', decimals:2},
msgText_10: {text: ' €'},
countEventsDP: ''
},
// Update ioBroker
// über Admin-Adapter
{
msgID: 'UPDATE_INFO',
triggerDP: 'admin.0.info.updatesList',
postMsgDP: {dp:'admin.0.info.updatesNumber', comp: '>', val:0},
removeMsgDP: {dp:'admin.0.info.updatesNumber', comp: '==', val:0},
msgText_1: {text: 'Adapter: '},
msgText_2: {dp: 'admin.0.info.updatesList'},
msgText_3: {text: '. Bitte aktualisieren.'},
countEventsDP: 'admin.0.info.updatesNumber'
},
// Deconz Warnung, wenn Verbindung ausgefallen im Adapter
// über Deconz-Adapter
{
msgID: 'DECONZ_Warning',
triggerDP: 'deconz.0.info.connection',
postMsgDP: {dp:'deconz.0.info.connection', comp: '==', val:false},
removeMsgDP: {dp:'deconz.0.info.connection', comp: '==', val:true},
msgText_1: {text: 'Zigbee offline! Deconz-Adapter nicht verbunden!'}
},
/*
// Gäste WLAN
// über tr.064-Adapter
{
msgID: 'GUEST_WIFI',
triggerDP: 'tr-064.0.states.wlanGuest',
postMsgDP: {dp:'tr-064.0.states.wlanGuest', comp: '==', val:true},
removeMsgDP: {dp:'tr-064.0.states.wlanGuest', comp: '==', val:false},
msgText_1: {text: 'Gäste WLAN eingeschalten'},
msgText_2: {dp: 'javascript.0.QR-Code.Gast'},
countEventsDP: ''
},
// Internetverbindung Down Fritz!Box
// Prüfung über UPNP-Adapter
// Github: https://github.com/Jey-Cee/ioBroker.upnp
// ioBroker-Forum: https://forum.iobroker.net/topic/14802/tutorial-vis-fritzbox-status-up-downloadanzeige
{
msgID: 'INTERNET_DOWN',
triggerDP: 'upnp.0.WANDevice_-_FRITZ!Box_6490_Cable_(kdg).WANDevice.WANCommonInterfaceConfig.GetCommonLinkProperties.NewPhysicalLinkStatus',
postMsgDP: {dp:'upnp.0.WANDevice_-_FRITZ!Box_6490_Cable_(kdg).WANDevice.WANCommonInterfaceConfig.GetCommonLinkProperties.NewPhysicalLinkStatus', comp: '==', val:'Down'},
//removeMsgDP: {dp:'upnp.0.WANDevice_-_FRITZ!Box_6490_Cable_(kdg).WANDevice.WANCommonInterfaceConfig.GetCommonLinkProperties.NewPhysicalLinkStatus', comp: '==', val:'Up'},
msgText_1: {text: 'Keine Internetverbindung'},
countEventsDP: ''
},
*/
];
// -------------------------------------------------------------------------------------
// Ab hier keine Konfiguration mehr durchführen!!!
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// MessageStateCreator
// -------------------------------------------------------------------------------------
class MessageStateCreator {
constructor() {
this.init();
}
//
init() {
// const
this.DEBUG = false;
this.VERSION = '0.2/2020-04-04';
this.NAME = 'MessageStateCreator';
this.so = this;
/****************************************************************************
* Global variables and constants
****************************************************************************/
this.G_DelayTimers = []; // Delay Timers
this.G_RepeatTimers = []; // Repeat Timers
// var
this.installed = false;
this.subscribers = [];
this.schedulers = [];
}
// start the script/class
start() {
if(!this.validate()) {
return;
}
if (this.doStart())
this.log('script started');
}
// stop the script/class
stop() {
if (this.doStop()) {
this.log('script stopped');
for (let i=0; i<this.subscribers.length; i++) if (this.subscribers[i] !== undefined) unsubscribe( this.subscribers[i] );
this.subscribers = [];
for (let i=0; i<this.schedulers.length; i++) if (this.schedulers[i] !== undefined) clearSchedule( this.schedulers[i] );
this.schedulers = [];
}
}
// start the script/class
doStart() {
let triggerDPArray = [];
let createMsgDPArray = [];
let id = 0;
for(const MsgConf of MESSAGE_EVENTS) {
// Vergabe eindeutiger Id zur Laufzeit für Timer
//
MsgConf.id = id++;
// initialize Timer
this.G_DelayTimers[MsgConf.id] = new myTimer();
this.G_RepeatTimers[MsgConf.id] = new myTimer();
let first = true;
// We are allowing multiple trigger for one msgID.
// triggerDP: [{dp: 'javascript.0.FensterUeberwachung.RoomsWithOpenWindows'}, {dp:'javascript.0.FensterUeberwachung.WindowsOpen'}],
// Ein Datenpunkt kann mehrfach als Trigger in verschiedenen MSGs auftreten,
// daher wird der Trigger nur einmal angelegt, pro Datenpunkt
if (typeof MsgConf.triggerDP == 'string') {
// If we just have one sensor as string
if(!triggerDPArray.includes(MsgConf.triggerDP)) {
triggerDPArray.push(MsgConf.triggerDP);
createMsgDPArray.push(MsgConf.triggerDP);
}
} else {
for (let key in MsgConf.triggerDP) {
if(!triggerDPArray.includes(MsgConf.triggerDP[key])) {
triggerDPArray.push(MsgConf.triggerDP[key]);
if(first) {
createMsgDPArray.push(MsgConf.triggerDP[key]);
first = false;
}
}
}
}
}
// Mit Skriptstart die Nachrichten auslösen
for (const msgDP of createMsgDPArray) {
this.createMessage(msgDP);
}
// subscriber erzeugen
for (const triggerDP of triggerDPArray) {
this.subscribers.push( on( triggerDP, obj => { this.onChangeDP(obj) } ));
}
return true;
}
doStop() { return true; }
// newMessage
onChangeDP(obj) {
if(this.DEBUG) {
this.log("New Value from state:" + obj.id);
}
this.createMessage(obj.id);
}
/*
/ Prüfung auf plausible Konfiguration
*/
validate() {
/*
msgID: 'WINDOW_ISOPEN_INFO',
triggerDP: ['javascript.0.FensterUeberwachung.RoomsWithOpenWindows', 'javascript.0.FensterUeberwachung.WindowsOpen'],
postMsgDP: {dp:'javascript.0.FensterUeberwachung.WindowsOpen', comp: '>', val:0}, // Nachricht erzeugen, wenn der DP geändert wird und der Bedingung entspricht
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'
*/
let errorCount = 0;
if (this.DEBUG) log('[DEBUG] ' + 'VALIDIERUNG *** START: Prüfung der Script-Konfiguration ***');
for(const MsgConf of MESSAGE_EVENTS) {
//-----------------------------------------------------
// 1. Prüfe, ob msgID vorhanden ist
// Beispiel: msgID: 'WINDOW_ISOPEN_INFO'
//-----------------------------------------------------
if (this.isLikeEmpty(MsgConf.msgID)) {
this.logError('Attribut "msgID" wurde nicht gesetzt in Script-Konfiguration. Bitte Script-Konfiguration überprüfen.');
errorCount++;
}
//-----------------------------------------------------
// 2. Prüfe, ob triggerDP vorhanden ist
// Beispiel: triggerDP: ['javascript.0.FensterUeberwachung.RoomsWithOpenWindows', 'javascript.0.FensterUeberwachung.WindowsOpen'],
//-----------------------------------------------------
if (this.isLikeEmpty(MsgConf.triggerDP)) {
this.logError('msgID: [' + MsgConf.msgID + '] Attribut: [triggerDP] wurde nicht gesetzt in Script-Konfiguration! Bitte Script-Konfiguration überprüfen.');
errorCount++;
}
// We are allowing multiple trigger for one msgID.
// triggerDP: ['javascript.0.FensterUeberwachung.RoomsWithOpenWindows', 'javascript.0.FensterUeberwachung.WindowsOpen'],
let triggerDPArray = [];
if (typeof MsgConf.triggerDP == 'string') {
// If we just have one sensor as string
triggerDPArray.push(MsgConf.triggerDP);
} else {
triggerDPArray = MsgConf.triggerDP;
}
// Erweiterte Prüfung, ob die trigger-Datenpunkte vorhanden sind
for (const triggerDP of triggerDPArray) {
if( !this.isLikeEmpty(triggerDP)) {
if(!this.existState(triggerDP)) {
this.logError('msgID: [' + MsgConf.msgID + '] Attribut: [triggerDP] Datenpunkt: [' + triggerDP + '] existiert nicht! Bitte Script-Konfiguration überprüfen.');
errorCount++;
}
}
}
//-----------------------------------------------------
// 3. Prüfe, ob postMsgDP vorhanden ist
// Beispiel: postMsgDP: {dp:'javascript.0.FensterUeberwachung.WindowsOpen', comp: '>', val:0}, // Nachricht erzeugen, wenn der DP geändert wird und der Bedingung entspricht
//-----------------------------------------------------
errorCount += this.checkField(MsgConf, 'postMsgDP');
//-----------------------------------------------------
// 4. Prüfe, ob msgText vorhanden ist
// Beispiel:
// msgText_1: {text: ''},
// msgText_2: {dp: 'javascript.0.FensterUeberwachung.RoomsWithOpenWindows'},
//-----------------------------------------------------
let msgText = '';
let MSGTEXT_KEYS = (Object.keys(MsgConf).filter(str => str.includes('msgText_'))); // gibt alle Keys mit 'msgText_' als Array zurück, also z.B. ['msgText_1','msgText_2']
if (this.isLikeEmpty(MSGTEXT_KEYS)) {
this.logWarn('msgID: [' + MsgConf.msgID + '] Es wurde kein Nachrichtentext definiert (msgText_1, etc.). Bitte Script-Konfiguration überprüfen.');
errorCount++;
} else {
for (const MSGTEXT_KEY of MSGTEXT_KEYS) {
let dp = MsgConf[MSGTEXT_KEY].dp;
if( ! this.isLikeEmpty(dp)) {
if(! this.existState(dp)) {
this.logError('msgID: [' + MsgConf.msgID + '] Attribut: [' + MSGTEXT_KEY + '] Datenpunkt: [' + dp + '] existiert nicht! Bitte Script-Konfiguration überprüfen.');
}
}
let text = MsgConf[MSGTEXT_KEY].text;
if( this.isLikeEmpty(text) ) {
//if (this.DEBUG) log('[DEBUG] VALIDIERUNG ' + MsgConf.msgID + ': In [' + MSGTEXT_KEY + '] wurde kein gültiger Text definiert .');
}
}
}
//-----------------------------------------------------
// 5. Prüfe, ob countEventsDP vorhanden ist
// Beispiel:
// countEventsDP: 'javascript.0.FensterUeberwachung.WindowsOpen'
//-----------------------------------------------------
let countEventsDP = 0;
if(MsgConf.countEventsDP != undefined && ! this.isLikeEmpty(MsgConf.countEventsDP) ) {
if(! this.existState(MsgConf.countEventsDP)) {
this.logError('msgID: [' + MsgConf.msgID + '] Attribut: [countEventsDP] Datenpunkt: [' + MsgConf.countEventsDP + '] existiert nicht! Bitte Script-Konfiguration überprüfen.');
}
}
errorCount += this.checkField(MsgConf, 'removeMsgDP');
}
if (errorCount == 0) {
if (this.DEBUG) this.log('[DEBUG] ' + 'VALIDIERUNG *** ENDE: Prüfung der Script-Konfiguration, Ergebnis: keine Fehler ***');
return true;
} else {
this.logError('Insgesamt ' + errorCount + ' Fehler in der Script-Konfiguration gefunden. Daher wird abgebrochen und das Script nicht weiter ausgeführt.');
return false;
}
}
// Hilfsfunktion zum Prüfen von Feldern
checkField(MsgConf, field) {
let errorCount = 0;
if (this.isLikeEmpty(MsgConf[field])) {
if(field == 'postMsgDP') {
this.log('HINWEIS: msgID: [' + MsgConf.msgID + '] Attribut: [' + field + '] wurde nicht gesetzt in Script-Konfiguration');
}
//errorCount++;
return errorCount;
}
// 3.a) Prüfung [postMsgDp].dp
let postMsgDP = MsgConf[field].dp; // VOrher MsgConf['postMsgDP'].dp;
if( this.isLikeEmpty(postMsgDP)) {
if(!this.existState(postMsgDP)) {
this.logError('msgID: [' + MsgConf.msgID + '] Attribut: [' + field + '] Datenpunkt: [' + postMsgDP + '] existiert nicht!');
errorCount++;
}
}
// 3.b) Prüfung [postMsgDp].comp
let condDP = MsgConf[field].comp;
if( condDP == undefined) {
condDP = '==';
}
if( this.isLikeEmpty(condDP)) {
this.logError('msgID[' + MsgConf.msgID + '] Attribut: [' + field + '] Operator ' + condDP + ' existiert nicht!');
errorCount++;
}
if( ! ( condDP == '!=' || condDP == '==' || condDP == '>' || condDP == '<' || condDP == '<=' || condDP == '>=' ) )
{
this.logError('msgID: [' + MsgConf.msgID + '] Attribut: [' + field + '] Operator ' + condDP + ' existiert nicht!');
errorCount++;
}
return errorCount;
}
// createMessage
createMessage(objID, hasDelay=true) {
for(const MsgConf of MESSAGE_EVENTS) {
// We are allowing multiple trigger for one msgID.
// triggerDP: [{dp: 'javascript.0.FensterUeberwachung.RoomsWithOpenWindows'}, {dp:'javascript.0.FensterUeberwachung.WindowsOpen'}],
let triggerDPArray = [];
if (typeof MsgConf.triggerDP == 'string') {
// If we just have one sensor as string
triggerDPArray.push(MsgConf.triggerDP);
} else {
triggerDPArray = MsgConf.triggerDP;
}
for (const triggerDP of triggerDPArray) {
if(triggerDP == objID) {
if(this.DEBUG) this.log("Trigger ausgelöst:" + triggerDP);
let createMsg = this.checkCondition(objID, MsgConf, 'postMsgDP', hasDelay);
let removeMsg = this.checkCondition(objID, MsgConf, 'removeMsgDP', hasDelay);
if(createMsg || removeMsg) {
// erzeugen Nachrichtentext aus Vorgabe und Datenpunkten
let msgText = '';
let MSGTEXT_KEYS = (Object.keys(MsgConf).filter(str => str.includes('msgText_'))); // gibt alle Keys mit 'msgText_' als Array zurück, also z.B. ['msgText_1','msgText_2']
if (this.isLikeEmpty(MSGTEXT_KEYS)) {
this.logWarn('Konfiguration der Textausgabe ' + MsgConf.msgID + 'Es wurde kein Nachrichtentext definiert (msgText_1, etc.). Bitte Script-Konfiguration überprüfen.');
} else {
for (const MSGTEXT_KEY of MSGTEXT_KEYS) {
let dp = MsgConf[MSGTEXT_KEY].dp;
if( ! this.isLikeEmpty(dp)) {
if(this.existState(dp)) {
let val = getState(dp).val;
// Aufbereitung States Mapping
let txtStates = this.getStatesObj(dp);
if (txtStates != null) {
msgText += this.getStatetxt(dp, val) ;
} else {
let decimals = MsgConf[MSGTEXT_KEY].decimals;
let format = MsgConf[MSGTEXT_KEY].format;
if( ! this.isLikeEmpty(decimals) && !this.isLikeEmpty(format)) {
val = formatValue(val, decimals, format);
} else if (!this.isLikeEmpty(format)) {
val = formatValue(val, 0, format);
}
msgText += val;
}
} else {
this.log('Datenpunkt ' + dp + ' existiert nicht! [' + MsgConf.msgID + '].');
}
}
let text = MsgConf[MSGTEXT_KEY].text;
if( this.isLikeEmpty(text) ) {
//if (DEBUG) log('[DEBUG] VALIDIERUNG ' + LPCONF.name + ': In [' + LP_PERIOD_KEY + '] wurden keine Sekunden zum Ausschalten definiert oder auf 0 gesetzt, daher wird nicht automatisch abgeschaltet.');
} else {
msgText += text;
}
}
}
// erzeugen Anzahl
let countEventsDP = 0;
if(MsgConf.countEventsDP != undefined && MsgConf.countEventsDP != '') {
countEventsDP = getState(MsgConf.countEventsDP).val;
}
if(createMsg) {
this.log("postMessage('" + MsgConf.msgID + "', '" + msgText + "', " + countEventsDP + ")");
// Erzeugen der Nachricht über MessageHandler
postMessage(MsgConf.msgID, msgText, countEventsDP);
}
if(removeMsg) {
this.log("removeMessage('" + MsgConf.msgID + "', '" + msgText + "')");
// Entfernen der Nachricht über MessageHandler
removeMessage(MsgConf.msgID, msgText);
}
if(createMsg && removeMsg) {
this.logError('msgID: [' + MsgConf.msgID + '] Die Nachricht mit Nachrichtentext "' + msgText + '" wird gleichzeitig erzeugt und entfernt. Bitte Skript-Konfiguration prüfen!' )
}
}
} // if
} // for
} // for
}
getStatesObj(id) {
if(!getObject(id)) {
//log(id + ': kein Objekt', 'warn');
return null;
}
var obj = getObject(id);
if (!obj.common.states) {
//log(id + ': keine Zustandtexte', 'warn');
return null;
}
var states = obj.common.states;
if (typeof states == 'string') {
var arr = states.split(';');
states = {};
for(var i = 0; i < arr.length; i++) {
var ele = arr[i].split(':');
states[ele[0]] = ele[1];
}
}
return states;
}
getStatetxt(id, val) {
var states = this.getStatesObj(id);
if(states) return states[val];
else return null;
}
checkCondition(objID, MsgConf, field, hasDelay) {
if (this.isLikeEmpty(MsgConf[field])) {
// if(this.DEBUG) this.logWarn(field + ' wurde nicht gesetzt in Script-Konfiguration.');
// Wenn postMsgDP nicht definiert, Nachricht erzeugen
// Wenn removeMsgDP nicht definiert ist, wird die Nachricht NICHT gelöscht
if(field == 'postMsgDP') {
return true;
} else if(field == 'removeMsgDP') {
return false;
}
}
let dp = MsgConf[field].dp;
let createMsg = false;
let comp = MsgConf[field].comp;
if( this.isLikeEmpty(comp)) {
comp = '==';
}
let val = MsgConf[field].val;
if( val == undefined) {
createMsg = true;
} else if(comp == '==') {
if( getState(dp).val == val) {
createMsg = true;
}
} else if(comp == '!=') {
if( getState(dp).val != val) {
createMsg = true;
}
} else if(comp == '>') {
if( getState(dp).val > val) {
createMsg = true;
}
} else if(comp == '<') {
if( getState(dp).val < val) {
createMsg = true;
}
} else if(comp == '>=') {
if( getState(dp).val >= val) {
createMsg = true;
}
} else if(comp == '<=') {
if( getState(dp).val <= val) {
createMsg = true;
}
}
if(this.DEBUG) this.log('msgID: [' + MsgConf.msgID + '] Datenpunkt: [' + field + '] dp: [' + dp + '] State dp.val: [' + getState(dp).val + '] comp: [' + comp + '] val: [' + val + '] createMsg:' + createMsg);
//-----------------------------------------------------------
// Falls Delay-Zeit vorgegeben: Timer prüfen / setzen
//-----------------------------------------------------------
let delayTime = MsgConf[field].delayTime;
if( field == 'postMsgDP' && !this.isLikeEmpty(delayTime) && (delayTime > 0) ) {
if(hasDelay) {
if (this.G_DelayTimers[MsgConf.id].isRunning()) {
if(createMsg == false) {
// Der Trigger ist nicht mehr aktiv, aber der Timer läuft, also Stop den Timer
this.G_DelayTimers[MsgConf.id].stop();
} else {
// Wenn innerhalb des Delays erneut getriggert wird verlängert sich nicht die Zeit!
this.log('[DEBUG] ' + MsgConf.msgID + '(' + MsgConf.id + '): Check #1: Delay-Timer ist noch aktiv für ' + Math.round(this.G_DelayTimers[MsgConf.id].getTimeLeft()/1000) + ' Sekunden, also schalten wir nicht und brechen hier ab.');
}
} else {
// Timer wird nur bei positiver Auslösung gestartet
if(createMsg == true) {
log('[DEBUG] ' + MsgConf.msgID + '(' + MsgConf.id + '): Start "Delay"-Timer. Die Nachricht wird nach ' + delayTime + ' Sekunden geprüft und eventuell ausgelöst. ');
// Timer-Start
this.G_DelayTimers[MsgConf.id].start(function() {
log('[DEBUG] ' + MsgConf.msgID + '(' + MsgConf.id + ') "Delay"-Timer ist nach ' + delayTime + ' Sekunden abgelaufen. Die Nachricht ' + objID + ' ausgelöst.');
this.G_DelayTimers[MsgConf.id].stop(); // just in case
this.createMessage(objID, false); // erzeugen Aktion neue Nachricht ohne Delay
}.bind(this), delayTime * 1000);
}
}
createMsg=false;
} else {
this.log('[DEBUG] ' + MsgConf.msgID + '(' + MsgConf.id + '): Nachricht hat kein Delay mehr! ');
}
}
//-----------------------------------------------------------
// Falls Repeat-Zeit vorgegeben: Timer prüfen / setzen
// Wenn innerhalb des laufenden Timers eine neue Nachricht ausgelöst wird,
// wird der Timer zurückgesetzt.
//-----------------------------------------------------------
let repeatTime = MsgConf[field].repeatTime;
if( field == 'postMsgDP' && !this.isLikeEmpty(repeatTime) && (repeatTime > 0) ) {
if (this.G_RepeatTimers[MsgConf.id].isRunning()) {
// Der Timer ist noch aktiv und wird gestoppt
this.G_RepeatTimers[MsgConf.id].stop();
log('[DEBUG] ' + MsgConf.msgID + '(' + MsgConf.id + '): Der "Repeat"-Timer wurde zurückgesetzt, weil die Nachricht zwischenzeitlich ausgelöst wurde. ');
}
// Timer wird nur bei positiver Auslösung gestartet
if(createMsg == true) {
log('[DEBUG] ' + MsgConf.msgID + '(' + MsgConf.id + '): Start "Repeat"-Timer. Die Nachricht wird nach ' + delayTime + ' Sekunden erneut geprüft und eventuell ausgelöst. ');
// Timer-Start
this.G_RepeatTimers[MsgConf.id].start(function() {
log('[DEBUG] ' + MsgConf.msgID + '(' + MsgConf.id + ') "Repeat"-Timer ist nach ' + delayTime + ' Sekunden abgelaufen. Die Nachricht ' + objID + ' ausgelöst.');
this.G_RepeatTimers[MsgConf.id].stop(); // just in case
this.createMessage(objID, false); // erzeugen Aktion neue Nachricht ohne Delay
}.bind(this), delayTime * 1000);
}
}
return createMsg;
}
//------------------------------------------------------------------------------
// --------------------- helper functions
//------------------------------------------------------------------------------
logDebug(msg) { if (this.DEBUG) console.log('['+this.NAME+'] '+msg); }
log(msg) { console.log('['+this.NAME+'] '+msg); }
logWarn(msg) { console.warn('['+this.NAME+'] '+msg); }
logError(msg) { console.error('['+this.NAME+'] '+msg); }
// über den $-Operator nachsehen, ob der state bereits vorhanden ist
// getState().notExists geht auch, erzeugt aber Warnmeldungen!
existState(id) {
return ( $(id).length==0?false:true);
}
/**
* Checks if Array or String is not undefined, null or empty.
* 08-Sep-2019: added check for [ and ] to also catch arrays with empty strings.
* @param inputVar - Input Array or String, Number, etc.
* @return true if it is undefined/null/empty, false if it contains value(s)
* Array or String containing just whitespaces or >'< or >"< or >[< or >]< is considered empty
*/
isLikeEmpty(inputVar) {
if (typeof inputVar !== 'undefined' && inputVar !== null) {
let strTemp = JSON.stringify(inputVar);
strTemp = strTemp.replace(/\s+/g, ''); // remove all whitespaces
strTemp = strTemp.replace(/\"+/g, ""); // remove all >"<
strTemp = strTemp.replace(/\'+/g, ""); // remove all >'<
strTemp = strTemp.replace(/[+/g, ""); // remove all >[<
strTemp = strTemp.replace(/]+/g, ""); // remove all >]<
if (strTemp !== '') {
return false;
} else {
return true;
}
} else {
return true;
}
}
}
/**
* Better timer function
* Features: Find out time remaining, stop timer easily, check status (running yes/no).
* Autor: Mic (ioBroker) | Mic-M (github)
* Version: 0.1 (14 March 2020)
* Source: https://stackoverflow.com/questions/3144711/
* -----------------------------------------------
* Make a timer:
let a = new myTimer();
a.start(function() {
// Do what ever
}, 5000);
* Time remaining: a.getTimeLeft()
* Stop (clear) timer: a.stop()
* Is timer running: a.isRunning()
* -----------------------------------------------
*/
function myTimer() {
let fcallback;
let id;
let started;
let remaining = 0;
let running = false;
this.start = function(callback, delay) {
fcallback = callback;
remaining = delay;
clearTimeout(id);
id = null;
running = true;
started = Date.now();
id = setTimeout(fcallback, remaining);
}
this.pause = function() {
running = false;
clearTimeout(id);
remaining -= Date.now() - started;
}
this.stop = function() {
running = false;
clearTimeout(id); id = null;
remaining = 0;
}
this.getTimeLeft = function() {
if (running) {
this.pause();
this.start(fcallback, remaining);
return remaining;
} else {
return 0;
}
}
this.isRunning = function() {
return running;
}
}
// create instance and start
var messageStateCreator = new MessageStateCreator();
messageStateCreator.start();
// on script stop, stop instance too
onStop(function () {
messageStateCreator.stop();
}, 1000 );