// Programm zur Autoschaltung basierend auf Stateaenderungen oder schedule Funktionen // Simple IFTTT - Autor Looxer01 10.06.2020 // 20.10.24 Vers. 1.0 optimiert // 21.10.24 Vers. 1.1 Code Anpassungen - weitere Optimierungen (Globale Definition AstroKeywords, Ausgabe der exakten Astroevents im Log) // Fix bei Berechnung der offsets bei astrozeiten (shiftpattern) // 22.10.24 Vers 1.2 Messaging Services hinzugefügt - Pushbullet geloescht // 23.10.24 Vers 1.3 bei Zeitvon und Zeitbis beide Formate zulassen hh:mm und hh:mm:ss // Datum von bis mit Zeitangabe const LogPath = "/opt/iobroker/log/"; // Pfad fuer CSV Datei const debug = false; // true fuer eine erweiterte logausgabe const StatistikAusgabe = true // Bei start des Programmes wird eine Statistik zur Konfiuration ausgegeben const Entprellzeit = 1; // Entprellzeit in Sekunden - Wird verwendet wenn Spalte 5 = true gesetzt ist const ShiftSmoothing = false; // wenn die Astroshiftzeiten zwischen zwei Monaten gelaettet - aufgeteilt auf Tage - werden sollen - Experimentell //Liste der verfuegbaren message services. Wenn aktiv gesetzt, dann muß in iobroker auch der entsprechende Adatpter installiert sein // Spalte 0: Dienst: nicht aendern // Spalte 1: Aktiv true/false. wenn aktiviert, dann versucht das script die message aus der Aktion zu senden // Spalte 2: Kontaktdaten. bei email= email addresse, const MessageServices = [ // 0.Dienst 1.Aktiv 2.Kontaktdaten ["Email", true, "Vorname.Nachname@web.de"], ["WhatsApp", true, ], ["Signal", false, ], ["Telegram", false, ], ["Pushover", false, ], ["Pushsafer", false, ], ] // Liste der Geraete/States auf die reagiert werden sollen bzw geprueft werden sollen // Spalte 0 = Trigger ID - damit koennen mehrere Trigger in einer UND Verknuepfung zusammengefasst werden (mehrere Bedingungen) // Spalte 1 = Trigger Objekt aus der ioBroker Objektliste // Spalte 2 = Vergleichswert operand // Operand - zulaessige Operanden sind // beliebiege Aenderung = "any" oder "" // gleich = "val" oder = // groesser = "valGt oder > // groesser gleich = "valGe" oder >= // kleiner = "valLt" oder < // kleiner gleich = "valLe" oder <= // ungleich = "valNe" oder != // Spalte 3 = Trigger wert (wenn dieser Wert mit entsprechend Operand erreicht wird, dann wird der Trigger ausgeloest). Wenn der Wert = "", dann wird immer ausgefuehrt (ohne Bedingung) // Spalte 4 = true = Nur pruefen. Es wird kein Trigger ausgeloest. Bei einem ausgeloesten Trigger wird aber die Bedingung ueberprueft (macht nur Sinn bei mehreren Bedingungen ) // Spalte 5 = true = Nur Ausfuehren wenn der neue Wert ungleich dem alten Wert ist. Das gilt nur fuer Subscriptions - nicht fuer Schedule und nicht fuer "nur pruefen-Bedinugnen" // wenn auf true, dann wird die Entprellzeit-Einstellung ebenfalls verwendet. Die Entprellzeit ist eine Verzoegerung um mehrfach Ausfuehrungen bei DP aenderungen zu vermeiden // ist nur relevant fuer Subscriptions (also nicht schedule und nicht "nur pruefen") // Triggername // Datenpunkt compare(Operand) Vergleichswert NurPruefen Check Ungleich Alter Wert const AutoTrigger = [ // ab hier subscriptions ["AWSAktivierung", 'javascript.0.Anwesenheitssteuerung.Userlist.JemandDa', "=", false, false, false], ["AWSAktivierung", 'heatingcontrol.0.PartyNow', "=", false, true, false], ["AWSDeAktivierung", 'javascript.0.Anwesenheitssteuerung.Userlist.JemandDa', "=", true, false, false], ["AWSDeAktivierung", 'javascript.0.Anwesenheitssteuerung.AWSAktiv', "=", true, true, false], ["ALARMFeuchteKeller", 'hm-rpc.1.00189A498DD8B9.1.MOISTURE_DETECTED', "=", true, false, false], // ab hier check kriterien fuer schedules (nur pruefen fuer autoschedule) ["RolladeKuecheAuf", 'hm-rpc.1.00111D8995432C.4.LEVEL', "=", 0, true, false], ["RolladeKuecheZu", 'hm-rpc.1.00111D8995432C.4.LEVEL', "=", 100, true, false], ["RolladeKuecheZu", 'heatingcontrol.0.PartyNow', "=", false, true, false], ["RolladeSchlafZimmerAuf", 'hm-rpc.1.001120C9A86A00.4.LEVEL', "<", 10, true, false], ]; // Spalte 0 = TriggerKey aus Tabelle Autotrigger - bei Autotrigger wird dann automatisch keine Subscription geplant // Spalte 1 = Schedule Pattern // Spalte 2 = astro Keyword - wenn ein keyword eingetragen wird dann wird Spalte 1 nicht verwendet // Spalte 3 = Zeit plus oder Minus ausgehend vom astro keyword 30 = 30 Minuten spaeter -30 = 30 Minuten frueher // Spalte 4 = Wenn hier ein gueltiger shiftpattern (wie in Tabelle "Shiftpattern" defibuert) wird dann kann ein abweichender Astrowert je Monat verwendet werden // Cron in Spalte 1 wird nicht verwendet wenn ein AstroKeyworkd in Spalte 2 eingetragen wurde // TriggerKey Schedule Astro Zeit(Min) Astro addieren Shiftpattern fuer Tabelle "ShiftPattern" const AutoSchedule = [ ["RolladeKuecheAuf", "", 'sunriseEnd', 0, "Rolladen-Ki1-Ki2-KuecheAuf"], ["RolladeKuecheZu", "", 'goldenHour', 0, "Rollade-KuecheZu"], ["RolladeKind1Auf", "", 'sunriseEnd', 0, "Rolladen-Ki1-Ki2-KuecheAuf"], ["RolladeKind1Zu", "", 'goldenHour', 50, ""], // zeit in Minuten ["RolladeKind2Auf", "" , 'sunriseEnd', 0, "Rolladen-Ki1-Ki2-KuecheAuf"], ["RolladeKind2Zu", "", 'goldenHour', 55, ""], // zeit in Minuten ["LichtEingangAn", '45 5 * * 1-2,4', "", 0, ""], ["MittagsTempAussen", '01 12 * * *', "", 0, ""], // Mittags TempLog ]; // Shiftpattern Tabelle - Wenn in Autschedule ein Astroschedule verwendet wird und bei shift keine Zahl sondern ein Text (=patternKey=) eingegeben wird // Fuer jeden Monat werden die ShiftMinuten verwendet, diese koennen positiv oder negativ sein // Spalte 1 - 12 - Eingabe der Minuten die von der Astrozeit abweichen soll. 1 = Januar -----12 = dezember const ShiftPattern = [ // PatternKey Monate---> 01-02-03-04-05-06-07-08- 09- 10- 11- 12- [ "Rollade-KuecheZu", 55,50,30,40,50,50,50,50, 50, 50, 50, 40 ], ]; // Spalte 0 = TriggerKey aus Tabelle AutoTrigger (wenn hier ein key eingegeben wird, dann wird dieser nicht fuer die action tablle genutzt sondern ausschliesslich die folgenden keys der folgenden spalten) // Spalte 1 und folgende zugeordnete Aktionen die durch Tabelle AutoAction ausgefuehrt werden. Reihenfolge der Ausfuehrung entspricht der Reihenfolge der TriggerActionMapping Tabelle // Beispiel: TriggerActionMapping[0] = [ "RolladeLoggiaAutoAuf" ,'RolladeLoggiaAutoAuf','RolladeStateSet ]; // Diese Tabelle muss nur gefuellt werden, wenn der TriggerKey aus Tabelle AutoTrigger nicht mit der Aktion der Tabelle AutoAction uebereinstimmmt // oder mehrere Aktionen hintereinader ausgefuehrt werden sollen const TriggerActionMapping = [ [ 'RolladeSchlafZimmerAuf', 'RolladeSchlafZimmerLinksAuf', 'RolladeSchlafZimmerRechtsAuf' ], ]; /* bei Zeitvon und Zeibis List of Keywords fuer die ZeitAb und ZeitBis (wenn keine Zeiten angegeben wurden) "sunrise": sunrise (top edge of the sun appears on the horizon) "sunriseEnd": sunrise ends (bottom edge of the sun touches the horizon) "goldenHourEnd":morning golden hour (soft light, best time for photography) ends "solarNoon": solar noon (sun is in the highest position) "goldenHour": evening golden hour starts "sunsetStart": sunset starts (bottom edge of the sun touches the horizon) "sunset": sunset (sun disappears below the horizon, evening civil twilight starts) "dusk": dusk (evening nautical twilight starts) "nauticalDusk": nautical dusk (evening astronomical twilight starts) "night": night starts (dark enough for astronomical observations) - dont use in DE sommertime as it will not be reached ever "nightEnd": night ends (morning astronomical twilight starts) - dont use in DE sommertime as it will not be reached ever "nauticalDawn": nautical dawn (morning nautical twilight starts) "dawn": dawn (morning nautical twilight ends, morning civil twilight starts) "nadir": nadir (darkest moment of the night, sun is in the lowest position) Spalte 0 = ActionKey kommt aus Tabelle TriggerActionMapping oder bei einer 1:1 Beziehung aus Tabelle AutoTrigger Spalte 1 = Datenpunkt der zu der Aktion gehoert und ggf upgedated werden soll Spalte 2 = Datum ab diese Aktion ausgefuehrt werden soll Format dd.mm oder dd.mm.yy oder mit Zeit dd.mm 08:00 Spalte 3 = Datum bis diese Aktion ausgefuehrt werden soll Format dd.mm oder dd.mm.yy oder mit Zeit dd.mm 08:00 Spalte 4 = Ausfuehrung begrenzen auf bestimmte Wochentage Pattern ist 0000000 Die Stellen Stehen fuer den Wochentag beginnend mit Montag 1 = aktiv 0 = inaktiv,"" = nicht relevant Spalte 5 = Zeit ab der diese Aktion ausgefuehrt werden soll Format hh:mm:ss optional sekunde - Es koennen Astro Keywords verwendet werden Spalte 6 = Zeit bis diese Aktion ausgefuehrt werden soll Format hh:mm:ss optional sekunde - Es koennen Astro Keywords verwendet werden Spalte 7 = Wert fuer den Update - wenn +n oder -n angegeben wird der WErt nach + oder minus dann entsprechend berechnet und gespeichert (Zaehler) Wenn &getState& angewgeben wird, dann wird der folgende Datenpunkt als Wert gelesen Beispiel &getState&hm-rpc.0.HEQ0402562.1.LEVEL Spalte 8 = Ruecksetzwert - falls eine Schaltdauer angegeben wird, dann wird dieser Wert gesetzt nach ablauf der Schaltdauer Spalte 9 = Zeitverzoegerung - Update wird nach Ablauf der Zeitverzoegerung in Sekunden vorgenommen Spalte 10 = Schaltdauer - in Sekunden danach wird der Ruecksetzwert gesetzt (wenn Schaltdauer > 0 ist, dann wird die angegebene Schaltdauer gesetzt - Angabe in Sekunden) Spalte 11 = zu sendende Message - Erklaerung zur Aktion Spalte 12 = soll die Message im System Log von ioBroker erscheinen - dann true - sonst false Spalte 13 = soll die Message per Messaging service versendet werden true oder false - Adapter muessen installiert sein Spalte 14 = frei fuer zukuenftige Erweiterungen - keine Funktion im Moment Spalte 15 = soll ein externes Log in eine Excel lesbare datei geschrieben werden Spalte 16 = Name der Datei ohne Pfadangabe und one Extention also ohne .csv gleiche ActionKeys sind erlaubt. Dann werden alle Actions mit demselben ActionKey ausgefuehrt */ const AutoAction = [ [ "AWSAktivierung", //Spalte 0 AktionsID 'javascript.0.Anwesenheitssteuerung.AWSAktiv', //Spalte 1 Geraet das reagieren soll "", //Spalte 2 Datum ab dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 3 Datum bis zu dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 4 Nur an diesen Wochentagen 0000000 = 1.Stelle=Sonntag 2.Stelle=Montag 7.Stelle=Samstag "", //Spalte 5 Zeit ab der reagiert werden soll - Format hh:mm:ss oder hh:mm "", //Spalte 6 Zeit bis zu reagiert werden soll - Format hh:mm:ss oder hh:mm true, //Spalte 7 Wert der fuer das zu reagierende Geraet gesetzt werden soll false, //Spalte 8 Ruecksetzwert falls automatisch nach der Schaltdauer wieder zurueckgesetzt werden soll 600, //Spalte 9 Zeitverzoegerung fuer das setzen des Wertes 0, //Spalte 10 Schaltdauer "AWS aktiviert", //Spalte 11 zu sendende Message true, //Spalte 12 Message in log false, //Spalte 13 Message mit den konfigurierten messaging services '', //Spalte 14 frei fuer zukuenftige erweiterungen true, //Spalte 15 Log in CSV Datei "IFTTT-AWS" //Spalte 16 Dateiname fuer die CSF Datei ohne ".csv" ], [ "AWSDeAktivierung", //Spalte 0 AktionsID 'javascript.0.Anwesenheitssteuerung.AWSAktiv', //Spalte 1 Geraet das reagieren soll "", //Spalte 2 Datum ab dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 3 Datum bis zu dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 4 Nur an diesen Wochentagen 0000000 = 1.Stelle=Sonntag 2.Stelle=Montag 7.Stelle=Samstag "", //Spalte 5 Zeit ab der reagiert werden soll - Format hh:mm:ss oder hh:mm "", //Spalte 6 Zeit bis zu reagiert werden soll - Format hh:mm:ss oder hh:mm false, //Spalte 7 Wert der fuer das zu reagierende Geraet gesetzt werden soll false, //Spalte 8 Ruecksetzwert falls automatisch nach der Schaltdauer wieder zurueckgesetzt werden soll 0, //Spalte 9 Zeitverzoegerung fuer das setzen des Wertes 0, //Spalte 10 Schaltdauer "AWS deaktiviert", //Spalte 11 zu sendende Message true, //Spalte 12 Message in log false, //Spalte 13 Message mit den konfigurierten messaging services '', //Spalte 14 frei fuer zukuenftige erweiterungen true, //Spalte 15 Log in CSV Datei "IFTTT-AWS" //Spalte 16 Dateiname fuer die CSF Datei ohne ".csv" ], [ "RolladeKuecheAuf", //Spalte 0 AktionsID 'hm-rpc.1.00111D8995432C.4.LEVEL', //Spalte 1 Geraet das reagieren soll "", //Spalte 2 Datum ab dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 3 Datum bis zu dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 4 Nur an diesen Wochentagen 0000000 = 1.Stelle=Sonntag 2.Stelle=Montag 7.Stelle=Samstag "", //Spalte 5 Zeit ab der reagiert werden soll - Format hh:mm:ss oder hh:mm "", //Spalte 6 Zeit bis zu reagiert werden soll - Format hh:mm:ss oder hh:mm 100, //Spalte 7 Wert der fuer das zu reagierende Geraet gesetzt werden soll 0, //Spalte 8 Ruecksetzwert falls automatisch nach der Schaltdauer wieder zurueckgesetzt werden soll 1, //Spalte 9 Zeitverzoegerung fuer das setzen des Wertes 0, //Spalte 10 Schaltdauer "Rollade Kueche hochgefahren", //Spalte 11 zu sendende Message true, //Spalte 12 Message in log false, //Spalte 13 Message mit den konfigurierten messaging services '', //Spalte 14 frei fuer zukuenftige erweiterungen true, //Spalte 15 Log in CSV Datei "IFTTT-Rolladen" //Spalte 16 Dateiname fuer die CSF Datei ohne ".csv" ], [ "RolladeKuecheZu", //Spalte 0 AktionsID 'hm-rpc.1.00111D8995432C.4.LEVEL', //Spalte 1 Geraet das reagieren soll "", //Spalte 2 Datum ab dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 3 Datum bis zu dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 4 Nur an diesen Wochentagen 0000000 = 1.Stelle=Sonntag 2.Stelle=Montag 7.Stelle=Samstag "", //Spalte 5 Zeit ab der reagiert werden soll - Format hh:mm:ss oder hh:mm "", //Spalte 6 Zeit bis zu reagiert werden soll - Format hh:mm:ss oder hh:mm 0, //Spalte 7 Wert der fuer das zu reagierende Geraet gesetzt werden soll 0, //Spalte 8 Ruecksetzwert falls automatisch nach der Schaltdauer wieder zurueckgesetzt werden soll 0, //Spalte 9 Zeitverzoegerung fuer das setzen des Wertes 0, //Spalte 10 Schaltdauer "Rollade Kueche runter gefahren", //Spalte 11 zu sendende Message true, //Spalte 12 Message in log false, //Spalte 13 Message mit den konfigurierten messaging services '', //Spalte 14 frei fuer zukuenftige erweiterungen true, //Spalte 15 Log in CSV Datei "IFTTT-Rolladen" //Spalte 16 Dateiname fuer die CSF Datei ohne ".csv" ], [ "RolladeKind1Auf", //Spalte 0 AktionsID 'hm-rpc.1.0036A0C9AFDCAE.4.LEVEL', //Spalte 1 Geraet das reagieren soll "", //Spalte 2 Datum ab dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 3 Datum bis zu dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 4 Nur an diesen Wochentagen 0000000 = 1.Stelle=Sonntag 2.Stelle=Montag 7.Stelle=Samstag "", //Spalte 5 Zeit ab der reagiert werden soll - Format hh:mm:ss oder hh:mm "", //Spalte 6 Zeit bis zu reagiert werden soll - Format hh:mm:ss oder hh:mm 100, //Spalte 7 Wert der fuer das zu reagierende Geraet gesetzt werden soll 0, //Spalte 8 Ruecksetzwert falls automatisch nach der Schaltdauer wieder zurueckgesetzt werden soll 2, //Spalte 9 Zeitverzoegerung fuer das setzen des Wertes 0, //Spalte 10 Schaltdauer "Rollade Kind1 hochgefahren", //Spalte 11 zu sendende Message true, //Spalte 12 Message in log false, //Spalte 13 Message mit den konfigurierten messaging services '', //Spalte 14 frei fuer zukuenftige erweiterungen true, //Spalte 15 Log in CSV Datei "IFTTT-Rolladen" //Spalte 16 Dateiname fuer die CSF Datei ohne ".csv" ], [ "RolladeKind1Zu", //Spalte 0 AktionsID 'hm-rpc.1.0036A0C9AFDCAE.4.LEVEL', //Spalte 1 Geraet das reagieren soll "", //Spalte 2 Datum ab dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 3 Datum bis zu dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 4 Nur an diesen Wochentagen 0000000 = 1.Stelle=Sonntag 2.Stelle=Montag 7.Stelle=Samstag "", //Spalte 5 Zeit ab der reagiert werden soll - Format hh:mm:ss oder hh:mm "", //Spalte 6 Zeit bis zu reagiert werden soll - Format hh:mm:ss oder hh:mm 0, //Spalte 7 Wert der fuer das zu reagierende Geraet gesetzt werden soll 0, //Spalte 8 Ruecksetzwert falls automatisch nach der Schaltdauer wieder zurueckgesetzt werden soll 2, //Spalte 9 Zeitverzoegerung fuer das setzen des Wertes 0, //Spalte 10 Schaltdauer "Rollade Kind1 runter gefahren", //Spalte 11 zu sendende Message true, //Spalte 12 Message in log false, //Spalte 13 Message mit den konfigurierten messaging services '', //Spalte 14 frei fuer zukuenftige erweiterungen true, //Spalte 15 Log in CSV Datei "IFTTT-Rolladen" //Spalte 16 Dateiname fuer die CSF Datei ohne ".csv" ], [ "RolladeKind2Auf", //Spalte 0 AktionsID 'hm-rpc.1.001120C9A8693A.4.LEVEL', //Spalte 1 Geraet das reagieren soll "", //Spalte 2 Datum ab dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 3 Datum bis zu dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 4 Nur an diesen Wochentagen 0000000 = 1.Stelle=Sonntag 2.Stelle=Montag 7.Stelle=Samstag "", //Spalte 5 Zeit ab der reagiert werden soll - Format hh:mm:ss oder hh:mm "", //Spalte 6 Zeit bis zu reagiert werden soll - Format hh:mm:ss oder hh:mm 100, //Spalte 7 Wert der fuer das zu reagierende Geraet gesetzt werden soll 0, //Spalte 8 Ruecksetzwert falls automatisch nach der Schaltdauer wieder zurueckgesetzt werden soll 3, //Spalte 9 Zeitverzoegerung fuer das setzen des Wertes 0, //Spalte 10 Schaltdauer "Rollade Kind2 hochgefahren", //Spalte 11 zu sendende Message true, //Spalte 12 Message in log false, //Spalte 13 Message mit den konfigurierten messaging services '', //Spalte 14 frei fuer zukuenftige erweiterungen true, //Spalte 15 Log in CSV Datei "IFTTT-Rolladen" //Spalte 16 Dateiname fuer die CSF Datei ohne ".csv" ], [ "RolladeKind2Zu", //Spalte 0 AktionsID 'hm-rpc.1.001120C9A8693A.4.LEVEL', //Spalte 1 Geraet das reagieren soll "", //Spalte 2 Datum ab dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 3 Datum bis zu dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 4 Nur an diesen Wochentagen 0000000 = 1.Stelle=Sonntag 2.Stelle=Montag 7.Stelle=Samstag "", //Spalte 5 Zeit ab der reagiert werden soll - Format hh:mm:ss oder hh:mm "", //Spalte 6 Zeit bis zu reagiert werden soll - Format hh:mm:ss oder hh:mm 0, //Spalte 7 Wert der fuer das zu reagierende Geraet gesetzt werden soll 0, //Spalte 8 Ruecksetzwert falls automatisch nach der Schaltdauer wieder zurueckgesetzt werden soll 3, //Spalte 9 Zeitverzoegerung fuer das setzen des Wertes 0, //Spalte 10 Schaltdauer "Rollade Kind2 runtergefahren", //Spalte 11 zu sendende Message true, //Spalte 12 Message in log false, //Spalte 13 Message mit den konfigurierten messaging services '', //Spalte 14 frei fuer zukuenftige erweiterungen true, //Spalte 15 Log in CSV Datei "IFTTT-Rolladen" //Spalte 16 Dateiname fuer die CSF Datei ohne ".csv" ], [ "RolladeSchlafZimmerLinksAuf", //Spalte 0 AktionsID 'hm-rpc.1.001120C9A869D5.4.LEVEL', //Spalte 1 Geraet das reagieren soll "", //Spalte 2 Datum ab dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 3 Datum bis zu dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 4 Nur an diesen Wochentagen 0000000 = 1.Stelle=Sonntag 2.Stelle=Montag 7.Stelle=Samstag "", //Spalte 5 Zeit ab der reagiert werden soll - Format hh:mm:ss oder hh:mm "", //Spalte 6 Zeit bis zu reagiert werden soll - Format hh:mm:ss oder hh:mm 100, //Spalte 7 Wert der fuer das zu reagierende Geraet gesetzt werden soll 0, //Spalte 8 Ruecksetzwert falls automatisch nach der Schaltdauer wieder zurueckgesetzt werden soll 4, //Spalte 9 Zeitverzoegerung fuer das setzen des Wertes 0, //Spalte 10 Schaltdauer "Rollade Schlafzimmer Links hochgefahren", //Spalte 11 zu sendende Message true, //Spalte 12 Message in log false, //Spalte 13 Message mit den konfigurierten messaging services '', //Spalte 14 frei fuer zukuenftige erweiterungen true, //Spalte 15 Log in CSV Datei "IFTTT-Rolladen" //Spalte 16 Dateiname fuer die CSF Datei ohne ".csv" ], [ "RolladeSchlafZimmerRechtsAuf", //Spalte 0 AktionsID 'hm-rpc.1.001120C9A86A00.4.LEVEL'/*Rollade Schlafzimmer rechts LEVEL*/, //Spalte 1 Geraet das reagieren soll "", //Spalte 2 Datum ab dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 3 Datum bis zu dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 4 Nur an diesen Wochentagen 0000000 = 1.Stelle=Sonntag 2.Stelle=Montag 7.Stelle=Samstag "", //Spalte 5 Zeit ab der reagiert werden soll - Format hh:mm:ss oder hh:mm "", //Spalte 6 Zeit bis zu reagiert werden soll - Format hh:mm:ss oder hh:mm 100, //Spalte 7 Wert der fuer das zu reagierende Geraet gesetzt werden soll 0, //Spalte 8 Ruecksetzwert falls automatisch nach der Schaltdauer wieder zurueckgesetzt werden soll 4, //Spalte 9 Zeitverzoegerung fuer das setzen des Wertes 0, //Spalte 10 Schaltdauer "Rollade Schlafzimmer Rechts hochgefahren", //Spalte 11 zu sendende Message true, //Spalte 12 Message in log false, //Spalte 13 Message mit den konfigurierten messaging services '', //Spalte 14 frei fuer zukuenftige erweiterungen true, //Spalte 15 Log in CSV Datei "IFTTT-Rolladen" //Spalte 16 Dateiname fuer die CSF Datei ohne ".csv" ], [ "LichtEingangAn", //Spalte 0 AktionsID 'hm-rpc.1.003AE0C9A8EF37.3.STATE', //Spalte 1 Geraet das reagieren soll "01.10", //Spalte 2 Datum ab dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "28.02", //Spalte 3 Datum bis zu dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "0110100", //Spalte 4 Nur an diesen Wochentagen 0000000 = 1.Stelle=Sonntag 2.Stelle=Montag 7.Stelle=Samstag "", //Spalte 5 Zeit ab der reagiert werden soll - Format hh:mm:ss oder hh:mm "", //Spalte 6 Zeit bis zu reagiert werden soll - Format hh:mm:ss oder hh:mm true, //Spalte 7 Wert fuer das zu reagierende Geraet gesetzt werden soll false, //Spalte 8 Ruecksetzwert falls automatisch nach der Schaltdauer wieder zurueckgesetzt werden soll 0, //Spalte 9 Zeitverzoegerung fuer das setzen des Wertes 1800, //Spalte 10 Schaltdauer "Licht Eingang eingeschalte fuer 30 Minuten", //Spalte 11 zu sendende Message true, //Spalte 12 Message in log false, //Spalte 13 Message mit den konfigurierten messaging services '', //Spalte 14 frei fuer zukuenftige erweiterungen true, //Spalte 15 Log in CSV Datei "IFTTT-Licht" //Spalte 16 Dateiname fuer die CSF Datei ohne ".csv" ], [ "ALARMFeuchteKeller", //Spalte 0 AktionsID 'hm-rpc.1.00189A498DD8B9.1.MOISTURE_DETECTED', //Spalte 1 Geraet das reagieren soll "", //Spalte 2 Datum ab dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 3 Datum bis zu dem reagiert werden soll - Format dd.mm optional Zeit hh:mm Beispiel: 24.10. 08:00 "", //Spalte 4 Nur an diesen Wochentagen 0000000 = 1.Stelle=Sonntag 2.Stelle=Montag 7.Stelle=Samstag "", //Spalte 5 Zeit ab der reagiert werden soll - Format hh:mm:ss oder hh:mm "", //Spalte 6 Zeit bis zu reagiert werden soll - Format hh:mm:ss oder hh:mm "", //Spalte 7 Wert fuer das zu reagierende Geraet gesetzt werden soll false, //Spalte 8 Ruecksetzwert falls automatisch nach der Schaltdauer wieder zurueckgesetzt werden soll 0, //Spalte 9 Zeitverzoegerung fuer das setzen des Wertes 0, //Spalte 10 Schaltdauer "ALARM Feuchtigkeit im Keller", //Spalte 11 zu sendende Message true, //Spalte 12 Message in log true, //Spalte 13 Message mit den konfigurierten messaging services '', //Spalte 14 frei fuer zukuenftige erweiterungen true, //Spalte 15 Log in CSV Datei "IFTTT-ALARM" //Spalte 16 Dateiname fuer die CSF Datei ohne ".csv" ], ]; // Ende Array // Ende Einstellungsbereich //------------------------------------------------------------------------------------------------------------------- const fs = require('fs'); // enable write fuer externes log let ClearSched = [] // wird gebraucht um den AstroSchedule zu loeschen let Cron = []; // enthaelt die cron pattern fuer astro schedules in standard cron // Globale definitin von Astro Keywords. Falls es aenderungen oder neuerungen gibt, dann kann das hier angepasst werden const astroEvents = { "SUNRISE": "sunrise", "SUNRISEEND": "sunriseEnd", "GOLDENHOUREND": "goldenHourEnd", "SOLARNOON": "solarNoon", "GOLDENHOUR": "goldenHour", "SUNSETSTART": "sunsetStart", "SUNSET": "sunset", "DUSK": "dusk", "NAUTICALDUSK": "nauticalDusk", "NIGHT": "night", "NIGHTEND": "nightEnd", "DAWN": "dawn", "NADIR": "nadir" }; //----------------------------------------------------------------------------------------------------- // Hauptprogramm - von hier aus wird das Programm initialisiert //----------------------------------------------------------------------------------------------------- Subscribe(); ScheduleNoAstro(); // zuerst der schedule ohne Astrodaten ScheduleAstro() // dann der schedule nur fuer Astrodaten schedule("1 0 * * *", function(obj) { ClearingSchedule(); // nach Tageswechsel loeschen des schedules fuer Astro / bei Astroschedule funktioniert ClearSchedule nicht - also workaround ScheduleAstro(); // neuEinplanung des Astroschedules um die geaenderten daten (astrozeiten und monatswechsel shift) zu beruecksichtigen }); // Statistiken generieren und ausgeben if (StatistikAusgabe) { generateStatistics(AutoAction);} //----------------------------------------------------------------------------------------------------- // Subscription //----------------------------------------------------------------------------------------------------- function Subscribe() { let timer = null; AutoTrigger.forEach(trigger => { const [TriggerKey, TriggerID, operand, TriggerWert, TriggerOnlyCheck, ValueDifferentCheck] = trigger; const TriggerOperand = CheckOperand(operand, "subscription"); let ScheduleCheck = AutoSchedule.some(schedule => schedule[0] === TriggerKey && (schedule[1] !== "" || schedule[2] !== "") ); if (ScheduleCheck) { if (debug) log(`Routine Subscribe - Fuer TriggerKey ${TriggerKey} keine Subscription, da ein Schedule eingestellt wurde`, "info"); return; } if (TriggerOnlyCheck) { if (debug) log(`Routine Subscribe - Fuer TriggerKey ${TriggerKey} keine Subscription, da nur Pruefen vorgesehen ist`, "info"); return; } if (debug) log(`Routine Subscribe - Fuer TriggerKey ${TriggerKey} Setze TriggerID ${TriggerID}, Operand ${TriggerOperand}, Triggerwert = ${TriggerWert}`, "info"); const subscribeObj = { id: TriggerID }; switch (TriggerOperand) { case "val": subscribeObj.val = TriggerWert; break; case "valGt": subscribeObj.valGt = TriggerWert; break; case "valGe": subscribeObj.valGe = TriggerWert; break; case "valLt": subscribeObj.valLt = TriggerWert; break; case "valLe": subscribeObj.valLe = TriggerWert; break; case "valNe": subscribeObj.valNe = TriggerWert; break; case "any": subscribeObj.change = "any"; break; } // @ts-ignore on(subscribeObj, obj => { if (ValueDifferentCheck && obj.state.val !== obj.oldState.val) { if (debug) log(`Routine Subscribe - Fuer TriggerKey ${TriggerKey} Subscription ausgefuehrt: obj.state.val != obj.oldState.val - old value ist ${obj.oldState.val}`, "info"); timer = setTimeout(() => { Execute_Main(TriggerKey); if (debug) log(`Routine Subscribe - Fuer TriggerKey ${TriggerKey} Funktion Execute Main wurde mit Timeout ausgefuehrt`, "info"); }, Entprellzeit * 1000); } else if (!ValueDifferentCheck) { if (debug) log(`Routine Subscribe - Fuer TriggerKey ${TriggerKey} Subscription ausgefuehrt ohne obj.state.val-Pruefung`, "info"); Execute_Main(TriggerKey); } }); }); } //----------------------------------------------------------------------------------------------------- // Funktion Schedule AstroTime //----------------------------------------------------------------------------------------------------- function ScheduleAstro() { AutoSchedule.forEach((scheduleEntry, x) => { const TriggerKey = scheduleEntry[0]; // @ts-ignore const AstroKeyWord = (scheduleEntry[2] || "").toUpperCase(); const AstroOffSet = ScheduleShift(scheduleEntry[3] || 0, scheduleEntry[4] ); if (!AstroKeyWord) { return; // Kein AstroKeyword eingetragen } const astroEvent = astroEvents[AstroKeyWord]; if (astroEvent) { const astroTime = getAstroDate(astroEvent).getTime() + AstroOffSet; Cron[x] = [formatDate(astroTime, 'm h * * *')]; ClearSched[x] = schedule(Cron[x], () => Execute_Main(TriggerKey)); log(`Routine ScheduleAstro -- Triggerkey ${TriggerKey} umgerechneter CRON Schedule ${Cron[x]} fuer ${astroEvent} Shift ist in Minuten ${Math.round(AstroOffSet / 60000)}`, "info"); } }); } //----------------------------------------------------------------------------------------------------- // Funktion Schedule No AstroTime //----------------------------------------------------------------------------------------------------- function ScheduleNoAstro() { // Durchlaufen des AutoSchedule-Arrays for (let x = 0; x < AutoSchedule.length; x++) { const TriggerKey = AutoSchedule[x][0]; // Schlüssel für den Trigger const cronPattern = AutoSchedule[x][1] || null; // Cron-Pattern // @ts-ignore const AstroKeyWord = (AutoSchedule[x][2] || "").toUpperCase(); // Astro-Ereignis AutoSchedule[x][3] = AutoSchedule[x][3] || 0; // Setze 0, falls undefined if (!cronPattern) { continue; // Kein Job eingetragen, weiter } // Überprüfen, ob AstroKeyWord in astroEvents enthalten ist if (AstroKeyWord in astroEvents) { continue; // Astrotime wird mit der Funktion ScheduleAstro geplant } if (debug) { log(`Routine ScheduleNoAstro - geplant wird für TriggerKey ${TriggerKey} Cron Pattern ${cronPattern}`, "info"); } // Überprüfen, ob das Cron-Pattern gültig ist if (isValidCronPattern(cronPattern)) { schedule(cronPattern, function(obj) { Execute_Main(TriggerKey); }); } else { log(`Routine ScheduleNoAstro - für TriggerKey ${TriggerKey} ist das Cron Pattern nicht korrekt: ${cronPattern}`, "warn"); } } } // endfunction //----------------------------------------------------------------------------------------------------- // Funktion Clearing the Schedule - per schedule bei Tageswechsel //----------------------------------------------------------------------------------------------------- function ClearingSchedule() { var AstroKeyWord = ""; for (let x = 0; x < AutoSchedule.length; x++) { const TriggerKey = AutoSchedule[x][0] ; if( typeof(AutoSchedule[x][2]) === "undefined") { AutoSchedule[x][2] = ""; }//endif if( typeof(AutoSchedule[x][3]) === "undefined") { AutoSchedule[x][3] = 0; }//endif // @ts-ignore AstroKeyWord = AutoSchedule[x][2].toUpperCase(); if(AstroKeyWord === ""|| AstroKeyWord === null) { // kein AstroKeyword eingetragen continue; }// endif log("Routine ClearingSchedule -- Triggerkey " + TriggerKey + " Astroschedule geloescht "+ Cron[x]+ " fuer " + AstroKeyWord,"info") clearSchedule(ClearSched[x]) }//endfor } //Endfunction //----------------------------------------------------------------------------------------------------- // Funktion: Execute_Main //----------------------------------------------------------------------------------------------------- function Execute_Main (TriggerKey) { if (CheckConditions(TriggerKey) ) { // ueberpruefe all Bedingungen. Wenn alle Bedingungen zutreffen kann die Aktionstabelle abgearbeitet werden if(debug ) { log("Routine Schedule - TriggerKey " + TriggerKey + " Alle Bedingungen treffen zu fuer TriggerKey "+ TriggerKey,"info");} Action(TriggerKey ); // jetzt die Aktionen abarbeiten } else { if(debug ) { log("Routine Execute_Main - Nicht alle Bedingungen treffen zu fuer Triggerkay " + TriggerKey,"info");} } // endif } //Endfunction //----------------------------------------------------------------------------------------------------- // Funktion: Check Conditions //----------------------------------------------------------------------------------------------------- function CheckConditions(TriggerKey) { if (debug) { log("Check Conditions mit Triggerkey " + TriggerKey); } for (var i in AutoTrigger) { // Loop ueber alle und Bedingungen ueberpruefen if (AutoTrigger[i][0] === TriggerKey) { const TriggerID = AutoTrigger[i][1]; const TriggerWert = AutoTrigger[i][3]; // @ts-ignore const exists = existsState(TriggerID); // @ts-ignore const getWert = exists ? getState(TriggerID).val : null; if (debug) { log("CheckConditions - TriggerKey " + TriggerKey + " getwert = " + getWert + " vergleichswert ist " + TriggerWert, "info"); log("CheckConditions - TriggerKey " + TriggerKey + " der gefundene VergleichsOperand ist " + CheckOperand(AutoTrigger[i][2], "condition"), "info"); log("CheckConditions - TriggerKey " + TriggerKey + " geprueft wird = " + TriggerID, "info"); } const operand = CheckOperand(AutoTrigger[i][2], "condition"); let CondCheck = false; // Vergleichsoperationen in einer Funktion zusammenfassen const evaluateCondition = (op, val1, val2) => { switch (op) { case "===": return val1 === val2; case "==": return val1 == val2; case "=": return val1 === val2; case ">": return val1 > val2; case ">=": return val1 >= val2; case "<": return val1 < val2; case "<=": return val1 <= val2; case "!==": return val1 !== val2; default: return false; } }; // Bedingungen ueberpruefen CondCheck = (TriggerID === "" || TriggerWert === "" || TriggerID === null || TriggerWert === null) ? true : evaluateCondition(operand, getWert, TriggerWert); if (!CondCheck) { return false; // Wenn eine Bedingung false ist, dann sofort abbrechen } } } return true; // Alle Bedingungen fuer den TriggerKey sind erfuellt } //----------------------------------------------------------------------------------------------------- // Action //----------------------------------------------------------------------------------------------------- // Spalte 0 = ActionKey um mehrere Aktivitaeten zusammenzufassen // Spalte 1 = Geraet das reagieren soll // Spalte 2 = Datum ab dem reagiert werden soll dd:mm // Spalte 3 = Datum bis zu dem reagiert werden soll dd:mm // Spalte 4 = Zeit ab der reagiert werden soll hh:mm // Spalte 5 = Zeit bis zu der reagiert werden soll hh:mm // Spalte 6 = Wert der als Status fuer das zu reagierende Geraet gesetzt werden soll // Spalte 7 = Ruecksetzwert falls automatisch wieder zurueckgesetzt werden soll (wenn Schaltdauer angegeben wird) // Spalte 8 = Zeitverzoegerung fuer das setzen des Wertes // Spalte 9 = Schaltdauer (wenn Schaltdauer > 0 ist, dann wird die angegebene Schaltdauer gesetzt - Angabe in Sekunden) // Spalte 10 = zu sendende Message // Spalte 11 = Message in ioBroker Log // Spalte 12 = Message mit den konfigurierten messaging services // Spalte 13 = frei fuer zukuenftige erweiterungen // Spalte 14 = Log in CSV - Datei // Spalte 15 = Dateiname fuer CSV Name //generelle Vorghensweise //1. check die Tabelle AutoTrigger und verwende die Keys //2. checke ob es Keys aus Autotrigger in der Mapping Tabelle gibt //3. Wenn nein, dann ueberpruefen ob es aktionen zu den Keys aus der Autotrigger in der Aktionstabelle gibt (1:1 Zuordnung) //4. jetzt muss die ActionList Tabelle gefuellt sein und kann abgearbeitet werden. function Action(TriggerKey) { let ActionList = []; // ueberpruefen, ob es zugeordnete Aktionen in der Mapping Tabelle gibt let mappedActions = TriggerActionMapping.filter(entry => entry[0] === TriggerKey); if (mappedActions.length > 0) { if (debug) log(`Routine Action - TriggerKey ${TriggerKey} wird aus Tabelle verwendet`, "info"); mappedActions.forEach(entry => { ActionList.push(...entry.slice(1)); // Fuege alle Aktionen ab dem 1. Index hinzu }); } else { if (debug) log(`Routine Action - TriggerKey ${TriggerKey} wird aus Tabelle verwendet`, "info"); ActionList.push(TriggerKey); // Falls keine Mapping gefunden wird, TriggerKey als Action verwenden } // Keine definierten Aktionen if (ActionList.length === 0) { log(`Routine - TriggerKey ${TriggerKey} es gibt keine definierten Aktionen in Tabelle `, "info"); return false; } // Debug-Logging, um zu ueberpruefen, welche Aktionen zugeordnet sind if (debug) { ActionList.forEach(action => { const actionExists = AutoAction.some(entry => entry[0] === action); if (actionExists) { log(`Routine Action - Action aus AutoAction: ${action}`, "info"); } else { log(`Routine - TriggerKey ${TriggerKey} es gibt keine zuordenbare Aktionen in Tabelle - ActionKey ist ${action}`, "info"); } }); } // Aktionen aus der ActionList ausfuehren ActionList.forEach(actionKey => { let actionEntry = AutoAction.find(entry => entry[0] === actionKey); if (!actionEntry) return; // Falls keine passende Aktion gefunden wird, ueberspringen const [ ActionKey, schaltendesGeraet, DatumAb, DatumBis, Wochentag, ZeitAb, ZeitBis, NeuerState, RuecksetzState, StateDelayInSec, SchaltdauerInSec, Message, MessageLog, MessageService, frei, MessageCSV, MessageFileName ] = actionEntry; if(debug ) { log("Routine Action - TriggerKey " + TriggerKey + " Action Key "+ ActionKey + " wird bearbeitet","info");} // @ts-ignore let AlterState = getState(schaltendesGeraet).val; let NeuerStateChecked = CheckVariable(NeuerState); let NeuerStateStr = NeuerStateChecked.toString(); let AlterStateStr = AlterState.toString(); // @ts-ignore let StateDelay = StateDelayInSec * 1000; // @ts-ignore let Schaltdauer = SchaltdauerInSec * 1000; if (debug) log(`AlterState = ${AlterStateStr} Neuer State = ${NeuerStateStr} Schaltdauer = ${Schaltdauer}`, "info"); // Pruefen, ob Datum und Zeit im Range sind, und ob eine Schaltung notwendig ist if (isDateInRange(DatumAb, DatumBis) && isTimeInRange(ZeitAb, ZeitBis) && isWeekDayInRange(Wochentag)) { if (NeuerStateStr !== "") { NeuerStateChecked = WertKalkulation(NeuerStateChecked, schaltendesGeraet); if (debug) log(`Routine Action - TriggerKey ${TriggerKey} AutoAction aenderung fuer ActionKey ${ActionKey} auf ${NeuerStateChecked} erkannt`, "info"); // Schalten des Geraets mit Verzoegerung // @ts-ignore setStateDelayed(schaltendesGeraet, NeuerStateChecked, StateDelay); if (Schaltdauer > 0) { // @ts-ignore setStateDelayed(schaltendesGeraet, RuecksetzState, StateDelay + Schaltdauer); } } // Logging und Benachrichtigungen if (MessageLog) log(Message, "info"); if (MessageService) { Messaging(Message); } if (MessageCSV && MessageFileName !== ".CSV") { writelog(`${TriggerKey};${ActionKey};${schaltendesGeraet};${NeuerStateChecked};${Message}`, `${MessageFileName}.CSV`); } } }); } //----------------------------------------------------------------------------------------------------- // Messaging - sendet Nachrichten an ausgewaehlte Messenger Dienste // diese muessen als Adapter konfiguriert sein //----------------------------------------------------------------------------------------------------- function Messaging(Nachricht) { const Headline = "Nachricht von ioBroker IFTTT"; for (let i = 0; i < MessageServices.length; i++) { if (MessageServices[i][1] === true) { if (MessageServices[i][0] === "Email") { sendTo("email", "send", { text: Nachricht, to: MessageServices[i][2], subject: Headline }); } else if (MessageServices[i][0] === "WhatsApp") { sendTo("whatsapp-cmb", "send", { text: Nachricht, }); } else if (MessageServices[i][0] === "Signal") { sendTo('signal-cmb.0', 'send', { text: Nachricht, // phone: '+491234567890' // optional, falls leer, wird an die Standardnummer gesendet }); } else if (MessageServices[i][0] === "Telegram") { sendTo("telegram", "send", { text: Nachricht, }); } else if (MessageServices[i][0] === "Pushover") { sendTo("pushover", "send", { message: Nachricht, sound: "", }); } else if (MessageServices[i][0] === "Pushsafer") { sendTo("pushsafer", { message: Nachricht, // mandatory - your text message title: Headline, // optional - your message's title, otherwise your app's name is used }); } } } } //----------------------------------------------------------------------------------------------------- // Wertkalkulation - falls addiert oder subtrahiert werden soll // wird verwendet bei den Aktionen falls ein Zaehler eingestellt werden soll //----------------------------------------------------------------------------------------------------- function WertKalkulation(Wert, StateToCalc) { if (typeof Wert !== "string") { return Wert; } const operator = Wert.charAt(0); const ChangeValue = parseInt(Wert.slice(1), 10); const AlterWert = getState(StateToCalc).val; if (typeof AlterWert !== "number") { log("Routine Wertkalkulation - Kalkulation nicht moeglich. Datentyp ist nicht 'number'.", "info"); return Wert; } let NeuerWert; switch (operator) { case "+": NeuerWert = AlterWert + ChangeValue; break; case "-": NeuerWert = AlterWert - ChangeValue; break; case "*": NeuerWert = AlterWert * ChangeValue; break; case "/": NeuerWert = ChangeValue !== 0 ? AlterWert / ChangeValue : AlterWert; // Vermeidung von Division durch 0 break; default: return Wert; // Unbekannter Operator, Rueckgabe des Originalwerts } return NeuerWert; } //----------------------------------------------------------------------------------------------------- // CheckVariable - liest state aus // wird verwendet bei den Aktionen ein datenpunkt eingelesen werden soll //----------------------------------------------------------------------------------------------------- function CheckVariable(StateToRead) { if (typeof StateToRead !== "string") { return StateToRead; } const keyword = StateToRead.slice(0, 9).toUpperCase(); if (keyword === "&GETSTATE") { const stateKey = StateToRead.slice(10); const stateValue = getState(stateKey).val; if (debug) { log(`Routine CheckVariable - state wird gelesen ueber &GETSTATE. State: ${stateKey}, Wert: ${stateValue}`); } return stateValue; } return StateToRead; } //----------------------------------------------------------------------------------------------------- // Check TriggerOperand //----------------------------------------------------------------------------------------------------- function CheckOperand(Operand, Origin) { // Zuordnungen fuer "subscription" und "condition" const operandMap = { subscription: { "=": "val", "val": "val", "==": "val", "===": "val", ">": "valGt", "valGt": "valGt", ">=": "valGe", "valGe": "valGe", "<": "valLt", "valLt": "valLt", "<=": "valLe", "valLe": "valLe", "!=": "valNe", "valNe": "valNe", "": "any", "any": "any" }, condition: { "=": "===", "val": "===", "==": "==", "===": "===", ">": ">", "<": "<", ">=": ">=", "<=": "<=", "!=": "!=", "=>": ">=", "=<": "<=", "!==": "!==", "valGt": ">", "valGe": ">=", "valLt": "<", "valLe": "<=", "valNe": "!==" } }; // ueberpruefung auf ungueltige Urspruenge if (!operandMap[Origin]) { log(`Funktion CheckOperand unbekannte origin: ${Origin}`, "warn"); return "==="; // Standardrueckgabe } // Zuweisung des passenden Operanden, falls vorhanden, ansonsten Standardwert zurueckgeben const result = operandMap[Origin][Operand]; if (result) { return result; } log(`Funktion CheckOperand - TriggerOperand - Bedingung traf nicht zu - bitte AutoTrigger Tabelle ueberpruefen - Operand nicht identifizierbar: ${Operand}, Standardwert any oder === wird verwendet`, "warn"); return Origin === "subscription" ? "any" : "==="; } //----------------------------------------------------------------------------------------------------- // Weekday in range prueft Sonntag bis Samstag im Format 0100101 - 1 = true wenn heute der Tag ist in diesem Fall Montag Donnerstag und Samstag //----------------------------------------------------------------------------------------------------- function isWeekDayInRange(Wochentag) { // Wenn Wochentag leer oder null ist, wird nicht geprueft if (!Wochentag) { return true; } // ueberpruefen, ob die Laenge der Wochentagdefinition korrekt ist (7 Zeichen) if (Wochentag.length !== 7) { log(`Routine isWeekDayInRange - Falsche Wochentagdefinition "${Wochentag}" wird ignoriert`, "info"); return true; } // Ermittlung des aktuellen Wochentags (0 = Sonntag, 1 = Montag, ...) const CurrentWeekday = new Date().getDay(); // Extrahiere das Zeichen fuer den aktuellen Tag und ueberpruefe es const WochenTagSingle = parseInt(Wochentag.charAt(CurrentWeekday), 10); // Ungueltiger Eintrag, falls nicht 0 oder 1 if (isNaN(WochenTagSingle) || WochenTagSingle > 1) { log(`Routine isWeekDayInRange - Falsche Wochentagdefinition "${Wochentag}" wird ignoriert`, "info"); return true; } // Optionales Debugging if (debug) { log(`Routine isWeekDayInRange - Wochentag: "${Wochentag}", aktueller Tag: ${CurrentWeekday}, Ergebnis: ${WochenTagSingle}`, "info"); } // Rueckgabe true, wenn der Wochentag fuer heute aktiviert ist (1) return WochenTagSingle === 1; } //----------------------------------------------------------------------------------------------------- // DateInRange //----------------------------------------------------------------------------------------------------- function isDateInRange(strDateFirst, strDateLast) { const today = new Date(); // Überprüfung der Datumsformate if (strDateFirst && !isValidFormat(strDateFirst, true)) { log(`Routine Falsches Format im Startdatum: ${strDateFirst}`, "warn"); return false; } if (strDateLast && !isValidFormat(strDateLast, true)) { log(`Routine Falsches Format im Enddatum: ${strDateLast}`, "warn"); return false; } // Start- und Enddatum parsen const dateFrom = strDateFirst ? parseDateTime(strDateFirst) : null; const dateTo = strDateLast ? parseDateTime(strDateLast) : null; // Fehlerbehandlung für falsche Eingaben if (dateFrom && isNaN(dateFrom.getTime())) { log(`Routine Falsches Format oder keine Angaben im Startdatum: ${strDateFirst}`, "info"); return true; // Standardmäßig true bei ungültiger Eingabe } if (dateTo && isNaN(dateTo.getTime())) { log(`Routine Falsches Format oder keine Angaben im Enddatum: ${strDateLast}`, "info"); return true; } // Wenn nur Startdatum vorhanden ist if (dateFrom && !dateTo) { return today >= dateFrom; // Vergleiche heutiges Datum mit Startdatum } // Wenn nur Enddatum vorhanden ist if (!dateFrom && dateTo) { return today <= dateTo; // Vergleiche heutiges Datum mit Enddatum } // Wenn beide Daten vorhanden sind if (dateFrom && dateTo) { // Überprüfen, ob das "bis"-Datum kein explizites Jahr enthält const toYearProvided = isYearProvided(strDateLast) log(" toYearProvided "+ toYearProvided) // Wenn das "bis"-Datum kleiner ist als das "von"-Datum und **kein** Jahr im "bis"-Datum angegeben wurde if (dateTo < dateFrom && !toYearProvided) { // Füge 1 Jahr hinzu, wenn das "bis"-Datum kleiner ist und kein Jahr angegeben wurde log("Jahr wird zu 'bis'-Datum hinzugefügt."); dateTo.setFullYear(dateFrom.getFullYear() + 1); // Jahr vom 'von'-Datum als Basis } // Rückgabe des Vergleichs des heutigen Datums mit dem Bereich return today >= dateFrom && today <= dateTo; } return true; // Standardmäßig true, wenn keine gültigen Daten vorhanden sind } //----------------------------------------------------------------------------------------------------- // parseDateTime wird om DateInRange genutzt // Funktion zum Überprüfen des Formats //----------------------------------------------------------------------------------------------------- function isValidFormat(str, isTimeIncluded = false) { const datePattern = /^\d{1,2}\.\d{1,2}\.(\d{2}|\d{4})?$/; const timePattern = isTimeIncluded ? /^(\d{1,2}):(\d{2})(:(\d{2}))?$/ : null; if (!str) return true; // Falls der String leer ist, ist es okay. const [datePart, timePart] = str.split(' '); if (!datePattern.test(datePart)) { return false; } if (isTimeIncluded && timePart && !timePattern.test(timePart)) { return false; } return true; } //----------------------------------------------------------------------------------------------------- // parseDateTime wird om DateInRange genutzt // Funktion zum Parsen des Datums (inklusive Zeit) //----------------------------------------------------------------------------------------------------- function parseDateTime(str) { const now = new Date(); const [datePart, timePart] = str.split(' '); // Datum verarbeiten (dd.mm.yy oder dd.mm.yyyy) const dateParts = datePart.split('.').map(part => parseInt(part, 10)); const day = dateParts[0] || now.getDate(); const month = (dateParts[1] || (now.getMonth() + 1)) - 1; // Monat 0-basiert const year = dateParts[2] && dateParts[2].toString().length === 2 ? 2000 + dateParts[2] // zweistelliges Jahr in 2000er umwandeln : dateParts[2] || now.getFullYear(); // Zeit verarbeiten (optional: hh:mm oder hh:mm:ss) let hour = 0, minute = 0, second = 0; if (timePart) { const timeParts = timePart.split(':').map(part => parseInt(part, 10)); hour = timeParts[0] || 0; minute = timeParts[1] || 0; second = timeParts[2] || 0; } return new Date(year, month, day, hour, minute, second); } //----------------------------------------------------------------------------------------------------- // isYearProvided wird om DateInRange genutzt //----------------------------------------------------------------------------------------------------- function isYearProvided(dateString) { // Regulärer Ausdruck, der nach einem Jahr sucht (entweder zweistellig oder vierstellig) const yearRegex = /\b(\d{2}|\d{4})\b/; const dateParts = dateString.trim().split(' '); // Aufteilen nach Leerzeichen (Datum und Zeit) // Nur den Datumsabschnitt prüfen, falls Zeit vorhanden ist const datePart = dateParts[0]; // Überprüfen, ob im Datumsabschnitt ein Jahr enthalten ist (zweistellig oder vierstellig) const dateElements = datePart.split('.'); const lastElement = dateElements[dateElements.length - 1]; // Überprüfen, ob das letzte Element ein Jahr ist (2 oder 4 Ziffern) return yearRegex.test(lastElement); } //----------------------------------------------------------------------------------------------------- // Funktion isTimeInRange - inklusive Astrozeiten // Ermittlung ob die aktuelle Zeit in der vorgegebenen Zeit liegt (inkl Astro) //----------------------------------------------------------------------------------------------------- function isTimeInRange(zeitvon, zeitbis) { // Wenn keine Zeitbegrenzung angegeben ist, direkt true zurückgeben if (!zeitvon || !zeitbis) { return true; } const regexHHMMSS = /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/; const regexHHMM = /^([01]\d|2[0-3]):([0-5]\d)$/; const now = new Date(); // Überprüfung, ob Astro-Zeiten angegeben sind const IsAstroVon = Object.keys(astroEvents).some(type => zeitvon.includes(type)); const IsAstroBis = Object.keys(astroEvents).some(type => zeitbis.includes(type)); if (IsAstroVon || IsAstroBis) { log(`Astrofunktion wird verwendet: zeitvon ${zeitvon}, zeitbis ${zeitbis}`, 'info'); } // Funktion zum Anpassen des Zeitformats function normalizeTime(time) { if (regexHHMM.test(time)) { return time + ":00"; // Sekunden hinzufügen, wenn nur HH:MM angegeben } return time; } // Berechnung der unteren und oberen Zeitgrenze const lower = IsAstroVon ? getAstroDate(zeitvon) : addTime(normalizeTime(zeitvon)); const upper = IsAstroBis ? getAstroDate(zeitbis) : addTime(normalizeTime(zeitbis)); // Validierung des Zeitformats, falls keine Astro-Zeit if (!IsAstroVon && !regexHHMMSS.test(normalizeTime(zeitvon))) { log(`Funktion isTimeInRange - Falsche Zeitangabe für "von": ${zeitvon}. Format muss HH:MM oder HH:MM:SS sein.`, 'warn'); return false; } if (!IsAstroBis && !regexHHMMSS.test(normalizeTime(zeitbis))) { log(`Funktion isTimeInRange - Falsche Zeitangabe für "bis": ${zeitbis}. Format muss HH:MM oder HH:MM:SS sein.`, 'warn'); return false; } // Wenn die obere Zeitgrenze kleiner oder gleich der unteren ist, einen Tag zu upper addieren if (upper <= lower) { upper.setDate(upper.getDate() + 1); } // Vergleich der Zeit mit ioBroker-Funktion "compareTime" return compareTime(lower, upper, "between", now); } // endfunction //----------------------------------------------------------------------------------------------------- // Funktion ScheduleShift - // Bei Astrozeit kann eine abweichende Shiftzeit ermittelt werden - Diese Zeit ist in Tabelle ShiftPattern eingetragen und zwar je Monat // Zeit ist in Minuten //----------------------------------------------------------------------------------------------------- function ScheduleShift(offset, Pattern) { if (Pattern === undefined || Pattern === "") { return offset * 60000 } if(debug ) { log("Routine Shiftpattern - offset ist "+ offset + " pattern ist "+ Pattern) } const Monat = new Date().getMonth() + 1; // Aktueller Monat (1-basiert) if (debug) { log(`Routine ShiftPattern - Monat fuer Pattern ist ${Monat}`, "info"); } // Schleife durch alle ShiftPatterns for (let i = 0; i < ShiftPattern.length; i++) { if (ShiftPattern[i][0] === Pattern ) { for (let x = 1; x < ShiftPattern[i].length; x++) { // schleife durch monate aus der shiftpattern tabelle if (x === Monat) { let ShiftTime = Number(ShiftPattern[i][x]); if(debug) { log("Routine ShiftPattern -pattern fuer " + ShiftPattern[i][0] + " Monat "+x+ " in Minuten: " + ShiftTime,"info" );} if ( ShiftSmoothing ) { return round(SmoothShift(i)*60000,0); // Rueckgabe in Millisekunden }//endif return ShiftTime * 60000; // Rueckgabe in Millisekunden }//endif }//endfor x } // endif }// endfor // Falls kein passender Eintrag gefunden wurde, Fehler protokollieren log(`Routine ShiftPattern - Fehlerhafter Eintrag in Tabelle ShiftPattern fuer Key ${Pattern} im Monat ${Monat} - wird ignoriert`, "info"); return 0; // Keine gueltige ShiftPattern gefunden } // endfuction //----------------------------------------------------------------------------------------------------- // Funktion SmoothShift - // Wenn bei Astrozeit Zeiten abweichend von der Astrozeit plus oder Minus verwendet werden, // dann smoothed diese Funktion ueber die Zeitspanne eines Monats (ausgehend von Mitte des Monats) //----------------------------------------------------------------------------------------------------- function SmoothShift(Pattern) { const now = new Date(); const yearNow = now.getFullYear(); const MonthNow = now.getMonth() + 1; const DayNow = now.getDate(); let ShiftTotal = 0; let Monthlength, pastdays; const PeriodRelated = new Date(); const PeriodCurrent = new Date(); // Hilfsfunktion zur Berechnung der Tage zwischen zwei Datumswerten function calculateDaysBetween(start, end) { return (end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24); } // Hilfsfunktion zur Berechnung der Shift-Werte function calculateShift(relatedMonth, currentMonth, patternShift) { // @ts-ignore const difference = ShiftPattern[patternShift][currentMonth] - ShiftPattern[patternShift][relatedMonth]; Monthlength = calculateDaysBetween(PeriodRelated, PeriodCurrent); pastdays = calculateDaysBetween(PeriodCurrent, now); // @ts-ignore return ShiftPattern[patternShift][relatedMonth] + (difference / Monthlength) * pastdays; } if (DayNow <= 15) { // Handle month transition for the first half of the month if (MonthNow === 1) { PeriodRelated.setFullYear(yearNow - 1, 11, 15); // 15. Dezember des Vorjahres PeriodCurrent.setFullYear(yearNow, 0, 15); // 15. Januar ShiftTotal = calculateShift(12, 1, Pattern); // Dez → Jan } else { PeriodRelated.setFullYear(yearNow, MonthNow - 2, 15); // 15. des Vormonats PeriodCurrent.setFullYear(yearNow, MonthNow - 1, 15); // 15. des laufenden Monats ShiftTotal = calculateShift(MonthNow - 1, MonthNow, Pattern); } } else { // Handle month transition for the second half of the month if (MonthNow === 12) { PeriodRelated.setFullYear(yearNow + 1, 0, 15); // 15. Januar des naechsten Jahres PeriodCurrent.setFullYear(yearNow, 11, 15); // 15. Dezember ShiftTotal = calculateShift(12, 1, Pattern); // Dez → Jan } else { PeriodRelated.setFullYear(yearNow, MonthNow, 15); // 15. des naechsten Monats PeriodCurrent.setFullYear(yearNow, MonthNow - 1, 15); // 15. des laufenden Monats ShiftTotal = calculateShift(MonthNow, MonthNow + 1, Pattern); } } // Debug-Logging if (debug) { log(`Routine SmoothShift - relevanter Eintrag der Tabelle ShiftPattern ist ${Pattern} Bezeichnung: ${ShiftPattern[Pattern][0]}`, "info"); log(`Routine SmoothShift - Tag: ${DayNow}`, "info"); log(`Routine SmoothShift - Monat: ${MonthNow}`, "info"); log(`Routine SmoothShift - Jahr: ${yearNow}`, "info"); log(`Routine SmoothShift - Shifttotal: ${ShiftTotal}`, "info"); log(`Routine SmoothShift - vergangene Tage: ${pastdays}`, "info"); log(`Routine SmoothShift - PeriodCurrent: ${PeriodCurrent}, PeriodRelated: ${PeriodRelated}`, "info"); log(`Routine SmoothShift - Monat totale Tage: ${Monthlength}`, "info"); } return ShiftTotal; } //----------------------------------------------------------------------------------------------------- // Funktion schreibt einen Logeintrag in das Filesystem und auch in das interne Log-System //----------------------------------------------------------------------------------------------------- function writelog(string, FileName) { const now = new Date(); // aktuelle Zeit const logdate = `${addZero(now.getDate())}.${addZero(now.getMonth() + 1)}.${now.getFullYear()}`; const logtime = `${addZero(now.getHours())}:${addZero(now.getMinutes())}:${addZero(now.getSeconds())}`; const logEntry = `${logdate} ;${logtime} ;${string}\n`; const File = LogPath + FileName; fs.readFile(File, 'utf8', (err, data) => { if (err) { // Datei existiert nicht, erstelle sie mit Header const headerLine = "Datum;Uhrzeit;TriggerKey;ActionKey;Ziel-ID;Neuer Wert;Message"; fs.appendFileSync(File, `${headerLine}\n`); } fs.appendFileSync(File, logEntry); // Fuege Log-Eintrag hinzu }); } //----------------------------------------------------------------------------------------------------- // Funktion Addzero - // Null links auffuelle z.B. fuer Zeitangaben //----------------------------------------------------------------------------------------------------- function addZero(i) { return i < 10 ? "0" + i : i.toString(); } //----------------------------------------------------------------------------------------------------- // Rundungsfunktion //----------------------------------------------------------------------------------------------------- function round(value, stellen = 0) { if (isNaN(value)) return 0; const umrechnungsfaktor = Math.pow(10, stellen); return Math.round(value * umrechnungsfaktor) / umrechnungsfaktor; } //----------------------------------------------------------------------------------------------------- //Funktion addTime wandelt einen String mit Format 00:00:00 in rechenbare Zeit um //----------------------------------------------------------------------------------------------------- function addTime(strTime) { const [hours, minutes, seconds] = strTime.split(':').map(Number); const d = new Date(); d.setHours(hours, minutes, seconds); return d; } //----------------------------------------------------------------------------------------------------- //Funktion generateStatistics gibt eine Statistik zur verwenden der Aktionen aus //----------------------------------------------------------------------------------------------------- function generateStatistics(actions) { const statistics = { totalTriggers: 0, messagesInLog: 0, messagesInMessageService: 0, messagesInCSV: 0, messagesVerzoegert: 0, messagesSchaltdauer: 0, messagesRuecksetzwert: 0, messagesUpdateWert: 0, messagesZeitVon: 0, messagesZeitBis: 0, messagesWochentag: 0, messagesDatumVon: 0, messagesDatumBis: 0, actionIds: [] // Zum Speichern der Aktions-IDs }; for (const action of actions) { statistics.totalTriggers++; // Zähle jeden Trigger // Aktions-ID zur Liste hinzufügen statistics.actionIds.push(action[0]); // Überprüfen, ob Messages in die entsprechenden Logs gesendet werden if (action[5] !== undefined && action[5] !== "") { statistics.messagesDatumVon++; } if (action[6] !== undefined && action[6] !== "") { statistics.messagesZeitBis++; } if (action[7] !== undefined && action[7] !== "") { statistics.messagesUpdateWert++; } if (action[8] !== undefined && action[8] !== "") { statistics.messagesRuecksetzwert++; } if (action[9]) statistics.messagesVerzoegert++; if (action[10]) statistics.messagesSchaltdauer++; if (action[12]) statistics.messagesInLog++; if (action[13]) statistics.messagesInMessageService++; if (action[15]) statistics.messagesInCSV++; } // Ausgabe der Statistik log(`Anzahl der Aktionen: ${statistics.totalTriggers}`); log(`Anzahl der Aktionen mit Messages im iobroker-Log: ${statistics.messagesInLog}`); log(`Anzahl der Aktionen mit Messages per MessageService: ${statistics.messagesInMessageService}`); log(`Anzahl der Aktionen mit Logs in CSV-Datei: ${statistics.messagesInCSV}`); log(`Anzahl der Aktionen mit Zeitverzögerungen: ${statistics.messagesVerzoegert}`); log(`Anzahl der Aktionen mit Schaltdauer: ${statistics.messagesSchaltdauer}`); log(`Anzahl der Aktionen mit Rücksetzwert: ${statistics.messagesRuecksetzwert}`); log(`Anzahl der Aktionen mit Updates: ${statistics.messagesUpdateWert}`); log(`Anzahl der Aktionen mit ZeitVon Angabe: ${statistics.messagesDatumVon}`); log(`Anzahl der Aktionen mit Zeitbis Angabe: ${statistics.messagesZeitBis}`); // Jetzt Auswertung der AutoSchedule-Tabelle let totalSchedules = AutoSchedule.length; let astroSchedules = 0; let nonAstroSchedules = 0; let shiftpatternUse = 0; for (let i = 0; i < AutoSchedule.length; i++) { // Überprüfen, ob das dritte Element (AstroKeyWord) vorhanden ist if (AutoSchedule[i][2] !== undefined) { // @ts-ignore let astroKeyWord = AutoSchedule[i][2].toUpperCase(); // Überprüfen, ob der Zeitplan ein astroEvent enthält if (astroKeyWord in astroEvents) { astroSchedules++; } else { nonAstroSchedules++; } } else { // Wenn kein AstroKeyWord vorhanden ist, erhöhen wir die nonAstroSchedules-Zahl nonAstroSchedules++; } if (AutoSchedule[i][4] !== undefined && AutoSchedule[i][4] !== "" && AutoSchedule[i][4] !== null) { shiftpatternUse++; } } log("Anzahl der Eintragungen für Schedules: " + totalSchedules); log("Anzahl der Schedules mit Astrozeit: " + astroSchedules); log("Anzahl der Schedules ohne Astrozeit: " + nonAstroSchedules); log("Anzahl der Schedules mit Nutzung von Shiftpattern (Astrozeit Add je Monat): " + shiftpatternUse); } //----------------------------------------------------------------------------------------------------- //Funktion isValidCronPattern ueberprueft die gueltigkeit der angegebenen cron pattern //----------------------------------------------------------------------------------------------------- function isValidCronPattern(cronPattern) { // Erlaubte Werte fuer jedes Feld const cronFields = [ { min: 0, max: 59 }, // Minuten { min: 0, max: 23 }, // Stunden { min: 1, max: 31 }, // Tage des Monats { min: 1, max: 12 }, // Monate { min: 0, max: 6 } // Wochentage (0=Sonntag, 1=Montag, ..., 6=Samstag) ]; // Teile das Pattern in Felder auf const fields = cronPattern.trim().split(/\s+/); if (fields.length < 5 || fields.length > 6) { return false; // Ungueltige Anzahl an Feldern } // ueberpruefe jedes Feld for (let i = 0; i < fields.length; i++) { const field = fields[i]; const { min, max } = cronFields[i]; // ueberpruefen, ob das Feld ein gueltiges Format hat if (!isValidField(field, min, max)) { return false; } } return true; // Das Cron-Pattern ist gueltig } //----------------------------------------------------------------------------------------------------- //Funktion isValidField fuer Cron Pattern ueberprueft die gueltigkeit der angegebenen cron pattern //----------------------------------------------------------------------------------------------------- function isValidField(field, min, max) { const parts = field.split(','); for (const part of parts) { let [start, end] = part.split('-').map(Number); const step = part.includes('/') ? Number(part.split('/')[1]) : 1; // Wenn nur eine Zahl angegeben ist if (!isNaN(start)) { if (start < min || start > max) return false; continue; } // ueberpruefe Bereich (z.B. 1-5) if (!isNaN(start) && !isNaN(end)) { if (start < min || end > max || start > end) return false; for (let j = start; j <= end; j += step) { if (j < min || j > max) return false; } continue; } // ueberpruefe Wildcards (z.B. '*') if (part === '*') { continue; } // ueberpruefe Schritte (z.B. '*/5') if (part.startsWith('*/')) { if (step < 1 || step > max) return false; continue; } // ueberpruefe Kombinationen (z.B. '1,2,3') if (part.includes(',')) { const values = part.split(','); for (const value of values) { if (isNaN(value) || value < min || value > max) return false; } continue; } return false; // Ungueltiges Feldformat } return true; // Das Feld ist gueltig }