// Beschreibung des Scriptes: https://forum.iobroker.net/topic/77816/vorlage-servicemeldungen-volume2 // Autor Looxer01 02.11.2024 Version 1.0 (initiale Version) // Version 2.00 - 08.12.2024 LOGs hinzugefuegt // HCU integration vorbereitet - Accesspoint im prinzip implementiert unklar aber ist die objdefinition der IDs // Sortierung fuer GeraeteIDTrinnger der historischen Meldungen nach Datum aufsteigend in JSON und Text // Umbenennung von DP TestVergangeneSM in TextVergangeneSM // Historische Servicemeldungen auch bei REGA Subscription // Umstrukturierungen // Version 2.20 - 30.12.2024 Sortierung fuer historische GeraeteID Trigger entfernt // komplette Ueberarbeitung der JSON Servicemeldungen // Historische Meldungen bei JSON jetzt bei GeraeteTriggerID = false ohne Wiederholungen / Bei JSON wahl ob mehrere Spalten oder eine Spalte mit allen Infos //--------------------------------------------------------------------------------------------------------------------------------------------------------------- // Muss-Einstellungen ( HM-Classic , HMIP, Wired - hier muessen zwingend die Instanzen stimmmen ) //--------------------------------------------------------------------------------------------------------------------------------.------------------------------- // Im folgenden sind die Instanzen gelistet fuer die die Selektion erfolgt bzw nicht erfolgt (Filter) // bitte 9 Eintragen falls eine Instanz nicht relevant ist // Gruppeninstanzen sind normalerweise nicht relevant // CuxD Instanzen duerfen nicht eingetragen werden const HMClassicInstanz = 0; // HM-Classic Instanz eintragen // 9 = falls nicht relevant const HMIPInstanz = 1; // Homematic IP instanz // 9 = falls nicht relevant const WiredIClassicInstanz = 9; // Wired Instanz // 9 = falls nicht relevant const GruppenInstanz = 9; // 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 Logsconstst //const PathSMLog = "/iobroker/log/ServicemeldungenVol2.csv";"; // Pfad fuer Windows/ iobroker ist der angenommene iobroker home-pfad // Text der erscheinen soll, wenn keine SM vorliegen const MessageBeiKeinerSM = 'Derzeit keine Servicemeldungen' // ein Kurztext ist erforderlich. kann aber angepasst werden //Geraete die nicht ueberwacht werden sollen. Geraete-IDs eingeben - Komma getrennt erfassen const Ausschlussliste = ['0000D7099xxx26', '00091D8xxx7410']; // immer mit Komma trennen // Filter auf Einzel-IDs // debug level kann eingestellt werden - wenn alles laeuft dann 0 = ruhe im log // debug level 0 kein log // debug level 1 - nur die wichtigsten Meldungen werden gelistet // debug level 2 - mehr als nur die wichtigsten Meldungen aber ohne einzelne IDs // debug level 3 - hier werden auch einzelne IDs gelistet (koennten lange listen werden) const debugLevel = 0 ; const SystemLog = false; // schreib das Sytemprotokoll in ein externes log (sollte normalerweise deaktviert sein nur bei Problemen verwenden) const PathSystemLog = "/opt/iobroker/log/ServicemeldungenSystemLog.csv"; // Pfad und Dateiname des externen Logs //const PathSystemLog = "/iobroker/log/ServicemeldungenSystemLog.csv";"; // Pfad fuer Windows // wenn GeraeteIDTrigger auf true gestellt wird, dann wird fuer jeden Datenpukt mit Relevanz fuer eine Servicemeldung eine subscription angelegt. // 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: keine Servicemeldungs Historie // 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 // 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 //----------------------------------------------------------------------------------------------------- const scheduleTimeClearSMTexte = "2 0 1 * *"; // Sam 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_JSON_Servicemeldung_Aktuell = path+'JSONAktuelleSM'; // JSON Tabelle Datenpunkt Aktuelle SM const id_JSON_Servicemeldung_Historie = path+'JSONVergangeneSM'; // JSON Tabelle Datenpunkt Historische SM // 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: '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"}, "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: 30.12.2024 Version:2.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 SMAktuellMessageLangTEXT = [], SMHistMessageLangTEXT = [],SMAktiuellMessageKurzTEXT = [], 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(); } //----------------------------------------------------------------------------------------------------- // Schedule zum Loeschen des Datenpunktwertes der Histore einmal pro Monat oder Woche je nach schedule-Einstellung //----------------------------------------------------------------------------------------------------- schedule(scheduleTimeClearSMTexte, function() { setState(id_Text_Servicemeldung_History, ''); setState(id_JSON_Servicemeldung_Historie, []); LOG(`Datenpunkte (Text und JSON) ${id_Text_Servicemeldung_History} geloescht`,`Ablauf`,`Start`,2); }); //----------------------------------------------------------------------------------------------------- // Funktion SubscribeRegaDP // Erstellung der Subscriptions auf REGA Datenpunkt // es werden 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(); SMAktuellMessageLangTEXT = []; SMHistMessageLangTEXT= []; SMAktiuellMessageKurzTEXT = []; AktuelleMeldungenJSON =[];HistorischeMeldungenJSON = []; 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 // Sequenz zur Speicherung der aktuellen Servicemeldungen in die Text und JSON -Datenpunkte-------------------------- if (Zaehler.find(e => e.alarmtype === 'SMAktuell').count === 0) { // wenn keine SM vorliegen setState(id_Text_ServicemeldungLang, MessageBeiKeinerSM); setState(id_Text_ServicemeldungKurz, MessageBeiKeinerSM); 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 let formattedMessagesLang = SMAktuellMessageLangTEXT.join('
');// Formatiere alle aktuellen Servicemeldungen / Zeilenumbruch // SMAktuellMessageLangTEXT wird im vorherigen loop gefuellt let formattedMessagesKurz = SMAktiuellMessageKurzTEXT.join('
'); setState(id_Text_ServicemeldungLang, formattedMessagesLang); // Aktuelle Meldungen speichern setState(id_Text_ServicemeldungKurz, formattedMessagesKurz); // Aktuelle Meldungen speichern } // JETZT: ZaehlerStaende in die ioBroker - Datenpunkte------------------- Zaehler.forEach(entry => { setState(entry.Datenpunkt, entry.count); }); //--------------------------------------------------------------------------------------------------------------------------------------------------- // Die folgende Sequenz ist relevant fuer HM-REGA Trigger fuer Historische SM und Message-Sending if (!GeraeteIDTrigger && RunType === "trigger") { // nur bei REGA Trigger// SaveHistSMREGATrigger() // Historie fuer REGA Trigger if (Zaehler.find(e => e.alarmtype === 'SMAktuell').count === 0) { addMessageToCollector('keineSM', MessageBeiKeinerSM, MessageBeiKeinerSM); sendMessage('keineSM'); }else{ sendMessage(); // senden der messages, die im message collector gesammelt wurden - bei HM-REGA sind es immer alle aktuellen } } //--------------------------------------------------------------------------------------------------------------------------------------------------- // Die folgende Sequenz ist relevant fuer GeraeteIDTrigger fuer Historische SM und Message-Sending basierend auf die letzte Aenderung einer single ID if (GeraeteIDTrigger && RunType === "trigger") { // nur bei GeraeteIDTrigger Trigger Case const GeraeteId = ExtractMetaData(obj.id, "GeraeteID"); if (!existsState(obj.id)) { // obj.id nicht mehr in ioBroker aber noch im Selector LOG(`Geraet ${GeraeteId} scheint zwischenzeitlich nicht mehr zu existieren - Empfehlung: script neu starten`, "WARN", "Check_All", 0, "warn"); return; } 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_textKurz = DefineServiceMessage(native_type, status, obj.id, "kurz"); let status_textPure = DefineServiceMessage(native_type, status, obj.id, "pure"); // nur status text aus der tabelle ohne textzusaetze if(status === 0 ) {// Status "0" wurde gesetzt. Bei HM-Classic war das der Status um zu melden, dass die Servicemeldung zurueckgesetzt wurde LOG(`Status ist 0 - status_text ist ${status_textLang} Meldungsart ist ${meldungsart}`, "Ablauf", "Check_All", 2); } const datum_seit = func_get_datum(obj.id); SaveHistGeraeteIDTrigger(datum_seit, meldungsart, common_name, GeraeteId, status, status_textLang,status_textPure) // Meldung in der Historie speichern if (Zaehler.find(e => e.alarmtype === 'SMAktuell').count === 0) { // keine Servicmeldungen addMessageToCollector('keineSM', MessageBeiKeinerSM, MessageBeiKeinerSM); sendMessage('keineSM'); } else { addMessageToCollector(meldungsart, status_textKurz, status_textLang); sendMessage(meldungsart); } writelog(common_name, GeraeteId, meldungsart, status, status_textLang); // externes log erzeugen LOG(`neue Servicemeldung fuer GeraeteIDTrigger ist ${status_textLang}`, "Ergebnis", "Check_All", 1); } // Log Ausgabe der Ergebnisse-------------------------------------------- LOG(`Es wurden insgesamt ${Zaehler.find(e => e.alarmtype === 'Gesamt').count} IDs gecheckt - insgesamt gibt es ${Zaehler.find(e => e.alarmtype === 'SMAktuell').count} Servicemeldungen`, "Ergebnis", "Check_All", 2); LOG(`Davon gibt es zurzeit aktuelle Servicemeldungen: ${Zaehler.find(e => e.alarmtype === 'SMAktuell').count}`, "Ergebnis", "Check_All", 2); Zaehler.forEach(({ alarmtype, count }) => { LOG(`SMAktuell fuer alarmtype: ${alarmtype} insgesamt festgestellte Servicemeldungen: ${count}`, "Ergebnis", "Check_All", 2); }); LOG(`Gesammelte Servicemeldungen Lang: ${JSON.stringify(SMAktuellMessageLangTEXT, null, 2)}`, "Ergebnis", "Check_All", 3); LOG(`Gesammelte Servicemeldungen Kurz: ${JSON.stringify(SMAktiuellMessageKurzTEXT, null, 2)}`, "Ergebnis", "Check_All", 3); LOG(`Zeitverbrauch fuer Routine Check_All: ${Date.now() - startTime} Millisekunden`, "Ergebnis", "Check_All", 1); SMAktuellMessageLangTEXT = []; SMAktiuellMessageKurzTEXT = []; AktuelleMeldungenJSON =[];HistorischeMeldungenJSON = []; MessageSendCollector = {}; // Speicher freigeben } //----------------------------------------------------------------------------------------------------- // Function SaveHistGeraeteIDTrigger // Diese Funktion ist nur fuer Check_ALL relevant // hier geht es um die historie der Servicemeldungen als JSON String inklusive Sortierung fuer GeraeteIDTrigger //----------------------------------------------------------------------------------------------------- function SaveHistGeraeteIDTrigger(datum_seit, meldungsart,common_name,GeraeteId,status,status_textLang,status_textPure) { LOG(`Routine SaveHistGeraeteIDTrigger wird ausgefuehrt`, "Ablauf", "SaveHistGeraeteIDTrigger", 2); let jsonString = getState(id_JSON_Servicemeldung_Historie).val || '[]'; // Lade den aktuellen JSON-String des Historie-Datenpunkts let parsedJson = JSON.parse(jsonString); appendToState(id_Text_Servicemeldung_History, status_textLang, false); // Historischen Text speichern, nicht sortieren parsedJson = CreateJsonEntry(parsedJson,datum_seit, meldungsart,common_name, GeraeteId, status, status_textLang,status_textPure, null,false ); if (Zaehler.find(e => e.alarmtype === 'SMAktuell').count === 0) { // Pruefen, ob keine aktuellen Servicemeldungen vorliegen parsedJson = CreateJsonEntry( parsedJson ,func_get_datum(),'', '', '','', func_get_datum() + " - " + MessageBeiKeinerSM,MessageBeiKeinerSM,null,false ); // Keine aktuellen Meldungen -> "Keine Meldungen" hinzufuegen appendToState( id_Text_Servicemeldung_History, `${func_get_datum()} - ${MessageBeiKeinerSM}`, false ); // Historie als Text speichern, nicht sortieren writelog("", "", "", "", MessageBeiKeinerSM); } parsedJson = Markiere_HistSM_als_Aufgehoben(parsedJson) // markiert hist messages als erledigt oder aktiv jsonString = JSON.stringify(parsedJson); // Finalen JSON-String speichern setState(id_JSON_Servicemeldung_Historie, jsonString); } //----------------------------------------------------------------------------------------------------- // Function HistSMREGATrigger // Diese Funktion ist nur fuer Check_ALL relevant - hier geht es um die historie der Servicemeldungen als JSON String fuer REGA Trigger //----------------------------------------------------------------------------------------------------- function SaveHistSMREGATrigger() { LOG(`Routine SaveHistSMREGATrigger wird ausgefuehrt`, "Ablauf", "SaveHistSMREGATrigger", 2); let jsonString = getState(id_JSON_Servicemeldung_Historie).val || '[]'; // Bestehenden JSON-Inhalt laden let parsedJSON = (JSON.parse(jsonString)); if (Zaehler.find(e => e.alarmtype === 'SMAktuell').count === 0) { if(!isLatestEntryMessageBeiKeinerSM(parsedJSON) ) { // nur wenn die MessageBeiKeinerSM nicht schon im JSOn vorhanden ist parsedJSON = CreateJsonEntry(parsedJSON, func_get_datum(), '', '', '', '', func_get_datum() + " - " + MessageBeiKeinerSM,MessageBeiKeinerSM,null,false); SMHistMessageLangTEXT.unshift(`${func_get_datum()} - ${MessageBeiKeinerSM}`); writelog("", "", "", "", MessageBeiKeinerSM); // Externes Log erzeugen LOG(`Die Meldung "keine Servicemeldung" wurde zur Historie hinzugefuegt fuer REGA-Subscription`, "Ergebnis", "SaveHistSMREGATrigger", 2); } } else { // Aktuelle Servicemeldungen vorhanden parsedJSON.unshift(...HistorischeMeldungenJSON); // die Punkte (spread) verhindern, dass das array HistorischeMeldungenJSON als array eingefuegt wird (kein array in array) LOG(`Die aktuellen Servicemeldungen wurden zur Historie hinzugefuegt fuer REGA-Subscription`, "Ergebnis", "SaveHistSMREGATrigger", 2); } parsedJSON = Markiere_HistSM_als_Aufgehoben(parsedJSON) // markiert hist messages als erledigt oder aktiv jsonString = JSON.stringify(parsedJSON); setState(id_JSON_Servicemeldung_Historie, jsonString); // JSON-Daten speichern const messageString = SMHistMessageLangTEXT.join('
'); // Text-Historie speichern if (messageString) { // leer dann wenn es keine neuen messages gibt, die in die Historie muessen appendToState(id_Text_Servicemeldung_History, messageString, true); } } //----------------------------------------------------------------------------------------------------- // Function processServiceMessage // Zuordnung der richtigen Servicemeldung zum Alarmtype / Zaehlung der Faelle //----------------------------------------------------------------------------------------------------- function processServiceMessage(alarmtype, status, id, RunType) { const GeraeteID = ExtractMetaData(id, "GeraeteID"); const common_name = ExtractMetaData(id, "CommonName"); const native_type = ExtractMetaData(id, "nativeType"); let datum_seit = func_get_datum(id); let ServiceMeldungTextLang = DefineServiceMessage(native_type, status, id, "lang"); // aktuelle Servicemeldung ableiten SMAktuellMessageLangTEXT.push(ServiceMeldungTextLang); // Aktuelle Servicemeldung in array pushen let ServiceMeldungTextKurz = DefineServiceMessage(native_type, status, id, "kurz"); SMAktiuellMessageKurzTEXT.push(ServiceMeldungTextKurz); // if (!GeraeteIDTrigger) { // historische Meldung fuer REGA Trigger let jsonString = getState(id_JSON_Servicemeldung_Historie).val || '[]'; // Bestehenden JSON-Inhalt laden let parsedJSON = (JSON.parse(jsonString)); if (!checkIFJSONfEntryExists(parsedJSON, datum_seit, GeraeteID, status) ) { // checken ob ein Eintrag schon in der Historie ist, wenn nicht dann hinzufuegen let status_textPure = DefineServiceMessage(native_type, status, id, "pure"); addMessageToCollector(alarmtype, ServiceMeldungTextKurz, ServiceMeldungTextLang); HistorischeMeldungenJSON = CreateJsonEntry(HistorischeMeldungenJSON,datum_seit, alarmtype, common_name, GeraeteID, status, ServiceMeldungTextLang, status_textPure, null, true ) SMHistMessageLangTEXT.push(ServiceMeldungTextLang) writelog(common_name, GeraeteID, alarmtype, status, ServiceMeldungTextLang); // externes log erzeugen // } } 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}`; AktuelleMeldungenJSON = CreateJsonEntry(AktuelleMeldungenJSON, datum_seit, meldungsart, common_name, GeraeteId, status, Status_Message_Lang, message, func_Batterie(native_type), true); // nur einmal unabhaengig der Version 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", 1); switch (service) { case "email": sendTo(`email.${MessengerInstanz[0]}`, "send", { text: combinedMessage, to: emailAddresse, subject: Headline }); break; case "whatsApp": sendTo(`whatsapp-cmb.${MessengerInstanz[1]}`, "send", { text: combinedMessage }); break; case "Signal": sendTo(`signal-cmb.${MessengerInstanz[2]}`, "send", { text: combinedMessage }); break; case "Telegram": sendTo(`telegram.${MessengerInstanz[3]}`, "send", { text: combinedMessage, user: TelegramUser // Telegram User ID, um den Nachrichteneempfaenger festzulegen }); break; case "Pushover": sendTo(`pushover.${MessengerInstanz[4]}`, "send", { message: combinedMessage, sound: "" }); break; case "Pushsafer": sendTo(`pushsafer.${MessengerInstanz[5]}`, "send", { message: combinedMessage, title: Headline }); break; default: LOG(`Unbekannter Service: ${service}`, "WARN", "sendToService", "warn"); } } //----------------------------------------------------------------------------------------------------- // 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 korrektur eingestellt - 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; } 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) { datum = formatDate(getState(id).lc, "TT.MM.JJ SS:mm:ss"); } else { datum = formatDate(new Date(), "TT.MM.JJ SS:mm:ss"); // Aktuelles Datum } return datum < '01.01.71 01:00:00' ? '' : `${datum} Uhr`; } //----------------------------------------------------------------------------------------------------- // func_Batterie Batterieermittlung //----------------------------------------------------------------------------------------------------- function func_Batterie(native_type) { LOG(`Routine wird ausgefuehrt`, "Ablauf", "func_Batterie", 2); const normalizedType = native_type.toUpperCase(); return Object.keys(batteryTypes).find(battery => batteryTypes[battery].some(device => device.toUpperCase() === normalizedType) ) || 'n/a'; } //----------------------------------------------------------------------------------------------------- // 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 || 'n/a', common_name: common_name || 'n/a', GeraeteId: GeraeteId || 'n/a', status: status || 'n/a', // 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 || 'n/a', 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 für eine Sekunde Unterschied const nurKeineMeldungen = AktuelleMeldungenJSON[0].status_message_Lang.includes(MessageBeiKeinerSM); // Prüfen, ob AktuelleMeldungenJSON nur eine Meldung mit "Derzeit keine Servicemeldungen" enthält if (nurKeineMeldungen) { HistorischeMeldungenJSON_transfer.forEach(meldung => { // Alle historischen Einträge als aufgehoben markieren if (meldung.SM_aufgehoben_seit === "aktiv") { meldung.SM_aufgehoben_seit = func_get_datum(); } }); return HistorischeMeldungenJSON_transfer; } HistorischeMeldungenJSON_transfer.forEach(historischeMeldung => { // Iteriere über alle historischen Meldungen (äußere Schleife) if (historischeMeldung.SM_aufgehoben_seit !== "aktiv") return; // Wenn die historische Meldung bereits aufgehoben wurde, überspringen let zugeordnet = false; const historischeDatum = extractDate(historischeMeldung.datum_seit); AktuelleMeldungenJSON.forEach(aktuelleMeldung => { // Iteriere über 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; // Prüfe, 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 zum Hinzufuegen einer neuen Zeile am Anfang des bestehenden States (nicht fuer JSON) //----------------------------------------------------------------------------------------------------- // Funktion zum Hinzufuegen einer neuen Zeile, Sortieren und Speichern im State function appendToState(id, newText, shouldSort = true) { console.log(newText) const currentText = getState(id).val; let textArray = currentText ? currentText.split('
') : []; // Trennen des aktuellen Textes in Zeilen (angenommen, Zeilen sind durch
getrennt) textArray.unshift(newText); // Der neue Text wird vorne hinzugefuegt if (shouldSort) { // Falls sortiert werden soll, dann sortiere die Zeilen nach Datum in absteigender Reihenfolge textArray.sort((a, b) => { const dateA = extractDate(a); // Extrahieren des Datums aus der Zeile const dateB = extractDate(b); return dateB.getTime() - dateA.getTime(); // Zeitstempel von Date-Objekten zum Vergleich }); } const updatedText = textArray.join('
'); // Die Zeilen wieder mit
verbinden setState(id, updatedText); // Den neuen Text im State speichern } //----------------------------------------------------------------------------------------------------- // 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 extractDateum // 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 schreibt einen Logeintrag in das Filesystem und auch in das interne Log-System (looxer) //----------------------------------------------------------------------------------------------------- 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 { await createStateAsync(id_Text_ServicemeldungLang, "", { read: true, write: true, type: 'string', name: 'Servicemeldungen LangText', desc: 'LangText Servicemeldung' }); await createStateAsync(id_Text_ServicemeldungKurz, "", { read: true, write: true, type: 'string', name: 'Servicemeldungen KurzText', desc: 'KurzText Servicemeldung' }); await createStateAsync(id_JSON_Servicemeldung_Aktuell, "", { read: true, write: true, type: 'string', name: 'Aktuelle Servicemeldungen als JSON', desc: 'Vergangene Servicemeldung JSON' }); await createStateAsync(id_JSON_Servicemeldung_Historie, "", { read: true, write: true, type: 'string', name: 'Servicemeldungen History', desc: 'History Servicemeldung' }); await createStateAsync(id_Text_Servicemeldung_History, "", { read: true, write: true, type: 'string', name: 'Servicemeldungen History', desc: 'History Servicemeldung' }); 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"); } }