// Beschreibung des Scriptes im Forum:
// Dies ist ein Alarmanlagenscript mit erweiterter Funktion. Der Ansatz unterscheidet sich von dem anderer Loesungen.
// Vorgehensweise ist zunaechst die Raumsituation zu beschreiben. Als naechstes werden dann ueber alle Raeume Sichten gelegt, die als drittes zu Scenarios zusammengefuegt werden
// Das macht die Alarmanlage sehr flexibel und doch sind nur wenige Einstellungen im taeglichen Betrieb motwendig (bei richtiger Einstellung)
// Autor Looxer01 06.05.2025 Version 1.0 (initiale Version) - Detaillierte Beschreibung im forum: https://forum.iobroker.net/topic/80967/vorlage-alarmanlage-mit-erweiterten-funktionen
//-----------------------------------------------------------------------------------------------------
//Einstellungen -- Einstellungen mit "BITTE ANPASSEN"-Vermerk muessen durchgearbeitet werden, damit das Script funktional ist
//-----------------------------------------------------------------------------------------------------
//PFADE---OPTIONAL------------------------------------------------------------------------------------------------------------------------------
// Beschreibung der Pfade // brauchen i.d. Regel nicht angepasst zu werden - koennen aber auf userdata umgestellt werden
const path = "javascript.0.Alarm.";
//const path = "0_userdata.0.Alarm."; // alternativ zum obigen javascript pfad
// Basis-Pfad fuer die Controls und status (objektliste)
const controlsPath = path + 'Controls.'; // Empfehlung - nicht aendern
const statusPath = path + 'Status.'; // Empfehlung - nicht aendern
//DEBUGGING----OPTIONAL-----------------------------------------------------------------------------------------------------------------------------
const debugLevel = 1 ; // Empfehlung = 1
const SystemLog = false; // schreib das Sytemprotokoll in ein externes log (sollte normalerweise deaktviert sein nur bei Problemen verwenden)
const PathSystemLog = "/opt/iobroker/log/Alarmscript.csv"; // Pfad und Dateiname des externen Logs
//const PathSMLog = "/iobroker/log/Alarmscript.csv"; // Pfad des externen logs fuer Windows/ iobroker ist der angenommene iobroker home-pfad
//EXTERNES PROKOLL SCHREIBEN IN CSV DATEI ----OPTIONAL-----------------------------------------------------------------------------------------
const ProkollExtern = false
const PathProkollExtern = "/opt/iobroker/log/AlarmMeldungen.csv"; // Pfad und Dateiname des externen Logs
// const PathProkollExtern = "/iobroker/log/AlarmMeldungen.csv"; // Pfad fuer Windows/ iobroker ist der angenommene iobroker home-pfad
//BESCHREIIUNG DER RAEUMLICHEN SITUATION-----BITTE ANPASSEN---------------------------------------------------------------------------------------------------------------------------
// hier ist eine Anpassung an die raeumlichen Gegebenheiten notwendig
// nicht benoetigte Zeilen mit Geraetetypen (Rauch, Wasser etc) koennen geloescht weden
// Achung die Eingaben sind alle case-Sensitiv
// hier eine komplette Liste der implementierten Felder // Fuer Erweiterungen muessen Tabellen raumDefinition, FilterDefinition, ScenarioDefinition und Alarmtypes ensprechend angepasst werden
/* Musterraum: (Dieser Name ist frei vergebbar)
{ Type: "innen", Position: "unten", (Typen koennen frei definiert werden)
Gueltige GeraeteTypen sind: Bewegung: [], Fenster: [], Tueren: [], Rollladen: [], Glasbruch: [], Wasser: [], }, (hier werden Datenpunkte zugeordnet)
*/
const raumDefinition = {
Wohnzimmer: { Type: "innen", Position: "oben",
Fenster: [],
Tueren: ['hm-rpc.1.00000000000000.1.STATE'], // Terrassentuere
},
Kueche: { Type: "innen", Position: "oben",
Fenster: ['hm-rpc.1.00000000000000.1.STATE','hm-rpc.1.00000000000000.1.STATE'], // Fenstersensor Kueche links und rechts
},
Elternbad: { Type: "innen", Position: "oben",
Fenster: ['hm-rpc.1.00000000000000.1.STATE'], // Fenstersensor Elternbad
},
Schlafzimmer: { Type: "innen", Position: "oben",
Fenster: ['hm-rpc.1.00000000000000.1.STATE'], // Fenstersensor Schlafzimmer
},
GaesteWC: { Type: "innen", Position: "oben",
Fenster: ['hm-rpc.1.00000000000000.1.STATE'],
},
Flur_Oben: { Type: "innen", Position: "oben",
Bewegung: [],
Fenster: ['hm-rpc.1.00000000000000.1.STATE'], // Fenstersensor Flur oben
Tueren: ['hm-rpc.1.00000000000000.1.LOCK_STATE'] // Keymatic
},
Flur_Unten: { Type: "innen", Position: "unten",
Bewegung: [],
},
KiZi1: { Type: "innen", Position: "unten",
Fenster: ['hm-rpc.1.00000000000000.1.STATE','hm-rpc.1.00000000000000.1.STATE','hm-rpc.1.00000000000000.1.STATE'], // Fenstersensor KiZi Nord links rechts UG
},
KiZi2: { Type: "innen", Position: "unten",
Fenster: ['hm-rpc.1.00000000000000.1.STATE','hm-rpc.1.00000000000000.1.STATE'], // Fenstersensor KiZi2 links rechts UG
},
Kinderbad: { Type: "innen", Position: "unten",
Fenster: ['hm-rpc.1.00000000000000.1.STATE'], // Fenstersensor Kinderbad
Bewegung: ['hm-rpc.1.00000000000000.1.MOTION'],
},
Hobbyraum: { Type: "innen", Position: "unten",
Bewegung: ['hm-rpc.1.00000000000000.1.MOTION'],
Tueren: ['hm-rpc.1.00000000000000.1.STATE'],
},
Schwimmbad: { Type: "innen", Position: "unten",
Bewegung: ['hm-rpc.1.00000000000000.1.MOTION'],
Tueren: ['hm-rpc.1.00000000000000.1.STATE'],
},
Vorkeller: { Type: "innen", Position: "unten",
Bewegung: [],
},
Keller: { Type: "innen", Position: "unten",
Wasser: ['hm-rpc.1.00000000000000.1.ALARMSTATE'],
Rauch: ['hm-rpc.1.00000000000000.1.SMOKE_DETECTOR_ALARM_STATUS'], // Status 0 = Idle / Status 1 = PRIMARY_ALARM
},
Garage: { Type: "aussen", Position: "oben",
Bewegung: [],
Tueren: ['hm-rpc.1.00000000000000.1.MOTION'], // bei false ist geoeffnet
},
Garagenhaus: { Type: "aussen", Position: "oben",
Bewegung: ['hm-rpc.1.00000000000000.1.MOTION', 'hm-rpc.1.00000000000000.1.MOTION'], // Melder Garagenhaus
},
Loggia: { Type: "aussen", Position: "unten",
Bewegung: [],
Rollladen: ['hm-rpc.0.HEQ0402562.1.LEVEL'], // Rolladen Loggia
},
Aussen_Norden: { Type: "aussen", Position: "aussen",
Bewegung: ['hm-rpc.0.MEQ1000000.1.MOTION'], // Melder Aussen Nordseite
}
};
//DFINITION DER FILTER DIE AUF DIE RAEUMLICHE SITUATION GELEGT WERDEN KOENNEN----FILTER WERDEN SCENARIOS ZUGEORDNET------BITTE ANPASSEN----------------------------------------------------------------------------------------------------------------------
// Eigene Scenarien koennen erstellt werden. RaumTypeen RaumPositionen, GeraeteTypen und raeume wirken wie filter auf die Raumdefinitionen
// die Inhalte der Filter sind frei waehlbar : raumPosition: z. B. oben, unten oder ErsterStock,ZweiterStock,DritterStock etc.
// Bei Eingabe von [] werden alle selektiert also z.B. raumTyp: [] = alle innen und aussen liegenden Raeume
// raumType,raumPosition,gerateTypen,raeume sind schliessen alle entsprechenden Datenpunkte ein. Anschliessend koennen einzelne Datenpunkte aber wieder ausgeschlossen werden
// moegliche keys sind raumTyp,raumPosition,geraeteTypen,raeume,Datenpunkte
// ein vorangestelltes minus (-) bedeutet den Ausschluss dieses Keys. z.B. raeume '-Wohnzimmer' = Der Raum Wohnzimmer wird aus dem zugeordneten Scenario ausgeschlossen - oder Datenpukte '-hm-rpc.1.00000000000000.1.ALARMSTATE'
// ein vorgestelltes doppelminus (--) bewirkt, dass das jeweilige Element (z.B. Raum Wohnzimmer ueber alle Scenarien ausgeschlossen wird)
let FilterDefinition = {
Schlafen: {
raumTyp: ['innen'], // Raumtypen (z. B. innen, aussen) als Array oder keine Angabe = alle
raumPosition: [], // Positionen (z. B. oben, unten oder ErsterStock,ZweiterStock,DritterStock) als Array oder keine Angabe = alle
geraetetypen: [], // Geraetegruppen (z. B. Bewegung, Fenster, etc.) oder keine Angabe = alle
raeume: [], // Einzelraeume bei keiner Angabe werden alle Raeume verwendet
Datenpunkte: ['-hm-rpc.1.00000000000000.1.STATE','-hm-rpc.1.00000000000000.1.STATE'] // Schlafzimmerfenster und Elternbadfenster ausgeschlossen (Lueftung)
},
Abwesend: {
raumTyp: ['innen'],
raumPosition: [],
geraetetypen: [],
raeume: [],
Datenpunkte: []
},
HochRisiko: {
raumTyp: [],
raumPosition: [],
geraetetypen: [],
raeume: ['Kinderbad','Schwimmbad'], // nur diese Raeume sollen ausgewaehlt werden, da diese am hoechsten Einbruchsgefaehrdet sind
Datenpunkte: []
},
Garage: {
raumTyp: ['aussen'],
raumPosition: [],
geraetetypen: [],
raeume: ['Garage','Garagenhaus'],
Datenpunkte: []
},
Gaeste: {
raumTyp: ['innen'],
raumPosition: [],
geraetetypen: [],
raeume: ['--KiZi1','--KiZi2','--Kinderbad'], // bei doppelminus werden diese raeume global entfernt, auch wenn andere Scenarios/Filter diese einschliessen
Datenpunkte: []
},
Haustiere: {
raumTyp: [],
raumPosition: [],
geraetetypen: [],
raeume: ['-Aussen_Norden'],
Datenpunkte: ['-hm-rpc.1.00000000000000.1.MOTION'], // Melder Hobbyraum
},
Urlaub: {
raumTyp: ['innen'],
raumPosition: [],
geraetetypen: [],
raeume: [],
Datenpunkte: []
},
Wasser: {
geraetetypen: ['Wasser'],
},
Rauch: {
geraetetypen: ['Rauch'],
},
};
//DEFINTION DER SCENARIEN----BITTE ANPASSEN-----------------------------------------------------------------------------------------------------------------------------
// Zuordnung von Filter zu Scenarien sowie Alerts zu Scenarios (Sirenen etc)
// Scenario-Struktur muss in den keys Scenario01 - Scenario99 enthalten - andere Definitionen werden ignoriert
// Scenarien koennen in den Datenpunkten z.B. ueber VIS aktiviert oder deaktiviert werden // Aktivierung mehrerer Scenarien ist moeglich
// Die Namen der Scenarios koennen frei vergeben werden.
// Filter sind in FilterDefinition definiert. Es koennen mehrere Filter eingetragen werden
// Bei einem Alarmevent wird das passende Scenario welches die Alarme steuert (Alert)gesucht. Wenn ein Datenpunkt in mehreren Scenarios vorkommt, wird das Scenario mit der hoechsten Prio selektiert
// Alerts steuern im Falle eines Alarms die AlertAusgabeDevices an und/oder die Gruppenschaltungen. Mehrere AusgabeDevices und/oder Gruppenschaltungen koennen eingetragen werden
const scenarioDefinition = {
Scenario01: { name: "Schlafen", Prio: 6, Filter: ['Schlafen'], Alert: ['FlashSchlafzimmerEIN',"FlashStandard1EIN"] },
Scenario02: { name: "Abwesend", Prio: 5, Filter: ["Abwesend"], Alert: ["SireneASIR2EIN","Licht_OG"] },
Scenario03: { name: "Garage", Prio: 10, Filter: ["Garage"], Alert: ['FlashSchlafzimmerEIN'] },
Scenario04: { name: "Urlaub", Prio: 5, Filter: ['Urlaub'], Alert: ["SireneASIR1EIN","SireneASIR2EIN"] },
Scenario05: { name: "Haustiere", Prio: 7, Filter: ['Haustiere'], Alert: [] },
Scenario06: { name: "Gaeste", Prio: 4, Filter: ["Gaeste"], Alert: ["FlashSchlafzimmerEIN"] },
Scenario07: { name: "frei", Prio: 8, Filter: [], Alert: [] },
Scenario08: { name: "Stoerung", Prio: 9, Filter: ['Stoerung'], Alert: [] }, // Filter ist bei Stoerung immer = Stoerung
Scenario09: { name: "Gefaehrdet", Prio: 3, Filter: ['HochRisiko'], Alert: ["SireneASIR2EIN","Licht_Hoch_Risiko_Aussen"] },
Scenario10: { name: "Wasser", Prio: 2, Filter: ['Wasser'], Alert: ["SireneASIR2EIN"] },
Scenario11: { name: "Rauch", Prio: 1, Filter: ['Rauch'], Alert: ["SireneASIR1EIN","SireneASIR2EIN"] },
};
//DEFINTION DER AUSGABEGERAETE----BITTE ANPASSEN-----------------------------------------------------------------------------------------------------------------------------
// verfuegbare devices sind: "HMIP-ASiR", "Datenpunkt_Standard" und "Datenpunkt_Flash"- abweichende Geraete muessen durch coding hinzugefuegt werden. Zentrale Routine ist: triggerAusgabe
// es koennen beliebig viele Ausgabe-Keys eingestellt werden. In diesem Beispiel ist ein Ausgabe-Key = "SireneASIR1EIN". Das ist ein frei vergebarer Name
// Funktionen:
// HMIP-ASIR - zustand true schaltet das device ein Zustand false schaltet das device aus.
// Datenpunkt_Standard - schaltet einen beliebigen Datenpunkt fuer die angegebene Dauer und stellt mit dem Rueckstellwert zurueck
// Datenpunkt_Flash - kann z.B. ein Blinken einer Standardlampe bewirken. Frequenz und Haeufigkeit lassen sich eisntellen
// AlertAusgabeDevices koennen mit der STILL-Funktion ausgeschaltet werden
// Bitte beachten "Zustand" bestimmt einfach den Eintrag der fuer den Key relevant ist, entweder zum einschalten (zustand = true) oder ausschalten (zustand = false) Also SireneEin muss Zustand true haben
const AlertAusgabeDevices = {
SireneASIR1EIN:{ Device: "HMIP-ASIR", // Device darf nicht veraendert werden. Die Logik der nachfolgenden Felder wird anhand des Device abgearbeitet - hart verdrahtet
Zustand: true, // true = ein - false = aus // Dieser Zustand wird nicht gesetzt, bedeutet Geraet ein-oder Ausschalten. Der zu setzende Wert wird weiter unten bestimmt
DP: "hm-rpc.1.00000000000000.",
AkustikSelectionDP: "3.ACOUSTIC_ALARM_SELECTION", valueTon: 2,
Duration_UnitDP: "3.DURATION_UNIT", valueUnit: 0, // 0 = sekunde
Duration_ValueDP: "3.DURATION_VALUE", valueDuration: 100,
OpticalSelectionDP: "3.OPTICAL_ALARM_SELECTION" , valueOpt: 1,
Startdelay: 1000,
},
SireneASIR1AUS:{ Device: "HMIP-ASIR", // Device darf nicht veraendert werden. Die Logik der nachfolgenden Felder wird anhand des Device abgearbeitet - hart verdrahtet
Zustand: false,// true = ein - false = aus // Dieser Zustand wird nicht gesetzt, bedeutet Geraet ein-oder Ausschalten. Der zu setzende Wert wird weiter unten bestimmt
DP: "hm-rpc.1.00000000000000.",
AkustikSelectionDP: "3.ACOUSTIC_ALARM_SELECTION", valueTon: 0,
Duration_UnitDP: "3.DURATION_UNIT", valueUnit: 0, // 0 = sekunde
Duration_ValueDP: "3.DURATION_VALUE", valueDuration: 0,
OpticalSelectionDP: "3.OPTICAL_ALARM_SELECTION" , valueOpt: 0,
Startdelay: 1000,
},
SireneASIR2EIN:{ Device: "HMIP-ASIR", // Device darf nicht veraendert werden. Die Logik der nachfolgenden Felder wird anhand des Device abgearbeitet - hart verdrahtet
Zustand: true,// true = ein - false = aus // Dieser Zustand wird nicht gesetzt, bedeutet Geraet ein-oder Ausschalten. Der zu setzende Wert wird weiter unten bestimmt
DP: "hm-rpc.1.00000000000000.",
AkustikSelectionDP: "3.ACOUSTIC_ALARM_SELECTION", valueTon: 2,
Duration_UnitDP: "3.DURATION_UNIT", valueUnit: 0, // 0 = sekunde
Duration_ValueDP: "3.DURATION_VALUE", valueDuration: 100,
OpticalSelectionDP: "3.OPTICAL_ALARM_SELECTION" , valueOpt: 1,
Startdelay: 1000
},
SireneASIR2AUS:{ Device: "HMIP-ASIR", // Device darf nicht veraendert werden. Die Logik der nachfolgenden Felder wird anhand des Device abgearbeitet - hart verdrahtet
Zustand: false,// true = ein - false = aus // Dieser Zustand wird nicht gesetzt, bedeutet Geraet ein-oder Ausschalten. Der zu setzende Wert wird weiter unten bestimmt
DP: "hm-rpc.1.00000000000000.",
AkustikSelectionDP: "3.ACOUSTIC_ALARM_SELECTION", valueTon: 0,
Duration_UnitDP: "3.DURATION_UNIT", valueUnit: 0, // 0 = sekunde
Duration_ValueDP: "3.DURATION_VALUE", valueDuration: 0,
OpticalSelectionDP: "3.OPTICAL_ALARM_SELECTION" , valueOpt: 0,
Startdelay: 1000
},
Standard1Ein:{ Device: "Datenpunkt_Standard", // Device darf nicht veraendert werden. Die Logik der nachfolgenden Felder wird anhand des Device abgearbeitet - hart verdrahtet
Zustand: true, // true = ein - false = aus // Dieser Zustand wird nicht gesetzt, bedeutet Geraet ein-oder Ausschalten. Der zu setzende Wert wird weiter unten bestimmt
DP: "hm-rpc.1.00000000000000.3.STATE", /*Licht WZ Terrassentuere Ausgang:2 STATE*/
Aktivierungswert: true, // true/false 1/100 was auch immer gesetzt werden soll
Rueckstellwert: false, // Rueckstellwert wird gesetzt wenn die Dauer abgelaufen ist. Keine Funktion wenn Dauer = null ist
Dauer: 100000, // in ms - null ohne Auschaltung
Startdelay: 0, // in ms
},
Standard1Aus:{ Device: "Datenpunkt_Standard", // Device darf nicht veraendert werden. Die Logik der nachfolgenden Felder wird anhand des Device abgearbeitet - hart verdrahtet
Zustand: false, // true = ein - false = aus // Dieser Zustand wird nicht gesetzt, bedeutet Geraet ein-oder Ausschalten. Der zu setzende Wert wird weiter unten bestimmt
DP: "hm-rpc.1.00000000000000.3.STATE", /*Licht WZ Terrassentuere Ausgang:2 STATE*/
Aktivierungswert: false, // true/false 1/100 was auch immer gesetzt werden soll
},
FlashStandard1EIN:{ Device: "Datenpunkt_Flash", // Device darf nicht veraendert werden. Die Logik der nachfolgenden Felder wird anhand des Device abgearbeitet - hart verdrahtet
Zustand: true, // true = ein - false = aus // Dieser Zustand wird nicht gesetzt, bedeutet Geraet ein-oder Ausschalten. Der zu setzende Wert wird weiter unten bestimmt
DP: "hm-rpc.1.00000000000000.3.STATE", /*Licht WZ Terrassentuere Ausgang:2 STATE*/
Aktivierungswert: true, // true/false 1/100 was auch immer gesetzt werden soll
Wiederholung: true,
AnzahlWiederholungen: 4,
Frequenz: 1000, // achtung das sind millisekunden
},
FlashStandard1AUS:{ Device: "Datenpunkt_Flash", // Device darf nicht veraendert werden. Die Logik der nachfolgenden Felder wird anhand des Device abgearbeitet - hart verdrahtet
Zustand: false, // true = ein - false = aus // Dieser Zustand wird nicht gesetzt, bedeutet Geraet ein-oder Ausschalten. Der zu setzende Wert wird weiter unten bestimmt
DP: "hm-rpc.1.00000000000000.3.STATE", /*Licht WZ Terrassentuere Ausgang:2 STATE*/
Aktivierungswert: false, // true/false 1/100 was auch immer gesetzt werden soll
Wiederholung: false,
AnzahlWiederholungen: 4,
Frequenz: 1000, // achtung das sind millisekunden
},
FlashSchlafzimmerEIN:{ Device: "Datenpunkt_Flash", // Device darf nicht veraendert werden. Die Logik der nachfolgenden Felder wird anhand des Device abgearbeitet - hart verdrahtet
Zustand: true, // true = ein - false = aus // Dieser Zustand wird nicht gesetzt, bedeutet Geraet ein-oder Ausschalten. Der zu setzende Wert wird weiter unten bestimmt
DP: "hm-rpc.1.00000000000000.3.STATE", /*Licht WZ Terrassentuere Ausgang:2 STATE*/
Aktivierungswert: true, // true/false 1/100 was auch immer gesetzt werden soll
Wiederholung: true,
AnzahlWiederholungen: 4,
Frequenz: 1000, // achtung das sind millisekunden
},
FlashSchlafzimmerAUS:{ Device: "Datenpunkt_Flash", // Device darf nicht veraendert werden. Die Logik der nachfolgenden Felder wird anhand des Device abgearbeitet - hart verdrahtet
Zustand: false, // true = ein - false = aus // Dieser Zustand wird nicht gesetzt, bedeutet Geraet ein-oder Ausschalten. Der zu setzende Wert wird weiter unten bestimmt
DP: "hm-rpc.1.00000000000000.3.STATE", /*Licht WZ Terrassentuere Ausgang:2 STATE*/
Aktivierungswert: false, // true/false oder 1/0 100/0 zulaessig
Wiederholung: false,
AnzahlWiederholungen: 4,
Frequenz: 1000, // achtung das sind millisekunden
},
}
//DEFINTION DER SCHALTGRUPPEN----BITTE ANPASSEN-----------------------------------------------------------------------------------------------------------------------------
// Schaltgruppen koennen frei definiert werden und dann Scenarios zugeordnet werden (Feld: Alert). z.B. Licht einschalten bei Alarm
// dauer null = keine Auschaltzeit / delay und dauer = Zeitangaben in Sekunden / 50 ms = 0.05 sekunden 10 ms = 0.01 sekudnen // 200 ms = 0.2 ms 250 ms = 0.25 sekunden
const Schaltgruppen = {
Licht_OG: [
{ Datenpunkt: 'hm-rpc.1.00000000000000.3.STATE', Wert: true, Delay: 0.1, Dauer: null }, // WZ Licht Terrasse Verzoegerung in sekunden // Dauer in sekunden // null = endlos
{ Datenpunkt: 'hm-rpc.1.00000000000000.4.LEVEL', Wert: 100, Delay: 0.1, Dauer: 600 } // WZ Esszimmertisch Dimmer// Feld Dauer: danach wird auf Ausgangswert zurueckgesetzt
],
Licht_Hoch_Risiko_Aussen: [
{ Datenpunkt: 'hm-rpc.1.00000000000000.1.STATE', Wert: true, Delay: 0.05, Dauer: 600 }, // Licht Kinderbad
{ Datenpunkt: 'hm-rpc.1.00000000000000.1.STATE', Wert: true, Delay: 0.1, Dauer: 600 }, // Licht aussen Nordseite
{ Datenpunkt: 'hm-rpc.1.00000000000000.1.STATE'/*Licht Hobbyraum Status:1 STATE*/, Wert: true, Delay: 150, Dauer: 600 },
{ Datenpunkt: 'hm-rpc.1.00000000000000.10.STATE'/*Kelleraktor 5 Maschinenraum PoolHalle:10 STATE*/, Wert: true, Delay: 0.2, Dauer: 600 },
{ Datenpunkt: 'hm-rpc.1.00000000000000.18.STATE'/*Keller Aktor 1 Aktor Licht Steinterasse:18 STATE*/, Wert: true, Delay: 0.25, Dauer: 600 },
]
};
//NACHRICHTEN VERSENDEN DEFINTION---------------------------------------------------------------------------------------------------------------------------------
// Messaging fuer Alarmsituationen
// fuer alle Spalten mit true werden die Nachrichten ueber den zugeordneten Dienst versendet, vorausgesetzt der Messenge Adapter ist in iobroker installiert/konfiguriert
const services = ['email', 'whatsApp', 'Signal', 'Telegram', 'Pushover', 'Pushsafer', 'Sprache',];
const MessengerScope = {
'Rauch': [true, true, false, false, false, false, false, ],
'Einbruch': [true, false, false, false, false, false, false, ],
'Wasser': [true, true, false, false, false, false, false, ],
'Sabotage': [false, false, false, false, false, false, false, ],
'Stoerung': [false, false, false, false, false, false, false, ],
'Alarm_Scharf': [true, false, false, false, false, false, false, ],
'Alarm_Unscharf': [true, false, false, false, false, false, false, ],
'ScenarioAktiviert': [true, false, false, false, false, false, false, ],
'ScenarioDeaktiviert': [true, false, false, false, false, false, false, ],
'Alarm_Still_Geschaltet': [false, false, false, false, false, false, false, ],
'Fallback': [false, false, false, false, false, false, false, ], // Fallback nicht loeschen. wird genutzt, wenn keine andere Definition gemacht worden ist
}
const MessengerInstanz = [0, 0, 0, 0, 0, 0, 0, ]; // Instanz des Messengers
const TextTypeKurz = [false, true, true, true, true, true, true, ]; // bei true wird der Kurztext gesendet - sonst der Langtext
// email-Einstellungen
let emailAddresse = "Vorname-Nachname@web.de" // Empfaengeraddresse
const Headline = "ioBroker Alarmmeldung"; // Ueberschrift Messages fuer email und Pushsafer
// telegram Einstellungen
const TelegramUser = "";
// Sprachnachricht ueber "sayit" - Sprache wird nur ausgegeben im angegebenen zeitrange wenn zeitvon = zeitbis dann ist die Gueltigkeit = immer bei zeitvon >zeitbis wird der naechste tag fuer zeitbis angenommen
const zeitvon = "00:00"
const zeitbis = "00:00"
//ALARMAKTIVIERUNGSEINSTELLUNG-----OPTIONAL---------------------------------------------------------------------------------------------------------
const AKTIVIERUNGSDELAY_SECONDS = 5; // Wartezeit von Scharfstellung bis der Alarm imn System aktiv ist in Sekunden
//AKTIONEN NACH SCHARFSCHALTUNG/UNSCHARFSCHALTUNG----OPTIONAL----------------------------------------------------------------------------------------------------------
const AktionenNachScharfSchaltung = [] // hier koennen die keys aus der Tabelle der Schaltgruppen eingetragen werden zB. ["Licht_OG","Licht_UG"] also mehrere bei Bedarf
const AktionenNachUnscharfSchaltung = [] // hier koennen die keys aus der Tabelle der Schaltgruppen eingetragen werden zB. ["Licht_OG","Licht_UG"] also mehrere bei Bedarf
// ANWESENHEIT---OPTIONAL----------------------------------------------------------------------------------------------------------------------
// Anwesenheitstracking. Falls eine Reaktion des Alarmscripts bei An/Abwesenheit gewuenscht ist
// Anmerkung: Script Anwesenheitstracking funktioniert in Zusammenhang mit dem Script. Es koennen aber auch andere Tools genutzt werden
const anwesenheit = 'javascript.0.AnwesenheitsTracking.JemandDa' // bitte anpassen falls Anwesenheitstracking genutzt wird
// das hier angegebene scenario wird bei Abwesenheit aktiviert und bei Anwesenheit deaktiviert
// es koennen mehrere Scenarios angegeben werden, ein fuehrendes minus dreht den wert um - aus true wird false (Ausschluss)
const AbwesenheitScenarioAktivierung = ["Scenario02Aktiv"];
//GAESTE Management - OPTIONAL----------------------------------------------------------------------------------------------------------------------
// Die Funktion GAESTE Management wird nur aktiviert, wenn es den Datenpunkt zum Gaeste-Flag gibt
const GaesteFlag = "javascript.0.Steuerungsflags.Gaeste"
// das hier angegebene scenario wird bei Anwesenheit von Gaesten (Uebernachtung) aktiviert und deaktiviert
const GaesteScenarioAktivierung = ["Scenario06Aktiv"];
// DATENPUNKTE DES SCRIPTES----NICHT AENDERN---------------------------------------------------------------------------------------------------------
// Dies sind die Haupt-Statusanzeigen und Steuerungen des Alarmssytems
const DP_Alarmaktivierung = controlsPath + 'AlarmAktivierung' // Empfehlung: nicht aendern - Steuerung - Scharf/Unscharf
const DP_Still = controlsPath + 'Still'; // Empfehlung: nicht aendern - Steuerung - wird der Datenpunkt auf true gesetzt werden laufende Sirenen etc ausgeschaltet
const DP_Einbruchsmeldung = statusPath + 'Einbruchsalarm'; // Empfehlung: nicht aendern - Anzeige und Quittierung
const DP_Wassermeldung = statusPath + 'Wasseralarm'; // Empfehlung: nicht aendern - Anzeige und Quittierung
const DP_GeoeffnetMeldung = statusPath + 'Geoeffnet'; // Empfehlung: nicht aendern - Anzeige
const DP_Rauchmeldung = statusPath + 'Rauchalarm'; // Empfehlung: nicht aendern - Anzeige und Quittierung
const DP_Sabotagemeldung = statusPath + 'Sabotagealarm'; // Empfehlung: nicht aendern - Anzeige und Quittierung
const DP_Stoerungsmeldung = statusPath + 'Stoerungsalarm'; // Empfehlung: nicht aendern - Anzeige
const DP_Raumuebersicht = statusPath + 'Raumuebersicht'; // Empfehlung: nicht aendern - Anzeige
// JSON DATENPUNKTE FUER DIE PROTOKOLLIERUNG----NICHT AENDERN-----------------------------------------------------------------------------------------
// JSON fuer das Logging der alarmmeldungen. Kann in VIS eingebunden werden
const id_JSON_Alarmmeldung_Aktuell = statusPath + "JSON_Alarmmeldung_Aktuell" // Empfehlung: nicht aendern
const id_JSON_Alarmmeldung_Historie = statusPath + "JSON_Alarmmeldung_Historie" // Empfehlung: nicht aendern
const id_Button_Refresh_Historie = controlsPath + "Button_Refresh_Historie" // Empfehlung: nicht aendern
const id_History_VerbleibendeTage = controlsPath + "HistoryVerbleibendeTage" // Empfehlung: nicht aendern
// HTML fuer das Logging der alarmmeldungen. Kann in VIS eingebunden werden
const id_HTML_Alarmmeldung_Aktuell = statusPath + "HTML_Alarmmeldung_Aktuell" // Empfehlung: nicht aendern
const id_HTML_Alarmmeldung_Historie = statusPath + "HTML_Alarmmeldung_Historie" // Empfehlung: nicht aendern
//Wenn aus den JSON Datenpunkte HTML DATENPUNKTE GENERIERT WERDEN SOLL DANN TRUE------OPTONAL-----------------------------------------------------------
// sinnvoll wenn inventwo nicht fuer die visualisierung genutzt werden kann
const UpdateHTML_Datenpunkte = true // bei true werden die Datenpunkte automatisch angelegt und bei false wieder geloescht
//REFESH DER JSON HISTORY - SCHEDULE----OPTONAL----------------------------------------------------------------------------------------------------
//Schedule Zeit fuer refresh der Historie
const ScheduleAktiv = true; // Bei "false" wird der schedule nicht durchlaufen. Manuelles Loeschen kann ueber den Datenpunkt id_Button_Refresh_Historie (Button) moeglich
const scheduleTimeClearSMTexte = "2 0 1 * *"; // am 1. tag des monats um 00:02 morgens sollen alle Alarmmeldungen des Monats geloescht werden id_History_VerbleibendeTage und aktive bleiben erhalten
// const scheduleTimeClearSMTexte = "58 23 * * 0"; // alternative Sonntags um 23:58 Uhr sollen alle Alarmmeldungen der Woche im datenpunkt der Protokoll-Texte geloescht werden
//SERVICEMELDUNGSSCRIPT VERKNUEPFUNG----OPTIONAL---------------------------------------------------------------------------------------------------
// Wer das Servicemeldungsscript nutzt kann damit verbinden und Sabotage oder Unreach als Alarm melden
const SM_CountSabotage = 'javascript.0.ServicemeldungenVol2.Anzahl_SABOTAGE' // bitte Pfad ggf anpassen falls das Servicemeldungsscript genutzt wird
const SM_CountUnreach = 'javascript.0.ServicemeldungenVol2.Anzahl_UNREACH' // bitte Pfad ggf anpassen falls das Servicemeldungsscript genutzt wird
const SM_Meldungen_JSON = 'javascript.0.ServicemeldungenVol2.JSONAktuelleSM' // bitte Pfad ggf anpassen falls das Servicemeldungsscript genutzt wird
// DEFINITION WIE EIN ALARM ERMITTELT WIRD--ANPASSEN FALS ERFORDERLICH------------------------------------------------------------------------------------------------
// die folgende Tabelle definiert die Ausloesungswerte fuer alarm: einfacher fall : true oder 1. Bei vorliegen von true wird ausgeloest
// bei zutreffen nur eines einzigen wertes wird der Zustand des jeweiligen Datenpunktes prinzipiell als alarm verstanden
// zulaessig: == > < >= <= != range exception
// Exceptions sind datenpunkte deren einordnung als Alarmausloeswert nicht einfach abzuleiten ist. Hier koennen feste Werte je Datenpunkt vergeben werden
// Beim Beschleunigungssensor der oft Garagentore ueberwacht wird die Ausloesung mit false signalisiert
// Exceptions werden immer zuerst geprueft - eine Ueberschneidung mit den vorangegangen Werte (z.B. true) kann es also nicht geben
const AlarmAusloesungswerte = [
{ op: '==', wert: true }, // nur true zulaessig - bitte beibehalten
{ op: '>', wert: 0 }, // groesser als 0 - bitte beibehalten
{ range: { min: 1, max: 2 } }, // Beispiel range
{exception: {dpAusnahme:'hm-rpc.1.00000000000000.1.MOTION', wert: false}}, // je Datenpunkt mit ausnahmen, z.B. fuer Garagentorsensor ist false = geoeffnet - Beispiel
{exception: {dpAusnahme:"hm-rpc.1.00000000000000.1.LOCK_STATE", wert: 2}}, // hmip-dld unlocked = 2
];
//DEFINTION DER STATUSBOARDS ELV SH SB8---NUR BEI VERWENDUNG DES STATUSBOARDS----OPTIONAL--------------------------------------------------------------------------------------------------------------------------
// im folgenden wird das Kontrollgeraet beschrieben und dessen Verknuepfung zu den Alarmzustaenden---> NUR FUER Geraete des typs ELV-SH-SB8
// der DP und die Zuordnung der Datenpunkte zu den LEDs koennen geaendert werden
// Es koennen Meldungen ausgeschaltet werden - Alarm an aus - sowie Scenarios ein aus geschaltet werden
const ControlDevices = [ {
DeviceID: "StatusBoard1",
Device: "ELV-SH-SB8",
DP: "hm-rpc.1.00000000000000x.",
LED1: DP_Alarmaktivierung, DP_2ndPart1 : "10.STATE", // Meldungsanzeige und Aktivierung/Deaktivierung erfolgt durch die entsprechenden Datenpunkte
LED2: DP_Einbruchsmeldung, DP_2ndPart2 : "14.STATE", // manuellAenderbar true laesst es zu den Alarm ueber das Board auszuschalten // dann werden auch alle AkustikGeraete Sirenen ausgeschaltet
LED3: DP_GeoeffnetMeldung, DP_2ndPart3 : "18.STATE", // DP und DP_2ndPart entspricht dem genauen Datenpunkt jeder LED
LED4: DP_Sabotagemeldung, DP_2ndPart4 : "22.STATE",
LED5: DP_Stoerungsmeldung, DP_2ndPart5 : "26.STATE",
LED6: DP_Rauchmeldung, DP_2ndPart6 : "30.STATE",
LED7: DP_Wassermeldung, DP_2ndPart7 : "34.STATE", // ausgeschaltet werden aber nicht die Schaltgruppen
LED8: DP_Still, DP_2ndPart8 : "38.STATE", // laufende Alarmgeraete (Sirenen etc) werden ausgeschaltet
},
{
DeviceID: "StatusBoard2",
Device: "ELV-SH-SB8",
DP: "hm-rpc.1.00000000000000x.",
LED1: "Scenario01Aktiv", DP_2ndPart1 : "10.STATE", // Szenarioschaltung fuer StatusBoard2
LED2: "Scenario02Aktiv", DP_2ndPart2 : "14.STATE",
LED3: "Scenario03Aktiv", DP_2ndPart3 : "18.STATE",
LED4: "Scenario03Aktiv", DP_2ndPart4 : "22.STATE",
LED5: "Scenario05Aktiv", DP_2ndPart5 : "26.STATE",
LED6: "Scenario06Aktiv", DP_2ndPart6 : "30.STATE",
LED7: "Scenario07Aktiv", DP_2ndPart7 : "34.STATE", // Szenarioschaltung fuer StatusBoard2
LED8: "Scenario08Aktiv", DP_2ndPart8 : "38.STATE",
}
];
//-----------------------------------------------------------------------------------------------------
// Experten Einstellungen - ALLES OPTIONAL
//-----------------------------------------------------------------------------------------------------
const AnzahlMaximalerScenarien = 12 // entsprechende Anzahl von Datenpunkten wird angelegt
// Texte
const NichtRelevantText = "......"
const MessageAlarmDeactivated = "Alarmanlage Unscharf"
const MessageAlarmActivated = "Alarmanlage Scharf"
const MessageScenarioAenderungAktiv = "Scenario wurde aktiviert"
const MessageScenarioAenderungDeAktiv = "Scenario wurde deaktiviert"
const MessageSTILLAktiviert = "Alarmausloesung mit STILL unterbrochen"
const ServiceMeldungSabotageAutoConfirm = true; // Wenn die Sabotage aufgehoben wird, dann wird die der SabotageDatenpunkt automatisch zurueckgesetzt bei true
const ServiceMMeldungStoerungAutoConfirm = true; // Wenn die Stoerung aufgehoben wird, dann wird die der StoerungsDatenpunkt automatisch zurueckgesetzt bei true
// die folgende Tabelle enthaelt die Alarmtypes, // braucht in der Regel nicht eingestellt zu werden
// alwaysOn heisst, dass ein Alarm ausgeloest wird auch wenn die Anlage unscharf geschaltet ist
// Beim AlarmType Geoeffnet wird der Datenpunkt DP_GeoeffnetMeldung bei oeffnung einer der DeviceTypes auf true gesetzt
const AlarmTypes = [
{ alarmtype: 'Rauch', Datenpunkt: DP_Rauchmeldung, alwaysOn: true, Prio: 1, deviceTypes: ["Rauch"] },
{ alarmtype: 'Einbruch', Datenpunkt: DP_Einbruchsmeldung, alwaysOn: false, Prio: 2, deviceTypes: ["Fenster","Tueren","Bewegung","Glasbruch","Sabotage"] },
{ alarmtype: 'Wasser', Datenpunkt: DP_Wassermeldung, alwaysOn: true, Prio: 3, deviceTypes: ["Wasser"] },
{ alarmtype: 'Stoerung', Datenpunkt: DP_Stoerungsmeldung, alwaysOn: false, Prio: 4, deviceTypes: ["Stoerung"] },
{ alarmtype: 'Geoeffnet',Datenpunkt: DP_GeoeffnetMeldung, alwaysOn: false, Prio: 5, deviceTypes: ["Fenster","Tueren","Rollladen"] }, // Rollladen werden nicht als Einbrauch interpretiert -Falls gewuenscht, dann Einbruch zuordnen];
]
// Struktur nicht aendern
// GeraeteID ID ist der unique identifier, Alarmfeld ist der Wert des Feldes, dass den Alarm ausloest
const StrukturDefinition = [
{ Adapter: 'hm-rpc', GeraeteID: 2, AlarmFeld: 4, nativeType: 3, common_name: 3 }, // die Ziffer ist die Positinierung des Feldes 'hm-rpc.1.00000000000000.0.UPDATE_PENDING_ALARM' 0=Adapter - 2 = ID 4= Feld Alarm / native Type = die ersten Strings bis zur dritten STelle fuer getObject
{ Adapter: 'hmip', GeraeteID: 3, AlarmFeld: 6, nativeType: 'hmip.xinstancex.devices.xidx.info.modelType', common_name: 'hmip.xinstancex.devices.xidx.info.label' }, // Positionierung wie bei Rm-rpc - bei hmip wird native type aber ueber den DP ausgelesen und nicht getObject "xidx" wird dann mit der geraeteiD ersetzt
]
// Umlaut Umwandlung und entfernung PUnkte - kann aber auch erweitert werden
const replacements = { '.': ' ', 'ä': 'ae', 'ü': 'ue', 'ö': 'oe', 'ß': 'ss', 'WZ':'Wohnzimmer'}; // Umwandlung fuer Namen der Geraete (common.name)
// im Folgenden werden die Bedingungen fuer die Protokollierung der History Alarmmeldungen in den JSON Tabellen beschrieben. (Die Aktuellen Meldungen werden immer erzeugt)
// Dabei werden Einbruch, Wasser Rauch immer in der JSON_Aktuell protokolliert
// Alle anderen werden bei true im Feld Protokoll_Schreiben in der Histore protokolliert bzw nicht protokolliert bei false
// null heisst, dass diese Situation nur bei Scharfem Alarm in die Historie geschrieben wird. Bei Prokoll_Schreiben = false aber bei scharfem Alarm nicht
const JSON_Protokollierung = [
{ Typ: "Einbruch", Aktiv: true, Protokoll_Schreiben: true, NurWennAlarmScharf: null }, // wird generell nur protollierrt bei Alarm scharf
{ Typ: "Wasser", Aktiv: true, Protokoll_Schreiben: true, NurWennAlarmScharf: null }, // wird generell nur protollierrt bei Alarm scharf
{ Typ: "Rauch", Aktiv: true, Protokoll_Schreiben: true, NurWennAlarmScharf: null }, // wird generell nur protollierrt bei Alarm scharf
{ Typ: "Sabotage", Aktiv: true, Protokoll_Schreiben: true, NurWennAlarmScharf: null }, // wird generell nur protollierrt bei Alarm scharf
{ Typ: "Stoerung", Aktiv: true, Protokoll_Schreiben: true, NurWennAlarmScharf: null }, // wird generell nur protollierrt bei Alarm scharf
{ Typ: "AlarmAktivierung", Aktiv: true, Protokoll_Schreiben: true, NurWennAlarmScharf: false },
{ Typ: "ScenarioAenderung", Aktiv: true, Protokoll_Schreiben: true, NurWennAlarmScharf: false },
{ Typ: "STILL", Aktiv: true, Protokoll_Schreiben: true, NurWennAlarmScharf: false },
{ Typ: "Einbruch", Aktiv: false, Protokoll_Schreiben: true, NurWennAlarmScharf: false },
{ Typ: "Wasser", Aktiv: false, Protokoll_Schreiben: true, NurWennAlarmScharf: false },
{ Typ: "Rauch", Aktiv: false, Protokoll_Schreiben: true, NurWennAlarmScharf: false },
{ Typ: "Sabotage", Aktiv: false, Protokoll_Schreiben: true, NurWennAlarmScharf: false },
{ Typ: "Stoerung", Aktiv: false, Protokoll_Schreiben: true, NurWennAlarmScharf: false },
{ Typ: "AlarmAktivierung", Aktiv: false, Protokoll_Schreiben: true, NurWennAlarmScharf: false },
{ Typ: "ScenarioAenderung", Aktiv: false, Protokoll_Schreiben: true, NurWennAlarmScharf: false },
{ Typ: "STILL", Aktiv: false, Protokoll_Schreiben: false, NurWennAlarmScharf: false },
];
//DEFINITION VON SPALTENBREITEN UND STYLES FUER DIE NUTZUNG VON HTML DATENPUNKTEN FUER DIE DARSTELLUNG DES PROTOKOLLS----OPTIONAL----------------
//
// Hauptkonfigurationsobjekt fuer die Tabellendarstellung
const HTML_TableWidthsAndStyles = {
// Definition der Spaltenbreiten in CSS-Einheiten
columnWidths: {
datum: '95px', // Breite fuer Datum/Uhrzeit-Spalte
alarmtype: '70px', // Breite fuer Alarmtyp-Spalte
raum: '90px', // Breite fuer Raum-Spalte
deviceType: '80px', // Breite fuer Geraetetyp-Spalte
scenario: '50px', // Breite fuer Szenario-Spalte
deviceName: '150px', // Breite fuer Geraetebezeichnung
deviceId: '50px', // Breite fuer Geraete-ID
message: '210px', // Breite fuer Nachrichtenspalte
quittiert: '95px' // Breite fuer Quittierungsspalte
},
// Visuelle Stileinstellungen fuer die Tabelle
styles: {
// Basistypografie
fontSize: '10px', // Allgemeine Schriftgroesse
// Kopfzeilenstil
headerColor: '#333333', // Hintergrundfarbe der Kopfzeile
headerTextColor: 'white', // Textfarbe der Kopfzeile
// Zeilenstile (Zebra-Pattern)
evenRowColor: '#4e5049', // Hintergrundfarbe gerade Zeilen
oddRowColor: '#333333', // Hintergrundfarbe ungerade Zeilen
evenRowTextColor: 'white', // Textfarbe gerade Zeilen
oddRowTextColor: 'white', // Textfarbe ungerade Zeilen
// Rahmeneinstellungen
borderColor: '#000000', // Farbe der Tabellenraender
borderWidth: '1px', // Staerke der Rahmenlinien
useBorders: true, // Soll Rahmen anzeigen? (true/false)
// Zellenlayout
cellPadding: '5px' // Innenabstand der Tabellenzellen
},
// Texte fuer die Tabellenkopfzeilen
headerTexts: {
datum: 'Datum/Uhrzeit', // ueberschrift Datumsspalte
alarmtype: 'Alarmtyp', // ueberschrift Alarmtyp
raum: 'Raum', // ueberschrift Raum
deviceType: 'Geraetetyp', // ueberschrift Geraetetyp
scenario: 'Szenario', // ueberschrift Szenario
deviceName: 'Geraetebez.', // ueberschrift Geraetebezeichnung (abgekuerzt)
deviceId: 'Geraete-ID', // ueberschrift Geraete-ID
message: 'Nachricht', // ueberschrift Nachrichteninhalt
quittiert: 'Quittiert seit' // ueberschrift Quittierungsstatus
},
// Konfiguration der Spaltensichtbarkeit - ein oder ausblenden
columns: {
datum: { visible: true },
alarmtype: { visible: true },
raum: { visible: true },
deviceType: { visible: true },
scenario: { visible: true },
deviceName: { visible: true },
deviceId: { visible: true },
message: { visible: true },
quittiert: { visible: true }
}
};
// Konfiguration der HTML fuer die Raumuebersicht
const HTML_Raumzuordnungen_WidthAndStyles = {
columnWidths: {
raum: '90px', scenario: '150px', deviceType: '100px',
},
styles: {
fontSize: '10px',
headerColor: '#333333',
headerTextColor: 'white',
evenRowColor: '#4e5049',
oddRowColor: '#333333',
evenRowTextColor: 'white',
oddRowTextColor: 'white',
borderColor: '#000000',
borderWidth: '1px',
useBorders: true,
cellPadding: '5px'
},
headerTexts: {
raum: 'Raum', scenario: 'Szenarien', deviceType: 'Geraetetyp'
},
columns: {raum: { visible: true },scenario: { visible: true }, deviceType: { visible: true }
}
};
//-----------------------------------------------------------------------------------------------------
// Ende Einstellungen
//-----------------------------------------------------------------------------------------------------
// Globale Routinen
LOG(`Routine wird ausgefuehrt`, "Ablauf", "Start", 2);
let MessageSendCollector = [], GeneratedSubscriptions = [] , AktuelleMeldungenJSON = [], HistorischeMeldungenJSON = [];
let AlarmAktivierungsStatus = false;
// Aufbau AlarmScenarios - Das sind die Datenpunkte die in der iobroker objektstuktur angelegt werden
// Scenario01Aktiv = false
// ....
// Scenario10Aktiv = false --etc.
const AlarmScenarios = [
...Array.from({ length: AnzahlMaximalerScenarien }, (_, i) => {
const ScenarioNum = String(i + 1).padStart(2, '0'); // Fuehrende Null hinzufuegen
return { Control: `Scenario${ScenarioNum}Aktiv`, Datenpunkt: path + `Controls.Scenario${ScenarioNum}Aktiv` };
}),
];
// Aufbau ScenarioStatus
// Scenario01: status: false
// Scenario02: status: false - etc.
const ScenarioStatus = Object.fromEntries(
Array.from({ length: AnzahlMaximalerScenarien }, (_, i) => {
const ScenarioNum = String(i + 1).padStart(2, '0'); // Fuehrende Null hinzufuegen
return [`Scenario${ScenarioNum}`, { status: false }];
})
);
let lastGeoeffnetUpdate = 0; // timer fuer Fenster, Tueren, Rolladen
let lastLEDUpdates = {}; // Speichert die letzten Update-Zeiten fuer jede LED im Statusboard falls verwendet
let activationTimeout = null; // Globale Variable zur Speicherung des Timeouts fuer Alarmaktivierung - sorgt dafuer, dass eine laufende Alarmaktivierung abgebrochen werden kann
// Globales Objekt, das die laufenden Intervalle speichert wird fuer die Funktion Execute_Still benoetigt
const blinkingIntervals = {}; // nur fuer Standard_Flash Geraete
CheckForNaNinFilterDefinition(FilterDefinition) // Ueberpruefung ob falsche Eingaben in den FilterDefinitions gemacht wurden (ausserhalb der Hochkomma) evt abbruch
let ScenarioToDPsTable = {}; // Enthaelt die Datenpunkte die subscribed sind und die Scenarios aus denen sie stammen
// innerhalb von Create States werden updateScenario(), CreateSubscriptions() , createAktivierungsSubscription() und ListSystemSubscriptions() aufgerufen
CreateStates()
if(!existsState(DP_Alarmaktivierung)) {
AlarmAktivierungsStatus = false
}else{
AlarmAktivierungsStatus = getState(DP_Alarmaktivierung).val
}
createSubscriptionsfuerControlDevices() // erstellt die subscriptions fuer evt StatusBoards
syncControlDevicesInitial()
//-----------------------------------------------------------------------------------------------------
// Schedule zum Loeschen des Datenpunktwertes der Histore einmal pro Monat oder Woche je nach schedule-Einstellung // die aktiven messages bleien erhaltebn -
// Anzahl der zu verbleibenden Tage des Protokolls kann in der VIS eingestellt werden - Datenpunkt: id_History_VerbleibendeTage
//-----------------------------------------------------------------------------------------------------
if(ScheduleAktiv ) {
schedule(scheduleTimeClearSMTexte, function() {
LOG(`Schedule zum refresh der Historie wurde ausgeloest - Historie is nun geloescht`, "Schedule", "ScheduleAktiv", 0);
Refresh_History()
});
}
//-----------------------------------------------------------------------------------------------------
// Function subscribeToAllScenarioDPs Die Funktion abonniert alle Datenpunkte aus Tabelle Raumdefinition in Verbindung mit den Scenarios
// die aktivierten Scenarion aus Tabelle scenarioDefinition sind ausgangspunkt
// ScenarioToDPsTable = {Standard_Schlafen: [datenpunkt1, datenpunkt2 etc]}
//-----------------------------------------------------------------------------------------------------
function subscribeToAllScenarioDPs() {
LOG(`Routine wird ausgefuehrt`, "Ablauf", "subscribeToAllScenarioDPs", 2);
unsubscribeAll();
ScenarioToDPsTable = {};
// Globale Ausschlusslisten fuer alle Kategorien
const globalExclusions = {
raumTyp: new Set(),
raumPosition: new Set(),
geraetetypen: new Set(),
raeume: new Set(),
datenpunkte: new Set()
};
// Phase 1: Globale Ausschluesse sammeln
for (const scenarioKey in scenarioDefinition) {
if (!ScenarioStatus[scenarioKey]?.status) continue;
const scenario = scenarioDefinition[scenarioKey];
const Filter = scenario.Filter || [];
for (const Filterbedingungen of Filter) {
const filter = FilterDefinition[Filterbedingungen];
if (!filter) continue;
// Alle Filterkategorien durchgehen
for (const category in globalExclusions) {
if (filter[category]) {
filter[category].forEach(item => {
if (item.startsWith('--')) {
globalExclusions[category].add(item.slice(2));
}
});
}
}
}
}
// Phase 2: Normale Verarbeitung mit Beruecksichtigung der globalen Ausschluesse
for (const scenarioKey in scenarioDefinition) {
if (!ScenarioStatus[scenarioKey]?.status) continue;
const scenario = scenarioDefinition[scenarioKey];
const Filter = scenario.Filter || [];
if (!ScenarioToDPsTable[scenarioKey]) ScenarioToDPsTable[scenarioKey] = [];
for (const Filterbedingungen of Filter) {
const filter = FilterDefinition[Filterbedingungen];
if (!filter) continue;
const getFilterInfo = (arr, globalExcludeSet) => {
return {
include: (arr || []).filter(e => !e.startsWith('-')),
exclude: (arr || []).filter(e => e.startsWith('-') && !e.startsWith('--')).map(e => e.slice(1)),
globalExclude: globalExcludeSet
};
};
const filters = {
raumTyp: getFilterInfo(filter.raumTyp, globalExclusions.raumTyp),
raumPosition: getFilterInfo(filter.raumPosition, globalExclusions.raumPosition),
geraetetypen: getFilterInfo(filter.geraetetypen, globalExclusions.geraetetypen),
raeume: getFilterInfo(filter.raeume, globalExclusions.raeume),
datenpunkte: getFilterInfo(filter.Datenpunkte, globalExclusions.datenpunkte)
};
// Raeume filtern mit allen Ausschluessen
const relevanteRaeume = Object.keys(raumDefinition).filter(raumName => {
const raum = raumDefinition[raumName];
// Globale Raumausschluesse
if (filters.raeume.globalExclude.has(raumName)) return false;
// Normale Filter
if (filters.raeume.exclude.includes(raumName)) return false;
if (filters.raeume.include.length > 0 && !filters.raeume.include.includes(raumName)) return false;
// RaumTyp Filter
if (filters.raumTyp.globalExclude.has(raum.Type)) return false;
if (filters.raumTyp.include.length > 0 && !filters.raumTyp.include.includes(raum.Type)) return false;
if (filters.raumTyp.exclude.includes(raum.Type)) return false;
// Position Filter
if (filters.raumPosition.globalExclude.has(raum.Position)) return false;
if (filters.raumPosition.include.length > 0 && !filters.raumPosition.include.includes(raum.Position)) return false;
if (filters.raumPosition.exclude.includes(raum.Position)) return false;
return true;
});
// Datenpunkte sammeln - NEUE LOGIK
const datenpunkteInSzenario = new Set();
// 1. Nur Datenpunkte aus relevanten Raeumen hinzufuegen, wenn keine expliziten Datenpunkte angegeben sind
if (filters.datenpunkte.include.length === 0) {
for (const raumName of relevanteRaeume) {
const raum = raumDefinition[raumName];
for (const typ in raum) {
if (typ === "Type" || typ === "Position") continue;
// Geraetetyp Filter
if (filters.geraetetypen.globalExclude.has(typ)) continue;
if (filters.geraetetypen.include.length > 0 && !filters.geraetetypen.include.includes(typ)) continue;
if (filters.geraetetypen.exclude.includes(typ)) continue;
const dpList = raum[typ];
if (!Array.isArray(dpList)) continue;
for (const dp of dpList) {
if (!filters.datenpunkte.globalExclude.has(dp)) {
datenpunkteInSzenario.add(dp);
}
}
}
}
}
// 2. Explizit angegebene Datenpunkte hinzufuegen (ohne Minus)
filters.datenpunkte.include.forEach(dp => {
if (!filters.datenpunkte.globalExclude.has(dp)) {
datenpunkteInSzenario.add(dp);
}
});
// 3. Explizit ausgeschlossene Datenpunkte entfernen (mit einfachem Minus)
filters.datenpunkte.exclude.forEach(dp => {
datenpunkteInSzenario.delete(dp);
});
// Ergebnis zum Szenario hinzufuegen
ScenarioToDPsTable[scenarioKey] = Array.from(datenpunkteInSzenario);
}
}
// Phase 3: Alle Datenpunkte aus raumDefinition fuer "NichtAktiv" sammeln
ScenarioToDPsTable["NichtAktiv"] = [];
const allDpsFromRooms = new Set();
// Alle Datenpunkte aus raumDefinition sammeln
for (const raumName in raumDefinition) {
const raum = raumDefinition[raumName];
for (const typ in raum) {
if (typ === "Type" || typ === "Position") continue;
const dpList = raum[typ];
if (Array.isArray(dpList)) {
dpList.forEach(dp => allDpsFromRooms.add(dp));
}
}
}
// Alle bereits in aktiven Szenarien enthaltenen Datenpunkte sammeln
const activeScenarioDps = new Set();
for (const scenario in ScenarioToDPsTable) {
if (scenario !== "NichtAktiv") {
ScenarioToDPsTable[scenario].forEach(dp => activeScenarioDps.add(dp));
}
}
// Nur die Datenpunkte hinzufuegen, die nicht in aktiven Szenarien sind
allDpsFromRooms.forEach(dp => {
if (!activeScenarioDps.has(dp)) {
ScenarioToDPsTable["NichtAktiv"].push(dp);
}
});
// Subscriptions setzen
GeneratedSubscriptions = [];
for (const scenario in ScenarioToDPsTable) {
for (const dpId of ScenarioToDPsTable[scenario]) {
if (!GeneratedSubscriptions.includes(dpId)) {
on({ id: dpId, change: "ne" }, (obj) => {
LOG(`Aenderung erkannt fuer ${dpId}: Neuer Wert = ${obj.state.val}`, "Ablauf", "subscribeToAllScenarioDPs", 2);
if (scenario !== "NichtAktiv") {
DetermineAlarm(dpId, obj.state.val);
} else {
LOG(`Statusaenderung bei inaktivem Datenpunkt: ${dpId} = ${obj.state.val}`, "Info", "subscribeToAllScenarioDPs", 3);
}
});
GeneratedSubscriptions.push(dpId);
}
}
}
// Statistik
LOG("Subscriptions wurden generiert fuer folgende Datenpunkte:", "Ablauf", "subscribeToAllScenarioDPs", 3);
for (const scenario in ScenarioToDPsTable) {
const scenarioName = scenarioDefinition[scenario]?.name || scenario;
LOG(`\nSzenario ${scenario} (${scenarioName}):`, "Ablauf", "subscribeToAllScenarioDPs", 3);
ScenarioToDPsTable[scenario].forEach(dpid => {
const commonName = ExtractMetaData(dpid, "COMMONNAME") || 'Kein Common-Name';
LOG(`- ${dpid} (${commonName})`, "Ablauf", "subscribeToAllScenarioDPs", 3);
});
}
const totalDPs = Object.values(ScenarioToDPsTable).flat().length;
const uniqueDPs = new Set(Object.values(ScenarioToDPsTable).flat()).size;
LOG(`\nGesamt: ${totalDPs} gefilterte Datenpunkte (${uniqueDPs} eindeutige Datenpunkte werden abonniert)`, "Ablauf", "subscribeToAllScenarioDPs", 1);
let scenarioNames = Object.keys(ScenarioToDPsTable);
LOG(`Anzahl Szenarien: ${scenarioNames.length}`, "Ergebnis", "subscribeToAllScenarioDPs", 1);
scenarioNames.forEach(scenario => {
let dps = ScenarioToDPsTable[scenario];
LOG(`- ${scenario}: ${dps.length} Datenpunkte`, "Ergebnis", "subscribeToAllScenarioDPs", 1);
});
setState(DP_Raumuebersicht,renderScenarioRoomDeviceHTML())
}
//-----------------------------------------------------------------------------------------------------
// Funktion fuer die Subscription der Datenpunkte Alarmaktivierung, Einbruch, Wassermeldung etc.
//-----------------------------------------------------------------------------------------------------
function createAktivierungsSubscription() {
LOG(`Routine wird ausgefuehrt`, "Ablauf", "createAktivierungsSubscription", 2);
for (const {Control, Datenpunkt } of AlarmScenarios) {
if (Control.startsWith('Scenario') && Control.endsWith('Aktiv')) {
on({ id: Datenpunkt, change: 'any' }, function (obj) {
if(obj.state.val != obj.oldState.val) {
LOG(`Datenpunkt fuer ${Datenpunkt} auf ${obj.state.val} gesetzt`, "Ablauf", "createAktivierungsSubscription", 1);
ScenarioAenderung(Control, obj.state.val); // Aufruf der Funktion ScenarioAenderung, wenn sich einer der ScenarioxxAktiv Datenpunkte aendert
setLEDState(Control,obj.state);
let jsonString = getState(id_JSON_Alarmmeldung_Historie).val || '[]'; // Lade den aktuellen JSON-String des Historie-Datenpunkts
HistorischeMeldungenJSON = JSON.parse(jsonString);
const ScenarioNummer = Control.substring(0, 10); // Ergibt z.B. "Scenario01"
const ScenarioName = scenarioDefinition[ScenarioNummer]?.name || "Unbekanntes Szenario";
let messagekurz, messagelang
if(obj.state.val) {
messagekurz = MessageScenarioAenderungAktiv
messagelang = func_get_datum() + " " + MessageScenarioAenderungAktiv
}else{
messagekurz = MessageScenarioAenderungDeAktiv+ " " + ScenarioNummer
messagelang = func_get_datum() + " " + MessageScenarioAenderungDeAktiv
}
if(obj.state.val && pruefeProtokollierung("ScenarioAenderung", true)) {
HistorischeMeldungenJSON = CreateJsonAlarmmeldung(HistorischeMeldungenJSON, func_get_datum(), messagekurz, func_get_datum(),"","","",ScenarioNummer,ScenarioName)
}
if(!obj.state.val && pruefeProtokollierung("ScenarioAenderung", false)) {
HistorischeMeldungenJSON = CreateJsonAlarmmeldung(HistorischeMeldungenJSON, func_get_datum(), messagekurz, func_get_datum(),"","","",ScenarioNummer,ScenarioName)
}
jsonString = JSON.stringify(HistorischeMeldungenJSON); // Stringify das Json um fuer die speicherung vorzubereiten
setState(id_JSON_Alarmmeldung_Historie, jsonString); // Aktuelle Alarmmeldung wird in JSON-Datenpunkt als String gespeichert
generateHtmlTable(HistorischeMeldungenJSON,"Hist")
writeProkollExtern( messagekurz, func_get_datum() ,"", "","", ScenarioNummer, ScenarioName)
messagekurz = messagekurz + " " + ScenarioNummer
messagelang = messagelang + " " + ScenarioNummer + " " + ScenarioName
if( obj.state.val) {
addMessageToCollector("ScenarioAktiviert", messagekurz, messagelang); // zur Abarbeitung der Tabelle MessengerScope
}else{
addMessageToCollector("ScenarioDeaktiviert", messagekurz, messagelang); // zur Abarbeitung der Tabelle MessengerScope
}
sendMessage();
}
});
}
}
on({ id: DP_Alarmaktivierung, change: 'any' }, function (obj) {
if(obj.state.val != obj.oldState.val) {
setLEDState(DP_Alarmaktivierung, obj.state.val);
LOG(`Datenpunkt fuer ${DP_Alarmaktivierung} auf ${obj.state.val} gesetzt`, "Ablauf", "createAktivierungsSubscription", 1);
if (activationTimeout) { // Vorhandenen Timeout abbrechen, falls vorhanden
clearTimeout(activationTimeout);
activationTimeout = null;
LOG(`Bestehender Aktivierungs-Timeout wurde abgebrochen`, "Ablauf", "createAktivierungsSubscription", 2);
}
if (obj.state.val) { // Nur bei Aktivierung (value = true) einen neuen Timeout setzen
activationTimeout = setTimeout(() => {
// Pruefen, ob der Alarm immer noch aktiviert werden soll
const currentState = getState(DP_Alarmaktivierung).val;
if (currentState) {
Alarmaktivierung(true);
} else {
LOG(`Alarmaktivierung wurde waehrend des Delays abgebrochen`, "Ablauf", "createAktivierungsSubscription", 2);
}
activationTimeout = null; // Timeout-Handle zuruecksetzen
}, AKTIVIERUNGSDELAY_SECONDS * 1000);
} else {
Alarmaktivierung(false); // Sofortige Deaktivierung
}
}
});
on({ id: DP_Einbruchsmeldung, change: 'any' }, function (obj) {
if(obj.state.val != obj.oldState.val) {
LOG(`Datenpunkt fuer ${DP_Einbruchsmeldung} auf ${obj.state.val} gesetzt`, "Ablauf", "createAktivierungsSubscription", 2);
setLEDState(DP_Einbruchsmeldung,obj.state.val);
if(!obj.state.val){
let jsonString = getState(id_JSON_Alarmmeldung_Aktuell).val || '[]'; // Lade den aktuellen JSON-String des Historie-Datenpunkts
AktuelleMeldungenJSON = JSON.parse(jsonString);
AktuelleMeldungenJSON = RemoveAlarmtypeFromJSON(AktuelleMeldungenJSON, "Einbruch")
jsonString = JSON.stringify(AktuelleMeldungenJSON);
setState(id_JSON_Alarmmeldung_Aktuell, jsonString); // Aktuelle Alarmmeldung wird in JSON-Datenpunkt als String gespeichert
generateHtmlTable(AktuelleMeldungenJSON,"Akt")
if(pruefeProtokollierung("Einbruch", false)) {
JSON_Alarmmeldungen_History_Aktualisierung(AktuelleMeldungenJSON,"Einbruch"); // Stateupdate ist Teil der aufgerufenen Funktion
}
}
}
});
on({ id: DP_Wassermeldung, change: 'any' }, function (obj) {
if(obj.state.val != obj.oldState.val) {
LOG(`Datenpunkt fuer ${DP_Wassermeldung} auf ${obj.state.val} gesetzt`, "Ablauf", "createAktivierungsSubscription", 2);
setLEDState(DP_Wassermeldung,obj.state.val);
if(!obj.state.val){
let jsonString = getState(id_JSON_Alarmmeldung_Aktuell).val || '[]'; // Lade den aktuellen JSON-String des Historie-Datenpunkts
AktuelleMeldungenJSON = JSON.parse(jsonString);
AktuelleMeldungenJSON = RemoveAlarmtypeFromJSON(AktuelleMeldungenJSON, "Wasser")
jsonString = JSON.stringify(AktuelleMeldungenJSON);
setState(id_JSON_Alarmmeldung_Aktuell, jsonString); // Aktuelle Alarmmeldung wird in JSON-Datenpunkt als String gespeichert
generateHtmlTable(AktuelleMeldungenJSON,"Akt")
if(pruefeProtokollierung("Wasser", false)) {
JSON_Alarmmeldungen_History_Aktualisierung(AktuelleMeldungenJSON,"Wasser"); // Stateupdate ist Teil der aufgerufenen Funktion
}
}
}
});
on({ id: DP_Rauchmeldung, change: 'any' }, function (obj) {
if(obj.state.val != obj.oldState.val) {
LOG(`Datenpunkt fuer ${DP_Rauchmeldung} auf ${obj.state.val} gesetzt`, "Ablauf", "createAktivierungsSubscription", 2);
setLEDState(DP_Rauchmeldung,obj.state.val);
if(!obj.state.val){
let jsonString = getState(id_JSON_Alarmmeldung_Aktuell).val || '[]'; // Lade den aktuellen JSON-String des Historie-Datenpunkts
AktuelleMeldungenJSON = JSON.parse(jsonString);
AktuelleMeldungenJSON = RemoveAlarmtypeFromJSON(AktuelleMeldungenJSON, "Rauch")
jsonString = JSON.stringify(AktuelleMeldungenJSON);
setState(id_JSON_Alarmmeldung_Aktuell, jsonString); // Aktuelle Alarmmeldung wird in JSON-Datenpunkt als String gespeichert
generateHtmlTable(AktuelleMeldungenJSON,"Akt")
if(pruefeProtokollierung("Rauch", false)) {
JSON_Alarmmeldungen_History_Aktualisierung(AktuelleMeldungenJSON,"Rauch"); // Stateupdate ist Teil der aufgerufenen Funktion
}
}
}
});
on({ id: DP_GeoeffnetMeldung, change: 'any' }, function (obj) {
if(obj.state.val != obj.oldState.val) {
LOG(`Datenpunkt fuer ${DP_GeoeffnetMeldung} auf ${obj.state.val} gesetzt`, "Ablauf", "createAktivierungsSubscription", 2);
setLEDState(DP_GeoeffnetMeldung, obj.state.val);
}
});
on({ id: DP_Sabotagemeldung, change: 'any' }, function (obj) {
if(obj.state.val != obj.oldState.val) {
LOG(`Datenpunkt fuer ${DP_Sabotagemeldung} auf ${obj.state.val} gesetzt`, "Ablauf", "createAktivierungsSubscription", 2);
setLEDState(DP_Sabotagemeldung,obj.state.val);
if(!obj.state.val){
if(pruefeProtokollierung("Sabotage", false)) {
let jsonString = getState(id_JSON_Alarmmeldung_Aktuell).val || '[]'; // Lade den aktuellen JSON-String des Historie-Datenpunkts
AktuelleMeldungenJSON = JSON.parse(jsonString);
AktuelleMeldungenJSON = RemoveAlarmtypeFromJSON(AktuelleMeldungenJSON, "Sabotage")
jsonString = JSON.stringify(AktuelleMeldungenJSON);
setState(id_JSON_Alarmmeldung_Aktuell, jsonString); // Aktuelle Alarmmeldung wird in JSON-Datenpunkt als String gespeichert
generateHtmlTable(AktuelleMeldungenJSON,"Akt")
JSON_Alarmmeldungen_History_Aktualisierung(AktuelleMeldungenJSON,"Sabotage"); // Stateupdate ist Teil der aufgerufenen Funktion
}
}
}
});
on({ id: DP_Stoerungsmeldung, change: 'any' }, function (obj) {
if(obj.state.val != obj.oldState.val) {
LOG(`Datenpunkt fuer ${DP_Stoerungsmeldung} auf ${obj.state.val} gesetzt`, "Ablauf", "createAktivierungsSubscription", 2);
setLEDState(DP_Stoerungsmeldung,obj.state.val);
if(!obj.state.val){
if(pruefeProtokollierung("Stoerung", false)) {
let jsonString = getState(id_JSON_Alarmmeldung_Aktuell).val || '[]'; // Lade den aktuellen JSON-String des Historie-Datenpunkts
AktuelleMeldungenJSON = JSON.parse(jsonString);
AktuelleMeldungenJSON = RemoveAlarmtypeFromJSON(AktuelleMeldungenJSON, "Stoerung")
jsonString = JSON.stringify(AktuelleMeldungenJSON);
setState(id_JSON_Alarmmeldung_Aktuell, jsonString); // Aktuelle Alarmmeldung wird in JSON-Datenpunkt als String gespeichert
generateHtmlTable(AktuelleMeldungenJSON,"Akt")
JSON_Alarmmeldungen_History_Aktualisierung(AktuelleMeldungenJSON,"Stoerung"); // Stateupdate ist Teil der aufgerufenen Funktion
}
}
}
});
on({ id: DP_Still, change: 'any' }, function (obj) {
if(obj.state.val != obj.oldState.val) {
LOG(`Datenpunkt fuer ${DP_Still} auf ${obj.state.val} gesetzt`, "Ablauf", "createAktivierungsSubscription", 2);
setLEDState(DP_Still, obj.state.val);
// Immer nach 2 Sekunden zuruecksetzen
if(obj.state.val === true) {
setTimeout(function() {
setState(DP_Still, false);
LOG(`Datenpunkt fuer ${DP_Still} automatisch zurueckgesetzt`, "Ablauf", "createAktivierungsSubscription", 2);
}, 2000);
}
// Nur ausfuehren wenn Bedingungen erfuellt sind: status true und eine der 3 meldungen true
if(obj.state.val === true && (getState(DP_Einbruchsmeldung).val || getState(DP_Rauchmeldung).val || getState(DP_Wassermeldung).val)) {
Execute_STILL();
if(pruefeProtokollierung("STILL", true)) {
let jsonString = getState(id_JSON_Alarmmeldung_Historie).val || '[]';
HistorischeMeldungenJSON = JSON.parse(jsonString);
HistorischeMeldungenJSON = CreateJsonAlarmmeldung(HistorischeMeldungenJSON, func_get_datum(), MessageSTILLAktiviert, func_get_datum() );
jsonString = JSON.stringify(HistorischeMeldungenJSON);
setState(id_JSON_Alarmmeldung_Historie, jsonString);
generateHtmlTable(HistorischeMeldungenJSON,"Hist")
}
writeProkollExtern( MessageSTILLAktiviert, func_get_datum() )
const messagekurz = MessageSTILLAktiviert;
const messagelang = func_get_datum() + " " + MessageSTILLAktiviert;
addMessageToCollector("Alarm_Still_Geschaltet", messagekurz, messagelang);
sendMessage();
}
}
});
// Subscription fuer Sabotage - Datenpunkt aus dem Servicemeldungsscript
if (existsState(SM_CountSabotage)) {
on({ id: SM_CountSabotage, change: "ne" }, (obj) => {
LOG(`Aenderung erkannt fuer ${SM_CountSabotage}: Neuer Wert = ${obj.state.val}`, "Ablauf", "createAktivierungsSubscription", 2);
const Sabotage_DP = Zustandsermittlung("Sabotage")
if(Sabotage_DP) {
DetermineAlarm(Sabotage_DP, true,true, false); // 1.true = status 2. = Sabotage 3. Stoerung
}
});
} else {
LOG(`Datenpunkt ${SM_CountSabotage} existiert nicht und wird nicht abonniert`, "Ablauf", "createAktivierungsSubscription", 3);
}
// Subscription fuer Unreach - Datenpunkt aus dem Servicemeldungsscript
if (existsState(SM_CountUnreach)) {
on({ id: SM_CountUnreach, change: "ne" }, (obj) => {
LOG(`Aenderung erkannt fuer ${SM_CountUnreach}: Neuer Wert = ${obj.state.val}`, "Ablauf", "createAktivierungsSubscription", 2);
const Stoerung_DP = Zustandsermittlung("Stoerung");
if(Stoerung_DP) {
DetermineAlarm(Stoerung_DP, true, false, true); // 1.true = status 2. = Sabotage 3. Stoerung
}
});
} else {
LOG(`Datenpunkt ${SM_CountUnreach} existiert nicht und wird nicht abonniert`, "Ablauf", "createAktivierungsSubscription", 3);
}
// Subscription fuer An/Abwesenheit - Datenpunkt optional
if(existsState(anwesenheit)) {
on({ id: anwesenheit, change: 'any' }, function (obj) {
if(obj.state.val != obj.oldState.val) {
if (!obj.state.val && existsState(anwesenheit)) {
AutoScenarioActivation(AbwesenheitScenarioAktivierung,true)
LOG(`Anwesenheit wurde festgestellt und folgendes Scenario deaktiviert: ${AbwesenheitScenarioAktivierung.join(', ')}`, "Ablauf", "createAktivierungsSubscription", 2);
}
if (obj.state.val && existsState(anwesenheit)) {
AutoScenarioActivation(AbwesenheitScenarioAktivierung,false)
LOG(`Anwesenheit wurde festgestellt und folgendes Scenario deaktiviert: ${AbwesenheitScenarioAktivierung.join(', ')}`, "Ablauf", "createAktivierungsSubscription", 2);
}
}
});
}
// Subscription fuer Gaeste Scenario Aktivierung - Datenpunkt optional
if(existsState(GaesteFlag)) {
on({ id: GaesteFlag, change: 'any' }, function (obj) {
if(obj.state.val != obj.oldState.val) {
if (obj.state.val) {
AutoScenarioActivation(GaesteScenarioAktivierung,true)
LOG(`Gaeste-Flag wurde gesetzt - Scenario fuer Gaeste wurde aktiviert: ${GaesteScenarioAktivierung.join(', ')}`, "Ablauf", "createAktivierungsSubscription", 2);
}
if (!obj.state.val) {
AutoScenarioActivation(GaesteScenarioAktivierung,false)
LOG(`Gaeste-Flag wurde zurueckgesetzt - Scenario fuer Gaeste wurde deaktiviert: ${GaesteScenarioAktivierung.join(', ')}`, "Ablauf", "createAktivierungsSubscription", 2);
}
}
});
}
// Subscription fuer Loeschen der History
if(existsState(id_Button_Refresh_Historie)) {
on({ id: id_Button_Refresh_Historie, change: 'any' }, function (obj) {
if (obj.state.val) {
Refresh_History()
}
});
}
}
//-----------------------------------------------------------------------------------------------------
// Function createSubscriptionsfuerControlDevices fuer die StatusBoards (ELV-SB-SH8)
//-----------------------------------------------------------------------------------------------------
function createSubscriptionsfuerControlDevices() {
LOG(`Routine wird ausgefuehrt`, "Ablauf", "createSubscriptionsfuerControlDevices", 2);
ControlDevices.forEach(function(device) {
const dp = device.DP;
Object.keys(device).forEach(function(ledKey) {
if (ledKey.startsWith("LED")) {
const ledIndex = parseInt(ledKey.replace("LED", ""));
const stateIndex = device[`DP_2ndPart${ledIndex}`];
if (!stateIndex) return;
const dataPoint = dp + stateIndex;
if(existsState(dataPoint)) {
on({ id: dataPoint, change: 'any' }, function (obj) {
if (obj.state.val != obj.oldState.val) {
LOG(`subscription aufgerufen fuer '${dataPoint}' `, "Alauf", "createSubscriptionsfuerControlDevices", 3);
setStatefromBoard(dataPoint, obj.state.val, device.DeviceID);
}
});
}
}
});
});
}
//-----------------------------------------------------------------------------------------------------
// Function Zustandsermittlung Initialsierung Status geoeffnet Sabotage und Stoerung
//-----------------------------------------------------------------------------------------------------
function Zustandsermittlung(Scope) {
LOG(`Starte Zustandsermittlung mit Scope: ${Scope}`, "Ablauf", "Zustandsermittlung", 2);
const LocalstartTimePerformance = Date.now();
let result = false;
let now = Date.now();
if (Scope === "init" || Scope === "Sabotage") {
if (existsState(SM_CountSabotage)) {
result = Servicemeldungen("Sabotage");
LOG(`Sabotage Ergebnis aus Routine Servicemeldungen ist ${result} `,"Ablauf",2);
if(Scope === "Sabotage") return result
}
}
if (Scope === "init" || Scope === "Stoerung") {
if (existsState(SM_CountUnreach)) {
result = Servicemeldungen("Stoerung");
LOG(`Stoerung Ergebnis aus Routine Servicemeldungen ist ${result} `,"Ablauf",2);
if(Scope === "Stoerung") return result
}
}
// jetzt noch die Ueberpruefung fuer Zustand geoeffnet
now = Date.now();
result = false;
if (now - lastGeoeffnetUpdate < 200) {
LOG("Update zu schnell - ueberspringe", "Ablauf", "Zustandsermittlung", 3);
return getState(DP_GeoeffnetMeldung).val;
}
if (Scope === "init") { //- logik ueberpruefe alle dpids des alarmtypes geoeffnet und checke zusstand je dpid
// Bei init alle Geoeffnet-DeviceTypes pruefen
const geoeffnetType = AlarmTypes.find(a => a.alarmtype === "Geoeffnet"); // DeviceTypes, die dem AlarmType "geoeffnet" in Tabelle AlarmTypes zugeordnet sind
for (const deviceType of geoeffnetType.deviceTypes) {
const currentCheck = DetermineGeoeffnetZustandJeDeviceType(deviceType);
result = result || currentCheck; // Wenn ein DeviceType true liefert, wird result true
LOG(`Init-Zustandspruefung fuer ${deviceType} ergab ${currentCheck} (Gesamtresult: ${result})`, "Ablauf", "Zustandsermittlung", 3);
if (result) break; // Wenn result true ist, koennen wir vorzeitig abbrechen
}
} else {
// Pruefen ob Scope zu Geoeffnet gehoert
const geoeffnetType = AlarmTypes.find(a => a.alarmtype === "Geoeffnet");
if (geoeffnetType.deviceTypes.includes(Scope)) {
result = DetermineGeoeffnetZustandJeDeviceType(Scope);
LOG(`${Scope} Zustandspruefung ergab: ${result}`, "Ablauf", "Zustandsermittlung", 3);
} else {
LOG(`${Scope} gehoert nicht zu Zustand Geoeffnet - keine Pruefung durchgefuehrt`, "Ablauf", "Zustandsermittlung", 3);
return false;
}
}
setState(DP_GeoeffnetMeldung, result);
lastGeoeffnetUpdate = now;
LOG(`Geoeffnet-Status aktualisiert: ${result}`, "Ablauf", "Zustandsermittlung", 2);
LOG(`Zeitverbrauch fuer Routine Zustandsermittlung: ${Date.now() - LocalstartTimePerformance} ms`, "Ablauf", "Zustandsermittlung", 1);
return result;
}
// -------------------------------------------------------------------------------------------------
// Function Servicemeldungen Liest die Sabotage/Unreach Messages from Servicmeldungsscript und analysiert diese
//-----------------------------------------------------------------------------------------------------
function Servicemeldungen(scope) {
LOG(`Starte Servicemeldungen-Auswertung fuer Scope: ${scope}`, "INFO", "Servicemeldungen", 2);
try {
const jsonString = getState(SM_Meldungen_JSON).val || '[]';
const meldungen = JSON.parse(jsonString);
LOG(`Verarbeite ${meldungen.length} Meldungen`, "Ablauf", "Servicemeldungen", 3);
const targetType = scope === "Sabotage" ? "SABOTAGE_ALARM" : "UNREACH_ALARM";
const meldung = meldungen.find(entry =>
entry.meldungsart === targetType && entry.GeraeteId
);
if (meldung) {
LOG(`${scope} erkannt: ${JSON.stringify(meldung)}`, "Ablauf", "Servicemeldungen", 2);
const dpid = findDatenpunktByGeraeteID(meldung.GeraeteId); // Suche nach Datenpunkt in raumDefinition
if (dpid) {
LOG(`${scope}-Datenpunkt gefunden: ${dpid}`, "Ablauf", "Servicemeldungen", 2);
setState(scope === "Sabotage" ? DP_Sabotagemeldung : DP_Stoerungsmeldung, true);
return dpid;
} else { // Die Sabotagemeldung findet sich nicht in Tablle RaumDefinition - also gibt es keine relevanten Servicemeldungen
if(targetType === "SABOTAGE_ALARM" && ServiceMeldungSabotageAutoConfirm) {
setState(DP_Sabotagemeldung,false)
}
if(targetType === "UNREACH_ALARM" && ServiceMMeldungStoerungAutoConfirm) {
setState(DP_Stoerungsmeldung,false)
}
LOG(`kein passender Datenpunkt fuer GeraeteID ${meldung.GeraeteId} gefunden`, "WARN", "Servicemeldungen", 2);
return null;
}
} else {
LOG(`keine relevanten Servicemeldungen sind derzeit aktiv`, "Ablauf", "Servicemeldungen", 3);
if(targetType === "SABOTAGE_ALARM" && ServiceMeldungSabotageAutoConfirm) {
setState(DP_Sabotagemeldung,false)
}
if(targetType === "UNREACH_ALARM" && ServiceMMeldungStoerungAutoConfirm) {
setState(DP_Stoerungsmeldung,false)
}
return null;
}
} catch (e) {
LOG(`Fehler in Servicemeldungen: ${e.message}`, "ERROR", "Servicemeldungen", 0,"error");
return false;
}
}
//-----------------------------------------------------------------------------------------------------
// Function findDatenpunktByGeraeteID Sucht in der Raumdefinition nach dem vollstaendigen Datenpunkt anhand der GeraeteID
// wird fuer Servicemeldungen Sabotage und Unreach benoetigt
//-----------------------------------------------------------------------------------------------------
function findDatenpunktByGeraeteID(geraeteID) {
for (const raum of Object.values(raumDefinition)) { // 1. Durchsuche alle Raeume (Object.values ist schneller als for-in bei grossen Objekten)
for (const datenpunkte of Object.values(raum).filter(Array.isArray)) { // 2. Filtere nur Array-Eigenschaften (Fenster, Tueren etc.) und ignoriere Metadaten
for (const dp of datenpunkte) { // 3. Durchsuche alle Datenpunkte dieser Kategorie
if (dp.includes(geraeteID)) { // 4. Case-sensitive Pruefung ob die GeraeteID im Datenpunkt enthalten ist
return dp; // Fruehes Return bei Fund
}
}
}
}
return null; // 5. Rueckgabe null wenn nichts gefunden wurde
}
//-----------------------------------------------------------------------------------------------------
// Function DetermineGeoeffnetZustandJeDeviceType Hilfsfunktion fuer Zustandsermittlung Initialsierung Status fuer Fenster / Rollladen etc. Allgemeine Device-Pruefung
//-----------------------------------------------------------------------------------------------------
function DetermineGeoeffnetZustandJeDeviceType(deviceType) {
let result = false;
const relevantDps = new Set();
// 1. Alle relevanten Datenpunkte aus ScenarioToDPsTable extrahieren
for (const [scenarioName, dpList] of Object.entries(ScenarioToDPsTable)) {
// Nur aktive Szenarien beruecksichtigen
// if (ScenarioStatus[scenarioName]?.status === true) {
dpList.forEach(dp => relevantDps.add(dp));
// }
}
const filteredDps = new Set();
// 2. Vergleiche mit raumDefinition und filtere relevante Datenpunkte
for (const raumName in raumDefinition) {
const devices = raumDefinition[raumName]?.[deviceType];
if (!devices?.length) continue;
devices.forEach(device => {
if (relevantDps.has(device)) {
filteredDps.add(device);
}
});
}
// 3. Den Status der relevanten Datenpunkte pruefen
for (const device of filteredDps) {
const status = getState(device).val;
LOG(`${deviceType} ${device}: ${status}`, "Ablauf", "DetermineGeoeffnetZustandJeDeviceType", 3);
if (AlarmStateAuswertung(device, status)) {
LOG(`Geoeffnetzustand erkannt: ${deviceType} ${device}`, "Ablauf", "DetermineGeoeffnetZustandJeDeviceType", 2);
result = true;
break; // Frueher abbrechen, wenn ein Zustand erkannt wird
}
}
return result;
}
//-----------------------------------------------------------------------------------------------------
// Function DetermineAlarm Routine zur Notwendigkeit der alarmausloesung - Liegt ein Alarm vor ?
//-----------------------------------------------------------------------------------------------------
function DetermineAlarm(dpid, status, Sabotage = false, Stoerung = false) {
LOG(`Routine wird ausgefuehrt fuer Datenpunkt ${dpid} mit Status ${status}`, "Ablauf", "DetermineAlarm", 2);
const LocalstartTimePerformance = Date.now();
// 1. Initiale Werte
let DeviceType, AlarmState, AlarmType, Scenario_Ausloeser
// 2. Geraetetyp und Alarmstatus ermitteln
if (Stoerung) {
dpid = Zustandsermittlung("Stoerung");
DeviceType = getDeviceTypeForDataPoint(dpid);
Scenario_Ausloeser = Object.values(scenarioDefinition).find(s => s.Filter.includes("Stoerung"))?.name || null
}
else if (Sabotage) {
dpid = Zustandsermittlung("Sabotage");
DeviceType = getDeviceTypeForDataPoint(dpid);
AlarmState = AlarmStateAuswertung(dpid, status);
Scenario_Ausloeser = DetermineScenario(dpid);
}
else {
DeviceType = getDeviceTypeForDataPoint(dpid);
AlarmState = AlarmStateAuswertung(dpid, status);
Scenario_Ausloeser = DetermineScenario(dpid);
// Geoeffnet-Status nur fuer relevante Geraetetypen pruefen
const geoeffnetType = AlarmTypes.find(a => a.alarmtype === "Geoeffnet");
Scenario_Ausloeser = DetermineScenario(dpid);
if (geoeffnetType?.deviceTypes.includes(DeviceType)) {
const currentState = AlarmState ? true : checkAnyGeoeffnetDevice();
setState(DP_GeoeffnetMeldung, currentState);
LOG(`${DeviceType} Zustandspruefung ergab: ${currentState}`, "Ablauf", "DetermineAlarm", 3);
}
}
// 3. Hilfsfunktion fuer Geoeffnet-Status-Check
function checkAnyGeoeffnetDevice() {
const geoeffnetType = AlarmTypes.find(a => a.alarmtype === "Geoeffnet");
return geoeffnetType?.deviceTypes.some(deviceType => DetermineGeoeffnetZustandJeDeviceType(deviceType) ) ?? false;
}
// 4. Fruehe Abbruchbedingungen pruefen
if (!AlarmState && !Stoerung) { // Stoerungen haben keinen AlarmState
LOG(`Kein Alarm fuer ${dpid} - Status ist ${AlarmState}`, "Ablauf", "DetermineAlarm", 2);
return;
}
if (!Scenario_Ausloeser || !ScenarioStatus[Scenario_Ausloeser]?.status) {
LOG(`Kein aktives Szenario fuer ${dpid} (DeviceType: ${DeviceType})`, "Ablauf", "DetermineAlarm", 2);
return;
}
// 5. Alarmverarbeitung
const alarmType = Stoerung ? "Stoerung" : DeriveAlarmType(DeviceType);
LOG(`Alarmtyp ermittelt: ${alarmType}`, "Ablauf", "DetermineAlarm", 2);
const DeviceAlarm = AlarmTypes.find(alarm => alarm.alarmtype === alarmType);
if (!DeviceAlarm || !existsState(DeviceAlarm.Datenpunkt)) {
LOG(`Ungueltiger Alarmtyp/Pfad: ${alarmType} / ${DeviceAlarm?.Datenpunkt}`, "Fehler", "DetermineAlarm", 0, "error");
return;
}
// 6. Alarmausloesung
if (AlarmAktivierungsStatus || DeviceAlarm.alwaysOn) {
setState(DeviceAlarm.Datenpunkt, true);
Execute_Alarm(Scenario_Ausloeser, dpid, DeviceType, alarmType, Sabotage);
LOG(`Ausfuehrungszeit: ${Date.now() - LocalstartTimePerformance}ms`, "Ergebnis", "DetermineAlarm", 1);
} else {
LOG(`Alarm deaktiviert fuer ${dpid} (kein AlwaysOn-Geraet)`, "Ablauf", "DetermineAlarm", 2);
}
}
//-----------------------------------------------------------------------------------------------------
// Function DetermineScenario Routine ermittelt das Scenario mit der hoechsten prioritaet
//-----------------------------------------------------------------------------------------------------
function DetermineScenario(dpid) {
LOG(`Bestimme Szenario fuer Datenpunkt ${dpid}`, "Ablauf", "DetermineScenario", 2);
const matchingScenarios = []; // Finde alle Szenarien, die diesen Datenpunkt enthalten
for (const scenario in ScenarioToDPsTable) {
if (ScenarioToDPsTable[scenario].includes(dpid)) {
matchingScenarios.push(scenario);
}
}
// Liste aller Szenarien in denen der DP vorkommt
LOG(`Datenpunkt ${dpid} ist in folgenden Szenarien enthalten: ${matchingScenarios.join(', ')}`,"Ablauf", "DetermineScenario", 2);
if (matchingScenarios.length === 0) { // kann eigentlich nicht vorkommen
LOG(`Kein passendes Szenario fuer Datenpunkt ${dpid} gefunden`, "Ablauf", "DetermineScenario", 2);
return null;
}
matchingScenarios.sort((a, b) => { // Sortiere nach Prioritaet und dann nach Szenario-Nummer
const prioA = scenarioDefinition[a]?.Prio || 10;
const prioB = scenarioDefinition[b]?.Prio || 10;
if (prioA !== prioB) {
return prioA - prioB; // Niedrigere Prio (hoehere Prioritaet) zuerst
} else {
// Bei gleicher Prioritaet nach Szenario-Nummer sortieren
const numA = parseInt(a.replace('Scenario', ''));
const numB = parseInt(b.replace('Scenario', ''));
return numA - numB;
}
});
// Sortierte Liste der Szenarien nach Priorisierung
LOG(`Priorisierte Szenarienreihenfolge fuer ${dpid}: ${matchingScenarios.join(' -> ')}`, "Ablauf", "DetermineScenario", 2);
const selectedScenario = matchingScenarios[0];
LOG(`Ausgewaehltes Szenario fuer ${dpid}: ${selectedScenario}`, "Ablauf", "DetermineScenario", 2);
return selectedScenario;
}
//-----------------------------------------------------------------------------------------------------
// Function AlarmStateAuswertung Routine Feststellung ob der State des dpid ein alarmstate ist
// es wird ein true zurueckgegeben sobald auch nur ein einziger wert zutrifft
//-----------------------------------------------------------------------------------------------------
function AlarmStateAuswertung(dpid, status) {
const ausnahme = AlarmAusloesungswerte.find(regel => // Zuerst nach Ausnahmen in AlarmAusloesungswerte suchen
regel.exception && regel.exception.dpAusnahme === dpid
);
if (ausnahme) {
LOG(`Ausnahme-Regel gefunden fuer ${dpid}: ${JSON.stringify(ausnahme.exception)}`, "Ablauf", "AlarmStateAuswertung", 3);
return status === ausnahme.exception.wert;
}
// Falls keine Ausnahme gefunden wurde, normale Auswertung durchfuehren
return AlarmAusloesungswerte.some(regel => {
if (regel.exception) return false; // Ueberspringe Regeln mit exception (da diese schon oben behandelt wurden)
switch (true) {
case regel.op === '==': return status === regel.wert;
case regel.op === '!=': return status !== regel.wert;
case regel.op === '>': return status > regel.wert;
case regel.op === '>=': return status >= regel.wert;
case regel.op === '<': return status < regel.wert;
case regel.op === '<=': return status <= regel.wert;
case regel.range !== undefined:
return status >= regel.range.min && status <= regel.range.max;
default: return false;
}
});
}
//-----------------------------------------------------------------------------------------------------
// Function DeriveAlarmType Routine zur Findung des Alarmtypes
// Devicetypes Fenster, Melder etc. Alarmtypen sind Einbruch, Wasser, Rauch, Sabotage, Stoerung
// diese routine muss evt erweitert werden fuer Sabotage und unreach
//-----------------------------------------------------------------------------------------------------
function DeriveAlarmType(DeviceType) {
const sortedAlarmTypes = [...AlarmTypes].sort((a, b) => a.Prio - b.Prio); // 1. AlarmTypes nach Prioritaet sortieren (niedrigste Prio-Zahl zuerst)
const foundEntry = sortedAlarmTypes.find(entry => // 2. Nach dem ersten passenden Eintrag suchen
entry.deviceTypes.includes(DeviceType)
);
if (foundEntry) { // 3. Gefundenen Alarmtyp zurueckgeben oder Default-Wert
return foundEntry.alarmtype;
}
LOG(`Unbekannter DeviceType "${DeviceType}" – Standardwert "Einbruch" verwendet`, "Warn", "DeriveAlarmType", 2,"warn"); // sollte eigentlich nicht vorkommen
return "Einbruch"; // 4. Fallback fuer unbekannte DeviceTypes
}
//-----------------------------------------------------------------------------------------------------
// Function Execute_Alarm Routine zur Abarbeitung von alarmen - Hauptroutine
//-----------------------------------------------------------------------------------------------------
function Execute_Alarm(Scenario, dpid,deviceType,alarmType, Sabotage) {
LOG(`nachdem ein Alarm erkannt wurde, wird jetzt der Alarm vorbereitet`, "Ablauf", "Execute_Alarm", 2);
// Sicherstellen, dass das ausgewaehlte Szenario in der Szenario-Definition existiert
const scenarioDetails = scenarioDefinition[Scenario];
if (!scenarioDetails) {
LOG(`Das Szenario ${Scenario} wurde nicht in der scenarioDefinition gefunden`, "Fehler", "Execute_Alarm", 0);
return;
}
const AlarmType = alarmType
const AlertType = scenarioDetails.Alert;
const Raum = GetRoomsForDataPoint(dpid)
let commonName = ExtractMetaData(dpid, "COMMONNAME");
commonName = ReplaceString(commonName);
let messagekurz, messagelang;
const GeraeteID = ExtractMetaData(dpid, "GERAETEID");
if (!Sabotage) { // keine Sabotage
messagekurz = `${AlarmType} ${deviceType} ${commonName}`;
messagelang = `${func_get_datum()} ${AlarmType} in Raum ${Raum.join(', ')} ${deviceType} ${commonName} Selektiertes Scenario: ${Scenario}`;
}else{ // Sabotage
messagekurz = `Sabotage ${deviceType} ${commonName}`;
messagelang = `${func_get_datum()} Sabotage/Einbruch in Raum ${Raum.join(', ')} ${deviceType} ${commonName} Selektiertes Scenario: ${Scenario}`;
}
LOG(`Lange Message ist ${messagelang}. Die Message wird jetzt verarbeitet.`, "Ablauf", "Execute_Alarm", 2);
LOG(`Kurze Message ist ${messagekurz}. Die Message wird jetzt verarbeitet.`, "Ablauf", "Execute_Alarm", 2);
addMessageToCollector(AlarmType, messagekurz, messagelang); // zur Abarbeitung der Tabelle MessengerScope
sendMessage();
Execute_Alerts(Scenario); // zur Abbarbeitung der Tabelle AlertAusgabeDevices
Execute_GruppenSchaltungen(Scenario); // zur Abbarbeitung der Tabelle Gruppenschaltungen
let jsonString = getState(id_JSON_Alarmmeldung_Aktuell).val || '[]'; // Lade den aktuellen JSON-String des Historie-Datenpunkts
AktuelleMeldungenJSON = JSON.parse(jsonString);
/* existingJson = [], datum_seit, MessageKurz Status, Alarmtype, raum, deviceType, Scenario,common_name, GeraeteId, id) */
AktuelleMeldungenJSON = CreateJsonAlarmmeldung(AktuelleMeldungenJSON, func_get_datum(), messagekurz, "aktiv", AlarmType, Raum , deviceType, Scenario, commonName, GeraeteID, dpid )
jsonString = JSON.stringify(AktuelleMeldungenJSON);
setState(id_JSON_Alarmmeldung_Aktuell, jsonString); // Aktuelle Alarmmeldung wird in JSON-Datenpunkt als String gespeichert
generateHtmlTable(AktuelleMeldungenJSON,"Akt")
if(pruefeProtokollierung(AlarmType, true)) {
jsonString = getState(id_JSON_Alarmmeldung_Historie).val || '[]'; // Lade den aktuellen JSON-String des Historie-Datenpunkts
HistorischeMeldungenJSON = JSON.parse(jsonString);
/* existingJson = [], datum_seit, MessageKurz Alarmtype, raum, deviceType, Scenario,common_name, GeraeteId, id) */
HistorischeMeldungenJSON = CreateJsonAlarmmeldung(HistorischeMeldungenJSON, func_get_datum(), messagekurz, "aktiv", AlarmType, Raum , deviceType, Scenario, commonName, GeraeteID, dpid )
jsonString = JSON.stringify(HistorischeMeldungenJSON); // Stringify das Json um fuer die speicherung vorzubereiten
setState(id_JSON_Alarmmeldung_Historie, jsonString); // Aktuelle Alarmmeldung wird in JSON-Datenpunkt als String gespeichert
generateHtmlTable(HistorischeMeldungenJSON,"Hist")
}
writeProkollExtern( messagekurz, "aktiv",AlarmType, Raum.join(', ') , deviceType, Scenario, commonName, GeraeteID, dpid)
}
//-----------------------------------------------------------------------------------------------------
// Funktion setLEDState zum Setzen des LED-Zustands - Hier wird nach setzen der Datenpunkte der LED Status nachgezogen
//-----------------------------------------------------------------------------------------------------
function setLEDState(DP_Change, value) {
LOG(`LED-State wird gesetzt: DP_Change=${DP_Change}, Wert=${value}`, "DEBUG", "setLEDState", 2);
ControlDevices.forEach(device => {
let updated = false;
for (let i = 1; i <= 8; i++) {
const ledKey = `LED${i}`;
const dpKey = `DP_2ndPart${i}`;
const ledDataPoint = device[ledKey];
const dpSuffix = device[dpKey];
if (!ledDataPoint || !dpSuffix) continue;
if (DP_Change === ledDataPoint) {
const fullDP = device.DP + dpSuffix;
const updateKey = `${device.DeviceID}_${fullDP}`; // Kombiniere DeviceID und fullDP fuer eindeutigen Key
const now = Date.now();
// Pruefe, ob fuer diese LED in den letzten 200ms bereits ein Update stattfand
if (lastLEDUpdates[updateKey] && (now - lastLEDUpdates[updateKey] < 500)) {
LOG(`Update fuer ${fullDP} (Device: ${device.DeviceID}) unterdrueckt - zuletzt aktualisiert vor ${now - lastLEDUpdates[updateKey]}ms`, "DEBUG", "setLEDState", 3);
updated = true; // Markiere als aktualisiert, um keine Fehlermeldung zu generieren
continue;
}
if (existsState(fullDP)) {
setState(fullDP, value);
lastLEDUpdates[updateKey] = now; // Speichere den Zeitpunkt des Updates
updated = true;
} else {
LOG(`Ziel-Datenpunkt ${fullDP} existiert nicht`, "Warn", "setLEDState", 3);
}
}
}
if (!updated) {
LOG(`Kein entsprechender LED-Datenpunkt fuer ${DP_Change} in Geraet ${device.DeviceID} gefunden.`, "Fehler", "setLEDState", 3);
}
});
}
//-----------------------------------------------------------------------------------------------------
// Funktion setStatefromBoard zum Setzen des Zustands basierend auf dem ausgeloesten Datenpunkt
// Die Funktion setzt die Datenpunkte der LED und der zugeordneten Datenpunkte
// Wenn aber manuellAenderbar = false ist, dann wird die LED zurueckgesetzt durch den Timer wird eine endlosloop verhindert
//-----------------------------------------------------------------------------------------------------
function setStatefromBoard(triggerDataPoint, value, deviceID) {
LOG(`Routine aufgerufen mit: triggerDataPoint=${triggerDataPoint}, value=${value}, deviceID=${deviceID}`, "Ablauf", "setStatefromBoard", 2);
const device = ControlDevices.find(dev => dev.DeviceID === deviceID);
if (!device) {
LOG(`Kein Geraet mit DeviceID '${deviceID}' gefunden`, "ERROR", "setStatefromBoard", 0);
return;
}
for (let i = 1; i <= 8; i++) {
const dpPart = device[`DP_2ndPart${i}`];
if (!dpPart) {
LOG(`DP_2ndPart${i} nicht definiert fuer ${deviceID}`, "WARN", "setStatefromBoard", 1);
continue;
}
const fullTriggerDP = device.DP + dpPart;
if (triggerDataPoint !== fullTriggerDP) continue;
const ledKey = `LED${i}`;
const ledDataPoint = device[ledKey];
if (!ledDataPoint) {
LOG(`Kein LED Datenpunkt unter '${ledKey}' gefunden`, "ERROR", "setStatefromBoard", 0);
return;
}
const dataPoint = ledDataPoint.includes("Scenario") && ledDataPoint.endsWith("Aktiv")
? path + "Controls." + ledDataPoint
: ledDataPoint;
setState(dataPoint, value);
return; // Passende LED gefunden und verarbeitet → fertig
}
LOG(`Kein passender DP_2ndPart fuer ${triggerDataPoint} in Geraet ${deviceID} gefunden`, "WARN", "setStatefromBoard", 1);
}
//-----------------------------------------------------------------------------------------------------
// Funktion syncControlDevicesInitial zum initialen synchen der states
//-----------------------------------------------------------------------------------------------------
function syncControlDevicesInitial() {
LOG('Starte initiale Synchronisation der ControlDevices', 'Ablauf', 'syncControlDevicesInitial', 2);
// Temporaere Variable um Endlosschleifen zu verhindern
const isSyncing = {};
// Hauptfunktion fuer die Synchronisation eines einzelnen Geraets
function syncDevice(device) {
const deviceKey = device.DeviceID;
if (isSyncing[deviceKey]) {
LOG(`Synchronisation fuer ${deviceKey} bereits im Gange - ueberspringe`, 'DEBUG', 'syncControlDevicesInitial', 3);
return;
}
isSyncing[deviceKey] = true;
LOG(`Starte Synchronisation fuer ${deviceKey}`, 'Ablauf', 'syncControlDevicesInitial', 2);
try {
// Durch alle LEDs des Geraets iterieren
for (let i = 1; i <= 8; i++) {
const ledKey = `LED${i}`;
const dpKey = `DP_2ndPart${i}`;
const stateDP = device[ledKey];
const controlDP = device.DP + device[dpKey];
if (!stateDP || !controlDP) continue;
// Existenz beider Datenpunkte pruefen
if (!existsState(stateDP) || !existsState(controlDP)) {
LOG(`Datenpunkt ${stateDP} oder ${controlDP} existiert nicht`, 'WARN', 'syncControlDevicesInitial', 3);
continue;
}
// Wert vom State-Datenpunkt lesen
const stateValue = getState(stateDP).val;
// Wert auf Control-Device setzen (mit Debounce-Check)
const updateKey = `${deviceKey}_${controlDP}`;
const now = Date.now();
if (lastLEDUpdates[updateKey] && (now - lastLEDUpdates[updateKey] < 500)) {
LOG(`Debounce: Ueberspringe Update fuer ${controlDP} (zuletzt vor ${now - lastLEDUpdates[updateKey]}ms)`, 'DEBUG', 'syncControlDevicesInitial', 3);
continue;
}
setState(controlDP, stateValue);
lastLEDUpdates[updateKey] = now;
LOG(`Synchronisiert ${stateDP} (${stateValue}) → ${controlDP}`, 'Ablauf', 'syncControlDevicesInitial', 3);
}
} catch (e) {
LOG(`Fehler bei Synchronisation fuer ${deviceKey}: ${e.message}`, 'ERROR', 'syncControlDevicesInitial', 1,"error");
} finally {
isSyncing[deviceKey] = false;
}
}
// Alle ControlDevices synchronisieren
ControlDevices.forEach(device => {
// Nur Geraete mit ELV-SH-SB8 synchronisieren (Beispiel-Filter)
if (device.Device === 'ELV-SH-SB8') {
syncDevice(device);
}
});
// Wichtige States initial synchronisieren
const importantStates = [
DP_Alarmaktivierung,
DP_Still,
DP_Einbruchsmeldung,
DP_GeoeffnetMeldung,
DP_Wassermeldung,
DP_Rauchmeldung,
DP_Sabotagemeldung,
DP_Stoerungsmeldung
];
importantStates.forEach(stateDP => {
if (existsState(stateDP)) {
const value = getState(stateDP).val;
setLEDState(stateDP, value);
LOG(`Initialer State ${stateDP} mit Wert ${value} gesetzt`, 'DEBUG', 'syncControlDevicesInitial', 3);
}
});
LOG('Initiale Synchronisation abgeschlossen', 'Ablauf', 'syncControlDevicesInitial', 2);
}
//-----------------------------------------------------------------------------------------------------
// Function Execute_GruppenSchaltungen Routine zur Schaltung aller Gruppenmitglieder der Tabelle Schaltgruppen - aufruf aus Execute_Alarm
// Es werden alle Mitglieder des Arrays "Alert" aus Tabelle ScenarioDefinition durchlaufen und durch ein Mapping auf Tabelle "Schaltgruppenb" werden die Datenpunkte gesetzt
// Uebergabewert "key" kann scenario sein oder der direkte key der schaltgruppe
//-----------------------------------------------------------------------------------------------------
function Execute_GruppenSchaltungen(key) {
LOG(`Starte GruppenSchaltung fuer Key: ${key}`, "Info", "Execute_GruppenSchaltungen", 2);
// 1. Input-Validierung
if (!key) {
LOG('Fehler: Kein Key angegeben', 'Error', 'Execute_GruppenSchaltungen', 0);
return;
}
// 2. Ermittle zu verarbeitende Alerts
const alertsToProcess = /^Scenario\d{2}$/.test(key)
? (scenarioDefinition[key]?.Alert || [])
: [key];
// 3. Verarbeite alle Alerts
alertsToProcess.forEach(alert => {
if (!Schaltgruppen[alert]) {
LOG(`Warnung: Schaltgruppen-Key '${alert}' nicht definiert`, 'Warnung', 'Execute_GruppenSchaltungen', 1);
return;
}
Schaltgruppen[alert].forEach(entry => {
// Validierung
if (!entry?.Datenpunkt) {
LOG(`Fehler: Kein Datenpunkt in Gruppe '${alert}'`, 'Error', 'Execute_GruppenSchaltungen', 0);
return;
}
if (!existsState(entry.Datenpunkt)) {
LOG(`Fehler: Datenpunkt '${entry.Datenpunkt}' existiert nicht`, 'Error', 'Execute_GruppenSchaltungen', 0);
return;
}
// Zeitumrechnung Sekunden → Millisekunden (fuer beide Werte)
const delayMs = (entry.Delay || 0) * 1000;
const dauerMs = (entry.Dauer || 0) * 1000;
// Schaltlogik
const originalValue = getState(entry.Datenpunkt).val;
setTimeout(() => {
// Hauptschaltung
setState(entry.Datenpunkt, entry.Wert);
LOG(`Schalte ${entry.Datenpunkt} auf ${entry.Wert} (nach ${entry.Delay}s)`, "Info", "Execute_GruppenSchaltungen", 2);
// Rueckstellung (falls Dauer > 0)
if (entry.Dauer > 0) {
setTimeout(() => {
setState(entry.Datenpunkt, originalValue);
LOG(`Ruecksetzung ${entry.Datenpunkt} auf ${originalValue} (nach ${entry.Dauer}s)`, "Info", "Execute_GruppenSchaltungen", 2);
}, dauerMs);
}
}, delayMs);
});
});
}
//-----------------------------------------------------------------------------------------------------
// Function Execute_Alerts Routine zur Schaltung der Sonderfunktionen (HMIP_ASIR und An/Aus-Schaltungen - aufruf aus Execute_Alarm
// Es werden alle Mitglieder des Arrays "Alert" aus Tabelle ScenarioDefinition durchlaufen und durch ein Mapping auf Tabelle "AlertAusgabeDevices" werden die Datenpunkte gesetzt
//-----------------------------------------------------------------------------------------------------
function Execute_Alerts(SelectedScenario) {
LOG(`Routine wird ausgefuehrt`, "Ablauf", "Execute_Alerts", 2);
LOG(`Selected scenario: ${SelectedScenario}`, "Ablauf", "Execute_Alerts", 2);
const AssignedAlerts = scenarioDefinition[SelectedScenario]?.Alert || [];
LOG(`Zugeordnete Alerts: ${JSON.stringify(AssignedAlerts)}`, "Ablauf", "Execute_Alerts", 2);
AssignedAlerts.forEach(alertKey => { // Hier ist alertKey der String-Key
const deviceConfig = AlertAusgabeDevices[alertKey];
if (!deviceConfig) {
LOG(`Alarmgeraet ${alertKey} nicht gefunden`, "WARN", "Execute_Alerts", 2);
return;
}
LOG(`Verarbeite Alert: ${alertKey}`, "Ablauf", "Execute_Alerts", 2);
setTimeout(() => {
// Hier den Key (String) uebergeben, nicht das Objekt
triggerAusgabe(alertKey, true);
}, deviceConfig.Startdelay || 0);
});
}
//-----------------------------------------------------------------------------------------------------
// Function triggerAusgabe Routine zur Schaltung der Sonderfunktionen (tabelle control devices)
//-----------------------------------------------------------------------------------------------------
function triggerAusgabe(ausgabegeraetKey, zustand) {
LOG(`Routine wird ausgefuehrt`, "Ablauf", "triggerAusgabe", 2);
// Durchsuche alle Eintraege in AlertAusgabeDevices nach passendem Geraet und Zustand
let device = null;
for (const key in AlertAusgabeDevices) {
const entry = AlertAusgabeDevices[key];
if (key === ausgabegeraetKey && entry.Zustand === zustand) {
device = entry;
break;
}
}
if (!device) {
LOG(`Kein passender Eintrag fuer Geraet ${ausgabegeraetKey} mit Zustand ${zustand} gefunden`, "WARN", "triggerAusgabe", 0);
return;
}
//---------------HMIP-ASIR Sirenen-------------------------------------
if (device.Device === "HMIP-ASIR") {
// Pruefe ob alle benoetigten Datenpunkte existieren
const dpsExist = [
device.DP + device.AkustikSelectionDP,
device.DP + device.Duration_UnitDP,
device.DP + device.Duration_ValueDP,
device.DP + device.OpticalSelectionDP
].every(dp => existsState(dp));
if (!dpsExist) {
LOG("Nicht alle benoetigten Datenpunkte fuer HMIP-ASIR existieren", "WARN", "triggerAusgabe", 0);
return;
}
// Setze alle Werte mit Verzoegerung (wenn Startdelay definiert ist)
const setStateDelayed = (dp, value, delay) => {
setTimeout(() => {
setState(dp, value);
LOG(`HMIP-ASIR ${dp} auf ${value} gesetzt`, "Ablauf", "triggerAusgabe", 3);
}, delay || 0);
};
setStateDelayed(device.DP + device.AkustikSelectionDP, device.valueTon, device.Startdelay);
setStateDelayed(device.DP + device.Duration_UnitDP, device.valueUnit, device.Startdelay);
setStateDelayed(device.DP + device.Duration_ValueDP, device.valueDuration, device.Startdelay);
setStateDelayed(device.DP + device.OpticalSelectionDP, device.valueOpt, device.Startdelay);
LOG(`HMIP-ASIR ${device.DP} Konfiguration abgeschlossen`, "Ablauf", "triggerAusgabe", 2);
}
//---------------Datenpunkt_Standard------------------------------------
if (device.Device === "Datenpunkt_Standard") {
if (!existsState(device.DP)) {
LOG(`Datenpunkt ${device.DP} existiert nicht`, "WARN", "triggerAusgabe", 0);
return;
}
// Nur fuer Zustand=true (Einschalten) die erweiterten Optionen verarbeiten
if (device.Zustand === true) {
const originalValue = getState(device.DP).val;
// Mit Startdelay verarbeiten falls vorhanden
const executeWithDelay = () => {
// Hauptschaltung
setState(device.DP, device.Aktivierungswert);
LOG(`Datenpunkt_Standard ${device.DP} auf ${device.Aktivierungswert} gesetzt (Startdelay: ${device.Startdelay || 0}ms)`, "Ablauf", "triggerAusgabe", 2);
// Rueckstellung nach Dauer falls definiert
if (device.Dauer > 0 && device.Rueckstellwert !== undefined) {
setTimeout(() => {
setState(device.DP, device.Rueckstellwert);
LOG(`Datenpunkt_Standard ${device.DP} zurueckgesetzt auf ${device.Rueckstellwert} (nach ${device.Dauer}ms)`, "Ablauf", "triggerAusgabe", 2);
}, device.Dauer);
}
};
if (device.Startdelay > 0) {
setTimeout(executeWithDelay, device.Startdelay);
} else {
executeWithDelay();
}
}
// Einfache Ausschaltung fuer Zustand=false
else {
setState(device.DP, device.Aktivierungswert);
LOG(`Datenpunkt_Standard ${device.DP} auf ${device.Aktivierungswert} gesetzt`, "Ablauf", "triggerAusgabe", 2);
}
return;
}
//---------------Datenpunkt_Flash---------------------------------------
if (device.Device === "Datenpunkt_Flash") {
if (device.Wiederholung) {
if (zustand) {
if (!blinkingIntervals[device.DP]) {
let counter = 0;
let isOn = true;
const totalSwitches = device.AnzahlWiederholungen * 2;
blinkingIntervals[device.DP] = setInterval(() => {
const activationValue = typeof device.Aktivierungswert === 'boolean'
? device.Aktivierungswert
: device.Aktivierungswert === 1;
setState(device.DP, isOn ? activationValue : !activationValue);
LOG(`Datenpunkt_Flash: ${device.DP} ${isOn ? 'ein' : 'aus'} (${counter + 1}/${totalSwitches})`, "Ablauf", "triggerAusgabe", 3);
if (++counter >= totalSwitches) {
clearInterval(blinkingIntervals[device.DP]);
delete blinkingIntervals[device.DP];
LOG("Datenpunkt_Flash: Alarmwiederholungen abgeschlossen", "Ablauf", "triggerAusgabe", 2);
}
isOn = !isOn;
}, device.Frequenz);
}
} else if (blinkingIntervals[device.DP]) {
clearInterval(blinkingIntervals[device.DP]);
delete blinkingIntervals[device.DP];
setState(device.DP, false);
LOG("Datenpunkt_Flash: Blinken beendet", "Ablauf", "triggerAusgabe", 2);
}
} else {
// Einfaches Setzen ohne Blinken
setState(device.DP, device.Aktivierungswert);
LOG(`Datenpunkt_Flash ${device.DP} auf ${device.Aktivierungswert} gesetzt`, "Ablauf", "triggerAusgabe", 2);
}
}
}
//-----------------------------------------------------------------------------------------------------
// Function Execute_STILL Routine schaltet alle Geraete aus Tabelle AlertAusgabeDevices aus
//-----------------------------------------------------------------------------------------------------
function Execute_STILL() {
LOG(`Routine wird ausgefuehrt`, "Ablauf", "Execute_STILL", 2);
let delayCounter = 0;
const baseDelay = 50; // Basisverzoegerung in ms
for (const deviceKey in AlertAusgabeDevices) {
const deviceConfig = AlertAusgabeDevices[deviceKey];
if (deviceConfig.Zustand === false) {
const currentDelay = delayCounter * baseDelay;
// Timeout fuer verzoegertes Absetzen der Befehle
setTimeout(() => {
LOG(`Deaktiviere Geraet: ${deviceKey} (Typ: ${deviceConfig.Device})`, "Ablauf", "Execute_STILL", 2);
switch(deviceConfig.Device) {
case "HMIP-ASIR":
if (existsState(deviceConfig.DP + deviceConfig.AkustikSelectionDP)) {
setState(deviceConfig.DP + deviceConfig.AkustikSelectionDP, deviceConfig.valueTon);
setState(deviceConfig.DP + deviceConfig.Duration_ValueDP, deviceConfig.valueDuration);
setState(deviceConfig.DP + deviceConfig.OpticalSelectionDP, deviceConfig.valueOpt);
}
break;
case "Datenpunkt_Flash":
if (blinkingIntervals[deviceConfig.DP]) {
clearInterval(blinkingIntervals[deviceConfig.DP]);
delete blinkingIntervals[deviceConfig.DP];
}
// Fall-through intentional
case "Datenpunkt_Standard":
if (existsState(deviceConfig.DP)) {
setState(deviceConfig.DP, deviceConfig.Aktivierungswert);
}
break;
}
}, currentDelay + (deviceConfig.Startdelay || 0));
delayCounter++;
}
}
setTimeout(() => {
LOG("STILL-Routine vollstaendig abgeschlossen", "Ablauf", "Execute_STILL", 2);
}, delayCounter * baseDelay + 1000);
}
//-----------------------------------------------------------------------------------------------------
// Function getDeviceTypeForDatapoint bestimmt ob der datenpunkt zu wasser oder rauch oder Sabotage
// Theoretisch sind mehrere devicetype zuordnungen moeglich. Sollte in der Praxis nicht vorkommen. Trotzdem wird das durch die Priorisierung abgefangen
//-----------------------------------------------------------------------------------------------------
function getDeviceTypeForDataPoint(datenpunkt) {
const foundTypes = new Set();
// Alle DeviceTypes finden, in denen der Datenpunkt auftaucht
for (const raum in raumDefinition) {
const definition = raumDefinition[raum];
for (const deviceType in definition) {
if (['Type', 'Position'].includes(deviceType)) continue;
const datenpunkte = definition[deviceType];
if (Array.isArray(datenpunkte) && datenpunkte.includes(datenpunkt)) {
foundTypes.add(deviceType);
}
}
}
if (foundTypes.size === 0) {
LOG(`Kein gueltiger Devicetype in Tabelle raumDefinition gefunden fuer Datenpunkt: ${datenpunkt}`, "Fehler", "getDeviceTypeForDataPoint", 0);
return null;
}
// Jetzt die Priorisierung aus AlarmTypes anwenden
let bestMatch = null;
let bestPrio = Infinity;
for (const alarm of AlarmTypes) { // in alarmtypes weird das Feld Prio abgefragt
for (const devType of alarm.deviceTypes) {
if (foundTypes.has(devType) && alarm.Prio < bestPrio) {
bestMatch = devType;
bestPrio = alarm.Prio;
}
}
}
if (!bestMatch) {
LOG(`Kein priorisierter Devicetype gefunden fuer Datenpunkt: ${datenpunkt}`, "Warnung", "getDeviceTypeForDataPoint", 0);
// Optional: irgendeinen der gefundenen Typen zurueckgeben
return Array.from(foundTypes)[0];
}
LOG(`Priorisierter Devicetype gefunden fuer Datenpunkt: ${datenpunkt} DeviceType ist:${bestMatch} `, "Warnung", "getDeviceTypeForDataPoint", 2);
return bestMatch;
}
//-----------------------------------------------------------------------------------------------------
// Function GetRoomsForDataPoint stellt alle betroffenen Raeume eines datenpunktes in ein Array
//-----------------------------------------------------------------------------------------------------
function GetRoomsForDataPoint(dpid) {
const Raum = [];
for (const raumName in raumDefinition) { // Durch alle Raeume iterieren
const raum = raumDefinition[raumName];
for (const DeviceType in raum) { // Durch alle DeviceTypes im Raum iterieren (Fenster, Tueren, Bewegung, etc.)
if (DeviceType === 'Type' || DeviceType === 'Position') continue; // Ueberspringe spezielle Felder wie Type und Position
if (Array.isArray(raum[DeviceType]) && raum[DeviceType].includes(dpid)) { // Pruefe ob der Datenpunkt in dieser Kategorie vorhanden ist
Raum.push(raumName);
}
}
}
return Raum;
}
//-----------------------------------------------------------------------------------------------------
// Function updateScenario Status aus den Datenpunkten lesen und in die Scenario-Tabelle setzen
//-----------------------------------------------------------------------------------------------------
function updateScenario() {
LOG(`Routine wird ausgefuehrt`, "Ablauf", "updateScenario", 2);
AlarmScenarios.forEach(control => {
if (control.Control.startsWith('Scenario') && control.Control.endsWith('Aktiv')) {
const status = getState(control.Datenpunkt).val; // Status vom Datenpunkt abrufen
const match = control.Control.match(/Scenario(\d+)Aktiv/); // Extrahiere die Szenario-Nummer
if (match) {
const scenarioKey = `Scenario${match[1]}`; // ScenarioXXAktiv
if (!ScenarioStatus.hasOwnProperty(scenarioKey)) {
LOG(`Warnung: ${scenarioKey} existiert nicht in ScenarioStatus`, "Warnung", "updateScenario", 2);
} else {
ScenarioStatus[scenarioKey].status = status; // Status setzen
// LOG(`Status von ${control.Control} wurde auf ${status} gesetzt`, "Ablauf", "updateScenario", 3);
}
} else {
LOG(`Fehler: Konnte keine Szenarionummer in ${control.Control} finden`, "Fehler", "updateScenario", 2);
}
}
});
}
//-----------------------------------------------------------------------------------------------------
// Function ScenarioAenderung Es wurde ein Scenario aktiviert oder deaktiviert
//-----------------------------------------------------------------------------------------------------
function ScenarioAenderung(Scenario, value) {
LOG(`Routine wird ausgefuehrt`, "Ablauf", "ScenarioAenderung", 2);
updateScenario();
subscribeToAllScenarioDPs()
ListSystemSubscriptions()
LOG(`Scenario geaendert: ${Scenario} - Neuer Wert = ${value}`, "Ablauf", "ScenarioAenderung", 2);
}
//-----------------------------------------------------------------------------------------------------
// Function Alarmaktivierung ALarm aktiviert oder deaktiviert
//-----------------------------------------------------------------------------------------------------
function Alarmaktivierung(value) {
LOG(`Routine wird ausgefuehrt`, "Ablauf", "Alarmaktivierung", 2);
AlarmAktivierungsStatus = getState(DP_Alarmaktivierung).val;
LOG(`Alarm Aktivierung geaendert: Neuer Wert = ${value}`, "Ablauf", "Alarmaktivierung", 2);
let jsonStringA = getState(id_JSON_Alarmmeldung_Aktuell).val || '[]'; // Lade den aktuellen JSON-String des Historie-Datenpunkts
AktuelleMeldungenJSON = JSON.parse(jsonStringA);
let jsonStringH = getState(id_JSON_Alarmmeldung_Historie).val || '[]'; // Lade den aktuellen JSON-String des Historie-Datenpunkts
HistorischeMeldungenJSON = JSON.parse(jsonStringH);
if (!value) { // Alarm wird deaktiviert
AktuelleMeldungenJSON = CreateJsonAlarmmeldung(AktuelleMeldungenJSON ,func_get_datum(),MessageAlarmDeactivated); // hinzufuegen
AktuelleMeldungenJSON = BereinigeAktuelleMeldungen(AktuelleMeldungenJSON)
jsonStringA = JSON.stringify(AktuelleMeldungenJSON);
setState(id_JSON_Alarmmeldung_Aktuell, jsonStringA); // Alarmanlage deaktiviert wird in JSON-Datenpunkt als String gespeichert
generateHtmlTable(AktuelleMeldungenJSON,"Akt")
if(pruefeProtokollierung("AlarmAktivierung", false)) {
HistorischeMeldungenJSON = CreateJsonAlarmmeldung(HistorischeMeldungenJSON, func_get_datum(), MessageAlarmDeactivated, func_get_datum())
jsonStringH = JSON.stringify(HistorischeMeldungenJSON); // Stringify das Json um fuer die speicherung vorzubereiten
setState(id_JSON_Alarmmeldung_Historie, jsonStringH); // Aktuelle Alarmmeldung wird in JSON-Datenpunkt als String gespeichert
generateHtmlTable(HistorischeMeldungenJSON,"Hist")
}
writeProkollExtern( MessageAlarmDeactivated, func_get_datum(),)
const messagekurz = MessageAlarmDeactivated;
const messagelang = `${func_get_datum()} ${MessageAlarmDeactivated}`;
addMessageToCollector("Alarm_Unscharf", messagekurz, messagelang); // zur Abarbeitung der Tabelle MessengerScope
sendMessage();
if (AktionenNachUnscharfSchaltung.length > 0) {
LOG(`Starte ${AktionenNachUnscharfSchaltung.length} Schaltgruppe(n)`, 'Info', 'Alarmaktivierung', 2);
AktionenNachUnscharfSchaltung.forEach((key, index) => {
LOG(`Starte Schaltgruppe ${index + 1}: ${key}`, 'Debug', 'Alarmaktivierung', 3);
Execute_GruppenSchaltungen(key);
});
} else {
LOG('Keine Schaltgruppen in AktionenNachUnscharfSchaltung definiert', 'Info', 'Alarmaktivierung', 3);
}
} else { // Alarm wird aktiviert
const OeffnungLiegtVor = getState(DP_GeoeffnetMeldung).val;
let messagekurz = MessageAlarmActivated;
let messagelang = `${func_get_datum()} ${MessageAlarmActivated}`;
if(OeffnungLiegtVor) {
messagekurz = messagekurz + " bei Status geoeffnet"
messagelang = messagelang + " bei Status geoeffnet"
}
AktuelleMeldungenJSON = CreateJsonAlarmmeldung(AktuelleMeldungenJSON ,func_get_datum(),messagekurz ); // hinzufuegen
AktuelleMeldungenJSON = BereinigeAktuelleMeldungen(AktuelleMeldungenJSON)
let jsonStringA = JSON.stringify(AktuelleMeldungenJSON);
setState(id_JSON_Alarmmeldung_Aktuell, jsonStringA); // Alarmanlage aktiviert wird in JSON-Datenpunkt als String gespeichert
generateHtmlTable(AktuelleMeldungenJSON,"Akt")
if(pruefeProtokollierung("AlarmAktivierung", true)) {
HistorischeMeldungenJSON = CreateJsonAlarmmeldung(HistorischeMeldungenJSON, func_get_datum(), messagekurz, func_get_datum())
jsonStringH = JSON.stringify(HistorischeMeldungenJSON); // Stringify das Json um fuer die speicherung vorzubereiten
setState(id_JSON_Alarmmeldung_Historie, jsonStringH); // Aktuelle Alarmmeldung wird in JSON-Datenpunkt als String gespeichert
generateHtmlTable(HistorischeMeldungenJSON,"Hist")
}
writeProkollExtern( messagekurz, func_get_datum(),)
addMessageToCollector("Alarm_Scharf", messagekurz, messagelang); // zur Abarbeitung der Tabelle MessengerScope
sendMessage();
// Ausfuehrung aller Schaltgruppen aus dem AktionenNachScharfSchaltung-Array
if (AktionenNachScharfSchaltung.length > 0) {
LOG(`Starte ${AktionenNachScharfSchaltung.length} Schaltgruppe(n)`, 'Info', 'Alarmaktivierung', 2);
AktionenNachScharfSchaltung.forEach((key, index) => {
LOG(`Starte Schaltgruppe ${index + 1}: ${key}`, 'Debug', 'Alarmaktivierung', 3);
Execute_GruppenSchaltungen(key);
});
} else {
LOG('Keine Schaltgruppen in AktionenNachScharfSchaltung definiert', 'Info', 'Alarmaktivierung', 3);
}
}
}
//-----------------------------------------------------------------------------------------------------
// JSON_Alarmmeldungen_History_Aktualisierung Die Historische Jason wird upgedated entsprechend der Meldungsupdates
//-----------------------------------------------------------------------------------------------------
function JSON_Alarmmeldungen_History_Aktualisierung(AktuelleMeldungenJSON,Meldungstyp) {
LOG(`Routine wird ausgefuehrt mit folgendem Meldungstyp ${Meldungstyp}`, "Ablauf", "JSON_Alarmmeldungen_History_Aktualisierung", 2);
// Aktuelle und historische Meldungen laden
let jsonAktuellString = getState(id_JSON_Alarmmeldung_Aktuell).val || '[]';
AktuelleMeldungenJSON = JSON.parse(jsonAktuellString);
let jsonStringHistorie = getState(id_JSON_Alarmmeldung_Historie).val || '[]';
let HistorischeMeldungenJSON = JSON.parse(jsonStringHistorie);
let aktiveMeldungen = HistorischeMeldungenJSON.filter(m => m.Quittiert_seit === "aktiv");
const datumJetzt = func_get_datum();
let meldungenGeaendert = false;
// Bestimme die zu verarbeitenden Alarmtypen
const zuVerarbeitendeTypen = (Meldungstyp === "AllAlarmtypes")
? [...new Set(HistorischeMeldungenJSON.map(m => m.Alarmtype))] // Eindeutige Alarmtypen aus Historie
: [Meldungstyp];
zuVerarbeitendeTypen.forEach(typ => {
if (!typ || typ === NichtRelevantText) return; // Nicht relevante Eintraege ueberspringen
// Pruefe ob dieser Alarmtyp aktuell aktiv ist
const istAktuell = AktuelleMeldungenJSON.some(e => e.Alarmtype === typ);
// Nur nicht-aktive Alarmtypen quittieren
if (!istAktuell) {
// Finde alle aktiven Meldungen dieses Alarmtyps
const zuQuittierendeMeldungen = aktiveMeldungen.filter(m => m.Alarmtype === typ);
if (zuQuittierendeMeldungen.length > 0) {
zuQuittierendeMeldungen.forEach(meldung => {
meldung.Quittiert_seit = datumJetzt;
meldungenGeaendert = true;
LOG(`Alarmmeldung quittiert: Alarmtype = '${meldung.Alarmtype}', Quittiert_seit = '${datumJetzt}'`, "Ablauf", "JSON_Alarmmeldungen_History_Aktualisierung", 2);
});
} else {
LOG(`Keine aktiven Meldungen vom Typ '${typ}' zum Quittieren gefunden.`, "Ablauf", "JSON_Alarmmeldungen_History_Aktualisierung", 3);
}
} else {
LOG(`Alarmtyp '${typ}' ist aktiv – keine Quittierung.`, "Ablauf", "JSON_Alarmmeldungen_History_Aktualisierung", 3);
}
});
if (meldungenGeaendert) {
const updatedString = JSON.stringify(HistorischeMeldungenJSON);
setState(id_JSON_Alarmmeldung_Historie, updatedString);
generateHtmlTable(HistorischeMeldungenJSON,"Hist")
LOG(`Quittierungen fuer '${Meldungstyp}' wurden erfolgreich vorgenommen.`, "Ablauf", "JSON_Alarmmeldungen_History_Aktualisierung", 2);
} else {
LOG(`Keine quittierbaren Meldungen fuer '${Meldungstyp}' gefunden.`, "Ablauf", "JSON_Alarmmeldungen_History_Aktualisierung", 3);
}
}
//-----------------------------------------------------------------------------------------------------
// Funktion BereinigeAktuelleMeldungen Die Aktuelle JSON wird analysiert. Es duerfen keine doppelten Meldungen je Alarmtype vorkommen
//-----------------------------------------------------------------------------------------------------
function BereinigeAktuelleMeldungen(AktuelleMeldungenJSON) {
LOG(`Starte Bereinigung der aktuellen Meldungen`, "Ablauf", "BereinigeAktuelleMeldungen", 2);
if (!Array.isArray(AktuelleMeldungenJSON)) {
LOG(`Ungueltige Eingabe - erwartet Array, erhalten: ${typeof AktuelleMeldungenJSON}`, "Fehler", "BereinigeAktuelleMeldungen", 1);
return AktuelleMeldungenJSON;
}
// Gruppiere Meldungen nach Alarmtype
const gruppierteMeldungen = AktuelleMeldungenJSON.reduce((acc, meldung) => {
const alarmtype = meldung.Alarmtype || NichtRelevantText;
if (!acc[alarmtype]) {
acc[alarmtype] = [];
}
acc[alarmtype].push(meldung);
return acc;
}, {});
// Behalte nur die neueste Meldung pro Alarmtype
const bereinigteMeldungen = Object.entries(gruppierteMeldungen).map(([alarmtype, meldungen]) => {
if (meldungen.length === 1) {
return meldungen[0]; // Nur eine Meldung - nichts zu tun
}
LOG(`Mehrfacheintraege gefunden fuer Alarmtype '${alarmtype}' (${meldungen.length} Eintraege)`, "Ablauf", "BereinigeAktuelleMeldungen", 2);
// Finde die neueste Meldung
const neuesteMeldung = meldungen.reduce((neueste, aktuelle) => {
const neuesteDatum = parseDatum(neueste.datum_seit);
const aktuelleDatum = parseDatum(aktuelle.datum_seit);
return aktuelleDatum > neuesteDatum ? aktuelle : neueste;
});
LOG(`Behalte nur Eintrag vom ${neuesteMeldung.datum_seit} fuer Alarmtype '${alarmtype}'`, "Ablauf", "BereinigeAktuelleMeldungen", 2);
return neuesteMeldung;
});
// Protokolliere Anzahl der entfernten Eintraege
const entfernteEintraege = AktuelleMeldungenJSON.length - bereinigteMeldungen.length;
if (entfernteEintraege > 0) {
LOG(`${entfernteEintraege} doppelte Eintraege wurden entfernt`, "Ablauf", "BereinigeAktuelleMeldungen", 1);
}
return bereinigteMeldungen;
}
//-----------------------------------------------------------------------------------------------------
// Funktion RemoveAlarmtypeFromJSON Die Aktuelle JSON wird analysiert. der uebergebene Alarmtype wird aus AktuelleMeldungenJSON geloescht
//-----------------------------------------------------------------------------------------------------
function RemoveAlarmtypeFromJSON(AktuelleMeldungenJSON, Alarmtype) {
LOG(`Starte Entfernung von Alarmtype '${Alarmtype}' aus JSON`, "Ablauf", "RemoveAlarmtypeFromJSON", 2);
// Validierung der Eingabeparameter
if (!Array.isArray(AktuelleMeldungenJSON)) {
LOG(`Ungueltige Eingabe - erwartet Array, erhalten: ${typeof AktuelleMeldungenJSON}`, "Fehler", "RemoveAlarmtypeFromJSON", 1);
return AktuelleMeldungenJSON;
}
if (!Alarmtype || Alarmtype === NichtRelevantText) {
LOG(`Ungueltiger Alarmtype '${Alarmtype}' - Abbruch`, "Fehler", "RemoveAlarmtypeFromJSON", 1);
return AktuelleMeldungenJSON;
}
// Filtere alle Eintraege die NICHT dem gesuchten Alarmtype entsprechen
const gefilterteMeldungen = AktuelleMeldungenJSON.filter(meldung => {
const currentAlarmtype = meldung.Alarmtype || NichtRelevantText;
return currentAlarmtype !== Alarmtype;
});
// Protokolliere das Ergebnis
const entfernteAnzahl = AktuelleMeldungenJSON.length - gefilterteMeldungen.length;
if (entfernteAnzahl > 0) {
LOG(`${entfernteAnzahl} Eintraege fuer Alarmtype '${Alarmtype}' wurden entfernt`,
"Ablauf", "RemoveAlarmtypeFromJSON", 1);
} else {
LOG(`Keine Eintraege fuer Alarmtype '${Alarmtype}' gefunden`,
"Ablauf", "RemoveAlarmtypeFromJSON", 2);
}
return gefilterteMeldungen;
}
//-----------------------------------------------------------------------------------------------------
// Funktion parseDatum Umwandeln JSON Datumsformat DD.MM.25 HH:MM:SS Uhr in Datum und Zeit
//-----------------------------------------------------------------------------------------------------
function parseDatum(datumString) {
try {
const [datePart, timePart] = datumString.split(' ');
const [day, month, year] = datePart.split('.');
const [hours, minutes, seconds] = timePart.split(':');
// 20 als Prefix fuer 20xx Jahre (da Format "25" fuer 2025)
return new Date(`20${year}-${month}-${day}T${hours}:${minutes}:${seconds}`);
} catch (e) {
LOG(`Fehler beim Parsen des Datums '${datumString}': ${e}`, "Fehler", "BereinigeAktuelleMeldungen", 1);
return new Date(0); // Gibt minimales Datum zurueck, falls Parsing fehlschlaegt
}
}
//-----------------------------------------------------------------------------------------------------
// addMessageToCollector Messages werden unter beruecksichtigung der folgenden Objekte in den MessageCollector genommen
// MessengerScope, Services, AlarmTypes, TextTypeKurz
//-----------------------------------------------------------------------------------------------------
function addMessageToCollector(messageType, MessageKurz, MessageLang) {
// Pruefe, ob der messageType in MessageSendCollector existiert, falls nicht initialisieren
if (!MessageSendCollector[messageType]) {
MessageSendCollector[messageType] = {}; // Initialisiere fuer den messageType ein leeres Objekt
}
// Hole die Konfiguration des Messengers fuer den jeweiligen messageType
let messengerConfig = MessengerScope[messageType] || MessengerScope['Sonstige'] || Array(services.length).fill(false);
// Verarbeite die Konfiguration fuer jeden Service
messengerConfig.forEach((isActive, index) => {
if (isActive) {
const service = services[index];
const instance = MessengerInstanz[index];
// Pruefe, ob eine gueltige Instanz vorhanden ist
if (instance !== null && !isNaN(instance) && instance >= 0) {
// Initialisiere MessageSendCollector[messageType][service], falls nicht vorhanden
if (!MessageSendCollector[messageType][service]) {
MessageSendCollector[messageType][service] = []; // Initialisiere das Array fuer diesen Service
}
const messageToAdd = TextTypeKurz[index] ? MessageKurz : MessageLang;
const existingMessages = MessageSendCollector[messageType][service];
// **Doppelpruefung:** Gibt es bereits eine Nachricht mit demselben Text und Instanz?
const isDuplicate = existingMessages.some(msg => msg.message.trim() === messageToAdd.trim() && msg.instance === instance);
if (!isDuplicate) {
MessageSendCollector[messageType][service].push({
message: messageToAdd + '\n',
instance: instance
});
LOG(`Nachricht hinzugefuegt fuer ${messageType} - Service: ${service} - Instanz: ${instance}`, "Ablauf", "addMessageToCollector", 3);
} else {
LOG(`Doppelte Nachricht erkannt und verworfen fuer ${messageType} - Service: ${service} - Instanz: ${instance}`, "DEBUG", "addMessageToCollector", 3);
}
} else {
LOG(`Ungueltige Instanz fuer Service: ${service} (Instanz: ${instance})`, "WARN", "addMessageToCollector", 0);
}
}
});
}
//-----------------------------------------------------------------------------------------------------
// Aktiviert oder deaktiviert Szenarien basierend auf dem uebergebenen Array und Boolean // fuer Scenarioaktivierung bei Gaesten und Abesenheiten
// @param {string[]} scenariosArray - Array von Szenario-Namen (koennen mit Minus beginnen)
// @param {boolean} activate - true fuer Aktivierung, false fuer Deaktivierung
//-----------------------------------------------------------------------------------------------------
function AutoScenarioActivation(scenariosArray, activate) {
scenariosArray.forEach(scenario => {
// Pruefen, ob das Szenario mit einem Minus beginnt (umgekehrte Logik)
const isInverted = scenario.startsWith('-');
const cleanScenarioName = isInverted ? scenario.substring(1) : scenario;
const fullPath = controlsPath + cleanScenarioName;
// Logik bestimmen
let shouldActivate;
if (isInverted) {
// Umgekehrte Logik: wenn activate true ist, setzen wir false und umgekehrt
shouldActivate = !activate;
} else {
// Normale Logik: direkt den activate-Parameter uebernehmen
shouldActivate = activate;
}
// Zustand setzen
setState(fullPath, shouldActivate);
// Optional: Logging fuer Debug-Zwecke
console.log(`Setze ${fullPath} auf ${shouldActivate} (${isInverted ? 'invertiert' : 'normal'})`);
});
}
//-----------------------------------------------------------------------------------------------------
// sendMessage Hier werden die Nachrichten fuer den jeweiligen Service aufbereitet
//-----------------------------------------------------------------------------------------------------
function sendMessage(messageType = null) {
LOG(`Routine sendMessage wird ausgefuehrt, meldungsart: ${messageType}`, "Ablauf", "sendMessage", 2);
const messageTypesToProcess = messageType ? [messageType] : Object.keys(MessageSendCollector); // Bestimme die MessageTypes, die verarbeitet werden sollen
messageTypesToProcess.forEach((type) => {
const messagesByService = MessageSendCollector[type];
if (messagesByService) {
Object.keys(messagesByService).forEach((service) => {
const serviceMessages = messagesByService[service];
let messagesToRemove = [];
serviceMessages.forEach((item) => {
const { message, instance } = item;
sendToService(service, message, instance);
messagesToRemove.push(item);
});
messagesToRemove.forEach((item) => { // Entferne alle verarbeiteten Nachrichten
const serviceMessagesIndex = serviceMessages.indexOf(item);
if (serviceMessagesIndex > -1) {
serviceMessages.splice(serviceMessagesIndex, 1);
}
});
});
}
});
}
//-----------------------------------------------------------------------------------------------------
// sendToService - hier wird der Versand vorgenommen
// Reihenfolge: Email, WhatsApp, Signal, Telegram, Pushover, Pushsafer
// MessengerInstanz = [0, 0, 0, 0, 0, 0] Instanzen der Messenger-Dienste in
//-----------------------------------------------------------------------------------------------------
function sendToService(service, message, instance) {
LOG(`Message wird versendet mit: ${service} : ${instance} : ${message}`, "Ablauf", "sendToService", 2);
switch (service) {
case "email":
sendTo(`email.${instance}`, "send", {
text: message,
to: emailAddresse,
subject: Headline
});
break;
case "whatsApp":
sendTo(`whatsapp-cmb.${instance}`, "send", {
text: message
});
break;
case "Signal":
sendTo(`signal-cmb.${instance}`, "send", {
text: message
});
break;
case "Telegram":
sendTo(`telegram.${instance}`, "send", {
text: message,
user: TelegramUser // Telegram User ID, um den Nachrichteneempfaenger festzulegen
});
break;
case "Pushover":
sendTo(`pushover.${instance}`, "send", {
message: message,
sound: ""
});
break;
case "Pushsafer":
sendTo(`pushsafer.${instance}`, "send", {
message: message,
title: Headline
});
break;
case "Sprache":
const sayitadapter = "sayit."+instance+".tts.text";
if(!existsState(sayitadapter) ){
LOG(`Sayit-Adapter nicht konfiguriert: ${sayitadapter}`, "WARN", "sendToService", 2);
break;
}
if(IsTimeInRange(zeitvon,zeitbis)){
setState(sayitadapter,message)
}
break;
default:
LOG(`Unbekannter Service: ${service}`, "WARN", "sendToService", "warn");
}
}
//-----------------------------------------------------------------------------------------------------
// Funktion unsubscribeAll unsubscribed und loescht alle subscriptions
//-----------------------------------------------------------------------------------------------------
function unsubscribeAll() {
LOG(`Loesche alle Subscriptions`, "Ablauf", "unsubscribeAll", 2);
let count = 0
GeneratedSubscriptions.forEach(subscription => {
count = count + 1;
unsubscribe(subscription);
});
LOG(`Zur Neuerstellung der Subscriptions wurden fuer ${count} zunaechst geloescht`, "Ablauf", "unsubscribeAll", 3);
GeneratedSubscriptions.length = 0; // Array leeren
}
//-----------------------------------------------------------------------------------------------------
// Funktion IsTimeInRange - Ermittlung ob die aktuelle Zeit in der vorgegebenen von bis Zeit liegt
//-----------------------------------------------------------------------------------------------------
function IsTimeInRange(zeitvon, zeitbis) {
if (!/^\d{2}:\d{2}$/.test(zeitvon) || !/^\d{2}:\d{2}$/.test(zeitbis)) { // Ueberpruefen, ob die Eingaben im gueltigen Format sind
LOG(`Es wurde ein Fehler bei der Konfiguration von zeitvon/zeitbis festgestellt (Sprach message)`, "WARN", "IsTimeInRange", 0);
return false;
}
let isInRange = false;
const getCurrentTime = () => { // Funktion, um die aktuelle Zeit im Format HH:MM zu bekommen
const now = new Date();
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
return `${hours}:${minutes}`;
}
const timeToMinutes = (zeit) => { // Zeit in Minuten umwandeln fuer den Vergleich
const [hours, minutes] = zeit.split(':').map(Number);
return hours * 60 + minutes;
}
const zeitvonMinuten = timeToMinutes(zeitvon); // Umwandlung der Zeiten in Minuten
const zeitbisMinuten = timeToMinutes(zeitbis);
const currentTime = getCurrentTime();
const currentMinutes = timeToMinutes(currentTime);
if (zeitvon === zeitbis) { // Wenn zeitvon und zeitbis identisch sind, gilt "immer"
isInRange = true;
}
// Wenn zeitvon kleiner als zeitbis, dann sind beide im gleichen Tag
if (zeitvonMinuten < zeitbisMinuten) {
if (currentMinutes >= zeitvonMinuten && currentMinutes <= zeitbisMinuten) {
isInRange = true;
}
} else {
if (currentMinutes >= zeitvonMinuten || currentMinutes <= zeitbisMinuten) { // Wenn zeitvon groesser als zeitbis, dann ist zeitbis am naechsten Tag
isInRange = true;
}
}
LOG(`Rueckgabewert zeitvon ist ${zeitvon} und zeitbis ist ${zeitbis}`, "Ergebnis", "IsTimeInRange", 3);
return isInRange;
}
//-----------------------------------------------------------------------------------------------------
// Funktion CreateJsonAlarmmeldung erzeugen einer JSON Tabelle
//-----------------------------------------------------------------------------------------------------
function CreateJsonAlarmmeldung(existingJson = [], datum_seit,Nachricht, Quittiert_seit, Alarmtype=null, raum=null, deviceType=null,Scenario=null, common_name=null, GeraeteId=null, id=null) {
let parsedJson;
if (Array.isArray(existingJson)) { // ueberpruefen, ob existingJson bereits ein Array ist
parsedJson = existingJson; // Nur das Array uebernehmen
} else {
parsedJson = []; // Falls es kein Array ist, setze es auf ein leeres Array
}
const neuerEintrag = {
datum_seit: datum_seit || func_get_datum(), // Fallback auf aktuelles Datum
Alarmtype: Alarmtype || NichtRelevantText,
Raum: raum || NichtRelevantText,
deviceType: deviceType || NichtRelevantText,
Scenario: Scenario || NichtRelevantText,
common_name: common_name || NichtRelevantText,
GeraeteId: GeraeteId || NichtRelevantText,
Nachricht: Nachricht || 'Keine Meldung', // Alarmmeldung mit Geraeteinfos
Quittiert_seit: Quittiert_seit || "aktiv", // status ob eine message bereits aufgeboben ist. wenn aufgehoben wird ein Datum eingestellt
DataPoint: id || NichtRelevantText,
};
parsedJson.unshift(neuerEintrag);
return parsedJson; // Gib das aktualisierte Array zurueck
}
//-----------------------------------------------------------------------------------------------------
// Funktion Refresh_History Loeschen der History JSON
//-----------------------------------------------------------------------------------------------------
function Refresh_History() {
LOG(`Refesh History aufgerufen`, "Ablauf", "unsubscribeAll", 2);
const verbleibendeTage = getState(id_History_VerbleibendeTage).val || 7;
const heute = new Date();
let jsonString = getState(id_JSON_Alarmmeldung_Historie).val || '[]';
let HistorischeMeldungenJSON = JSON.parse(jsonString);
const AnzahlHistMeldungenVorher = HistorischeMeldungenJSON.length
HistorischeMeldungenJSON = HistorischeMeldungenJSON.filter(meldung => {
if (meldung.Quittiert_seit === "aktiv") {
return true;
}
try {
const meldungsDatum = parseDatum(meldung.datum_seit);
// Explizite Verwendung von getTime() fuer numerische Berechnung
const differenzInTagen = Math.floor((heute.getTime() - meldungsDatum.getTime()) / (1000 * 60 * 60 * 24));
return differenzInTagen < verbleibendeTage;
} catch (e) {
LOG(`Fehler bei der Verarbeitung: ${JSON.stringify(meldung)}. Fehler: ${e}`, "Fehler", "Refresh_History", 1);
return false;
}
});
setState(id_JSON_Alarmmeldung_Historie, JSON.stringify(HistorischeMeldungenJSON));
generateHtmlTable(HistorischeMeldungenJSON,"Hist")
LOG(`Historische Meldungen aktualisiert. Anzahl Meldungen vorher:${AnzahlHistMeldungenVorher} Verbleibende Meldungen: ${HistorischeMeldungenJSON.length}`, "Info", "Refresh_History", 1);
}
//-----------------------------------------------------------------------------------------------------
// Funktion schreibt einen Logeintrag in das Filesystem und auch in das interne Log-System
//-----------------------------------------------------------------------------------------------------
function writeProkollExtern(Nachricht, quittiert, Alarmtype="", raum="", deviceType="", Scenario="",common_name="",GeraeteId="", dpid="") {
/*writeProkollExtern( Nachricht, quittiert,Alarmtype, raum, deviceType, Scenario, common_name, GeraeteId, id) Nachricht und quittiert sind mussfelder */
if (!ProkollExtern) return;
const fs = require('fs'); // enable write fuer externes log
const logdate = formatDate(new Date(), "TT.MM.JJJJ");
const logtime = formatDate(new Date(), "SS:mm:ss");
const logEntry = `${logdate} ;${logtime} ;${Alarmtype} ;${raum} ; ${deviceType} ;${Scenario} ;${common_name};${GeraeteId};${Nachricht};${quittiert};${dpid}\n`;
const headerLine = "Datum;Uhrzeit;Alarmtype;Raum;GeraeteTyp;Szenario;Geraetebezeichnung;GeraeteID;Nachricht;quittiert;Datenpunkt\n";
fs.readFile(PathProkollExtern, 'utf8', function(err, data) {
if (!err) {
fs.appendFileSync(PathProkollExtern, logEntry, 'utf8');
} else {
LOG(`Logfile nicht gefunden - wird angelegt`, "Ablauf", "writelog", 0);
fs.writeFileSync(PathProkollExtern, headerLine + logEntry, 'utf8');
}
});
}
//-----------------------------------------------------------------------------------------------------
// Funktion ExtractMetaData Verarbeitung des Datenpunkts zur Extraktion der Metadaten: GeraeteId, meldungsart
//-----------------------------------------------------------------------------------------------------
function ExtractMetaData(datenpunkt, ExtractType) {
// LOG(`Routine ExtractMetaData wird ausgefuehrt datenpunkt fuer Metadaten ${datenpunkt} ExtractType ${ExtractType} `, "Ablauf", "ExtractMetaData", 2);
ExtractType = ExtractType.toUpperCase();
const teile = datenpunkt.split('.'); // Splitte den Datenpunkt in Teile
const adapter = teile[0]; // Der erste Teil ist immer der Adapter
const instance = teile[1]; // Der zweite Teil ist immer die Instance
const struktur = StrukturDefinition.find(s => s.Adapter === adapter); // Strukturzeile aus Tabelle StrukturDefinition fuer den Adapter aus dem uebergebenen DP
const GeraeteID = teile[struktur.GeraeteID]; // Extrahiere Felder basierend auf der Strukturdefinition
if (!struktur) {
LOG(`Die Struktur fuer MetaDaten Extract ist nicht korrekt eingestellt. Der uebergebenen datenpunkt war:$(datenpunkt)- Abbruch`, "Fehler", "ExtractMetaData", 0);
return null;
}
if(ExtractType === "ADAPTER") { return adapter; }
if(ExtractType === "GERAETEID") { return GeraeteID; }
if(ExtractType === "MELDUNGSART") {
const meldungsart = teile[struktur.AlarmFeld]; // Extrahiere Felder basierend auf der Strukturdefinition
return meldungsart;
}
if(ExtractType === "NATIVETYPE" && typeof struktur.nativeType === 'number') {
const MetaDP = datenpunkt.split('.').slice(0, struktur.nativeType).join('.'); // ergibt den Datenpunkt fuer getObject z.B. hm-rpc.1.00085D89B14067
const nativeType = getObject(MetaDP).native.TYPE;
return nativeType;
}
if(ExtractType === "NATIVETYPE" && typeof struktur.nativeType === 'string') { // bei string liegt der native-type im datenpunkt
let DPNativeTypeDP = struktur.nativeType;
DPNativeTypeDP = DPNativeTypeDP.replace('xidx', GeraeteID);
DPNativeTypeDP = DPNativeTypeDP.replace('xinstancex', instance);
let nativeType = getState(DPNativeTypeDP).val
return nativeType;
}
if(ExtractType === "COMMONNAME" && typeof struktur.common_name === 'number') {
const MetaDP = datenpunkt.split('.').slice(0, struktur.common_name).join('.'); // ergibt den Datenpunkt fuer getObject z.B. hm-rpc.1.00085D89B14067
const common_name = getObject(MetaDP).common.name;
return common_name;
}
if(ExtractType === "COMMONNAME" && typeof struktur.common_name === 'string') { // bei string liegt der common_name im datenpunkt
let DPcommmon_name = struktur.common_name;
DPcommmon_name = DPcommmon_name.replace('xidx', GeraeteID).replace('xinstancex', instance);
let common_name = getState(DPcommmon_name).val
return common_name;
}
LOG(`Der Datenpunkt/ExtractType, die erforderlich zur Ermittlung von Metadaten sind nicht bekannt Der Datenpunkt lautet ${datenpunkt} ExtractType ${ExtractType} - Abbruch`, "Fehler", "ExtractMetaData", 0);
return null
}
//-----------------------------------------------------------------------------------------------------
// Funktion CheckForNaNinFilterDefinition checked ob in der filter Tabelle eintragungen ausserhalb der hochkomma vorgenommen wurden (z.B. Minus ausserhalb der Hochkomma ist ein NaN)
//-----------------------------------------------------------------------------------------------------
function CheckForNaNinFilterDefinition(filterDef) {
for (const filterKey in filterDef) {
const filter = filterDef[filterKey];
for (const key of ['raumTyp', 'raumPosition', 'geraetetypen', 'raeume', 'Datenpunkte']) {
if (Array.isArray(filter[key])) {
for (const val of filter[key]) {
if (typeof val !== 'string' || val === 'NaN') {
LOG(`[Fehler] In "${filterKey}.${key}" wurde ein ungueltiger Wert erkannt: ${val} Beispiel: du hast ein Minus asserhalb der Hochkomma im Filter eingegeben`, "error", "CheckForNaNinFilterDefinition", 0,"error");
}
}
}
}
}
}
//-----------------------------------------------------------------------------------------------------
// func_get_datum aktuelles Datum formatiert
//-----------------------------------------------------------------------------------------------------
function func_get_datum(id) {
let datum;
if (id && getState(id)) {
datum = formatDate(getState(id).lc, "TT.MM.JJ SS:mm:ss");
} else {
datum = formatDate(new Date(), "TT.MM.JJ SS:mm:ss"); // Aktuelles Datum
}
let datumDate = new Date(datum); // korrekte verwendung des datumsformates
let cutoffDate = new Date('1971-01-01T01:00:00');
return datumDate < cutoffDate ? '' : `${datum} Uhr`;
}
//-----------------------------------------------------------------------------------------------------
// pruefeProtokollierung // ueberprueft ob ein JSON Protokoll geschrieben werden soll
//-----------------------------------------------------------------------------------------------------
function pruefeProtokollierung(typ, aktiv) {
// Suche den passenden Eintrag in der Tabelle
const eintrag = JSON_Protokollierung.find(
item => item.Typ === typ && item.Aktiv === aktiv
);
if (!eintrag) { return true; } // im zweifel wird das Protokoll geschrieben
const alarmScharf = getState(DP_Alarmaktivierung).val; // Lies den Alarmstatus aus
// Ueberpruefe die Bedingungen
const protokollierenGrundsaetzlich = eintrag.Protokoll_Schreiben;
const nurBeiAlarm = eintrag.NurWennAlarmScharf !== null ? eintrag.NurWennAlarmScharf : true;
// Wenn NurWennAlarmScharf true ist, muss der Alarm auch scharf sein
const alarmBedingungErfuellt = !nurBeiAlarm || alarmScharf;
// Alle drei Bedingungen muessen erfuellt sein
return protokollierenGrundsaetzlich && alarmBedingungErfuellt;
}
//-----------------------------------------------------------------------------------------------------
// ReplaceString // ersetzen entsprechend tabelle replacements
//-----------------------------------------------------------------------------------------------------
function ReplaceString(string) {
for (const [key, value] of Object.entries(replacements)) {
// Escape den Punkt (.) fuer den regulaeren Ausdruck
const escapedKey = key.replace('.', '\\.');
string = string.replace(new RegExp(escapedKey, 'g'), value);
}
return string;
}
//-----------------------------------------------------------------------------------------------------
// getRaumUebersichtVerdichtet // erzeugt eine Raumkarte der aktivierten Geraete
//-----------------------------------------------------------------------------------------------------
function getRaumUebersichtVerdichtet() {
const overview = {};
for (const scenario in ScenarioToDPsTable) {
const dps = ScenarioToDPsTable[scenario];
for (const dp of dps) {
for (const raum in raumDefinition) {
const def = raumDefinition[raum];
for (const geraetetyp in def) {
if (geraetetyp === "Type" || geraetetyp === "Position") continue;
const dpList = def[geraetetyp];
if (Array.isArray(dpList) && dpList.includes(dp)) {
// Initialisierung
if (!overview[raum]) {
overview[raum] = {
szenarien: new Set(),
geraete: {},
gezaehlteDPs: {} // zur Vermeidung von Doppelzaehlungen
};
}
// Szenario hinzufuegen
overview[raum].szenarien.add(scenario);
// Doppelzaehlung pro Geraetetyp verhindern
if (!overview[raum].gezaehlteDPs[geraetetyp]) {
overview[raum].gezaehlteDPs[geraetetyp] = new Set();
}
if (!overview[raum].gezaehlteDPs[geraetetyp].has(dp)) {
overview[raum].gezaehlteDPs[geraetetyp].add(dp);
if (!overview[raum].geraete[geraetetyp]) {
overview[raum].geraete[geraetetyp] = 0;
}
overview[raum].geraete[geraetetyp]++;
}
}
}
}
}
}
// Ergebnis formatieren
const result = {};
for (const raum in overview) {
result[raum] = {
szenarien: Array.from(overview[raum].szenarien),
geraete: overview[raum].geraete
};
}
return result;
}
//-----------------------------------------------------------------------------------------------------
// ReplaceString // ersetzen entsprechend tabelle replacements
//-----------------------------------------------------------------------------------------------------
function renderScenarioRoomDeviceHTML() {
const map = getRaumUebersichtVerdichtet();
const styles = HTML_Raumzuordnungen_WidthAndStyles.styles;
const headers = HTML_Raumzuordnungen_WidthAndStyles.headerTexts;
const widths = HTML_Raumzuordnungen_WidthAndStyles.columnWidths;
const visibleCols = HTML_Raumzuordnungen_WidthAndStyles.columns;
let html = ``;
html += `
`;
if (visibleCols.raum?.visible) html += `| ${headers.raum} | `;
if (visibleCols.scenario?.visible) html += `${headers.scenario} | `;
if (visibleCols.deviceType?.visible) html += `${headers.deviceType} | `;
html += `
`;
const sortedRaeume = Object.keys(map).sort();
for (const raum of sortedRaeume) {
html += ``;
if (visibleCols.raum?.visible) {
html += `| ${raum} | `;
}
if (visibleCols.scenario?.visible) {
const szenarien = map[raum].szenarien.map(s => {
if (s === "NichtAktiv") return "nicht aktiv";
return `${s} (${scenarioDefinition[s]?.name || "?"})`;
}).join(', ');
html += `${szenarien} | `;
}
if (visibleCols.deviceType?.visible) {
const geraete = Object.entries(map[raum].geraete)
.map(([typ, count]) => `${typ}: ${count}`)
.join('
');
html += `${geraete} | `;
}
html += `
`;
}
if (sortedRaeume.length === 0) {
html += `| Keine Zuordnungen gefunden. |
`;
}
html += `
`;
return html;
}
//-----------------------------------------------------------------------------------------------------
// generateHtmlTable // Generieren HtML Table
//-----------------------------------------------------------------------------------------------------
function generateHtmlTable(data, HistOderAkt) {
LOG(`Routine wird ausgefuehrt fuer HTML-Datenset ${HistOderAkt}`, "Ablauf", "generateHtmlTable", 2);
if (!UpdateHTML_Datenpunkte) return;
const tableId = "alarm-table-" + Math.random().toString(36).substr(2, 9);
const w = HTML_TableWidthsAndStyles.columnWidths;
const s = HTML_TableWidthsAndStyles.styles;
const h = HTML_TableWidthsAndStyles.headerTexts;
const c = HTML_TableWidthsAndStyles.columns;
// Tabellenstruktur
let table = `
`;
// Tabellenkopf
Object.keys(c).forEach(colKey => {
if (c[colKey].visible) {
table += `
|
${h[colKey]}
| `;
}
});
table += `
`;
// Tabelleninhalt
data.forEach((item, index) => {
const rowColor = index % 2 === 0 ? s.evenRowColor : s.oddRowColor;
const textColor = index % 2 === 0 ? s.evenRowTextColor : s.oddRowTextColor;
table += ``;
Object.keys(c).forEach(colKey => {
if (!c[colKey].visible) return;
const value = (() => {
switch(colKey) {
case 'datum': return item.datum_seit || NichtRelevantText;
case 'alarmtype': return item.Alarmtype || NichtRelevantText;
case 'raum': return item.Raum || NichtRelevantText;
case 'deviceType': return item.deviceType || NichtRelevantText;
case 'scenario': return item.Scenario || NichtRelevantText;
case 'deviceName': return item.common_name || NichtRelevantText;
case 'deviceId': return item.GeraeteId || NichtRelevantText;
case 'message': return item.Nachricht || 'Keine Meldung';
case 'quittiert': return item.Quittiert_seit || 'aktiv';
default: return NichtRelevantText;
}
})();
table += `| ${value} | `;
});
table += `
`;
});
table += `
`;
const targetDP = HistOderAkt === "Akt"
? id_HTML_Alarmmeldung_Aktuell
: id_HTML_Alarmmeldung_Historie;
setState(targetDP, table);
}
//-----------------------------------------------------------------------------------------------------
// Funktion schreibt die System-Log-Eintraege in ein externes CSV - File
//-----------------------------------------------------------------------------------------------------
function LOG(Message,Kategorie,Routine, Level, type) {
if(type !== "warn" && type !== "error") {type = "info"}
if (!SystemLog && debugLevel >= Level) { log(Message+" Routine:"+Routine,type); return;} // Wenn SystemLog false ist und der Debug-Level hoeher oder gleich dem uebergebenen Level, schreibe normalen Logeintrag
if (Level === 0) { log(Message+" Routine:"+Routine,type);} // bei level 0 soll auf jeden fall auch in das normale log geschrieben werden
if (SystemLog && debugLevel >= Level) { // Wenn SystemLog true ist und der Debug-Level hoeher oder gleich dem uebergebenen Level
const fs = require('fs');
const now = new Date();
const logdate = `${now.getDate().toString().padStart(2, '0')}.${(now.getMonth() + 1).toString().padStart(2, '0')}.${now.getFullYear()}`;
const logtime = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}:${now.getMilliseconds().toString().padStart(3, '0')}`;
const logEntry = `${logdate};${logtime};${Level};${Kategorie};${Routine};${Message}\n`;
const headerLine = "Datum;Uhrzeit;Debug Level;Kategorie;Routine;Log-Message\n";
const logFilePath = PathSystemLog || './defaultLog.csv'; // falls PathSystemLog nicht definiert ist, standardmaessigen Pfad verwenden
try {
if (!fs.existsSync(logFilePath)) {
log(`Routine:LOG - Logfile nicht gefunden - wird angelegt`,"info")
fs.writeFileSync(logFilePath, headerLine + logEntry, 'utf8'); // Datei erstellen und Header hinzufuegen
} else {
fs.appendFileSync(logFilePath, logEntry, 'utf8'); // Eintrag zum Logfile hinzufuegen
}
} catch (err) {
log(`Routine:LOG - Fehler beim Schreiben in das Logfile: ${err}`, "error"); // Fehler beim Schreiben
}
}
}
//-----------------------------------------------------------------------------------------------------
// Funktion ListSystemSubscriptions da es einen fehler in iobroker gibt mit async werden in iobroker die anzahl der subscriptions nicht richtig ausgegeben
//-----------------------------------------------------------------------------------------------------
function ListSystemSubscriptions() {
const subscriptions = getSubscriptions(); // Hole alle Subscriptions
let selectedsubscriptions = 0; // Zaehler fuer ausgewaehlte Subscriptions
let subscriptionCount = 0; // Zaehler fuer die Gesamtzahl der Subscriptions
for (var dp in subscriptions) { // Iteriere ueber die Subscriptions
for (var i = 0; i < subscriptions[dp].length; i++) {
if (subscriptions[dp][i].name && subscriptions[dp][i].name.includes("script.js.common.Anwesenheit.Alarm")) { // Pruefe, ob der Name der Subscription den Skriptnamen "ALARM" enthaelt
selectedsubscriptions++; // Erhoehe den Zaehler fuer ausgewaehlte Subscriptions
}
subscriptionCount++; // Zaehle jede Subscription
}
}
LOG(`Anzahl der Subscriptions fuer das Skript "ALARM": ${selectedsubscriptions} / Gesamt: ${subscriptionCount}`, "Ergebnis", "ListSystemSubscriptions", 0);
}
//-----------------------------------------------------------------------------------------------------
// Funktion Create States/
//-----------------------------------------------------------------------------------------------------
async function CreateStates() {
LOG(`Routine wird ausgefuehrt`, "Ablauf", "CreateStates", 2);
try {
// Erstelle die Promises fuer die asynchronen createStateAsync-Aufrufe
const promises = [];
for (const { alarmtype, Datenpunkt } of AlarmTypes) { // AlarmTypes Datenpunkte erstellen
promises.push(createStateAsync(Datenpunkt, false, {
read: true,
write: true,
type: 'boolean',
name: `${alarmtype} Alarm`,
desc: `Status des ${alarmtype} Alarms`
}));
}
for (const { Control, Datenpunkt } of AlarmScenarios) { // Scenarios Datenpunkte erstellen
promises.push(createStateAsync(Datenpunkt, false, {
read: true,
write: true,
type: 'boolean',
name: `${Control} Steuerung`,
desc: `Steuerung fuer ${Control}`
}));
}
promises.push(createStateAsync(DP_Alarmaktivierung, false, { // Alarmaktivierung datenpunkt
read: true,
write: true,
type: 'boolean',
name: 'Alarm Aktivierung / Deaktivierung',
desc: 'true = Alarm ist aktiviert / false = Alarm ist deaktiviert'
}));
promises.push(createStateAsync(DP_Still, false, { // STILL datenpunkt
read: true,
write: true,
type: 'boolean',
name: 'Still = Alarmsirenen aus ',
desc: 'laufende Alarmgeraete werden ausgeschaltet - neuer Alarm ist immer still'
}));
promises.push(createStateAsync(id_JSON_Alarmmeldung_Aktuell, "", { // Zusaetzliche Datenpunkte fuer Alarmmeldungen und Historie
read: true,
write: true,
type: 'string',
name: 'Aktuelle Alarmmeldungen als JSON',
desc: 'Aktuelle Alarmmeldungen JSON'
}));
promises.push(createStateAsync(id_JSON_Alarmmeldung_Historie, "", {
read: true,
write: true,
type: 'string',
name: 'Alarmmeldungen History',
desc: 'History Alarmmeldungen'
}));
promises.push(createStateAsync(id_Button_Refresh_Historie, false, {
read: true,
write: true,
type: 'boolean',
name: 'Refresh Button History',
desc: 'Loescht die Historie Alarmmeldungen wenn true'
}));
promises.push(createStateAsync(id_History_VerbleibendeTage, 7, {
read: true,
write: true,
type: 'number',
name: 'Anzahl der zu blebienden Alarmmeldungen beim Loeschen',
desc: 'Behaelt die angegebene Anzahl von Tagen in der History'
}));
if(UpdateHTML_Datenpunkte) {
promises.push(createStateAsync(id_HTML_Alarmmeldung_Aktuell, "", {
read: true,
write: true,
type: 'string',
name: 'Protokoll Alarmmeldunge HTML-Format',
desc: 'Protokoll Alarmmeldunge HTML-Format aus JSON generiert'
}));
promises.push(createStateAsync(id_HTML_Alarmmeldung_Historie, "", {
read: true,
write: true,
type: 'string',
name: 'Protokoll Alarmmeldunge HTML-Format',
desc: 'Protokoll Alarmmeldunge HTML-Format aus JSON generiert'
}));
}
promises.push(createStateAsync(DP_Raumuebersicht, "", {
read: true,
write: true,
type: 'string',
name: 'Raumuebersicht im HTML Format',
desc: 'ebersicht aller Raeume in HTML Format'
}));
if(!UpdateHTML_Datenpunkte) { // nur wenn Text-Datenpunkte auch gewuenscht sind
if (existsState(id_HTML_Alarmmeldung_Aktuell)) { deleteState(id_HTML_Alarmmeldung_Aktuell); }
if (existsState(id_HTML_Alarmmeldung_Historie)) { deleteState(id_HTML_Alarmmeldung_Historie); }
}
await Promise.all(promises); // Warten, bis alle createStateAsync-Aufrufe abgeschlossen sind
LOG(`Alle Datenpunkte sind in der Objektliste vorhanden/erstellt!`, "Ablauf", "CreateStates", 2);
await updateScenario(); // Nun updateScenario auf
LOG(`updateScenario abgeschlossen.`, "Ablauf", "CreateStates", 2);
await subscribeToAllScenarioDPs(); // Nach updateScenario createSubscriptions auf
LOG(` abgeschlossen.`, "Ablauf", "CreateStates", 2);
await createAktivierungsSubscription(); // Optional: Fuehre createAktivierungsSubscription danach aus
LOG(`createAktivierungsSubscription abgeschlossen.`, "Ablauf", "CreateStates", 2);
await ListSystemSubscriptions();
LOG(`CountSubscriptions abgeschlossen.`, "Ablauf", "CreateStates", 2);
await Zustandsermittlung("init") // ermittelt den initialen Zustand von Fenster, Rollladen, Tueren, Sabotage und Stoerungen
LOG(`Zustandsermittlung im Initiallauf abgeschlossen.`, "Ablauf", "CreateStates", 2);
} catch (error) {
LOG(`Kategorie:WARN; Routine:CreateStates; Fehler: ${error}`, 0, "warn");
}
}