// Beschreibung des Scriptes: https://forum.iobroker.net/topic/77816/vorlage-servicemeldungen-volume2 // Autor Looxer01 02.11.2024 Version 1.0 (initiale Version) // Version 1.20 - 19.11.24 Optimierung der Selektoren // Kurztext Option fuer Messenger hinzugefuegt // Einstellungsbereich restrukturiert // Code Optimierungen // Version 1.30 - 22.11.24 Kommentare hinzugefuegt // Alarmtypes koennen fuer jede Instanz getrennt ausgeschlossen werden // Ausschlussliste geht jetzt auch bei REGA subscription // 3 Alarmtypes entfernt (error_reduced / U_Source_Fail / USBH_POWERFAIL) // System Log kann extern gespeichert werden // LOW_BAT_ALARM -Log angepasst // Version 1.31 - 22.11.24 Korrekturen bei Log Meldungen // Version 1.32 - 25.11.24 SubscriptionID umgestellt auf For-Schleife // Log Messages hinzugefügt // subscribe gaendert (Es gab sporadisch nicht gemeldete Servicemeldungen) // Schwellwert fuer Ausführungsverzögerung von 50 auf 25 reduziert // Pause reduziert von 5 auf 3 Minuten //--------------------------------------------------------------------------------------------------------------------------------------------------------------- // Muss-Einstellungen ( HM-Classic , HMIP, Wired - hier muessen zwingend die Instanzen stimmmen ) //--------------------------------------------------------------------------------------------------------------------------------------------------------------- // Im folgenden sind die Instanzen gelistet fuer die die Selektion erfolgt bzw nicht erfolgt (Filter) // bitte 9 Eintragen falls eine Instanz nicht relevant ist // Gruppeninstanzen sind normalerweise nicht relevant // CuxD Instanzen duerfen nicht eingetragen werden const HMClassicInstanz = 0; // HM-Classic Instanz eintragen // 9 = falls nicht relevant const HMIPInstanz = 1; // Homematic IP instanz // 9 = falls nicht relevant const WiredIClassicInstanz = 9; // Wired Instanz // 9 = falls nicht relevant const GruppenInstanz = 9; // bzw virtuelle GeraeteInstanz- 9 = nicht relevant - Die Gruppen werden i.d.R. nicht gebraucht - Empfehlung: 9 //--------------------------------------------------------------------------------------------------------------------------------------------------------------- // Kann-Einstellungen //--------------------------------------------------------------------------------------------------------------------------------------------------------------- // Pfad kann angepasst werden fuer userdata pfad einfach // entfernen const path = "javascript.0.ServicemeldungenVol2."; //const path = "0_userdata.0.ServicemeldungenVol2."; // alternativ zum javascript pfad // schreibt das Protokoll der Servicemeldungen in ein externes file (Excel Format) / nur bei GeraeteIDTrigger = true const SMProtokoll = true; const PathSMLog = "/opt/iobroker/log/ServicemeldungenVol2.csv"; // Pfad und Dateiname des externen Logsconstst / nur bei GeraeteIDTrigger = true //const PathSMLog = "/iobroker/log/ServicemeldungenVol2.csv";"; // Pfad fuer Windows/ nur bei GeraeteIDTrigger = true // iobroker ist der angenommene iobroker home-pfad // Text der erscheinen soll, wenn keine SM vorliegen const MessageBeiKeinerSM = 'Derzeit keine Servicemeldungen' // auf '' setzen wenn kein Text gezeigt werden soll //Geraete die nicht ueberwacht werden sollen. Geraete-IDs eingeben - Komma getrennt erfassen const Ausschlussliste = ['0000D7099xxx26', '00091D8xxx7410']; // immer mit Komma trennen // Filter auf Einzel-IDs // debug level kann eingestellt werden - wenn alles laeuft dann 0 = ruhe im log // debug level 0 kein log // debug level 1 - nur die wichtigsten Meldungen werden gelistet // debug level 2 - mehr als nur die wichtigsten Meldungen aber ohne einzelne IDs // debug level 3 - hier werden auch einzelne IDs gelistet (koennten lange listen werden) const debugLevel = 0 ; const SystemLog = false; // schreib das Sytemprotokoll in ein externes log (sollte normalerweise deaktviert sein nur bei Problemen verwenden) const PathSystemLog = "/opt/iobroker/log/ServicemeldungenSystemLog.csv"; // Pfad und Dateiname des externen Logs //const PathSystemLog = "/iobroker/log/ServicemeldungenSystemLog.csv";"; // Pfad fuer Windows // wenn GeraeteIDTrigger auf true gestellt wird, dann wird fuer jeden Datenpukt mit Relevanz fuer eine Servicemeldung eine subscription angelegt. // Vorteil ist, dass auch eine Historie und ein Log fuer Servicemeldungen geschrieben werden kann: Nachteil: bei 80 CCU Geraeten ungefaehr 300 Susbsriptions // Wenn die variable auf false steht, dann wird auf hm.rega.0.maintenance eine subsription angelegt: Vorteil: 1 Subscription , Nachteil: keine Servicemeldungs Historie const GeraeteIDTrigger = true; // true = viele subscriptions - false = 1 subscription // fuer alle Spalten mit true werden die Nachrichten ueber den zugeordneten Dienst versendet // Voraussetzung ist, dass der entsprechende Adapter installiert und konfiguriert ist const services = ['email', 'whatsApp', 'Signal', 'Telegram', 'Pushover', 'Pushsafer']; const MessengerScope = { 'UNREACH_ALARM': [false, true, false, false, false, false], 'LOWBAT_ALARM': [false, true, false, false, false, false], 'SABOTAGE_ALARM': [false, true, false, false, false, false], 'CONFIG_PENDING': [false, true, false, false, false, false], 'Sonstige': [false, false, false, false, false, false], 'keineSM': [false, true, false, false, false, false], } const MessengerInstanz = [0, 0, 0, 0, 0, 0 ]; // Instanz des Messengers const TextTypeKurz = [false, true, true, true, true, true ]; // bei true wird der Kurztext gesendet - sonst der Langtext // email-Einstellungen const emailAddresse = "Vorname-Nachname@web.de" const Headline = "ioBroker Servicemeldung"; // Ueberschrift Messages fuer email und Pushsafer // telegram Einstellungen const TelegramUser = ""; //----------------------------------------------------------------------------------------------------- //Experten Einstellungen //----------------------------------------------------------------------------------------------------- // Texte werden in "history" hinzugefuegt und etsprechend des schedules wieder geloescht -- nur relvant wenn GeraeteIDTrigger = false const scheduleTimeClearSMTexte = "2 0 1 * *"; // Sam 1. tag des monats um 00:02 morgens - sollen alle Servicemeldungen der Woche datenpunkt der SM-Texte geloescht werden // const scheduleTimeClearSMTexte = "58 23 * * 0"; // alernative Sonntags um 23:58 Uhr sollen alle Servicemeldungen der Woche im datenpunkt der SM-Texte geloescht werden //Batterie-Zuordnungen fuer Servicmeldungen const batteryTypes = { '1x CR2016': ['HM-RC-4', 'HM-RC-4-B', 'HM-RC-Key3', 'HM-RC-Key3-B', 'HM-RC-P1', 'HM-RC-Sec3', 'HM-RC-Sec3-B', 'ZEL STG RM HS 4'], '1x CR2032': ['HM-PB-2-WM', 'HM-PB-4-WM', 'HM-PBI-4-FM', 'HM-SCI-3-FM', 'HM-Sec-TiS', 'HM-SwI-3-FM', 'HmIP-FCI1'], '2x LR14': ['HM-Sec-Sir-WM', 'HM-OU-CFM-TW', 'HM-OU-CFM-Pl', 'HM-OU-CF-Pl'], '2x LR44/AG13': ['HM-Sec-SC', 'HM-Sec-SC2L', 'HM-Sec-SC-2', 'HM-Sec-RHS'], '2x LR6/AA': ['HM-CC-VD', 'HM-CC-RT-DN', 'HM-Sec-WDS', 'HM-Sec-WDS-2', 'HM-CC-TC', 'HM-Dis-TD-T', 'HB-UW-Sen-THPL-I', 'HM-WDS40-TH-I', 'HM-WDS40-TH-I-2', 'HM-WDS10-TH-O', 'HmIP-SMI', 'HMIP-eTRV', 'HM-WDS30-OT2-SM-2', 'HmIP-SMO', 'HmIP-SMO-A', 'HmIP-SPI', 'HmIP-eTRV-2', 'HmIP-SPDR', 'HmIP-STHO-A', 'HmIP-eTRV-B', 'HmIP-PCBS-BAT', 'HmIP-STHO', 'HmIP-eTRV-C', 'HmIP-WGC', 'HmIP-eTRV-C-2', 'HmIP-eTRV-E', 'HmIP-eTRV-2 I9F', 'HmIP-eTRV-E-S', 'ELV-SH-SW1-BAT'], '3x LR6/AA': ['HmIP-SWO-PL', 'HM-Sec-MDIR', 'HM-Sec-MDIR-2', 'HM-Sec-SD', 'HM-Sec-Key', 'HM-Sec-Key-S', 'HM-Sec-Key-O', 'HM-Sen-Wa-Od', 'HM-Sen-MDIR', 'HM-Sen-MDIR-O', 'HM-Sen-MDIR-O-2', 'HM-WDS100-C6-O', 'HM-WDS100-C6-O-2', 'HmIP-ASIR', 'HmIP-SWO-B', 'HM-Sen-MDIR-O-3', 'HM-Sec-MDIR-3', 'HmIP-SWO-PR', 'HmIP-DLD', 'HmIP-ASIR-2'], '4x LR6/AA': ['HM-CCU-1', 'HM-ES-TX-WM', 'HM-WDC7000'], '1x LR3/AAA': ['HM-RC-4-2', 'HM-RC-4-3', 'HM-RC-Key4-2', 'HM-RC-Key4-3', 'HM-RC-Sec4-2', 'HM-RC-Sec4-3', 'HM-Sec-RHS-2', 'HM-Sec-SCo', 'HmIP-KRC4', 'HmIP-KRCA', 'HmIP-SRH', 'HMIP-SWDO', 'HmIP-DBB', 'HmIP-RCB1', 'HmIP-KRCK', 'HmIP-SWDO-2'], '2x LR3/AAA': ['HmIP-WRCR', 'HmIP-SWD','HM-TC-IT-WM-W-EU', 'HM-Dis-WM55', 'HM-Dis-EP-WM55', 'HM-PB-2-WM55', 'HM-PB-2-WM55-2', 'HM-PB-6-WM55', 'HM-PBI-2-FM', 'HM-RC-8', 'HM-Sen-DB-PCB', 'HM-Sen-EP', 'HM-Sen-MDIR-SM', 'HM-Sen-MDIR-WM55', 'HM-WDS30-T-O', 'HM-WDS30-OT2-SM', 'HmIP-STH', 'HmIP-STHD', 'HmIP-WRC2', 'HmIP-WRC6', 'HmIP-WTH', 'HmIP-WTH-2', 'HmIP-SAM', 'HmIP-SLO', 'HMIP-SWDO-I', 'HmIP-FCI6', 'HmIP-SMI55', 'HM-PB-2-FM', 'HmIP-SWDM', 'HmIP-SCI', 'HmIP-SWDM-B2', 'HmIP-RC8', 'ALPHA-IP-RBG', 'HmIP-DSD-PCB', 'HmIP-WRCD', 'HmIP-WRC2-A', 'HmIP-WTH-B-2', 'HmIP-WTH-A', 'HmIP-STV', 'HmIP-WKP'], '3x LR3/AAA': ['HM-PB-4Dis-WM', 'HM-PB-4Dis-WM-2', 'HM-RC-Dis-H-x-EU', 'HM-Sen-LI-O'], '3x AAA Akkus - bitte laden': ['HM-RC-19', 'HM-RC-19-B', 'HM-RC-12', 'HM-RC-12-B', 'HM-RC-12-W'], '3x LR14/C': ['HmIP-MP3P'], '9Volt Block leer oder unbestimmt': ['HM-LC-Sw1-Ba-PCB', 'HM-LC-Sw4-PCB', 'HM-MOD-EM-8', 'HM-MOD-Re-8', 'HM-Sen-RD-O', 'HM-OU-CM-PCB', 'HM-LC-Sw4-WM'], 'Festbatterie leer': ['HmIP-STE2-PCB', 'HM-Sec-SD-2', 'HmIP-SWSD', 'HmIP-PCBS'], 'ohne Batterie': ['HM-LC-Sw1PBU-FM', 'HM-LC-Sw1-Pl-DN-R1', 'HM-LC-Sw1-DR', 'HM-LC-RGBW-WM', 'HM-LC-Sw1-Pl-CT-R1', 'HmIP-HEATING', 'HM-LC-Sw1-FM', 'HM-LC-Sw2-FM', 'HM-LC-Sw4-DR', 'HM-LC-Sw1-Pl', 'HM-LC-Sw1-Pl-2', 'HM-LC-Sw4-Ba-PCB', 'HM-LC-Sw1-SM', 'HM-LC-Sw4-SM', 'HM-Sys-sRP-Pl', 'HM-LC-Sw2PBU-FM', 'HM-LC-Sw1-PCB', 'HM-LC-Sw4-DR-2'], 'Akku entladen - bitte aufladen': ['HM-Sec-Win', 'HM-Sec-SFA-SM', 'HM-RC-19-SW'] }; // Rega Pfad und TextPfade const PathRega = 'hm-rega.0.maintenance'; const id_Text_ServicemeldungLang = path+'TextLangAktuelleSM'; // Objekt wo die Servicemeldung hingeschrieben werden soll (String) = path+'TextLang'; // Objekt wo die Servicemeldung hingeschrieben werden soll (String) Lange (normale) Version const id_Text_ServicemeldungKurz = path+'TextKurzAktuelleSM'; // Objekt wo die Servicemeldung hingeschrieben werden soll (String) - kurze Version const id_Text_Servicemeldung_History = path+'TestLangVergangeneSM'; // Objekt wo die Servicemeldung hinzugefuegt werden soll (String) - Lange Version (es gibt nur lang) const id_JSON_Servicemeldung_Aktuell = path+'JSONAktuelleSM'; // JSON Tabelle Datenpunkt Aktuelle SM const id_JSON_Servicemeldung_Historie = path+'JSONVergangeneSM'; // JSON Tabelle Datenpunkt Historische SM // Count-Pfade (Zaehlungen) const id_IST_LOWBAT = path+'Anzahl_LOWBAT' // HM classic; & HMIP = const id_IST_UNREACH = path+'Anzahl_UNREACH' const id_IST_STICKY_UNREACH = path+'Anzahl_STICKY_UNREACH' //*Anzahl Sticky Unreach (zu bestaetigende unreach) const id_IST_CONFIG_PENDING = path+'Anzahl_CONFIG_PENDING'; const id_IST_UPDATE_PENDING = path+'Anzahl_Update_PENDING'; const id_IST_DEVICE_IN_BOOTLOADER = path+'Anzahl_DEVICE_IN_BOOTLOADER'; const id_IST_ERROR = path+'Anzahl_in_ERROR'; const id_IST_ERROR_NON_FLAT_POSITIONING = path+'Anzahl_NON_FLAT_POSITIONING'; const id_IST_FAULT_REPORTING = path+'Anzahl_FAULT_REPORTING'; const id_IST_SABOTAGE = path+'Anzahl_SABOTAGE'; const id_IST_STICKY_SABOTAGE = path+'Anzahl_Sticky_SABOTAGE'; const id_IST_Gesamt = path+'Anzahl_GESAMT' const id_IST_SMAktuell = path+'Anzahl_SM-Aktuell' // Standard Servicemeldungen, wenn alle anderen nicht zutreffen const StandardstatusMessages = ['keine Stoerung', 'Stoerung', 'bestaetigte Servicemeldung']; // hier sind alle bekannten Servicemeldungen zugeordnet (ueber Status-Datenpunkte des Geraetes) const statusMessages = { UNREACH_ALARM: ['keine Kommunikationsfehler', 'Kommunikation gestoert', 'Kommunikation war gestoert'], STICKY_UNREACH_ALARM: ['keine Kommunikationsfehler', 'Kommunikation gestoert', 'Kommunikation war gestoert'], SABOTAGE_ALARM: ['Keine Sabotage', 'Sabotage', 'Sabotage aufgehoben'], LOWBAT_ALARM: ['Batterie ok', 'Batterie niedrig', 'Batterie ok'], LOW_BAT_ALARM: ['Batterie ok', 'Batterie niedrig', 'Batterie ok'], ERROR_NON_FLAT_POSITIONING_ALARM: ['Keine Meldung', 'Geraet wurde angehoben.', 'Geraet wurde angehoben: Bestaetigt'], CONFIG_PENDING_ALARM: ['keine Meldung', 'Konfigurationsdaten stehen zur Uebertragung an', 'Konfigurationsdaten standen zur Uebertragung an'], UPDATE_PENDING_ALARM: ['kein Update verfuegbar', 'Update verfuegbar', 'Update wurde eingespielt'], DEVICE_IN_BOOTLOADER_ALARM: ['Keine Meldung', 'Geraet startet neu', 'Geraet wurde neu gestartet'], }; //ErrorMessages für HM-Classic Geraete const errorMessages = { 'HM-Sec-RHS': { 7: 'Sabotage' }, 'HM-Sec-RHS-2': { 7: 'Sabotage' }, 'HM-Sec-SC': { 7: 'Sabotage' }, 'HM-Sec-SC-2': { 7: 'Sabotage' }, 'HM-Sec-SCo': { 7: 'Sabotage' }, 'HM-Sec-MD': { 7: 'Sabotage' }, 'HM-Sec-MDIR': { 7: 'Sabotage' }, 'HM-Sec-MDIR-2':{ 7: 'Sabotage' }, 'HM-Sec-Key': { 1: 'Einkuppeln fehlgeschlagen', 2: 'Motorlauf abgebrochen' }, 'HM-Sec-Key-S': { 1: 'Einkuppeln fehlgeschlagen', 2: 'Motorlauf abgebrochen' }, 'HM-Sec-Key-O': { 1: 'Einkuppeln fehlgeschlagen', 2: 'Motorlauf abgebrochen' }, 'HM-CC-VD': { 1: 'Ventil Antrieb blockiert', 2: 'Ventil nicht montiert', 3: 'Stellbereich zu klein', 4: 'Batteriezustand niedrig' } }; //ErrorMessages für HM-Classic Geraet (Heizung) const faultMessages = { 'HM-CC-RT-DN': { 0: 'keine Stoerung', 1: 'Ventil blockiert', 2: 'Einstellbereich Ventil zu gross', 3: 'Einstellbereich Ventil zu klein', 4: 'Kommunikationsfehler', 6: 'Spannung Batterien/Akkus gering', 7: 'Fehlstellung Ventil' } }; // Umlaut Umwandlung und entfernung PUnkte - kann aber auch erweitert werden const replacements = { // Umwandlung fuer Namen der Geraete (common.name) '.': ' ', 'ä': 'ae', 'ü': 'ue', 'ö': 'oe', 'ß': 'ss' }; // moegliche Homematic Instanzen (CuxD ausgeschlossen) const instanceIds = [ { name: 'HMClassicInstanz', id: HMClassicInstanz }, { name: 'HMIPInstanz', id: HMIPInstanz }, { name: 'GruppenInstanz', id: GruppenInstanz }, { name: 'WiredIClassicInstanz', id: WiredIClassicInstanz }, ]; // hier koennen die Alarmgruppen ggf erweitert werden - Aus Alarmgruppe und Instanz wird der Selector gebastelt und in Tabelle Selectors gesammelt const alarmTypes = [ { key: 'UNREACH_ALARM', suffixes: ['UNREACH_ALARM'] }, { key: 'STICKY_UNREACH_ALARM', suffixes: ['STICKY_UNREACH_ALARM'] }, { key: 'CONFIG_PENDING_ALARM', suffixes: ['CONFIG_PENDING_ALARM'] }, { key: 'UPDATE_PENDING_ALARM', suffixes: ['UPDATE_PENDING_ALARM'] }, { key: 'LOWBAT_ALARM', suffixes: ['LOWBAT_ALARM', 'LOW_BAT_ALARM'] }, { key: 'DEVICE_IN_BOOTLOADER_ALARM', suffixes: ['DEVICE_IN_BOOTLOADER_ALARM'] }, { key: 'ERROR', suffixes: ['ERROR'] }, { key: 'FAULT_REPORTING', suffixes: ['FAULT_REPORTING'] }, { key: 'SABOTAGE_ALARM', suffixes: ['SABOTAGE_ALARM'] }, { key: 'ERROR_NON_FLAT_POSITIONING_ALARM', suffixes: ['ERROR_NON_FLAT_POSITIONING_ALARM'] }, { key: 'OVERHEAT_ALARM', suffixes: ['ERROR_OVERHEAT_ALARM'] }, { key: 'UNDERVOLTAGE_ALARM', suffixes: ['ERROR_UNDERVOLTAGE_ALARM'] }, ]; // Konfigurationsobjekt fuer Ausnahmen / es koennen Arlarmtypes je Instanz ausgeschlossen werden / Filter auf AlarmTypes const exceptions = { HMClassicInstanz: [], HMIPInstanz: [], GruppenInstanz: ['ERROR_NON_FLAT_POSITIONING_ALARM'], WiredIClassicInstanz: ['LOWBAT_ALARM', 'LOW_BAT_ALARM','ERROR_NON_FLAT_POSITIONING_ALARM'], }; LOG(`Kategorie:Autor: Looxer01 Datum 25.11.2024 Version ist 1.32`,`Einstellungen`,`Start`,0 ) LOG(`HMClassicInstanz: ${HMClassicInstanz}; HMIPInstanz: ${HMIPInstanz}; WiredIClassicInstanz: ${WiredIClassicInstanz}; GruppenInstanz: ${GruppenInstanz}`, "Einstellungen", "Start", 2); LOG(`GeraeteIDTrigger: ${GeraeteIDTrigger}`, "Einstellungen", "Start", 2); LOG(`Inhalt Messengerscope: ${JSON.stringify(MessengerScope)}`, "Einstellungen", "Start", 2); //LOG(`Inhalt Messengerscope: ${JSON.stringify(MessengerScope, null, 2)}`, "Einstellungen", "Start", 2); LOG(`Inhalt Services: ${JSON.stringify(services)}`, "Einstellungen", "Start", 2); LOG(`Inhalt Instanzen: ${JSON.stringify(MessengerInstanz)}`, "Einstellungen", "Start", 2); LOG(`Inhalt TextTypeKurz: ${JSON.stringify(TextTypeKurz)}`, "Einstellungen", "Start", 2); //----------------------------------------------------------------------------------------------------- //Ende Einstellungen //----------------------------------------------------------------------------------------------------- let totalIdsCount = 0; // Variable für die Gesamtzahl der IDs const selectors = alarmTypes.map(alarmType => { const collectedIds = []; instanceIds.forEach(instance => { if (instance.id !== 9) { // Prüfen, ob der aktuelle Alarmtype für die Instanz ausgeschlossen ist const excludedAlarmTypes = exceptions[instance.name] || []; // Hole Ausnahmen oder leere Liste if (!excludedAlarmTypes.includes(alarmType.key)) { // Wenn nicht in der Ausnahme alarmType.suffixes.forEach(suffix => { // @ts-ignore $(`state[id=hm-rpc.${instance.id}.*.${suffix}]`).each(id => collectedIds.push(id)); // IDs sammeln }); } } }); const filteredIds = collectedIds.filter(id => { // IDs anhand der Geräte-IDs in der Ausschlussliste filtern const deviceId = id.split('.')[2]; // Geräte-ID aus der vollständigen ID extrahieren (z. B. "0000D7099xxx26" aus "hm-rpc.1.0000D7099xxx26.0.LOW_BAT_ALARM") return !Ausschlussliste.includes(deviceId); // Behalten, wenn nicht in der Ausschlussliste }); totalIdsCount += filteredIds.length; // IDs zur Gesamtzahl hinzufügen return { name: alarmType.key, ids: filteredIds }; }); LOG(`Es wurden ${totalIdsCount} IDs für alle Alarmtypen gefunden. Alle drei Filter (Instanzen, Alarmtypen, Einzel-IDs) angewendet`, "Ablauf", "Start", 0); // Ergebnisse loggen (pro Alarmtype) selectors.forEach(selector => { // Ergebnisse loggen (pro Alarmtyp) if(SystemLog) { LOG(`für den Alarmtype: ${selector.name} wurden ${selector.ids.length} IDs gefunden${debugLevel >= 3 ? ` ${JSON.stringify(selector.ids)}` : ''}`, "Ablauf", "Start", 3); }else{ LOG(`für den Alarmtype: ${selector.name} wurden ${selector.ids.length} IDs gefunden${debugLevel >= 3 ? ` ${JSON.stringify(selector.ids)}` : ''}`, "Ablauf", "Start", 0); } }); let AktuelleSMjsonString = [] // alle aktuellen Servicemeldungen als JSON // Aufbau MessageCollector - Beispiel //{ "ERROR": { "Email": [ "Error Alarm " ], "SMS": ["Error" ] }, // "SABOTAGE_ALARM": { "Email": [ "Alarm: Eindringling im Gebaeude!"], "SMS": [ "Eindringling" ] } } let MessageSendCollector = {}; // alle aktuellen Servicemeldungen zum senden an messaging services // Initialisierung des Objekts MessageCollector fuer alle Alarmtypen und Services alarmTypes.forEach((type) => { const alarmKey = type.key; // Verwende die `key`-Eigenschaft als Schluessel MessageSendCollector[alarmKey] = {}; // Erstelle ein Objekt fuer den Alarmtyp services.forEach((service) => { MessageSendCollector[alarmKey][service] = []; // Erstelle ein leeres Array fuer jeden Service }); }); // create States CreateStates(() => { Check_All() // Check_All wird aufgerufen, wenn CreateStates fertig ist (callback) }); // Subscriptions erstellen if (GeraeteIDTrigger) { // Subscription auf IDs SubscribeGeraeteID(); }else{ // Subscription auf HMREGA (nur 1 Subscripton) SubscribeRegaDP(); } //----------------------------------------------------------------------------------------------------- // Schedule zum Loeschen des Datenpunktwertes der Histore einmal pro Tag //----------------------------------------------------------------------------------------------------- schedule(scheduleTimeClearSMTexte, function() { setState(id_Text_Servicemeldung_History, ''); setState(id_JSON_Servicemeldung_Historie, []); LOG(`Datenpunkte (Text und JSON) ${id_Text_Servicemeldung_History} geloescht`,`Ablauf`,`Start`,2); }); //----------------------------------------------------------------------------------------------------- // Funktion SubscribeRegaDP // Erstellung der Subscriptions auf REGA Datenpunkt // es werden 5 Sekunden lang vorliegende Aenderungen gesammelt //----------------------------------------------------------------------------------------------------- // Initalisierung Variablen fuer den Timer, um die 5-Sekunden-Wartezeit zu steuern - relevant fuer HM-REGA subscription let changeTimeout = null; // Globale Variable - wird in funtion servicemeldung wieder zurückgesetzt let countIdsInnerhalbTimeout = 0; // Globale Variable - wird in funtion servicemeldung wieder zurückgesetzt function SubscribeRegaDP() { LOG(`Routine wird ausgefuehrt - Pfad des Datenpunkts: ${PathRega}`, "Ablauf", "SubscribeRegaDP", 2); on({id: PathRega, change: 'any'}, function (obj) { LOG(`Subscription Datenpunkt ${PathRega} getriggert. Neuer Wert: ${obj.state.val} alter Wert:${obj.oldState.val}`, "Ablauf", "SubscribeRegaDP", 2); if (obj.state.val === obj.oldState.val) { // Ãœberprüfen, ob sich der Wert des Datenpunkts geändert hat return; // Keine Änderung, also nichts tun } countIdsInnerhalbTimeout++; LOG(`Subscription Datenpunkt wird ausgefuehrt ${PathRega} aktiv. Neuer Wert: ${obj.state.val}`, "Ablauf", "SubscribeRegaDP", 2); if (changeTimeout) { // Falls schon ein Timer läuft, diesen abbrechen clearTimeout(changeTimeout); } changeTimeout = setTimeout(function() { // Setze einen neuen Timer auf 5 Sekunden, um die Funktion Servicemeldung aufzurufen Servicemeldung(); // Funktion wird nach 5 Sekunden ohne weitere Änderungen aufgerufen }, 5000); // 5000 ms = 5 Sekunden }); } // ende funktion //----------------------------------------------------------------------------------------------------- // Funktion Subscribe GeraeteID // Erstellung der Subscriptions auf GeraeteID und Datenpunkt Ebene //----------------------------------------------------------------------------------------------------- function SubscribeGeraeteID() { LOG(`Routine SubscribeGeraeteID wird ausgefuehrt`, "Ablauf", "SubscribeGeraeteID", 2); let callCount = 0; // Zähler für Aufrufe // maximal 25 let timeoutActive = false; // Status für Timeout for (let j = 0; j < selectors.length; j++) { // Schleife über alle Selectoren const selector = selectors[j]; if (selector.ids.length > 0) { // Schleife über IDs der Selektoren LOG(`Erstellung der Subscriptions - Prozessiere selector: ${selector.name}`, "Ablauf", "SubscribeGeraeteID", 2); for (let i = 0; i < selector.ids.length; i++) { // Schleife über alle IDs des aktuellen Selectors const id = selector.ids[i]; LOG(`Erstellung der Subscriptions - Prozessiere Datenpunkt: ${id}`, "Ablauf", "SubscribeGeraeteID", 3); on({ id: id, change: "ne" }, obj => { if (obj.state.val === obj.oldState.val) { LOG(`Datenpunkt ${obj.id} hatte keine Aenderung`, "Ablauf", "SubscribeGeraeteID", 2); return; // Wenn der Wert nicht geändert wurde, keine weitere Verarbeitung } LOG(`Subscription Datenpunkt Wert geaendert fuer ${obj.id} wird ausgefuehrt`, "Ablauf", "SubscribeGeraeteID", 1); if (timeoutActive) { return; // Wenn der Timeout aktiv ist, keine weitere Verarbeitung } callCount++; if (callCount >= 25) { // Wenn mehr als 25 Aufrufe innerhalb von 1 Sekunde timeoutActive = true; // Wartezeit aktivieren LOG("Zu viele Servicemeldungen wurden generiert, warte 5 Minuten.", "WARN", "SubscribeGeraeteID", 0, "warn"); setTimeout(() => { // Timeout nach 5 Minuten timeoutActive = false; // Timeout beenden callCount = 0; // Zähler zurücksetzen LOG("Wartezeit beendet, weitere Servicemeldungen sind jetzt moeglich.", "Ablauf", "SubscribeGeraeteID", 0); }, 3 * 60 * 1000); // 3 Minuten in Millisekunden } Servicemeldung(obj); setTimeout(() => { // Zurücksetzen des Zählers nach 1 Sekunde callCount = Math.max(0, callCount - 1); }, 1000); }); } // endloop ids per selector } else { LOG(`No matching states found for ${selector.name}`, "Ablauf", "SubscribeGeraeteID", 2); } // endfor loop selector } //endif Selector } // endfunction //----------------------------------------------------------------------------------------------------- // Kernfunktion Sevicemeldung // erstmal die aktuelle Servicemeldung analysieren und loggen //----------------------------------------------------------------------------------------------------- function Servicemeldung(obj) { LOG("Routine Servicemeldung wird ausgefuehrt", "Ablauf", "Servicemeldung", 2); if (!GeraeteIDTrigger) { // nur wenn ueber REGA Servicemeldungen von der CCU reagiert werden soll LOG(`Es wurden insgesamt ${countIdsInnerhalbTimeout} aenderungen festgestellt`, "Ablauf", "Servicemeldung", 1); countIdsInnerhalbTimeout = 0; // Reset der Zaehlung changeTimeout = null; // Reset des Timeouts fuer den REGA Trigger const AnzahlSM = Check_All(); const regaState = getState(PathRega).val; LOG(`REGA Anzahl SM: ${regaState} AnzahlSM: ${AnzahlSM}`, "Ablauf", "Servicemeldung", 1); if (regaState === 0 || AnzahlSM === 0) { setState(id_Text_ServicemeldungLang, MessageBeiKeinerSM); setState(id_Text_ServicemeldungKurz, MessageBeiKeinerSM); addMessageToCollector('keineSM', MessageBeiKeinerSM, MessageBeiKeinerSM); sendMessage('keineSM'); return; } sendMessage(); return; } // Ende Statements fuer HMREGA Subscription // in diesem Teil werden die subscriptions (Servicemeldungen) bei GeraeteIDTriggerID = true in die Historie geschrieben const AnzahlSM = Check_All(); const id_name = obj.id.split('.')[2]; if (!existsState(obj.id)) { LOG(`Geraet ${id_name} scheint zwischenzeitlich nicht mehr zu existieren - Empfehlung: script neu starten`, "WARN", "Servicemeldung", 0, "warn"); return; } if (AnzahlSM === 0) { // keine Servicmeldungen delete MessageSendCollector['keineSM']; addMessageToCollector('keineSM', MessageBeiKeinerSM, MessageBeiKeinerSM); sendMessage('keineSM'); } let commonObj = getObject(obj.id.substring(0, obj.id.lastIndexOf('.') - 2)); let common_name = ReplaceString(commonObj.common.name); const meldungsart = obj.id.split('.')[4]; const status = obj.newState.val; const native_type = getObject(obj.id.substring(0, obj.id.lastIndexOf('.') - 2)).native.TYPE; let status_textLang = DefineServiceMessage(meldungsart, native_type, status,obj.id,"lang"); let status_textKurz = DefineServiceMessage(meldungsart, native_type, status,obj.id,"kurz"); LOG(`neue Servicemeldung ist ${status_textLang} Meldungsart ist ${meldungsart}`, "Ergebnis", "Servicemeldung", 1); const datum_seit = func_get_datum(obj.id); // Status "0" wurde gesetzt. Bei HM-Classic war das der Status um zu melden, dass die Servicemeldung zurueckgesetzt wurde if(status === 0 ) { status_textLang = DefineServiceMessage(meldungsart, native_type, status, obj.id, "lang") status_textKurz = DefineServiceMessage(meldungsart, native_type, status, obj.id, "kurz") LOG(`Status ist 0 - status_text ist ${status_textLang} Meldungsart ist ${meldungsart}`, "Ablauf", "Servicemeldung", 2); } writelog(common_name, id_name, meldungsart, status, status_textLang); // externes log erzeugen appendToState(id_Text_Servicemeldung_History, status_textLang); // Historische Servicemeldung als Text speichern //historische Servicemeldung als Json speichern (additiv) let HistorischeSMjsonString = [] const stateValue = getState(id_JSON_Servicemeldung_Historie).val; if (stateValue) { try { HistorischeSMjsonString = JSON.parse(stateValue); } catch (error) { LOG(`Fehler beim Parsen des JSON: ${error} - Historie ohne update`, "Fehler", "Servicemeldung", 0); HistorischeSMjsonString = []; // Fallback auf leeres Array } } HistorischeSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, id_name, status,status_textLang)); const jsonString = JSON.stringify(HistorischeSMjsonString); setState(id_JSON_Servicemeldung_Historie, jsonString); // Messenger Dienste aktivieren delete MessageSendCollector[meldungsart]; addMessageToCollector(meldungsart, status_textKurz, status_textLang); sendMessage(meldungsart); } //----------------------------------------------------------------------------------------------------- // Kompeletter Durchgang // jetzt alle Servicemeldungen durchzaehlen und Servicemeldungentexte speichern //----------------------------------------------------------------------------------------------------- function Check_All() { LOG("Routine Check_All wird ausgefuehrt", "Ablauf", "Check_All", 2); let count_all = 0; let count_Akut = 0; let count_Akut_UNREACH_ALARM = 0; let count_Akut_Sticky_UNREACH_ALARM = 0; let count_Akut_CONFIG_PENDING_ALARM = 0; let count_Akut_UPDATE_PENDING_ALARM = 0; let count_Akut_LOWBAT_ALARM = 0; let count_Akut_DEVICE_IN_BOOTLOADER_ALARM = 0; let count_Akut_ERROR = 0; let count_Akut_FAULT_REPORTING = 0; let count_Akut_SABOTAGE_ALARM = 0; let count_Akut_Sticky_SABOTAGE_ALARM = 0; let count_Akut_ERROR_NON_FLAT_POSITIONING_ALARM = 0; let count_Akut_STICKY_SABOTAGE = 0; let id; let parts; let common_name; let id_name; let native_type; let GeraeteID; let meldungsart; let ServiceMeldungTextKurz; let ServiceMeldungTextLang; let status; let datum_seit; let commonObj; let ServicemeldungMessagesLang = []; let ServicemeldungMessagesKurz = []; let objectcheck; AktuelleSMjsonString = []; for (let j = 0; j < selectors.length; j++) { // Umstellung auf klassische for-Schleife und Durchgang durch alle Selectors, um "continue" zu ermoeglichen const selector = selectors[j]; if (selector.ids.length > 0) { for (let i = 0; i < selector.ids.length; i++) { id = selector.ids[i]; parts = id.split('.'); // Extrahiere die Geraete-ID aus der vollstaendigen ID (dritte Stelle) GeraeteID = parts[2]; // Hier ist die Geraete-ID das dritte Element if (!existsState(id)) { LOG(`Geraet ${GeraeteID} scheint zwischenzeitlich nicht mehr zu existieren - Empfehlung: script neu starten`, "WARN", "Check_All", 0, "warn"); continue; // ueberspringe dieses Element und fahre mit der naechsten fort } objectcheck = getObject(id.substring(0, id.lastIndexOf('.') - 2)); // evt Fehlermeldung mit falscher ID behandeln if (objectcheck && objectcheck.native && objectcheck.native.TYPE) { native_type = objectcheck.native.TYPE; } else { LOG(`Das Objekt mit ID ${id} oder die native.TYPE-Eigenschaft ist nicht vorhanden - wird uebersprungen.`, "Fehler", "Check_All", 0, "warn"); continue; // Zur naesten ID im Loop } commonObj = getObject(id.substring(0, id.lastIndexOf('.') - 2)); common_name = ReplaceString(commonObj.common.name); id_name = id.split('.')[2]; meldungsart = id.split('.')[4]; status = getState(id).val; datum_seit = func_get_datum(id); // Switch-Case zur ueberpruefung der verschiedenen Alarmtypen switch (selector.name) { case 'UNREACH_ALARM': if(status === 1) { ServiceMeldungTextLang = DefineServiceMessage(selector.name, native_type, status, id, "lang"); ServicemeldungMessagesLang.push(ServiceMeldungTextLang); ServiceMeldungTextKurz = DefineServiceMessage(selector.name, native_type, status, id, "kurz"); ServicemeldungMessagesKurz.push(ServiceMeldungTextLang); addMessageToCollector(selector.name, ServiceMeldungTextKurz, ServiceMeldungTextLang); count_Akut_UNREACH_ALARM++; count_Akut++; } break; case 'Sticky_UNREACH_ALARM': if(status === 1) { ServiceMeldungTextLang = DefineServiceMessage(selector.name, native_type, status, id, "lang"); ServicemeldungMessagesLang.push(ServiceMeldungTextLang); ServiceMeldungTextKurz = DefineServiceMessage(selector.name, native_type, status, id, "kurz"); ServicemeldungMessagesKurz.push(ServiceMeldungTextKurz); addMessageToCollector(selector.name, ServiceMeldungTextKurz, ServiceMeldungTextLang); count_Akut_Sticky_UNREACH_ALARM++; count_Akut++; } break; case 'UPDATE_PENDING_ALARM': if(status === 1) { ServiceMeldungTextLang = DefineServiceMessage(selector.name, native_type, status, id, "lang"); ServicemeldungMessagesLang.push(ServiceMeldungTextLang); ServiceMeldungTextKurz = DefineServiceMessage(selector.name, native_type, status, id, "kurz"); ServicemeldungMessagesKurz.push(ServiceMeldungTextKurz); addMessageToCollector(selector.name, ServiceMeldungTextKurz, ServiceMeldungTextLang); count_Akut_UPDATE_PENDING_ALARM++; count_Akut++; } break; case 'LOWBAT_ALARM': if(status === 1) { ServiceMeldungTextLang = DefineServiceMessage(selector.name, native_type, status, id, "lang"); ServicemeldungMessagesLang.push(ServiceMeldungTextLang); ServiceMeldungTextKurz = DefineServiceMessage(selector.name, native_type, status, id, "kurz"); ServicemeldungMessagesKurz.push(ServiceMeldungTextKurz); addMessageToCollector(selector.name, ServiceMeldungTextKurz, ServiceMeldungTextLang); count_Akut_LOWBAT_ALARM++; count_Akut++; } break; case 'DEVICE_IN_BOOTLOADER_ALARM': if(status === 1) { ServiceMeldungTextLang = DefineServiceMessage(selector.name, native_type, status, id, "lang"); ServicemeldungMessagesLang.push(ServiceMeldungTextLang); ServiceMeldungTextKurz = DefineServiceMessage(selector.name, native_type, status, id, "kurz"); ServicemeldungMessagesKurz.push(ServiceMeldungTextKurz); addMessageToCollector(selector.name, ServiceMeldungTextKurz, ServiceMeldungTextLang); count_Akut_DEVICE_IN_BOOTLOADER_ALARM++; count_Akut++; } break; case 'ERROR': if (status >= 1 && status <= 7) { ServiceMeldungTextLang = DefineServiceMessage(selector.name, native_type, status, id, "lang"); ServicemeldungMessagesLang.push(ServiceMeldungTextLang); ServiceMeldungTextKurz = DefineServiceMessage(selector.name, native_type, status, id, "kurz"); ServicemeldungMessagesKurz.push(ServiceMeldungTextKurz); addMessageToCollector(selector.name, ServiceMeldungTextKurz, ServiceMeldungTextLang); if (status === 7) { count_Akut_SABOTAGE_ALARM++; // Nur fuer HM-Classic-messagetype = error status = 7 entspricht sabotage } else { count_Akut_ERROR++; } count_Akut++; } break; case 'FAULT_REPORTING': if(status === 1) { ServiceMeldungTextLang = DefineServiceMessage(selector.name, native_type, status, id, "lang"); ServicemeldungMessagesLang.push(ServiceMeldungTextLang); ServiceMeldungTextKurz = DefineServiceMessage(selector.name, native_type, status, id, "kurz"); ServicemeldungMessagesKurz.push(ServiceMeldungTextKurz); addMessageToCollector(selector.name, ServiceMeldungTextKurz, ServiceMeldungTextLang); count_Akut_FAULT_REPORTING++; count_Akut++; } break; case 'SABOTAGE_ALARM': if(status === 1) { ServiceMeldungTextLang = DefineServiceMessage(selector.name, native_type, status, id, "lang"); ServicemeldungMessagesLang.push(ServiceMeldungTextLang); ServiceMeldungTextKurz = DefineServiceMessage(selector.name, native_type, status, id, "kurz"); ServicemeldungMessagesKurz.push(ServiceMeldungTextKurz); addMessageToCollector(selector.name, ServiceMeldungTextKurz, ServiceMeldungTextLang); count_Akut_SABOTAGE_ALARM++; count_Akut++; } break; case 'ERROR_NON_FLAT_POSITIONING_ALARM': if(status === 1) { ServiceMeldungTextLang = DefineServiceMessage(selector.name, native_type, status, id, "lang"); ServicemeldungMessagesLang.push(ServiceMeldungTextLang); ServiceMeldungTextKurz = DefineServiceMessage(selector.name, native_type, status, id, "kurz"); ServicemeldungMessagesKurz.push(ServiceMeldungTextKurz); addMessageToCollector(selector.name, ServiceMeldungTextKurz, ServiceMeldungTextLang); count_Akut_ERROR_NON_FLAT_POSITIONING_ALARM++; count_Akut++; } break; case 'CONFIG_PENDING': if(status === 1) { ServiceMeldungTextLang = DefineServiceMessage(selector.name, native_type, status, id, "lang"); ServicemeldungMessagesLang.push(ServiceMeldungTextLang); ServiceMeldungTextKurz = DefineServiceMessage(selector.name, native_type, status, id, "kurz"); ServicemeldungMessagesKurz.push(ServiceMeldungTextKurz); addMessageToCollector(selector.name, ServiceMeldungTextKurz, ServiceMeldungTextLang); count_Akut_CONFIG_PENDING_ALARM++; count_Akut++; } break; case 'STICKY_SABOTAGE': if(status === 2) { ServiceMeldungTextLang = DefineServiceMessage(selector.name, native_type, status, id, "lang"); ServicemeldungMessagesLang.push(ServiceMeldungTextLang); ServiceMeldungTextKurz = DefineServiceMessage(selector.name, native_type, status, id, "kurz"); ServicemeldungMessagesKurz.push(ServiceMeldungTextKurz); addMessageToCollector(selector.name, ServiceMeldungTextKurz, ServiceMeldungTextLang); count_Akut_STICKY_SABOTAGE++; count_Akut++; } break; default: if(status === 1) { // fuer alle MessageTypes, die zuvor nicht explizit behandelt wurden ServiceMeldungTextLang = DefineServiceMessage(selector.name, native_type, status, id, "lang"); ServicemeldungMessagesLang.push(ServiceMeldungTextLang); ServiceMeldungTextKurz = DefineServiceMessage(selector.name, native_type, status, id, "kurz"); ServicemeldungMessagesKurz.push(ServiceMeldungTextKurz); addMessageToCollector('ERROR', ServiceMeldungTextKurz, ServiceMeldungTextLang); count_Akut_ERROR++; count_Akut++; } break; } count_all++; // Zaehlt die Gesamtanzahl der ueberprueften IDs } } else { LOG(`No matching states found for ${selector.name}`, "Ablauf", "Check_All", 1); } } // Ende der aeusseren Schleife const jsonString = JSON.stringify(AktuelleSMjsonString); // AtkuelleSMjasonString wird gefuellt in Funktion DefineServiceMessage setState(id_JSON_Servicemeldung_Aktuell, jsonString); // Formatiere die Nachrichten let formattedMessagesLang = ServicemeldungMessagesLang.join('
'); let formattedMessagesKurz = ServicemeldungMessagesKurz.join('
'); // Speichere die formatierten Nachrichten if(count_Akut === 0){ setState(id_Text_ServicemeldungLang, MessageBeiKeinerSM); setState(id_Text_ServicemeldungKurz, MessageBeiKeinerSM); } else { setState(id_Text_ServicemeldungLang, formattedMessagesLang); setState(id_Text_ServicemeldungKurz, formattedMessagesKurz); } // Einzelne Zaehler speichern setState(id_IST_UNREACH, count_Akut_UNREACH_ALARM); setState(id_IST_STICKY_UNREACH, count_Akut_Sticky_UNREACH_ALARM); setState(id_IST_LOWBAT, count_Akut_LOWBAT_ALARM); setState(id_IST_CONFIG_PENDING, count_Akut_CONFIG_PENDING_ALARM); setState(id_IST_UPDATE_PENDING, count_Akut_UPDATE_PENDING_ALARM); setState(id_IST_DEVICE_IN_BOOTLOADER, count_Akut_DEVICE_IN_BOOTLOADER_ALARM); setState(id_IST_ERROR, count_Akut_ERROR); setState(id_IST_ERROR_NON_FLAT_POSITIONING, count_Akut_ERROR_NON_FLAT_POSITIONING_ALARM); setState(id_IST_FAULT_REPORTING, count_Akut_FAULT_REPORTING); setState(id_IST_SABOTAGE, count_Akut_SABOTAGE_ALARM); setState(id_IST_STICKY_SABOTAGE, count_Akut_Sticky_SABOTAGE_ALARM); // Gesamtzaehler speichern setState(id_IST_Gesamt, count_all); setState(id_IST_SMAktuell, count_Akut); LOG(`Es wurden insgesamt ${count_all} IDs gecheckt - insgesamt gibt es ${count_Akut} Servicemeldungen`, "Ergebnis", "Check_All", 2); LOG(`Davon gibt es zur Zeit aktuelle Servicemeldungen: ${count_Akut}`, "Ergebnis", "Check_All", 2); LOG(`SM count_Akut_UNREACH_ALARM ${count_Akut_UNREACH_ALARM}`, "Ergebnis", "Check_All", 2); LOG(`SM count_Akut_CONFIG_PENDING_ALARM ${count_Akut_CONFIG_PENDING_ALARM}`, "Ergebnis", "Check_All", 2); LOG(`SM count_Akut_UPDATE_PENDING_ALARM ${count_Akut_UPDATE_PENDING_ALARM}`, "Ergebnis", "Check_All", 2); LOG(`SM count_Akut_LOWBAT_ALARM ${count_Akut_LOWBAT_ALARM}`, "Ergebnis", "Check_All", 2); LOG(`SM count_Akut_DEVICE_IN_BOOTLOADER_ALARM ${count_Akut_DEVICE_IN_BOOTLOADER_ALARM}`, "Ergebnis", "Check_All", 2); LOG(`SM count_Akut_ERROR ${count_Akut_ERROR}`, "Ergebnis", "Check_All", 2); LOG(`SM count_Akut_FAULT_REPORTING ${count_Akut_FAULT_REPORTING}`, "Ergebnis", "Check_All", 2); LOG(`SM count_Akut_SABOTAGE_ALARM ${count_Akut_SABOTAGE_ALARM}`, "Ergebnis", "Check_All", 2); LOG(`SM count_Akut_ERROR_NON_FLAT_POSITIONING_ALARM ${count_Akut_ERROR_NON_FLAT_POSITIONING_ALARM}`, "Ergebnis", "Check_All", 2); LOG(`SM count_Akut_STICKY_SABOTAGE ${count_Akut_STICKY_SABOTAGE}`, "Ergebnis", "Check_All", 2); LOG(`Gesammelte Servicemeldungen Lang: ${JSON.stringify(ServicemeldungMessagesLang, null, 2)}`, "Ergebnis", "Check_All", 3); LOG(`Gesammelte Servicemeldungen Kurz: ${JSON.stringify(ServicemeldungMessagesKurz, null, 2)}`, "Ergebnis", "Check_All", 3); return count_Akut; } //----------------------------------------------------------------------------------------------------- // Message ERmittlung //----------------------------------------------------------------------------------------------------- function DefineServiceMessage(meldungsart, native_type, status, id, version) { LOG(`Routine DefineServiceMessage wird ausgefuehrt meldungsart ${meldungsart}`, "Ablauf", "DefineServiceMessage", 2); let ServiceMessage; // Rueckgabewert let parts = id.split('.'); // Extrahiere die Geraete-ID aus der vollstaendigen ID (dritte Stelle) let commonObj = getObject(id.substring(0, id.lastIndexOf('.') - 2)); let common_name = ReplaceString(commonObj.common.name); let id_name = parts[2]; let datum_seit = func_get_datum(id); // Im Folgenden werden lange und kurze Versionen von Servicemeldungen erzeugt switch (meldungsart) { case "CONFIG_PENDING": ServiceMessage = version === "lang" ? `${datum_seit} - ${meldungsart} - ${common_name} - (${id_name}) - ${status} - ${statusMessages.CONFIG_PENDING_ALARM[status]}` : `${common_name} ${statusMessages.CONFIG_PENDING_ALARM[status]}`; if (version === "lang") { AktuelleSMjsonString.push( createJsonEntry ( datum_seit, meldungsart, common_name, id_name, status, statusMessages.CONFIG_PENDING_ALARM[status], null) ); } break; case "LOW_BAT_ALARM": case "LOWBAT_ALARM": ServiceMessage = version === "lang" ? `${datum_seit} - ${meldungsart} - ${common_name} - (${id_name}) - ${status} - ${statusMessages.LOWBAT_ALARM[status]} - Batteriebezeichnung: ${func_Batterie(native_type)}` : `${common_name} ${statusMessages.LOWBAT_ALARM[status]}`; if (version === "lang") { AktuelleSMjsonString.push( createJsonEntry ( datum_seit, meldungsart, common_name, id_name,status,statusMessages.LOWBAT_ALARM[status],func_Batterie(native_type) )); } break; case "STICKY_UNREACH_ALARM": ServiceMessage = version === "lang" ? `${datum_seit} - ${meldungsart} - ${common_name} - (${id_name}) - ${status} - ${statusMessages.STICKY_UNREACH_ALARM[status]}` : `${common_name} ${statusMessages.STICKY_UNREACH_ALARM[status]}`; if (version === "lang") { AktuelleSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, id_name,status,statusMessages.STICKY_UNREACH_ALARM[status],null )); } break; case "UNREACH_ALARM": ServiceMessage = version === "lang" ? `${datum_seit} - ${meldungsart} - ${common_name} - (${id_name}) - ${status} - ${statusMessages.UNREACH_ALARM[status]}` : `${common_name} ${statusMessages.UNREACH_ALARM[status]}`; if (version === "lang") { AktuelleSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, id_name,status,statusMessages.UNREACH_ALARM[status],null )); } break; case "DEVICE_IN_BOOTLOADER_ALARM": ServiceMessage = version === "lang" ? `${datum_seit} - ${meldungsart} - ${common_name} - (${id_name}) - ${status} - ${statusMessages.DEVICE_IN_BOOTLOADER_ALARM[status]}` : `${common_name} ${statusMessages.DEVICE_IN_BOOTLOADER_ALARM[status]}`; if (version === "lang") { AktuelleSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, id_name,status,statusMessages.DEVICE_IN_BOOTLOADER_ALARM[status],null )); } break; case "UPDATE_PENDING_ALARM": ServiceMessage = version === "lang" ? `${datum_seit} - ${meldungsart} - ${common_name} - (${id_name}) - ${status} - ${statusMessages.UPDATE_PENDING_ALARM[status]}` : `${common_name} ${statusMessages.UPDATE_PENDING_ALARM[status]}`; if (version === "lang") { AktuelleSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, id_name,status,statusMessages.UPDATE_PENDING_ALARM[status],null )); } break; case "SABOTAGE_ALARM": case "SABOTAGE": ServiceMessage = version === "lang" ? `${datum_seit} - ${meldungsart} - ${common_name} - (${id_name}) - ${status} - ${statusMessages.SABOTAGE_ALARM[status]}` : `${common_name} ${statusMessages.SABOTAGE_ALARM[status]}`; if (version === "lang") { AktuelleSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, id_name,status,statusMessages.SABOTAGE_ALARM[status],null )); } break; case "ERROR_NON_FLAT_POSITIONING_ALARM": ServiceMessage = version === "lang" ? `${datum_seit} - ${meldungsart} - ${common_name} - (${id_name}) - ${status} - ${statusMessages.ERROR_NON_FLAT_POSITIONING_ALARM[status]}` : `${common_name} ${statusMessages.ERROR_NON_FLAT_POSITIONING_ALARM[status]}`; if (version === "lang") { AktuelleSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, id_name,status,statusMessages.ERROR_NON_FLAT_POSITIONING_ALARM[status],null )); } break; case "ERROR": if (status >= 1 && status <= 7) { // nur wenn kein status = 0 if (errorMessages[native_type] && errorMessages[native_type][status]) { ServiceMessage = version === "lang" ? `${datum_seit} - ${meldungsart} - ${common_name} - (${id_name}) - ${status} - ${errorMessages[native_type][status]}` : `${common_name} ${errorMessages[native_type][status]}`; if (version === "lang") { AktuelleSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, id_name,status,errorMessages[native_type][status],null )); } } // endif errorMessages } // endif Status >=1... if (status === 0) { // nicht HIMIP Geraete die auf Error und status 0 stehen - Message aufgehoben fuer Historie ServiceMessage = version === "lang" ? `${datum_seit} - ${meldungsart} - ${common_name} - (${id_name}) - ${status} - $StandardstatusMessages[status]}` : `${common_name} ${errorMessages[native_type][status]}`; if (version === "lang") { AktuelleSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, id_name,status,StandardstatusMessages[status],null )); } } break; case "FAULT_REPORTING": if (faultMessages[native_type] && faultMessages[native_type][status]) { ServiceMessage = version === "lang" ? `${datum_seit} - ${meldungsart} - ${common_name} - (${id_name}) - ${status} - ${faultMessages[native_type][status]}` : `${common_name} ${faultMessages[native_type][status]}`; if (version === "lang") { AktuelleSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, id_name,status,faultMessages[native_type][status],null )); } } break; default: if (status < 0 || status >= StandardstatusMessages.length) { ServiceMessage = `datum_seit, meldungsart, common_name, id_name,- Ungueltiger Status`; // fuer ungueltige Statuswerte } else { ServiceMessage = version === "lang" ? `${datum_seit} - ${meldungsart} - ${common_name} - (${id_name}) - ${status} - ${StandardstatusMessages[status]}` : `${common_name} ${StandardstatusMessages[status]}`; if (version === "lang") { AktuelleSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, id_name,status,StandardstatusMessages[status],null )); } } } return ServiceMessage; // Nur einmal am Ende zurueckgeben } //----------------------------------------------------------------------------------------------------- // 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); const combinedMessagesByService = {}; // Kombiniere Nachrichten fuer jeden Dienst ueber alle Typen hinweg messageTypesToProcess.forEach((type) => { const messagesByService = MessageSendCollector[type] || {}; Object.keys(messagesByService).forEach((service) => { if (!combinedMessagesByService[service]) { combinedMessagesByService[service] = []; } combinedMessagesByService[service] = combinedMessagesByService[service].concat(messagesByService[service]); }); // Loesche den Typ nach der Verarbeitung delete MessageSendCollector[type]; }); // Sende kombinierte Nachrichten an die jeweiligen Dienste Object.keys(combinedMessagesByService).forEach((service) => { const combinedMessage = combinedMessagesByService[service].join('\n'); if (combinedMessage.trim().length > 0) { sendToService(service, combinedMessage); } }); } // ende Funktion //----------------------------------------------------------------------------------------------------- // 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, combinedMessage) { LOG(`Message wird versendet mit: ${service}: ${combinedMessage}`, "Ablauf", "sendToService", 1); switch (service) { case "email": sendTo(`email.${MessengerInstanz[0]}`, "send", { text: combinedMessage, to: emailAddresse, subject: Headline }); break; case "whatsApp": sendTo(`whatsapp-cmb.${MessengerInstanz[1]}`, "send", { text: combinedMessage }); break; case "Signal": sendTo(`signal-cmb.${MessengerInstanz[2]}`, "send", { text: combinedMessage }); break; case "Telegram": sendTo(`telegram.${MessengerInstanz[3]}`, "send", { text: combinedMessage, user: TelegramUser // Telegram User ID, um den Nachrichteneempfaenger festzulegen }); break; case "Pushover": sendTo(`pushover.${MessengerInstanz[4]}`, "send", { message: combinedMessage, sound: "" }); break; case "Pushsafer": sendTo(`pushsafer.${MessengerInstanz[5]}`, "send", { message: combinedMessage, title: Headline }); break; default: LOG(`Unbekannter Service: ${service}`, "WARN", "sendToService", "warn"); } } //----------------------------------------------------------------------------------------------------- // addMessageToCollector Messages werden unter beruecksichtigung der folgenden Objekte in den MessageCollector genommen // MessengerScope, Services, AlarmTypes, TextTypeKurz //----------------------------------------------------------------------------------------------------- function addMessageToCollector(messageType, MessageKurz, MessageLang) { // ueberpruefung, ob der `messageType` in `alarmTypes` existiert const isValidMessageType = alarmTypes.some((type) => type.key === messageType); // Wenn der messageType ungueltig ist, "Sonstige" als Fallback verwenden let actualMessageType = isValidMessageType ? messageType : "Sonstige"; if (!isValidMessageType && debugLevel >= 1 && messageType != "keineSM") { LOG(`messageType ${messageType} - umgeleitet auf Sonstige`, "Ablauf", "addMessageToCollector", 3); } // Sonderbehandlung fuer "keineSM" - Diese wird behandelt wie jeder andere messageType if (messageType === "keineSM") { actualMessageType = "keineSM"; // Setze den messageType explizit auf "keineSM" } // Sicherstellen, dass der Typ im MessageSendCollector existiert if (!MessageSendCollector[actualMessageType]) { MessageSendCollector[actualMessageType] = {}; } // ueberpruefen, ob der actualMessageType im MessengerScope vorhanden ist, falls nicht, 'Sonstige' verwenden let messengerConfig = MessengerScope[actualMessageType]; // Wenn der messageType nicht im MessengerScope definiert ist, benutze die Konfiguration fuer 'Sonstige' if (!messengerConfig) { messengerConfig = MessengerScope['Sonstige'] || Array(services.length).fill(false); // false ist fallback - messengerConfig ist ein array } // Nachricht nur hinzufuegen, wenn die Konfiguration fuer den Service aktiv ist messengerConfig.forEach((isActive, index) => { if (isActive) { const service = services[index]; // Nachricht nur hinzufuegen, wenn die Konfiguration fuer den Service aktiv ist if (!MessageSendCollector[actualMessageType][service]) { MessageSendCollector[actualMessageType][service] = []; } // Auswahl von MessageKurz oder MessageLang basierend auf TextTypeKurz const messageToAdd = TextTypeKurz[index] ? MessageKurz : MessageLang; // Nachricht nur hinzufuegen, wenn sie noch nicht existiert if (!MessageSendCollector[actualMessageType][service].includes(messageToAdd)) { MessageSendCollector[actualMessageType][service].push(messageToAdd); } } }); } //----------------------------------------------------------------------------------------------------- // 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; } //----------------------------------------------------------------------------------------------------- // func_get_datum aktuelles Datum formatiert //----------------------------------------------------------------------------------------------------- function func_get_datum(id) { // const datum = formatDate(getState(id).lc, "TT.MM.JJ SS:mm:ss"); const datum = formatDate(getState(id).ts, "TT.MM.JJ SS:mm:ss"); return datum < '01.01.71 01:00:00' ? '' : `${datum} Uhr`; } //----------------------------------------------------------------------------------------------------- // func_Batterie Batterieermittlung //----------------------------------------------------------------------------------------------------- function func_Batterie(native_type) { LOG(`Routine wird ausgefuehrt`, "Ablauf", "func_Batterie", 2); const normalizedType = native_type.toUpperCase(); return Object.keys(batteryTypes).find(battery => batteryTypes[battery].some(device => device.toUpperCase() === normalizedType) ) || 'unbekannt'; } //----------------------------------------------------------------------------------------------------- // Funktion createJsonEntry erzeugen einer JSON Tabelle //----------------------------------------------------------------------------------------------------- function createJsonEntry(datum_seit, meldungsart, common_name, id_name, status, statusMessages, Batterie) { // Erstelle das JSON-Objekt return { datum_seit: datum_seit, meldungsart: meldungsart, common_name: common_name, id_name: id_name, status: status, status_message: statusMessages, batterie_bezeichnung: Batterie }; } //----------------------------------------------------------------------------------------------------- // Funktion schreibt einen Logeintrag in das Filesystem und auch in das interne Log-System (looxer) //----------------------------------------------------------------------------------------------------- function writelog(Name, id_name, SMType, SMStatus, SMStatus_Text) { LOG(`Routine wird ausgefuehrt`, "Ablauf", "writelog", 2); const fs = require('fs'); // enable write fuer externes log if (!SMProtokoll) return; const logdate = formatDate(new Date(), "TT.MM.JJJJ"); const logtime = formatDate(new Date(), "SS:mm:ss"); const logEntry = `${logdate} ;${logtime} ;${Name} ;${id_name} ; ${SMType} ;${SMStatus} ;${SMStatus_Text}\n`; const headerLine = "Datum;Uhrzeit;Name;ID-Name;Meldungssart;Status;Servicemeldung\n"; fs.readFile(PathSMLog, 'utf8', function(err, data) { if (!err) { fs.appendFileSync(PathSMLog, logEntry, 'utf8'); } else { LOG(`Logfile nicht gefunden - wird angelegt`, "Ablauf", "writelog", 0); fs.writeFileSync(PathSMLog, headerLine + logEntry, 'utf8'); } }); } //----------------------------------------------------------------------------------------------------- // Funktion schreibt die Syste-Log-Eintraege in ein externes CSV - File //----------------------------------------------------------------------------------------------------- function LOG(Message,Kategorie,Routine, Level, type) { if(type !== "warn" && type !== "error") {type = "info"} // Wenn SystemLog false ist und der Debug-Level hoeher oder gleich dem uebergebenen Level, schreibe normalen Logeintrag if (!SystemLog && debugLevel >= Level) { log(Message+" Routine:"+Routine,type); return;} if (Level === 0) { log(Message+" Routine:"+Routine,type);} // bei level 0 soll auf jeden fall auch in das normale log geschrieben werden // Wenn SystemLog true ist und der Debug-Level hoeher oder gleich dem uebergebenen Level if (SystemLog && debugLevel >= 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 zum Hinzufuegen einer neuen Zeile am Anfang des bestehenden State (looxer) //----------------------------------------------------------------------------------------------------- function appendToState(id, newText) { LOG(`Routine wird ausgefuehrt`, "Ablauf", "appendToState", 3); const updatedText = newText + '
' + getState(id).val; setState(id, updatedText); } //----------------------------------------------------------------------------------------------------- // Funktion Create States //----------------------------------------------------------------------------------------------------- async function CreateStates(callback) { LOG(`Kategorie:Ablauf; Routine:CreateStates; Routine wird ausgefuehrt`,2) try { await createStateAsync(id_Text_ServicemeldungLang, "", { read: true, write: true, type: 'string', name: 'Servicemeldungen LangText', desc: 'LangText Servicemeldung' }); await createStateAsync(id_Text_ServicemeldungKurz, "", { read: true, write: true, type: 'string', name: 'Servicemeldungen KurzText', desc: 'KurzText Servicemeldung' }); await createStateAsync(id_JSON_Servicemeldung_Aktuell, "", { read: true, write: true, type: 'string', name: 'Aktuelle Servicemeldungen als JSON', desc: 'Vergangene Servicemeldung JSON' }); await createStateAsync(id_JSON_Servicemeldung_Historie, "", { read: true, write: true, type: 'string', name: 'Servicemeldungen History', desc: 'History Servicemeldung' }); await createStateAsync(id_Text_Servicemeldung_History, "", { read: true, write: true, type: 'string', name: 'Servicemeldungen History', desc: 'History Servicemeldung' }); await createStateAsync(id_IST_Gesamt, 0, { read: true, write: true, type: 'number', name: 'Servicemeldungen Anzahl Total', desc: 'Anzahl Servicemeldungen' }); await createStateAsync(id_IST_SMAktuell, 0, { read: true, write: true, type: 'number', name: 'Servicemeldungen Anzahl Aktuelle SM', desc: 'Anzahl Aktuelle Servicemeldungen' }); await createStateAsync(id_IST_LOWBAT, 0, { read: true, write: true, type: 'number', name: 'Servicemeldungen Lowbat Anzahl Total', desc: 'Lowbat Anzahl Servicemeldungen' }); await createStateAsync(id_IST_UNREACH, 0, { read: true, write: true, type: 'number', name: 'Servicemeldungen Unreach Anzahl Total', desc: 'Unreach Anzahl Servicemeldungen' }); await createStateAsync(id_IST_STICKY_UNREACH, 0, { read: true, write: true, type: 'number', name: 'Servicemeldungen Sticky Unreach Anzahl Total', desc: 'Sticky Unreach Anzahl Servicemeldungen' }); await createStateAsync(id_IST_ERROR, 0, { read: true, write: true, type: 'number', name: 'Servicemeldungen ERROR Anzahl Total', desc: 'ERROR Anzahl Servicemeldungen' }); await createStateAsync(id_IST_CONFIG_PENDING, 0, { read: true, write: true, type: 'number', name: 'Servicemeldungen ausstehende Konfig Anzahl Total', desc: 'Ausstehende Konfig Anzahl Servicemeldungen' }); await createStateAsync(id_IST_UPDATE_PENDING, 0, { read: true, write: true, type: 'number', name: 'Servicemeldungen ausstehende Updates Anzahl Total', desc: 'Ausstehende Updates Anzahl Servicemeldungen' }); await createStateAsync(id_IST_DEVICE_IN_BOOTLOADER, 0, { read: true, write: true, type: 'number', name: 'Servicemeldungen DEVICE_IN_BOOTLOADER Anzahl Total', desc: 'DEVICE_IN_BOOTLOADER Anzahl Servicemeldungen' }); await createStateAsync(id_IST_ERROR_NON_FLAT_POSITIONING, 0, { read: true, write: true, type: 'number', name: 'Servicemeldungen ERROR_NON_FLAT_POSITIONING Anzahl Total', desc: 'ERROR_NON_FLAT_POSITIONING Anzahl Servicemeldungen' }); await createStateAsync(id_IST_FAULT_REPORTING, 0, { read: true, write: true, type: 'number', name: 'Servicemeldungen FAULT_REPORTING Anzahl Total', desc: 'FAULT_REPORTING Anzahl Servicemeldungen' }); await createStateAsync(id_IST_SABOTAGE, 0, { read: true, write: true, type: 'number', name: 'Servicemeldungen SABOTAGE Anzahl Total', desc: 'SABOTAGE Anzahl Servicemeldungen' }); await createStateAsync(id_IST_STICKY_SABOTAGE, 0, { read: true, write: true, type: 'number', name: 'Servicemeldungen STICKY_SABOTAGE Anzahl Total', desc: 'STICKY_SABOTAGE Anzahl Servicemeldungen' }); // Aufruf des Callbacks nach Abschluss aller Operationen if (callback) await callback(); } catch (error) { LOG(`Kategorie:WARN; Routine:CreateStates; Routine Create States - Fehler beim Erstellen der Zustaende: ${error}`,0, "warn"); } }