// 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.01 - 06.01.2024 n/a Meldungen koennen auch durch andere Zeichen ersetzt werden // Version 3.02 - 20.01.2024 bei Monatswechsel wurde die TEXT ableitung nicht korrekt durchgefuehrt // HTML Generierung als Alternative zu JSON // Version 3.04 - 21.01.2024 weitere HTML Attribute für die Expereneinstellungen hinzugefuegt - 3.05 - Korrektur globaler CSS Einstellung // Version 3.06 - 22.01.2024 leftover log entfernt // Batteriebezeichnung fuer Historie hinzugefuegt //HTML Tabelle äusserer Rahmen nicht notwendig(entfernt) // Versipm 3.07 - 23.01.2024 Moeglichkeit zum manuellen Loeschen der Historie per Button hinhzugefuegt. Schedule zur Loeschung kann beibehalten oder deaktiviert werden // 3.08 Korrektur //--------------------------------------------------------------------------------------------------------------------------------------------------------------- // 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 = false; // die langen Servicemeldungen als auch die historischen werden immer im JSON Format abgelegt. Alternativ koennen diese auch in einem HTML Format in Datenpunkten gespeichert 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']; const MessengerScope = { 'UNREACH_ALARM': [false, false, false, false, false, false], 'LOWBAT_ALARM': [false, false, false, false, false, false], 'SABOTAGE_ALARM': [false, false, false, false, false, false], 'CONFIG_PENDING': [false, false, false, false, false, false], 'Sonstige': [false, false, false, false, false, false], 'keineSM': [false, false, 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 let emailAddresse = "Vorname-Nachname@web.de" 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 const instanceIds = [ { name: 'HMClassicInstanz', adapter: 'hm-rpc', Instanz: HMClassicInstanz }, { name: 'HMIPInstanz', adapter: 'hm-rpc', Instanz: HMIPInstanz }, { name: 'GruppenInstanz', adapter: 'hm-rpc', Instanz: GruppenInstanz }, { name: 'WiredIClassicInstanz', adapter: 'hm-rpc', Instanz: WiredIClassicInstanz }, { name: 'HMIPAccessPoint', adapter: 'hmip' , Instanz: HMIPAccessPoint }, ]; // Konfigurationsobjekt fuer Ausnahmen / es koennen Arlarmtypes je Instanz ausgeschlossen werden / Filter auf AlarmTypes const exceptions = { HMClassicInstanz: [], HMIPInstanz: ['DUTY_CYCLE'], GruppenInstanz: ['ERROR_NON_FLAT_POSITIONING_ALARM'], WiredIClassicInstanz: ['LOWBAT_ALARM', 'LOW_BAT_ALARM','ERROR_NON_FLAT_POSITIONING_ALARM'], }; //----------------------------------------------------------------------------------------------------- //Ende Einstellungen //----------------------------------------------------------------------------------------------------- LOG(`Script: Servicemeldungen Volume 2 - Autor: Looxer01 Datum: 23.01.2025 Version:3.08`,`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; // Variable fuer die Gesamtzahl der IDs const selectors = alarmTypes.map(alarmType => { const collectedIds = []; instanceIds.forEach(instance => { if (instance.Instanz !== 9) { // Umbenennung von `instance.id` zu `instance.Instanz` const excludedAlarmTypes = exceptions[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)); } }); } } }); const filteredIds = collectedIds.filter(id => { // IDs anhand der Geraete-IDs in der Ausschlussliste filtern const deviceId = id.split('.')[2]; // Geraete-ID aus der vollstaendigen 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 hinzufuegen return { name: alarmType.key, ids: filteredIds }; }); 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() { if( !ScheduleAktiv) {return }; 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 }, 2000); // 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 callCount = 0; // Zaehler fuer Aufrufe // maximal 25 let timeoutActive = false; // Status fuer Timeout 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 (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 3 Minuten timeoutActive = false; // Timeout beenden callCount = 0; // Zaehler zuruecksetzen LOG("Wartezeit beendet, weitere Servicemeldungen sind jetzt moeglich.", "Ablauf", "SubscribeGeraeteID", 0); }, 3 * 60 * 1000); // 3 Minuten in Millisekunden } Check_All("trigger",obj); setTimeout(() => { // Zuruecksetzen des Zaehlers 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 //----------------------------------------------------------------------------------------------------- // 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 'STICKY_SABOTAGE_ALARM': if(status >= 1 ) {processServiceMessage(selector.name, status, id,RunType); } break 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 flache Kopie 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) ) { // 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) } } // 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) ) { // 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 addMessageToCollector(aktuelleMeldung.meldungsart, aktuelleMeldung.status_message_Pure, aktuelleMeldung.status_message_Lang); 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 ) 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 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'); // Message senden } // 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); // nur einmal unabhaengig der Version increaseCount(alarmtype); increaseCount('SMAktuell'); } //----------------------------------------------------------------------------------------------------- // 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 // Aufbau MessageCollector // 1. Key AlarmType i.e. LowBat // 2. Key Messenger i.e. email // 3. Nachricht i.e "Batstadn nierig" // - Beispiel //{ "ERROR": { "Email": [ "Error Alarm " ], // "SMS": ["Error" ] }, // "SABOTAGE_ALARM": { "Email": [ "Alarm: Eindringling im Gebaeude!"], //----------------------------------------------------------------------------------------------------- function addMessageToCollector(messageType, MessageKurz, MessageLang) { let actualMessageType, matchingAlarmType; if (messageType != "keineSM") { // keineSM ist sonderbehandlung let isValidMessageType = alarmTypes.some((type) => type.suffixes.includes(messageType)); // ist der Messagytype in alarmtype.suffixes enthalten ? if (isValidMessageType) { // in suffixes enthalten matchingAlarmType = (alarmTypes.find((type) => type.suffixes.includes(messageType))).key; // wenn in alarmtype.suffixes enthalten, dann umsetzen in matchingAlarmType actualMessageType = messageType }else{ LOG(`MessageType: ${messageType} ist nicht konfiguriert in alarmtypes`, "WARN", "addMessageToCollector", 0); // wird weiter unten auf Sonstige gestellt } } if (messageType === "keineSM") { // Sonderbehandlung fuer "keineSM" - Diese wird behandelt wie jeder andere messageType actualMessageType = "keineSM", matchingAlarmType = "keineSM" } LOG(`messagetype ist ${messageType} -- aktueller messsagetype ${actualMessageType} -- matchingAlarmString = ${matchingAlarmType}`, "Ablauf", "addMessageToCollector", 2); if (!MessageSendCollector[actualMessageType]) { // Sicherstellen, dass der Typ im MessageSendCollector existiert MessageSendCollector[actualMessageType] = {}; // hinzufuegen des messageTypes } let messengerConfig = MessengerScope[matchingAlarmType]; // ueberpruefen, ob der alarmtype im MessengerScope vorhanden ist, falls nicht, 'Sonstige' verwenden if (!messengerConfig) { // Wenn der alarmtype nicht im MessengerScope definiert ist, benutze die Konfiguration fuer 'Sonstige' messengerConfig = MessengerScope['Sonstige'] || Array(services.length).fill(false); // false ist fallback - messengerConfig ist ein array. services=liste der messenger } messengerConfig.forEach((isActive, index) => { // Nachricht nur hinzufuegen, wenn die Konfiguration fuer den Service aktiv ist if (isActive) { const service = services[index]; if (!MessageSendCollector[actualMessageType][service]) { // Nachricht nur hinzufuegen, wenn die Konfiguration fuer den Service aktiv ist MessageSendCollector[actualMessageType][service] = []; } const messageToAdd = TextTypeKurz[index] ? MessageKurz : MessageLang; // Auswahl von MessageKurz oder MessageLang basierend auf TextTypeKurz index entspricht dem service MessageSendCollector[actualMessageType][service].push(messageToAdd + '\n'); // Messages werden nacheinander reingepusht. mehrere messages bei gleichen keys (messagtype und service) } }); } //----------------------------------------------------------------------------------------------------- // 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]); }); delete MessageSendCollector[type]; // Loesche den Typ nach der Verarbeitung }); Object.keys(combinedMessagesByService).forEach((service) => { // Sende kombinierte Nachrichten an die jeweiligen Dienste 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", 2); 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"); } } //----------------------------------------------------------------------------------------------------- // 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) { 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 }; 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) { if (!Array.isArray(parsedJson)) { // ueberpruefen, ob parsedJson ein Array ist LOG(`parsedJson ist kein Array`, "WARN","checkIFJSONfEntryExists", 1); return false; } for (let entry of parsedJson) { // Durch das Array iterieren if (entry.datum_seit === datum_seit && entry.GeraeteId === GeraeteId && entry.status === status) { // ueberpruefen, ob die Felder datum_seit, GeraeteId und status uebereinstimmen 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); } } }