// 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");
}
}