// Beschreibung des Scriptes: https://forum.iobroker.net/topic/77816/vorlage-servicemeldungen-volume2 // Autor Looxer01 02.11.2024 Version 1.0 (initiale Version) // Version 3.00 - 05.01.2025 Variable MessageBeiKeinerSM in die Experteneinstellungen geschoben // Problem bei der Datumsberechnung behoben // Zaehler fuer Historie hinzugefuegt // die Datenpunkte fuer Text-Format werden jetzt optional gefuellt // Version 3.10 - 01.02.2025 Wartezeit fuer HM-Rega von 2 auf 3,5 Sekunden erhoeht - Funktion der Tabelle MessengerScope erweitert // Version 3.20 - 24.02.2025 Doppelte Nachrichten im messageCollector verhindern - Vermeidung Meldung "keine Servicemeldungen..." in der Historie wenn bereits vorhanden - Wartezeit für Rega-Trigger wieder auf 5s // StickyMessages koennen automatisch bestaetigt werden // Version 3.21 - 01.03.2025 JSON um ein Feld (DataPoint) erweitert // Tabelle "instanceIds" um feld "Messaging" erweitert(Ausschalten von Nachrichten Adapter/Instanz Kombi) - DataPoint Filter für selektor hinzugefuegt //--------------------------------------------------------------------------------------------------------------------------------------------------------------- // 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 --- ACHTUNG: bei HMIPAccessPoint-Nutzung muss GeraeteTriggerID auf true stehen 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; // virtuelle GeraeteInstanz- 9 = nicht relevant - Die Gruppen werden i.d.R. nicht gebraucht - Empfehlung: 9 let HMIPAccessPoint = 9; // AccessPoint Servicemeldungen - (normalerweise instanz = 0) bei nicht Verwendung Instanz = 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) const SMProtokoll = true; const PathSMLog = "/opt/iobroker/log/ServicemeldungenVol2.csv"; // Pfad und Dateiname des externen Logs //const PathSMLog = "/iobroker/log/ServicemeldungenVol2.csv"; // Pfad fuer Windows/ iobroker ist der angenommene iobroker home-pfad //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 = 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/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. // Vorteile: mehr Details in der Historie von Servicemeldungen: 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: erweiterte Funktion fuer nicht CCU Datenpunkte nicht verfuegbar // bei HMIP AccessPoint Cloud HCU - funktioniert das nur bei GeraeteTrigger = true let GeraeteIDTrigger = false; // true = viele subscriptions aber praezise Historie der Servicemeldungen - false = 1 subscription - weniger praezise Historie - Empfehlung = false // Speicherung der Servicemeldungen im Text-Format - Empfehlung = false // davon ausgehend, dass das JSON Format in der Visualisierung gut dargestellt werden kann const UpdateTEXT_Datenpunkte = false; // die kurz- und langen Servicemeldungen als auch die historischen werden immer im JSON Format abgelegt. Alternativ koennen diese auch in einem TEXT Format in Datenpunkten gespeichert werden // Speicherung der Servicemeldungen im HTML-Format - Wird empfohlen zu nutzen, wenn es Problem bei der Visualisierung von JSON gibt. const UpdateHTML_Datenpunkte = true; // die langen Servicemeldungen als auch die historischen werden immer im JSON Format abgelegt. Zusaetzlich koennen diese auch in einem HTML Format in Datenpunkten gespeichert werden // Die Autobestaetigung bestaetigt automatisch evt Sticky-Meldungen der CCU const AutoBestaetigungCCUMeldungen = true; // Empfehlung ist true - nur relevant fur HM-Classic Geraete //bei true sollte in der CCU die autobestaetigung ausgeschaltet werden // 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', 'Pushsafer',]; const MessengerScope = { 'UNREACH_ALARM': [true, false, false, false, false, false, false,], 'STICKY_UNREACH_ALARM': [false, false, false, false, false, false, false,], // Sticky sollte nicht gesendet werden - alles auf false setzen 'SABOTAGE_ALARM': [true, false, false, false, false, false, false,], 'STICKY_SABOTAGE_ALARM': [false, false, false, false, false, false, false,], // Sticky sollte nicht gesendet werden - alles auf false setzen 'CONFIG_PENDING_ALARM': [true, false, false, false, false, false, false,], 'LOWBAT_ALARM': [true, false, false, false, false, false, false,], 'Sonstige': [true, false, false, false, false, false, false,], // text ist fest verdrahtet nicht ändern 'keineSM': [true, false, false, false, false, false, false,], // text ist fest verdrahtet nicht ändern } const MessengerInstanz = [0, 0, 0, 0, 0, 0, 1, ]; // Instanz des Messengers const TextTypeKurz = [false, true, true, true, true, true, false,]; // bei true wird der Kurztext gesendet - sonst der Langtext // email-Einstellungen let emailAddresse = "Vorname-Nachname@web.de" // Empfängeraddresse const Headline = "ioBroker Servicemeldung"; // Ueberschrift Messages fuer email und Pushsafer // telegram Einstellungen const TelegramUser = ""; //----------------------------------------------------------------------------------------------------- //Experten Einstellungen - Empfehlung: Absprache mit dem Autor //----------------------------------------------------------------------------------------------------- const MessageBeiKeinerSM = 'Derzeit keine Servicemeldungen' // Text der erscheinen soll, wenn keine SM vorliegen Ein Kurztext ist erforderlich //const NichtRelevantText = 'n/a'; // statt n/a kann ein beliebiger anderer Text in den JSON verwendet werden const NichtRelevantText = "......"; // die folgenden Einstellungen sind nur relvant bei Verwendung von HTML als Datenpunkte // Definieren der Farbwerte für Kopfzeile, gerade und ungerade Zeilen const headerColor = '#333333'; // Standardfarbe (Fuellfarbe) für die Kopfzeile const evenRowColor = '#4e5049'; // Standardfarbe (Fuellfarbe) für gerade Zeilen const oddRowColor = '#333333'; // Standardfarbe (Fuellfarbe) für ungerade Zeilen // Definieren der Textfarben für Kopfzeile, gerade und ungerade Zeilen const headerTextColor = 'white'; // Textfarbe für Kopfzeile const evenRowTextColor = 'white'; // Textfarbe für gerade Zeilen const oddRowTextColor = 'white'; // Textfarbe für ungerade Zeilen // Definieren von Zellenumrandungseinstellungen const useBorderAll = true; // true, wenn ein Gitternetz erzeugt werden soll const borderColor = '#000000'; // Farbe des Gitternetzes (Zellenumrandung) const borderWidth = '1px'; // Strichstärke der Zellenumrandung //Schedule Zeit fuer refresh der Historie const ScheduleAktiv = true; // Bei "false" wird der schedule nicht durchlaufen. Manuelles Löschen kann über den Datenpunkt id_Button_Refresh_Historie (Button) möglich const scheduleTimeClearSMTexte = "2 0 1 * *"; // am 1. tag des monats um 00:02 morgens sollen alle Servicemeldungen des Monats geloescht werden // const scheduleTimeClearSMTexte = "58 23 * * 0"; // alternative 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 const PathRega = ['hm-rega.0.maintenance',]; // Array um ggf die HCU zu konfigurieren // Pfade fuer die Speicherung aktueller und vergangener Servicemeldungen const id_Text_ServicemeldungLang = path+'TextLangAktuelleSM'; // Objekt wo die Servicemeldung hingeschrieben werden soll (String) = path+'TextLang'; const id_Text_ServicemeldungKurz = path+'TextKurzAktuelleSM'; // Objekt wo die Servicemeldung hingeschrieben werden soll (String) - kurze Version const id_Text_Servicemeldung_History = path+'TextLangVergangeneSM'; // Objekt wo die Servicemeldung hinzugefuegt werden soll (String) - Lange Version (es gibt nur lang) const id_HTML_Servicemeldung_Aktuell = path+'HtmlAktuelleSM'; // Objekt wo die Servicemeldung hingeschrieben werden soll (HTML formatiere Tabelle) = path+'htmlLang'; const id_HTML_Servicemeldung_History = path+'HtmlVergangeneSM'; // Objekt wo die Servicemeldung hinzugefuegt werden soll ((HTML formatiere Tabelle) - 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 const id_Button_Refresh_Historie = path+'ButtonRefreshHistorie'; // Wenn DP auf true gesetzt wird, dann wird die historie refreshed (geloescht) // Count-Pfade und counts in einer Tabelle - wird auch fuer CREATE STATES verwendet const Zaehler = [ { alarmtype: 'UNREACH_ALARM', count: 0, Datenpunkt: path+'Anzahl_UNREACH' }, { alarmtype: 'STICKY_UNREACH_ALARM', count: 0, Datenpunkt: path+'Anzahl_STICKY_UNREACH' }, { alarmtype: 'CONFIG_PENDING_ALARM', count: 0, Datenpunkt: path+'Anzahl_CONFIG_PENDING' }, { alarmtype: 'UPDATE_PENDING_ALARM', count: 0, Datenpunkt: path+'Anzahl_Update_PENDING' }, { alarmtype: 'LOWBAT_ALARM', count: 0, Datenpunkt: path+'Anzahl_LOWBAT' }, { alarmtype: 'DEVICE_IN_BOOTLOADER_ALARM', count: 0, Datenpunkt: path+'Anzahl_DEVICE_IN_BOOTLOADER' }, { alarmtype: 'ERROR', count: 0, Datenpunkt: path+'Anzahl_in_ERROR' }, { alarmtype: 'FAULT_REPORTING', count: 0, Datenpunkt: path+'Anzahl_FAULT_REPORTING' }, { alarmtype: 'SABOTAGE_ALARM', count: 0, Datenpunkt: path+'Anzahl_SABOTAGE' }, { alarmtype: 'ERROR_NON_FLAT_POSITIONING_ALARM', count: 0, Datenpunkt: path+'Anzahl_NON_FLAT_POSITIONING' }, { alarmtype: 'STICKY_SABOTAGE_ALARM', count: 0, Datenpunkt: path+'Anzahl_Sticky_SABOTAGE' }, { alarmtype: 'SMAktuell', count: 0, Datenpunkt: path+'Anzahl_SM-Aktuell' }, { alarmtype: 'SMHistorie', count: 0, Datenpunkt: path+'Anzahl_SM-Historie' }, { alarmtype: 'Gesamt', count: 0, Datenpunkt: path+'Anzahl_GESAMT' } ]; // statusmessages je messagetype und adapter // Fallback ist fuer unbekannte messagetypes - z.B. alle Error Messages // FALLBACK repraesentiert Standardmessages const statusMessages = { UNREACH_ALARM: { "hm-rpc": { 0: "keine Kommunikationsfehler", 1: "Kommunikation gestoert", 2: "Kommunikation war gestoert" } }, STICKY_UNREACH_ALARM: { "hm-rpc": { 0: "keine Kommunikationsfehler", 1: "Sticky Kommunikation gestoert", 2: "Sticky Kommunikation war gestoert" } }, SABOTAGE_ALARM: { "hm-rpc": { 0: "Keine Sabotage", 1: "Sabotage", 2: "Sabotage aufgehoben" } }, STICKY_SABOTAGE_ALARM: { "hm-rpc": { 0: "Keine Sabotage", 1: "Sticky Sabotage", 2: "Sticky Sabotage aufgehoben" } }, LOWBAT_ALARM: { "hm-rpc": { 0: "Batterie ok", 1: "Batterie niedrig", 2: "Batterie ok" } }, LOW_BAT_ALARM: { "hm-rpc": { 0: "Batterie ok", 1: "Batterie niedrig", 2: "Batterie ok" } }, ERROR_NON_FLAT_POSITIONING_ALARM: { "hm-rpc": { 0: "Keine Meldung", 1: "Geraet wurde angehoben.", 2: "Geraet wurde angehoben: Bestaetigt" } }, CONFIG_PENDING_ALARM: { "hm-rpc": { 0: "keine Meldung", 1: "Konfigurationsdaten stehen zur Uebertragung an", 2: "Konfigurationsdaten standen zur Uebertragung an",}, }, UPDATE_PENDING_ALARM: { "hm-rpc": { 0: "kein Update verfuegbar", 1: "Update verfuegbar", 2: "Update wurde eingespielt" } }, ERROR_OVERHEAT_ALARM: { "hm-rpc": { 0: "kein Overheat Alarm", 1: "Overheat gemeldet", 2: "Overheat geloest" } }, ERROR_UNDERVOLTAGE_ALARM: { "hm-rpc": { 0: "Kein Undervoltage Alarm", 1: "Undervoltage gemeldet", 2: "Undervoltage geloest" } }, DEVICE_IN_BOOTLOADER_ALARM: { "hm-rpc": { 0: "Keine Meldung", 1: "Geraet startet neu", 2: "Geraet wurde neu gestartet" } }, DUTY_CYCLE: { "hm-rpc": { false: "Geraete-Duty Cycle ok", true: "Geraete-Duty Cycle erreicht", null: "unbekannter Status (Duty_Cycle" } }, lowBat: { "hmip": { false: "Batterie ok", true: "Batterie niedrig", null: "Batterie ok" } }, unreach: { "hmip": { false: "keine Kommunikationsfehler", true: "Kommunikation gestoert", null: "Kommunikation war gestoert" } }, sabotage: { "hmip": { false: "Keine Sabotage", true: "Sabotage", null: "Sabotage aufgehoben" } }, configPending: { "hmip": { false: "Keine Meldung", true: "Konfigurationsdaten stehen zur Uebertragung an", null: "Konfigurationsdaten standen zur Uebertragung an" } }, FALLBACK: { "hm-rpc": { 0: "keine Stoerung", 1: "Stoerung", 2: "Stoerung aufgehoben", false: "Keine Stoerung", true: "Stoerung", null: "unbekannter Status Fallback"}, "hmip": { false: "keine Stoerung", true: "Stoerung", null: "Stoerung aufgehoben" }, } }; //ErrorMessages fuer HM-Classic Geraete - Sonderfaelle 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 fuer HM-Classic Geraet (Heizung) - Sonderfaelle 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' } }; // 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','unreach' ] },//UNREACH_ALARM = HM-Classic & HMIP-CCU - unreach = HMIP Accesspoint { key: 'STICKY_UNREACH_ALARM', suffixes: ['STICKY_UNREACH_ALARM'] }, { key: 'CONFIG_PENDING_ALARM', suffixes: ['CONFIG_PENDING_ALARM','configPending'] }, //configPending ist eine HMIP Meldung { key: 'UPDATE_PENDING_ALARM', suffixes: ['UPDATE_PENDING_ALARM'] }, { key: 'LOWBAT_ALARM', suffixes: ['LOWBAT_ALARM', 'LOW_BAT_ALARM','lowBat'] }, //LOWBAT_ALARM = HM-Classic - LOW_BAT_ALARM = HMIP CCU - lowBat = HMIP Accesspoint { key: 'DEVICE_IN_BOOTLOADER_ALARM', suffixes: ['DEVICE_IN_BOOTLOADER_ALARM'] }, { key: 'ERROR', suffixes: ['ERROR','DUTY_CYCLE'] }, // error ist ein Sammler fuer hier nicht definierte Meldungen { key: 'FAULT_REPORTING', suffixes: ['FAULT_REPORTING'] }, { key: 'SABOTAGE_ALARM', suffixes: ['SABOTAGE_ALARM','sabotage'] }, // sabotage ist eine HMIP Meldung { key: 'STICKY_SABOTAGE_ALARM', suffixes: ['STICKY_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'] }, ]; // Umlaut Umwandlung und entfernung PUnkte - kann aber auch erweitert werden const replacements = { '.': ' ', 'ä': 'ae', 'ü': 'ue', 'ö': 'oe', 'ß': 'ss' }; // Umwandlung fuer Namen der Geraete (common.name) // Definition der Datenstrukturen fuer MetaDaten je nach Adapter, da abweichend const StrukturDefinition = [ { Adapter: 'hm-rpc', GeraeteID: 2, AlarmFeld: 4, nativeType: 3, common_name: 3 }, // die Ziffer ist die Positinierung des Feldes 'hm-rpc.1.00085D89B14067.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 ] // Moegliche Homematic Instanzen (CuxD ausgeschlossen) // Adapter hinzugefuegt um ggf HCU zu konfigurieren // Messaging "false" bedeutet, dass keine Messages für diesen Adapter und diese Instanz gesendet werden (wenn servicemeldungen vorliegen) const instanceIds = [ { name: 'HMClassicInstanz', adapter: 'hm-rpc', Instanz: HMClassicInstanz, Messaging: true }, { name: 'HMIPInstanz', adapter: 'hm-rpc', Instanz: HMIPInstanz, Messaging: true }, { name: 'GruppenInstanz', adapter: 'hm-rpc', Instanz: GruppenInstanz, Messaging: false }, { name: 'WiredIClassicInstanz', adapter: 'hm-rpc', Instanz: WiredIClassicInstanz, Messaging: true }, { name: 'HMIPAccessPoint', adapter: 'hmip' , Instanz: HMIPAccessPoint, Messaging: true }, ]; // Die StickingMappingsTabelle stellt sicher, dass die Sticky entsprechend dieser Regel bestaetigt werden kann const StickyMappings = { STICKY_UNREACH_ALARM: ['UNREACH_ALARM'], STICKY_SABOTAGE_ALARM: ['SABOTAGE_ALARM'], }; // Konfigurationsobjekt fuer Ausnahmen / es koennen Arlarmtypes je Instanz ausgeschlossen werden / Filter auf AlarmTypes const AlarmTypeExceptions = { HMClassicInstanz: [], HMIPInstanz: ['DUTY_CYCLE'], GruppenInstanz: ['ERROR_NON_FLAT_POSITIONING_ALARM'], WiredIClassicInstanz: ['LOWBAT_ALARM', 'LOW_BAT_ALARM','ERROR_NON_FLAT_POSITIONING_ALARM'], }; // Tabelle für DataPoint-Ausnahmen mit Wildcards // wie bei AlarmTypeExceptions und Ausschlusslisten wirken diese als Filter in der Selektion const DataPointExceptions = [ "hmip*groups*", // Wildcard, um alle hmip"groups" auszufiltern // Weitere DataPoints können hier hinzugefügt werden ]; //----------------------------------------------------------------------------------------------------- //Ende Einstellungen //----------------------------------------------------------------------------------------------------- LOG(`Script: Servicemeldungen Volume 2 - Autor: Looxer01 Datum: 01.03.2025 Version:3.20`,`Einstellungen`,`Start`,0 ) if(HMIPAccessPoint !== 9 && !GeraeteIDTrigger) { GeraeteIDTrigger = true // Wenn der HMIPAccessPoint aktiv ist, dann funktioniert der REGA Trigger nicht. also setzen auf geraeteID Trigger=true LOG(`HMIP AccessPoint bzw Cloud HCU sind aktiv - GeraeteTriggerID muss aktiviert sein - wurde aktiviert. Bitte auch manuell korrigieren und auf GeraeteTriggerID = true setzen`,`Einstellungen`,`Start`,0 ) } LOG(`HMClassicInstanz: ${HMClassicInstanz}; HMIPInstanz: ${HMIPInstanz}; WiredIClassicInstanz: ${WiredIClassicInstanz}; GruppenInstanz: ${GruppenInstanz}`, "Einstellungen", "Start", 2); LOG(`Inhalt instanceIds: ${JSON.stringify(instanceIds)}`, "Einstellungen", "Start", 2); LOG(`GeraeteIDTrigger: ${GeraeteIDTrigger}`, "Einstellungen", "Start", 2); LOG(`Inhalt Messengerscope: ${JSON.stringify(MessengerScope)}`, "Einstellungen", "Start", 2); LOG(`Inhalt Services: ${JSON.stringify(services)}`, "Einstellungen", "Start", 2); LOG(`Inhalt Messenger-Instanzen: ${JSON.stringify(MessengerInstanz)}`, "Einstellungen", "Start", 2); LOG(`Inhalt TextTypeKurz: ${JSON.stringify(TextTypeKurz)}`, "Einstellungen", "Start", 2); let HistorischeMeldungenJSON = [], AktuelleMeldungenJSON = [], MessageSendCollector = {}; // jetzt selectors fuellen anhand der Tabelle InstanceIDs, sowie der Auscchlusstabellen let totalIdsCount = 0; const selectors = alarmTypes.map(alarmType => { const collectedIds = []; instanceIds.forEach(instance => { if (instance.Instanz !== 9) { // Umbenennung von `instance.id` zu `instance.Instanz` const excludedAlarmTypes = AlarmTypeExceptions[instance.name] || []; // Hole Ausnahmen oder leere Liste if (!excludedAlarmTypes.includes(alarmType.key)) { // Wenn Alarmtype nicht in der Ausnahme alarmType.suffixes.forEach(suffix => { // wenn suffix (meldungsart) nicht in der Ausnahme if (!excludedAlarmTypes.includes(suffix)) { // @ts-ignore $(`state[id=${instance.adapter}.${instance.Instanz}.*.${suffix}]`).each(id => collectedIds.push(id)); } }); } } }); // Filter für DataPointExceptions und Ausschlussliste anwenden const finalFilteredIds = collectedIds.filter(id => { // Prüfen, ob der DataPoint mit einer der Ausnahmen übereinstimmt (einschließlich Wildcards) return !DataPointExceptions.some(exception => { const regex = new RegExp(`^${exception.replace(/\*/g, '.*')}$`); return regex.test(id); }) && !Ausschlussliste.some(deviceId => id.includes(deviceId)); // Prüfen, ob der DataPoint mit einer der Geräte-IDs aus der Ausschlussliste übereinstimmt }); totalIdsCount += finalFilteredIds.length; // IDs zur Gesamtzahl hinzufuegen return { name: alarmType.key, ids: finalFilteredIds }; }); LOG(`Es wurden ${totalIdsCount} IDs fuer 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(`fuer den Alarmtype: ${selector.name} wurden ${selector.ids.length} IDs gefunden${debugLevel >= 3 ? ` ${JSON.stringify(selector.ids)}` : ''}`, "Ablauf", "Start", 3); }else{ LOG(`fuer den Alarmtype: ${selector.name} wurden ${selector.ids.length} IDs gefunden${debugLevel >= 3 ? ` ${JSON.stringify(selector.ids)}` : ''}`, "Ablauf", "Start", 0); } }); CreateStates(() => { Check_All("init") }); // Check_All wird aufgerufen, wenn CreateStates fertig ist (callback) if (GeraeteIDTrigger) { // Subscription auf IDs SubscribeGeraeteID(); }else{ // Subscription auf HMREGA (nur 1 Subscripton) SubscribeRegaDP(); } //----------------------------------------------------------------------------------------------------- // Subscription - Optional kann bei push auf button "id_Button_Refresh_Historie" die Servicemeldung Histore geloescht werden //----------------------------------------------------------------------------------------------------- on({ id: id_Button_Refresh_Historie , val: true, ack: false }, function (obj) { LOG(`Button zum refresh der Historie wurde ausgeloest - Historie is nun geloescht`, "Event", "id_Button_Refresh_Historie", 0); Refresh_History() setState(id_Button_Refresh_Historie, false, true); }); //----------------------------------------------------------------------------------------------------- // Schedule zum Loeschen des Datenpunktwertes der Histore einmal pro Monat oder Woche je nach schedule-Einstellung // die aktiven messages bleien erhaltebn //----------------------------------------------------------------------------------------------------- if(ScheduleAktiv ) { schedule(scheduleTimeClearSMTexte, function() { LOG(`Schedule zum refresh der Historie wurde ausgeloest - Historie is nun geloescht`, "Event", "scheduleTimeClearSMTexte", 0); Refresh_History() }); } //----------------------------------------------------------------------------------------------------- // Funktion SubscribeRegaDP // Erstellung der Subscriptions auf REGA Datenpunkt // es werden 2 Sekunden lang vorliegende Aenderungen gesammelt //----------------------------------------------------------------------------------------------------- function SubscribeRegaDP() { LOG(`Routine SubscribeRegaDP wird ausgefuehrt`, "Ablauf", "SubscribeRegaDP", 2); let changeTimeout = null; PathRega.forEach(path => { // Iteriere ueber das Array PathRega und abonniere jeden Pfad on({id: path, change: 'ne'}, function (obj) { LOG(`Subscription Datenpunkt ${path} getriggert. Neuer Wert: ${obj.state.val} alter Wert:${obj.oldState.val}`, "Ablauf", "SubscribeRegaDP", 2); if (changeTimeout) { // Falls schon ein Timer laeuft, diesen abbrechen clearTimeout(changeTimeout); } changeTimeout = setTimeout(function() { // Setze einen neuen Timer auf 2 Sekunden, um die Funktion Servicemeldung aufzurufen Check_All("trigger",null); // Funktion wird nach 2 Sekunden ohne weitere aenderungen aufgerufen }, 5000); // 2000 ms = 2 Sekunden }); }); } // Ende der Funktion //----------------------------------------------------------------------------------------------------- // Funktion Subscribe GeraeteID// Erstellung der Subscriptions auf GeraeteID und Datenpunkt Ebene //----------------------------------------------------------------------------------------------------- function SubscribeGeraeteID() { LOG(`Routine SubscribeGeraeteID wird ausgefuehrt`, "Ablauf", "SubscribeGeraeteID", 2); let delay = 0; for (let j = 0; j < selectors.length; j++) { // Schleife ueber alle Selectoren const selector = selectors[j]; if (selector.ids.length > 0) { // Schleife ueber IDs der Selektoren LOG(`Erstellung der Subscriptions - Prozessiere selector: ${selector.name}`, "Ablauf", "SubscribeGeraeteID", 2); for (let i = 0; i < selector.ids.length; i++) { // Schleife ueber 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 geaendert wurde, keine weitere Verarbeitung } LOG(`Subscription Datenpunkt Wert geaendert fuer ${obj.id} wird ausgefuehrt`, "Ablauf", "SubscribeGeraeteID", 1); if (obj.id.includes("STICKY_")) { // Timer nur, wenn obj.id den String "Sticky_" enthält setTimeout(() => { Check_All("trigger", obj); }, 5000+delay); // 5 Sekunden Verzögerung } else { setTimeout(() => { Check_All("trigger", obj); }, delay); // 5 Sekunden Verzögerung delay = delay + 50; } }); } // endloop ids per selector } else { LOG(`No matching states found for ${selector.name}`, "Ablauf", "SubscribeGeraeteID", 2); } // endfor loop selector } //endif Selector } // endfunction //----------------------------------------------------------------------------------------------------- // Fumktion Check_All Kompeletter Durchgang durch alle IDs des selectors - Dies ist die Kernfunktion des scriptes - alle Servicemeldungen durchzaehlen und Servicemeldungentexte speichern //----------------------------------------------------------------------------------------------------- function Check_All(RunType,obj) { LOG("Routine Check_All wird ausgefuehrt", "Ablauf", "Check_All", 2); const startTime = Date.now(); AktuelleMeldungenJSON = []; let jsonString = getState(id_JSON_Servicemeldung_Historie).val || '[]'; // Lade den aktuellen JSON-String des Historie-Datenpunkts HistorischeMeldungenJSON = JSON.parse(jsonString); Zaehler.forEach(entry => { entry.count = 0; }); // Alle count-Werte im Objekt auf 0 setzen for (let j = 0; j < selectors.length; j++) { const selector = selectors[j]; if (selector.ids.length > 0) { for (let i = 0; i < selector.ids.length; i++) { let id = selector.ids[i], status = getState(id).val; if (!existsState(id)) { LOG(`Geraet ${obj.id} scheint zwischenzeitlich nicht mehr zu existieren - Empfehlung: script neu starten`, "WARN", "Check_All", 0, "warn"); continue; } switch (selector.name) { // fuer alle alarmtypes - erst die Sonderfaelle case 'ERROR': if (status >= 1 && status <= 7) { if (status === 7) { // Sonderfall bei HM-Classic processServiceMessage('SABOTAGE_ALARM', status, id,RunType); } else { processServiceMessage(selector.name, status, id,RunType); } } break; default: // Standardfall fuer alle anderen alarmtypes if(status === 1 || status === true ) {processServiceMessage(selector.name, status, id,RunType); } // bei status = true = AccessPoint break; } increaseCount('Gesamt'); } // Ende Loop der Mitglieder des Alarmtypes } // Endif selector ist gefuellt } // Ende Loop ueber den Selector const AnzahlAktuellerSM = Zaehler.find(e => e.alarmtype === 'SMAktuell').count; // Sequenz zur Speicherung der aktuellen Servicemeldungen in die JSON -Datenpunkte-------------------------- if (AnzahlAktuellerSM === 0) { AktuelleMeldungenJSON =CreateJsonEntry([] ,func_get_datum(),'', '', '','',func_get_datum() + " - " + MessageBeiKeinerSM,MessageBeiKeinerSM,null,false ); // Keine aktuellen Meldungen -> "Keine Meldungen" hinzufuegen let jsonString = JSON.stringify(AktuelleMeldungenJSON); setState(id_JSON_Servicemeldung_Aktuell, jsonString); // Aktuelle SM wird in JSON-Datenpunkt als String gespeichert } else { let jsonString = JSON.stringify(AktuelleMeldungenJSON); // AktuelleMeldungenJSON wird in Funktion DefineServiceMessage gefuellt setState(id_JSON_Servicemeldung_Aktuell, jsonString); // Aktuelle SM wird in JSON-Datenpunkt als String gespeichert } if (GeraeteIDTrigger && RunType === "trigger" && !existsState(obj.id)) { // obj.id nicht mehr in ioBroker aber noch im Selector const GeraeteId = ExtractMetaData(obj.id, "GeraeteID"); LOG(`Geraet ${GeraeteId} scheint zwischenzeitlich nicht mehr zu existieren - Empfehlung: script neu starten`, "WARN", "Check_All", 0, "warn"); return; } // Speziell fuer GerateIDTrigger = true ist, dass es "aufgehoben"-Meldungen gibt, die fuer REGA Trigger nicht ausgewertet werden koennen. diese duerfen auch nicht als aktuelle meldungen in den DP gelangen let ExtendedAktuelleMeldungenJSON = [...AktuelleMeldungenJSON]; // Erstelle eine flat copy if (GeraeteIDTrigger && RunType === "trigger") { const GeraeteId = ExtractMetaData(obj.id, "GeraeteID"); const status = obj.newState.val; const common_name = ExtractMetaData(obj.id, "CommonName"); const native_type = ExtractMetaData(obj.id, "nativeType"); const meldungsart = ExtractMetaData(obj.id, "meldungsart"); let status_textLang = DefineServiceMessage(native_type, status, obj.id, "lang"); let status_textPure = DefineServiceMessage(native_type, status, obj.id, "pure"); if (!checkIFJSONfEntryExists(ExtendedAktuelleMeldungenJSON,func_get_datum(obj.id), GeraeteId,status, meldungsart) ) { // checken ob ein Eintrag schon in der Historie ist, wenn nicht dann hinzufuegen ExtendedAktuelleMeldungenJSON = CreateJsonEntry(ExtendedAktuelleMeldungenJSON, func_get_datum(obj.id), meldungsart, common_name, GeraeteId, status, status_textLang, status_textPure, func_Batterie(native_type), true,obj.id) } } // Historische Meldungen hinzufuegen, wenn nicht bereits vorhanden ExtendedAktuelleMeldungenJSON.forEach(aktuelleMeldung => { // Iteriere ueber alle aktuellen Meldungen und fuege aktuelle meldungen in die historie wenn noch nicht vorhanden if (!checkIFJSONfEntryExists(HistorischeMeldungenJSON,aktuelleMeldung.datum_seit ,aktuelleMeldung.GeraeteId, aktuelleMeldung.status, aktuelleMeldung.meldungsart) ) { // checken ob ein Eintrag schon in der Historie ist, wenn nicht dann hinzufuegen if(!isLatestEntryMessageBeiKeinerSM(ExtendedAktuelleMeldungenJSON)) { // nicht wenn nicht "keine Servicemeldungen" in aktuellJSON vorhanden ist const message_kurz = aktuelleMeldung.common_name + " " + aktuelleMeldung.status_message_Pure addMessageToCollector(aktuelleMeldung.meldungsart, message_kurz, aktuelleMeldung.status_message_Lang,aktuelleMeldung.DataPoint); HistorischeMeldungenJSON = CreateJsonEntry(HistorischeMeldungenJSON, aktuelleMeldung.datum_seit, aktuelleMeldung.meldungsart, aktuelleMeldung.common_name, aktuelleMeldung.GeraeteId, aktuelleMeldung.status, aktuelleMeldung.status_message_Lang, aktuelleMeldung.status_message_Pure, aktuelleMeldung.batterie_bezeichnung, true, aktuelleMeldung.DataPoint ) writelog(aktuelleMeldung.common_name, aktuelleMeldung.GeraeteId, aktuelleMeldung.meldungsart, aktuelleMeldung.status, aktuelleMeldung.status_message_Lang); // externes log erzeugen } } }); // Handling "keine aktuellen Servicemeldungen" if (AnzahlAktuellerSM === 0 && RunType === "trigger") { // wenn keine SM vorliegen, die Meldung "keine Servicmeldungen" in die History if(!isLatestEntryMessageBeiKeinerSM(HistorischeMeldungenJSON)) { // nur wenn "keine Servicemeldungen" in Historische Meldung nicht bereits an erster Stelle vorhanden ist HistorischeMeldungenJSON =CreateJsonEntry(HistorischeMeldungenJSON ,func_get_datum(),'', '', '','',func_get_datum() + " - " + MessageBeiKeinerSM,MessageBeiKeinerSM,null,false ); // Keine aktuellen Meldungen writelog("", "", "", "", MessageBeiKeinerSM); // log erzeugen addMessageToCollector('keineSM', MessageBeiKeinerSM, MessageBeiKeinerSM); // Vorbereitung Messagesending sendMessage('keineSM'); } } // AutoBestaetigung fuer StickyMessages, wenn diese nicht mehr aktuell sind. nur relevant fuer HM-Classic if (AutoBestaetigungCCUMeldungen ) {ConfirmStickyMessages() } // Messages senden fuer alle Faelle mit Servicemeldungen if (AnzahlAktuellerSM > 0 && RunType ==="trigger") { // Servicemeldungen versenden, falls servicemeldungen vorliegen sendMessage(); } HistorischeMeldungenJSON = Markiere_HistSM_als_Aufgehoben(HistorischeMeldungenJSON) // markiert hist messages als erledigt falls nicht mehr in aktuelle meldungen enthalten jsonString = JSON.stringify(HistorischeMeldungenJSON); // Vorbereitung speichern setState(id_JSON_Servicemeldung_Historie, jsonString); // hist JSON-Daten speichern // falls so eingestellt werden jetzt die TEXTe aus den JSON (aktuell und historie) erzeugt und in die Datenpunkte geschrieben GenerateTEXT_Datenpunkte(); generateHtmlTable(AktuelleMeldungenJSON,"AKT"); // Aktuelle SM wird in HTML Format gespeichert falls so eingestellt generateHtmlTable(HistorischeMeldungenJSON,"HIST"); // Historische SM wird in HTML Format gespeichert falls so eingestellt // anzahl der Eintraege in HistorischeMeldungenJSON ermitteln und alle counts in die Statistik schreiben let entry = Zaehler.find(item => item.alarmtype === "SMHistorie"); if (entry) {entry.count = HistorischeMeldungenJSON.length; } // Wenn der Eintrag gefunden wird, aktualisiere den count-Wert Zaehler.forEach(entry => { // JETZT: ZaehlerStaende in die ioBroker - Datenpunkte-------------------fuer aktuelle Meldungen setState(entry.Datenpunkt, entry.count); }); // Log Ausgabe der Ergebnisse-------------------------------------------- LOG(`Es wurden insgesamt ${Zaehler.find(e => e.alarmtype === 'Gesamt').count} IDs gecheckt - insgesamt gibt es ${AnzahlAktuellerSM} Servicemeldungen`, "Ergebnis", "Check_All", 2); Zaehler.forEach(({ alarmtype, count }) => { LOG(`SMAktuell fuer alarmtype: ${alarmtype} insgesamt festgestellte Servicemeldungen: ${count}`, "Ergebnis", "Check_All", 2); }); LOG(`Zeitverbrauch fuer Routine Check_All: ${Date.now() - startTime} Millisekunden`, "Ergebnis", "Check_All", 1); AktuelleMeldungenJSON =[]; HistorischeMeldungenJSON = []; MessageSendCollector = {}; // Speicher freigeben } //----------------------------------------------------------------------------------------------------- // Function processServiceMessage // Zuordnung der richtigen Servicemeldung zum Alarmtype / Zaehlung der Faelle //----------------------------------------------------------------------------------------------------- function processServiceMessage(alarmtype, status, id, RunType) { const native_type = ExtractMetaData(id, "nativeType"); const meldungsart = ExtractMetaData(id, "meldungsart"); const common_name = ReplaceString(ExtractMetaData(id, "CommonName")); const GeraeteId = ExtractMetaData(id, "GeraeteID"); let ServiceMeldungTextLang = DefineServiceMessage(native_type, status, id, "lang"); // aktuelle Servicemeldung ableiten let ServiceMeldungTextPure = DefineServiceMessage(native_type, status, id, "pure"); AktuelleMeldungenJSON = CreateJsonEntry(AktuelleMeldungenJSON, func_get_datum(id), meldungsart, common_name, GeraeteId, status, ServiceMeldungTextLang, ServiceMeldungTextPure, func_Batterie(native_type), true,id); // nur einmal unabhaengig der Version increaseCount(alarmtype); increaseCount('SMAktuell'); } //----------------------------------------------------------------------------------------------------- // Funktion ConfirmStickyMessages Autoconfirm Servicemeldungen (STICKY bei HM-Classic) //----------------------------------------------------------------------------------------------------- function ConfirmStickyMessages() { LOG("Routine ConfirmStickyMessages wird ausgeführt", "Ablauf", "ConfirmStickyMessages", 2); let StickyMeldungsart, StickyMappedString, GeraeteID; let Adapter, Instanz, ID_Constructed ; let AckFlag = false; let delay = 0; const foundInstance = instanceIds.find(item => item.name === 'HMClassicInstanz'); // die HMClassic Instanz wird ermittelt if (foundInstance) { Adapter = foundInstance.adapter; Instanz = foundInstance.Instanz; } for (let i = 0; i < AktuelleMeldungenJSON.length; i++) { const aktuelleMeldung = AktuelleMeldungenJSON[i]; StickyMeldungsart = aktuelleMeldung.meldungsart; StickyMappedString = StickyMappings[StickyMeldungsart]; GeraeteID = aktuelleMeldung.GeraeteId; const DataPoint = aktuelleMeldung.DataPoint; if (StickyMappedString === undefined) { continue } // wenn konfiguriert in StickyMappings // ID_Constructed = Adapter + "." + Instanz + "." + GeraeteID + ".0." + StickyMeldungsart; // Gilt nur bei HM-Classic AckFlag = CheckStickyNochAktuell(StickyMappedString, GeraeteID); // kann die Message bestaetigt werden ? true = ja if (AckFlag) { setTimeout(() => { setState(DataPoint, 2); LOG("Eine Sticky Meldung wurde bestaetigt fuer ID " + DataPoint, "Ergebnis", "ConfirmStickyMessages", 2); }, delay); // Verzögerung delay = delay + 500 } else { LOG("Eine Sticky Meldung ist noch aktuell und wird nicht bestaetigt fuer ID " + DataPoint, "Ergebnis", "ConfirmStickyMessages", 2); } } } //----------------------------------------------------------------------------------------------------- // Funktion CheckStickyNochAktuell checked of Sticky Meldung bestaetigt werden kann //----------------------------------------------------------------------------------------------------- function CheckStickyNochAktuell(MappedString, GeraeteID) { let AckFlag = true; MappedString = String(MappedString); // Wandelt MappedString in einen String um GeraeteID = String(GeraeteID); // Wandelt GeraeteID in einen String um for (let j = 0; j < AktuelleMeldungenJSON.length; j++) { // Iteriere über alle aktuellen Meldungen, um nach einer Nicht-Sticky-Meldung zu suchen const AktuelleMeldung = AktuelleMeldungenJSON[j]; const aktuelleMeldungsart = String(AktuelleMeldung.meldungsart).trim(); const aktuelleGeraeteId = String(AktuelleMeldung.GeraeteId).trim(); if (aktuelleMeldungsart === MappedString && aktuelleGeraeteId === GeraeteID) { // ueberpruefen ob die Sticky message noch aktuell ist AckFlag = false; break; // Schleife abbrechen } } return AckFlag; } //----------------------------------------------------------------------------------------------------- // Function increaseCount // Diese Funktion reduziert Wiederholungen der Case abfragen in Check_ALL //----------------------------------------------------------------------------------------------------- function increaseCount(alarmtype) { const entry = Zaehler.find(e => e.alarmtype === alarmtype); if (entry) entry.count++; } //----------------------------------------------------------------------------------------------------- // Funktion DefineServiceMessage Message ERmittlung //----------------------------------------------------------------------------------------------------- function DefineServiceMessage(native_type, status, id, version) { const meldungsart = ExtractMetaData(id, "meldungsart"); LOG(`Routine DefineServiceMessage wird ausgefuehrt meldungsart ${meldungsart} und ID:${id}`, "Ablauf", "DefineServiceMessage", 2); const adapter = ExtractMetaData(id, "adapter"); const GeraeteId = ExtractMetaData(id, "GeraeteID"); const common_name = ReplaceString(ExtractMetaData(id, "CommonName")); const datum_seit = func_get_datum(id); let matchingAlarmType = meldungsart; let isValidMessageType = alarmTypes.some((type) => type.suffixes.includes(meldungsart)); if (isValidMessageType) { // Bestimmen des passenden Alarmtyps matchingAlarmType = alarmTypes.find((type) => type.suffixes.includes(meldungsart)).key; if (matchingAlarmType === "ERROR" && meldungsart !== "ERROR") { matchingAlarmType = meldungsart; } } const createServiceMessage = (message) => { // Hilfsfunktion zum Erzeugen der ServiceMessage if (version === "lang") { // Unterschiedliche Formate je nach Version let Status_Message_Lang = `${datum_seit} - ${meldungsart} - ${common_name} - (${GeraeteId}) - ${status} - ${message}`; return Status_Message_Lang; } else if (version === "pure") { return message; // Nur die pure status Nachricht } else { return `${common_name} ${message}`; // Kurze Nachricht, falls kurze Nachrichten gewuenscht fuer das messaging } }; switch (matchingAlarmType) { // Logik je nach meldungsart case "LOWBAT_ALARM": const lowBatMessage = `${getStatusMessage(meldungsart, status, adapter)} - Batteriebezeichnung: ${func_Batterie(native_type)}`; return createServiceMessage(lowBatMessage); case "ERROR": if (status >= 1 && status <= 7 && errorMessages[native_type] && errorMessages[native_type][status]) { return createServiceMessage(errorMessages[native_type][status]); } if (status === 0) { const fallbackMessage = getStatusMessage("FALLBACK", status, adapter); return createServiceMessage(fallbackMessage); } break; case "FAULT_REPORTING": if (faultMessages[native_type] && faultMessages[native_type][status]) { return createServiceMessage(faultMessages[native_type][status]); } break; default: if (isValidMessageType) { return createServiceMessage(getStatusMessage(meldungsart, status, adapter)); } else { const fallbackMessage = getStatusMessage("FALLBACK", status, adapter); return createServiceMessage(fallbackMessage); } } return createServiceMessage(getStatusMessage("FALLBACK", status, adapter)); // kann eigentlich nicht aufteten } //----------------------------------------------------------------------------------------------------- //Funktion getStatusMessage zur Abfrage der Meldung je messageType, Adapter und Status (fuer Routine DefineServiceMessage) //----------------------------------------------------------------------------------------------------- function getStatusMessage(messageType, statusKey, adapter) { if ( statusMessages[messageType] && statusMessages[messageType][adapter] &&statusMessages[messageType][adapter][statusKey] !== undefined ) { // Existenzpruefung- ggf regagieren return statusMessages[messageType][adapter][statusKey]; } else { return `Keine passende Meldung fuer MessageType: "${messageType}", Adapter: "${adapter}", Status: "${statusKey}".`; } } //----------------------------------------------------------------------------------------------------- // addMessageToCollector Messages werden unter beruecksichtigung der folgenden Objekte in den MessageCollector genommen // MessengerScope, Services, AlarmTypes, TextTypeKurz //----------------------------------------------------------------------------------------------------- function addMessageToCollector(messageType, MessageKurz, MessageLang, DataPoint) { if (!checkForMessagingScope(DataPoint)) { return } // nicht im Scope für Messaging let actualMessageType, matchingAlarmType; if (messageType !== "keineSM") { let isValidMessageType = alarmTypes.some((type) => type.suffixes.includes(messageType)); if (isValidMessageType) { matchingAlarmType = alarmTypes.find((type) => type.suffixes.includes(messageType)).key; actualMessageType = messageType; } else { LOG(`MessageType: ${messageType} ist nicht konfiguriert in alarmTypes`, "WARN", "addMessageToCollector", 0); } } if (messageType === "keineSM") { actualMessageType = "keineSM"; matchingAlarmType = "keineSM"; } if (!MessageSendCollector[actualMessageType]) { MessageSendCollector[actualMessageType] = {}; } let messengerConfig = MessengerScope[matchingAlarmType] || MessengerScope['Sonstige'] || Array(services.length).fill(false); messengerConfig.forEach((isActive, index) => { if (isActive) { const service = services[index]; const instance = MessengerInstanz[index]; if (instance !== null && !isNaN(instance) && instance >= 0) { if (!MessageSendCollector[actualMessageType][service]) { MessageSendCollector[actualMessageType][service] = []; } const messageToAdd = TextTypeKurz[index] ? MessageKurz : MessageLang; const existingMessages = MessageSendCollector[actualMessageType][service]; // **Doppelprüfung:** 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[actualMessageType][service].push({ message: messageToAdd + '\n', instance: instance }); LOG(`Nachricht hinzugefügt für ${actualMessageType} - Service: ${service} - Instanz: ${instance}`, "Ablauf", "addMessageToCollector", 3); } else { LOG(`Doppelte Nachricht erkannt und verworfen für ${actualMessageType} - Service: ${service} - Instanz: ${instance}`, "DEBUG", "addMessageToCollector", 3); } } else { LOG(`Ungültige Instanz für Service: ${service} (Instanz: ${instance})`, "WARN", "addMessageToCollector", 0); } } }); } //----------------------------------------------------------------------------------------------------- // checkForDataPoint Stellt fest ob für diesen Adappter und Instanz das Messaging überhaupt erfolgen soll //----------------------------------------------------------------------------------------------------- function checkForMessagingScope(DataPoint) { if (typeof DataPoint !== 'string' || DataPoint === '' || DataPoint === undefined) { log ("DataPoint ist entweder undefined, null oder kein String. " + DataPoint); return true; } if(DataPoint === NichtRelevantText){return true } for (const entry of instanceIds) { const combined = `${entry.adapter}.${entry.Instanz}`; // Kombiniere Adapter und Instanz mit einem Punkt const MessagingScope = entry.Messaging; // Speichern des Messaging-Feldes in einer Variablen if (DataPoint.includes (combined)) { // Überprüfen, ob "DataPoint" im kombinierten String enthalten ist return MessagingScope } } return false; // sollte nicht vorkommen } //----------------------------------------------------------------------------------------------------- // 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; default: LOG(`Unbekannter Service: ${service}`, "WARN", "sendToService", "warn"); } } //----------------------------------------------------------------------------------------------------- // 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 } //----------------------------------------------------------------------------------------------------- // 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) { 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`; } //----------------------------------------------------------------------------------------------------- // 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) ) || NichtRelevantText; } //----------------------------------------------------------------------------------------------------- // Funktion CreateJsonEntry erzeugen einer JSON Tabelle //----------------------------------------------------------------------------------------------------- function CreateJsonEntry(existingJson = [], datum_seit = '', meldungsart = '', common_name = '', GeraeteId = '', status = '', Status_Lang = '', Status_Pure, Batterie = '',sortierung = false,id) { 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 meldungsart: meldungsart || NichtRelevantText, common_name: common_name || NichtRelevantText, GeraeteId: GeraeteId || NichtRelevantText, status: status || NichtRelevantText, // Status entweder number oder boolean status_message_Lang: Status_Lang || 'Keine Meldung', // Langer Text status_message_Pure: Status_Pure || 'Keine Meldung', // nur die Status aus den Status-Tabellen batterie_bezeichnung: Batterie || NichtRelevantText, SM_aufgehoben_seit: "aktiv", // status ob eine message bereits aufgeboben ist. wenn aufgehoben wird ein Datum eingestellt DataPoint: id || NichtRelevantText, }; parsedJson.unshift(neuerEintrag); if(sortierung) { sortJason(parsedJson);} return parsedJson; // Gib das aktualisierte Array zurueck } //----------------------------------------------------------------------------------------------------- // Funktion checkIfEntryExists Checken of ein Eintrag schon vorhanden ist //----------------------------------------------------------------------------------------------------- function checkIFJSONfEntryExists(parsedJson, datum_seit, GeraeteId, status, meldungsart) { if (!Array.isArray(parsedJson)) { // Überprüfen, ob parsedJson ein Array ist LOG(`parsedJson ist kein Array`, "WARN", "checkIFJSONfEntryExists", 1); return false; } for (let entry of parsedJson) { // Durch das Array iterieren und kriterien checken // meldungsart ist optional if (entry.datum_seit === datum_seit && entry.GeraeteId === GeraeteId && entry.status === status && (meldungsart === undefined || entry.meldungsart === meldungsart) ) { return true; // Eintrag gefunden } } return false; // Kein passender Eintrag gefunden } //----------------------------------------------------------------------------------------------------- // Funktion isLatestEntryMessageBeiKeinerSM Checken ob die letzte message der variablen MessageBeiKeinerSM entspricht //----------------------------------------------------------------------------------------------------- function isLatestEntryMessageBeiKeinerSM(existingJson = []) { if (!Array.isArray(existingJson) || existingJson.length === 0) { // ueberpruefen, ob existingJson ein Array ist und Eintraege enthaelt return false; } const latestEntry = existingJson[0]; // Neuesten Eintrag extrahieren (erster Eintrag im Array) if (latestEntry.status_message_Lang && latestEntry.status_message_Lang.includes(MessageBeiKeinerSM)) { return true; } return false; } //----------------------------------------------------------------------------------------------------- // Funktion Markiere_HistSM_als_Aufgehoben Zuordnung von historischen Messages zu "aufgehoben" mit Datum der Zuordnung //----------------------------------------------------------------------------------------------------- function Markiere_HistSM_als_Aufgehoben(HistorischeMeldungenJSON_transfer) { const maxTimeDiffMs = 1000; // z.B. 1000 ms fuer eine Sekunde Unterschied const nurKeineMeldungen = AktuelleMeldungenJSON[0].status_message_Lang.includes(MessageBeiKeinerSM); // Pruefen, ob AktuelleMeldungenJSON nur eine Meldung mit "Derzeit keine Servicemeldungen" enthaelt if (nurKeineMeldungen) { HistorischeMeldungenJSON_transfer.forEach(meldung => { // Alle historischen Eintraege als aufgehoben markieren if (meldung.SM_aufgehoben_seit === "aktiv") { meldung.SM_aufgehoben_seit = func_get_datum(); } }); return HistorischeMeldungenJSON_transfer; } HistorischeMeldungenJSON_transfer.forEach(historischeMeldung => { // Iteriere ueber alle historischen Meldungen (aeussere Schleife) if (historischeMeldung.SM_aufgehoben_seit !== "aktiv") return; // Wenn die historische Meldung bereits aufgehoben wurde, ueberspringen let zugeordnet = false; const historischeDatum = extractDate(historischeMeldung.datum_seit); AktuelleMeldungenJSON.forEach(aktuelleMeldung => { // Iteriere ueber alle aktuellen Meldungen (innere Schleife) const aktuelleDatum = extractDate(aktuelleMeldung.datum_seit); const gleicheGeraeteID = historischeMeldung.GeraeteId === aktuelleMeldung.GeraeteId; const gleicheStatusMessage = historischeMeldung.status_message_Pure === aktuelleMeldung.status_message_Pure; const zeitDifferenz = Math.abs(aktuelleDatum.getTime() - historischeDatum.getTime()); // Berechne den Zeitunterschied in Millisekunden const gleicheOderNaheZeit = zeitDifferenz <= maxTimeDiffMs; // Pruefe, ob die Zeitdifferenz innerhalb des erlaubten Bereichs liegt if (gleicheGeraeteID && gleicheStatusMessage && gleicheOderNaheZeit) { historischeMeldung.SM_aufgehoben_seit = "aktiv"; // Als nicht aufgehoben markieren zugeordnet = true } }); if (!zugeordnet) { historischeMeldung.SM_aufgehoben_seit = func_get_datum() // kein pendant gefunden, also muss die message obsolet sein } }); return HistorischeMeldungenJSON_transfer; } //----------------------------------------------------------------------------------------------------- // Funktion sortJason zum Sortieren des History Jason nach datum //----------------------------------------------------------------------------------------------------- function sortJason(parsedJson) { if (!Array.isArray(parsedJson)) { LOG("Die Eingabe fuer sortJason ist kein Array.", "Fehler", "sortJason", 2); return[] ;} const parseDate = (datum) => { try { const [date, time] = datum.split(' '); // Datum und Zeit trennen const [day, month, year] = date.split('.'); // Datum aufteilen in Tag, Monat, Jahr const [hours, minutes, seconds] = time.split(':'); // Zeit aufteilen in Stunden, Minuten, Sekunden return new Date(`20${year}-${month}-${day}T${hours}:${minutes}:${seconds}`); // ISO-Format erstellen } catch (error) { LOG(`Fehler beim Parsen des Datums: ${datum}`, "Fehler", "sortJason", 1); return null; // Rueckgabe von null, wenn das Datum ungueltig ist } }; return parsedJson.sort((a, b) => { const dateA = parseDate(a.datum_seit); const dateB = parseDate(b.datum_seit); if (dateA === null && dateB === null) return 0; // Keine aenderung bei ungueltigen Datumswerten if (dateA === null) return 1; // Null-Werte ans Ende verschieben if (dateB === null) return -1; return dateB.getTime() - dateA.getTime(); // Neueste zuerst }); } //----------------------------------------------------------------------------------------------------- // Funktion extractDatum // Hilfsfunktion zur Extraktion des Datums // Beispieltext: "01.12.24 12:04:55 Uhr - SABOTAGE_ALARM ..." //----------------------------------------------------------------------------------------------------- function extractDate(text) { if (!text || typeof text !== "string") { LOG(`extractDate Fehler: Ungueltiger Eingabetext: ${text}`, "Fehler", "extractDate", 2); return new Date(0); // Fallback auf ein Default-Datum } const match = text.match(/^(\d{2})\.(\d{2})\.(\d{2}) (\d{2}):(\d{2}):(\d{2})/); // Regulaerer Ausdruck, um Datum und Uhrzeit zu extrahieren if (!match) { LOG(`extractDate Fehler: Kein gueltiges Datum gefunden: ${text}`, "Fehler", "extractDate", 2); return new Date(0); // Fallback auf ein Default-Datum } const [_, day, month, year, hours, minutes, seconds] = match; // Teile aus dem Match extrahieren const fullYear = year.length === 2 ? `20${year}` : year; // Jahr vervollstaendigen, falls noetig const dateString = `${fullYear}-${month}-${day}T${hours}:${minutes}:${seconds}`; // Erstellen des Datums im Format "YYYY-MM-DDTHH:mm:ss" const date = new Date(dateString); // Umwandlung in ein Date-Objekt if (isNaN(date.getTime())) { // ueberpruefen, ob das Datum gueltig ist LOG(`extractDate Fehler: Ungueltiges Datum erzeugt: ${dateString}`, "Fehler", "extractDate", 2); return new Date(0); } return date; } //----------------------------------------------------------------------------------------------------- // Funktion GenerateTEXT_Datenpunkte // Generiert TEXT format aus den JSON Arrays und speichert diese in die Datenpunkte //----------------------------------------------------------------------------------------------------- function GenerateTEXT_Datenpunkte() { LOG(`Routine wird ausgefuehrt`, "Ablauf", "GenerateTEXT_Datenpunkte", 2); if(!UpdateTEXT_Datenpunkte){return}; // wenn keine TEXT-Datenpunkte updates erfolgen sollen nicht ausfuehren let SMAktuellMessageLangTEXT = [], SMHistMessageLangTEXT = [],SMAktuellMessageKurzTEXT = []; HistorischeMeldungenJSON.forEach(Meldung => { // Iteriere durch HistorischeMeldungenJSON und extrahiere die status_message_Lang SMHistMessageLangTEXT.push(Meldung.status_message_Lang + " - Meldung aufgehoben: " + Meldung.SM_aufgehoben_seit + "
"); // Status-Lang-Text extrahieren und ins Array pushen }); setState(id_Text_Servicemeldung_History, SMHistMessageLangTEXT.join('')); // Umwandlung des Arrays in einen String und Speichern im Datenpunkt id_Text_ServicemeldungLang AktuelleMeldungenJSON.forEach(Meldung => { // Iteriere durch AktuelleMeldungenJSON und extrahiere die status_message_Pure und status_message_Lang if (Meldung.status_message_Pure) { SMAktuellMessageKurzTEXT.push(Meldung.datum_seit + " " + Meldung.status_message_Pure + "
"); // Status-Kurz-Text extrahieren und ins Array pushen } if (Meldung.status_message_Lang) { SMAktuellMessageLangTEXT.push(Meldung.status_message_Lang + "
"); // Status-Lang-Text extrahieren und ins Array pushen } }); setState(id_Text_ServicemeldungKurz, SMAktuellMessageKurzTEXT.join('')); // Umwandlung der Arrays in Strings und Speichern in den entsprechenden Datenpunkten setState(id_Text_ServicemeldungLang, SMAktuellMessageLangTEXT.join('')); } //----------------------------------------------------------------------------------------------------- // Funktion generateHtmlTable // Funktion, aus den JSON Tabellen ein HTML-Format erzeugt //----------------------------------------------------------------------------------------------------- function generateHtmlTable(data, HistOderAkt) { if (!UpdateHTML_Datenpunkte) { return; } // Keine Updates, wenn HTML-Datenpunkte deaktiviert sind const uniqueClass = "custom-table"; // Eindeutige Klasse für die Tabelle const cellPadding = '6px'; // Baue die Tabelle mit einer eindeutigen Klasse auf let table = ` `; // Scoped Stile für die Tabelle table += ` `; // Kopfzeile table += ` `; // Tabellenkörper table += ''; data.forEach((item, index) => { const rowColor = index % 2 === 0 ? evenRowColor : oddRowColor; // Farben der Zeilen const textColor = index % 2 === 0 ? evenRowTextColor : oddRowTextColor; // Textfarbe table += ` `; }); table += ''; table += '
Datum_Seit Meldungsart Name GeraeteId Status Batterie SM_aufgehoben_seit
${item.datum_seit} ${item.meldungsart} ${item.common_name} ${item.GeraeteId} ${item.status_message_Pure} ${item.batterie_bezeichnung} ${item.SM_aufgehoben_seit}
'; // Zustand setzen je nach HistOderAkt if (HistOderAkt === "AKT") { setState(id_HTML_Servicemeldung_Aktuell, table); } else { setState(id_HTML_Servicemeldung_History, table); } } //----------------------------------------------------------------------------------------------------- // Funktion Refresh_History loescht die Historie - Ausloeser entweder subcription oder schedule //----------------------------------------------------------------------------------------------------- function Refresh_History() { let jsonString = getState(id_JSON_Servicemeldung_Historie).val || '[]'; // zunaechst die JSON Eintraege HistorischeMeldungenJSON = JSON.parse(jsonString); HistorischeMeldungenJSON = HistorischeMeldungenJSON.filter(meldung => meldung.SM_aufgehoben_seit === "aktiv"); // Filtern der Meldungen, um nur die zu behalten, die den Wert "aktiv" in "SM_aufgehoben_seit" haben let updatedJsonString = JSON.stringify(HistorischeMeldungenJSON); // Speichern der gefilterten historischen Meldungen zurueck in den Datenpunkt setState(id_JSON_Servicemeldung_Historie, updatedJsonString); Check_All("init") } //----------------------------------------------------------------------------------------------------- // Funktion schreibt einen Logeintrag in das Filesystem und auch in das interne Log-System //----------------------------------------------------------------------------------------------------- function writelog(Name, GeraeteId, SMType, SMStatus, SMStatus_Text) { if (!SMProtokoll) 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} ;${Name} ;${GeraeteId} ; ${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"} 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 Create States //----------------------------------------------------------------------------------------------------- async function CreateStates(callback) { LOG(`Routine wird ausgefuehrt`, "Ablauf", "CreateStates", 2); try { if(UpdateTEXT_Datenpunkte) { // nur wenn Text-Datenpunkte auch gewuenscht sind 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_Text_Servicemeldung_History, "", { read: true, write: true, type: 'string', name: 'Servicemeldungen History', desc: 'History Servicemeldung' }); } if(UpdateHTML_Datenpunkte) { // nur wenn Text-Datenpunkte auch gewuenscht sind await createStateAsync(id_HTML_Servicemeldung_Aktuell, "", { read: true, write: true, type: 'string', name: 'Servicemeldungen HTML-Text', desc: 'HTML-Text Servicemeldung' }); await createStateAsync(id_HTML_Servicemeldung_History, "", { read: true, write: true, type: 'string', name: 'Servicemeldungen HTML-History', desc: 'HTML-History 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_Button_Refresh_Historie, false, { read: true, write: true, type: 'boolean', name: 'Refresh Button History', desc: 'Loescht die Historie Servicemeldungem wenn true' }); for (const { alarmtype, count, Datenpunkt } of Zaehler) { // Schleife ueber das Zaehler-Array und anlegen der States await createStateAsync(Datenpunkt, count, {read: true, write: true,type: 'number',name: `Servicemeldungen Anzahl ${alarmtype}`,desc: `Anzahl ${alarmtype} Servicemeldungen`}); } if (callback) await callback(); // Aufruf des Callbacks nach Abschluss aller Operationen } catch (error) { LOG(`Kategorie:WARN; Routine:CreateStates; Routine Create States - Fehler beim Erstellen der Zustaende: ${error}`, 0, "warn"); } if (!UpdateTEXT_Datenpunkte) { if (existsState(id_Text_ServicemeldungLang)) { deleteState(id_Text_ServicemeldungLang); } if (existsState(id_Text_ServicemeldungKurz)) { deleteState(id_Text_ServicemeldungKurz); } if (existsState(id_Text_Servicemeldung_History)) { deleteState(id_Text_Servicemeldung_History); } } if(!UpdateHTML_Datenpunkte) { // nur wenn Text-Datenpunkte auch gewuenscht sind if (existsState(id_HTML_Servicemeldung_Aktuell)) { deleteState(id_HTML_Servicemeldung_Aktuell); } if (existsState(id_HTML_Servicemeldung_History)) { deleteState(id_HTML_Servicemeldung_History); } } }