// 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.10 - 15.12.2024 HMIP Accesspoint / HCU vollstaendig implementiert // Overheat und Uncervoltage Alarm hinzugefuegt // Performance Verbesserung Faktor 4
// Version 2.11 - 15.12.2024 Korrekturen HMIP AccessPoint/HCU - jetzt getestet
// Version 2.12 - 20.12.2024 Korrektur Senden von Messages.Es gab Situationen bei denen kein Versand erfolgte
// Version 2.13 - 22.12.2024 Geraete DUTY_CYCLE hinzugefuegt - Zur Nutzung muss Tabelle "exceptions" konfiguriert werden und DUTY_CYCLE entfernt werden - funktioniert nur bei GeraeterTriggerID=true
//---------------------------------------------------------------------------------------------------------------------------------------------------------------
// 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' // auf '' setzen wenn kein Text gezeigt werden soll
//Geraete die nicht ueberwacht werden sollen. Geraete-IDs eingeben - Komma getrennt erfassen
const Ausschlussliste = ['0000D7099xxx26', '00091D8xxx7410']; // immer mit Komma trennen // Filter auf Einzel-IDs
// debug level kann eingestellt werden - wenn alles laeuft dann 0 = ruhe im log
// debug level 0 kein log // debug level 1 - nur die wichtigsten Meldungen werden gelistet // debug level 2 - mehr als nur die wichtigsten Meldungen aber ohne einzelne IDs
// debug level 3 - hier werden auch einzelne IDs gelistet (koennten lange listen werden)
const debugLevel = 0 ;
const SystemLog = false; // schreib das Sytemprotokoll in ein externes log (sollte normalerweise deaktviert sein nur bei Problemen verwenden)
const PathSystemLog = "/opt/iobroker/log/ServicemeldungenSystemLog.csv"; // Pfad und Dateiname des externen Logs
//const PathSystemLog = "/iobroker/log/ServicemeldungenSystemLog.csv";"; // Pfad fuer Windows
// wenn GeraeteIDTrigger auf true gestellt wird, dann wird fuer jeden Datenpukt mit Relevanz fuer eine Servicemeldung eine subscription angelegt.
// 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
//-----------------------------------------------------------------------------------------------------
// Texte werden in "history" hinzugefuegt und etsprechend des schedules wieder geloescht -- nur relvant wenn GeraeteIDTrigger = false
const scheduleTimeClearSMTexte = "2 0 1 * *"; // Sam 1. tag des monats um 00:02 morgens - sollen alle Servicemeldungen der Woche datenpunkt der SM-Texte geloescht werden
// const scheduleTimeClearSMTexte = "58 23 * * 0"; // alternative Sonntags um 23:58 Uhr sollen alle Servicemeldungen der Woche im datenpunkt der SM-Texte geloescht werden
const TrennlinieHistSMREGA = "--------------------------------------------"; // Trennlinie einbauen bei REGA Trigger fuer Historie / Erhoehung der Uebersichtlichkeit - ""=keine Trennung
//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 = { // Umwandlung fuer Namen der Geraete (common.name)
'.': ' ',
'ä': 'ae',
'ü': 'ue',
'ö': 'oe',
'ß': 'ss'
};
// 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: 22.12.2024 Version:2.13`,`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 ServicemeldungMessagesLang = [];let ServicemeldungMessagesKurz = [];let HistorischeSMjson = [];let AktuelleSMjsonString =[], 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();
ServicemeldungMessagesLang = []; ServicemeldungMessagesKurz = []; AktuelleSMjsonString =[];HistorischeSMjson = [];
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
case 'UNREACH_ALARM':
case 'STICKY_UNREACH_ALARM':
case 'UPDATE_PENDING_ALARM':
case 'LOW_BAT_ALARM':
case 'LOWBAT_ALARM':
case 'DEVICE_IN_BOOTLOADER_ALARM':
case 'FAULT_REPORTING':
case 'SABOTAGE_ALARM':
case 'ERROR_NON_FLAT_POSITIONING_ALARM':
case 'OVERHEAT_ALARM' :
case 'UNDERVOLTAGE_ALARM':
case 'CONFIG_PENDING':
if(status === 1 || status === true ) {processServiceMessage(selector.name, status, id,RunType); } // bei status = true = AccessPoint
break;
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: // falls alarmtype nicht gelistet, dann geht dieser in Kategorie error
if (status === 1|| status === true ) { processServiceMessage('ERROR', status, id,RunType); } // sollte nicht vorkommen
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);
if(RunType === "trigger") {
let parsedJson = []
let neuerEintrag = createJsonEntry(func_get_datum(), '', '', '', '', MessageBeiKeinerSM);
parsedJson.push(neuerEintrag);
let jsonString = JSON.stringify(parsedJson);
setState(id_JSON_Servicemeldung_Aktuell, jsonString); // Aktuelle SM wird in JSON-Datenpunkt als String gespeichert
}
} else { // wenn SM vorliegen
let jsonString = JSON.stringify(AktuelleSMjsonString); // AktuelleSMjsonString wird in Funktion DefineServiceMessage gefuellt
setState(id_JSON_Servicemeldung_Aktuell, jsonString); // Aktuelle SM wird in JSON-Datenpunkt als String gespeichert
let formattedMessagesLang = ServicemeldungMessagesLang.join('
');// Formatiere alle aktuellen Servicemeldungen / Zeilenumbruch
let formattedMessagesKurz = ServicemeldungMessagesKurz.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
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");
if(status === 0 ) {// Status "0" wurde gesetzt. Bei HM-Classic war das der Status um zu melden, dass die Servicemeldung zurueckgesetzt wurde
status_textLang = DefineServiceMessage(native_type, status, obj.id, "lang")
status_textKurz = DefineServiceMessage(native_type, status, obj.id, "kurz")
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) // 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(ServicemeldungMessagesLang, null, 2)}`, "Ergebnis", "Check_All", 3);
LOG(`Gesammelte Servicemeldungen Kurz: ${JSON.stringify(ServicemeldungMessagesKurz, null, 2)}`, "Ergebnis", "Check_All", 3);
LOG(`Zeitverbrauch fuer Routine Check_All: ${Date.now() - startTime} Millisekunden`, "Ergebnis", "Check_All", 1);
ServicemeldungMessagesLang = []; ServicemeldungMessagesKurz = []; AktuelleSMjsonString =[];HistorischeSMjson = []; 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) {
LOG(`Routine SaveHistGeraeteIDTrigger wird ausgefuehrt`, "Ablauf", "SaveHistGeraeteIDTrigger", 2);
let jsonString = getState(id_JSON_Servicemeldung_Historie).val;
let parsedJson = []; // Hilfsvariable fuer JSON-Umwandlung
appendToState(id_Text_Servicemeldung_History, status_textLang); // Historische Servicemeldung als Text speichern - Sortiert wird inerhalb appendToState
jsonString = jsonString.replace(/(\[\s*\]|\{\s*\}|\[\s*\{\s*\}\s*\])/g, "").trim(); // entfernen von moeglichen leeren Arrays [] und {} und [{}]
try {
parsedJson = JSON.parse(jsonString); // Versuch, den aktuellen JSON-Inhalt des Datenpunkts zu laden und zu parsen
} catch (error) {
LOG(`Fehler beim Parsen der JSON-Struktur: ${error.message}. Struktur ist vermutlich leer oder bestehender Inhalt wird ueberschrieben.`, "Fehler", "SaveHistGeraeteIDTrigger", 2);
}
let neuerEintrag = createJsonEntry(datum_seit, meldungsart, common_name, GeraeteId, status, status_textLang); // Neuen Eintrag erstellen
parsedJson.push(neuerEintrag);
parsedJson.sort((a, b) => {
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 der JSON-Struktur: ${datum}.`, "Fehler", "SaveHistGeraeteIDTrigger", 2);
return null; // Rueckgabe von null, wenn das Datum ungueltig ist
}
};
const dateA = parseDate(a.datum_seit);
const dateB = parseDate(b.datum_seit);
if (dateA === null && dateB === null) return 0; // Wenn beide null sind, keine aenderung der Reihenfolge // ueberpruefen, ob das Datum gueltig ist
if (dateA === null) return 1; // Wenn dateA null ist, kommt b zuerst
if (dateB === null) return -1; // Wenn dateB null ist, kommt a zuerst
return dateB.getTime() - dateA.getTime(); // Neueste zuerst
});
if (Zaehler.find(e => e.alarmtype === 'SMAktuell').count === 0) {
neuerEintrag = createJsonEntry(func_get_datum(), '', '', '', '', MessageBeiKeinerSM);
HistorischeSMjson.unshift(neuerEintrag);
parsedJson.unshift(HistorischeSMjson); // Fuegt die aktuellen Meldungen als historische Eintraege hinzu
appendToState(id_Text_Servicemeldung_History, func_get_datum() + " - " + MessageBeiKeinerSM,false); // Historie in TEXTstring zurueckspeichern aber nicht sortieren
writelog("", "", "", "", MessageBeiKeinerSM); // log erzeugen
}
jsonString = JSON.stringify(parsedJson); // Das sortierte Array als JSON String umwandeln
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 neuerEintrag;
let jsonString = getState(id_JSON_Servicemeldung_Historie).val;
let parsedJson = []; // Hilfsvariable fuer JSON-Umwandlung
jsonString = jsonString.replace(/(\[\s*\]|\{\s*\}|\[\s*\{\s*\}\s*\])/g, "").trim(); // entfernen von moeglichen leeren Arrays [] und {} und [{}]
try { // Versuch, den aktuellen JSON-Inhalt des Datenpunkts zu laden und zu parsen
parsedJson = JSON.parse(jsonString); // Umwandlung in JSON
} catch (error) { // Fehler beim Parsen: Loggen und leere Struktur erstellen
LOG(`Fehler beim Parsen der JSON-Struktur: ${error.message}. Struktur ist vermutlich leer. Bestehender Inhalt wird ueberschrieben.`, "Fehler", "SaveHistSMREGATrigger", 2);
parsedJson = []; // Neue leere Struktur
}
if (Zaehler.find(e => e.alarmtype === 'SMAktuell').count === 0) { // Wenn es keine aktuellen Servicemeldungen gibt
if(TrennlinieHistSMREGA.length > 0) {
neuerEintrag = createJsonEntry(func_get_datum(), '', '', '', '', TrennlinieHistSMREGA); // Trennlinie fuer REGA trigger wegen der wiederholung aller offenen SM
HistorischeSMjson.unshift(neuerEintrag);
}
neuerEintrag = createJsonEntry(func_get_datum(), '', '', '', '', MessageBeiKeinerSM);
HistorischeSMjson.unshift(neuerEintrag);
parsedJson.unshift(HistorischeSMjson); // Fuegt die aktuellen Meldungen als historische Eintraege hinzu
ServicemeldungMessagesLang.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);
}
if (Zaehler.find(e => e.alarmtype === 'SMAktuell').count > 0) { // Wenn es aktuelle Servicemeldungen gibt
if(TrennlinieHistSMREGA.length > 0) {
neuerEintrag = createJsonEntry(func_get_datum(), '', '', '', '', TrennlinieHistSMREGA); // Trennlinie fuer REGA trigger wegen der wiederholung aller offenen SM
HistorischeSMjson.push(neuerEintrag);
}
parsedJson.unshift(HistorischeSMjson); // Fuegt die aktuellen Meldungen als historische Eintraege hinzu
LOG(`Die aktuellen Servicemeldungen wurden zur Historie hinzugefuegt fuer REGA-Subscription`, "Ergebnis", "SaveHistSMREGATrigger", 2);
}
jsonString = JSON.stringify(parsedJson);
setState(id_JSON_Servicemeldung_Historie, jsonString); // Historie in JSON zurueckspeichern
if(TrennlinieHistSMREGA.length > 0) {
ServicemeldungMessagesLang.push(func_get_datum() + " - "+ TrennlinieHistSMREGA); // Trennlinie fuer REGA trigger wegen der wiederholung aller offenen SM
}
const messageString = ServicemeldungMessagesLang.join('
');
appendToState(id_Text_Servicemeldung_History, messageString,false); // Historie in TEXTstring zurueckspeichern aber nicht sortieren
}
//-----------------------------------------------------------------------------------------------------
// 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");
ServicemeldungMessagesLang.push(ServiceMeldungTextLang);
let ServiceMeldungTextKurz = DefineServiceMessage(native_type, status, id, "kurz");
ServicemeldungMessagesKurz.push(ServiceMeldungTextKurz);
if (!GeraeteIDTrigger && RunType === "trigger") { // historische Meldung fuer REGA Trigger
addMessageToCollector(alarmtype, ServiceMeldungTextKurz, ServiceMeldungTextLang);
let neuerEintrag = createJsonEntry(datum_seit, alarmtype, common_name, GeraeteID, status, ServiceMeldungTextLang);
HistorischeSMjson.unshift(neuerEintrag);
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}`, "Ablauf", "DefineServiceMessage", 2);
let ServiceMessage; // Rueckgabewert
const adapter = ExtractMetaData(id, "adapter");
const GeraeteId = ExtractMetaData(id, "GeraeteID");
const common_name = ReplaceString(ExtractMetaData(id, "CommonName"))
let datum_seit = func_get_datum(id);
// Im Folgenden werden lange und kurze Versionen von Servicemeldungen erzeugt. zulaessige meldungsarten sind unter alarmtypes.suffixes definiert
switch (meldungsart) {
case 'lowBat': // HMIP AccessPoint
case "LOW_BAT_ALARM":
case "LOWBAT_ALARM":
ServiceMessage = version === "lang"
? `${datum_seit} - ${meldungsart} - ${common_name} - (${GeraeteId}) - ${status} - ${getStatusMessage(meldungsart, status, adapter)} - Batteriebezeichnung: ${func_Batterie(native_type)}`
: `${common_name} ${getStatusMessage(meldungsart, status, adapter)}`;
if (version === "lang") {AktuelleSMjsonString.push( createJsonEntry ( datum_seit, meldungsart, common_name, GeraeteId,getStatusMessage(meldungsart, status, adapter),func_Batterie(native_type) )); }
break;
case "CONFIG_PENDING":
case "STICKY_UNREACH_ALARM":
case 'unreach': // HMIP AccessPoint
case 'sabotage': // HMIP AccessPoint
case 'configPending': // HMIP AccessPoint
case "UNREACH_ALARM":
case "DEVICE_IN_BOOTLOADER_ALARM":
case "UPDATE_PENDING_ALARM":
case "SABOTAGE_ALARM":
case "SABOTAGE":
case "ERROR_NON_FLAT_POSITIONING_ALARM":
ServiceMessage = version === "lang"
? `${datum_seit} - ${meldungsart} - ${common_name} - (${GeraeteId}) - ${status} - ${getStatusMessage(meldungsart, status, adapter)}`
: `${common_name} ${getStatusMessage(meldungsart, status, adapter)}`;
if (version === "lang") { AktuelleSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, GeraeteId,status,getStatusMessage(meldungsart, status, adapter),null )); }
break;
case "ERROR":
if (status >= 1 && status <= 7) { // nur wenn kein status = 0
if (errorMessages[native_type] && errorMessages[native_type][status]) {
ServiceMessage = version === "lang"
? `${datum_seit} - ${meldungsart} - ${common_name} - (${GeraeteId}) - ${status} - ${errorMessages[native_type][status]}`
: `${common_name} ${errorMessages[native_type][status]}`;
if (version === "lang") { AktuelleSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, GeraeteId,status,errorMessages[native_type][status],null )); }
} // endif errorMessages
} // endif Status >=1...
if (status === 0) { // nicht HIMIP Geraete die auf Error und status 0 stehen - Message aufgehoben fuer Historie
ServiceMessage = version === "lang"
? `${datum_seit} - ${meldungsart} - ${common_name} - (${GeraeteId}) - ${status} - $getStatusMessage("FALLBACK", status, adapter)}`
: `${common_name} ${getStatusMessage("FALLBACK", status, adapter)}`;
if (version === "lang") { AktuelleSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, GeraeteId,status,getStatusMessage("FALLBACK", status, adapter),null )); }
}
break;
case "FAULT_REPORTING":
if (faultMessages[native_type] && faultMessages[native_type][status]) {
ServiceMessage = version === "lang"
? `${datum_seit} - ${meldungsart} - ${common_name} - (${GeraeteId}) - ${status} - ${faultMessages[native_type][status]}`
: `${common_name} ${faultMessages[native_type][status]}`;
if (version === "lang") { AktuelleSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, GeraeteId,status,faultMessages[native_type][status],null )); }
}
break;
default: // falls message type hier nicht gelistet wird vom FALLBACK case ausgegangen
ServiceMessage = version === "lang"
? `${datum_seit} - ${meldungsart} - ${common_name} - (${GeraeteId}) - ${status} - ${getStatusMessage("FALLBACK", status, adapter)}`
: `${common_name} ${getStatusMessage("FALLBACK", status, adapter)}`;
if (version === "lang") { AktuelleSMjsonString.push(createJsonEntry(datum_seit, meldungsart, common_name, GeraeteId,status,getStatusMessage("FALLBACK", status, adapter),null )); }
}
return ServiceMessage; // Nur einmal am Ende zurueckgeben
}
//-----------------------------------------------------------------------------------------------------
//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).ts, "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)
) || 'unbekannt';
}
//-----------------------------------------------------------------------------------------------------
// Funktion createJsonEntry erzeugen einer JSON Tabelle
//-----------------------------------------------------------------------------------------------------
function createJsonEntry(datum_seit, meldungsart, common_name, GeraeteId, status, statusMessages, Batterie) {
// Erstelle das JSON-Objekt
return {
datum_seit: datum_seit,
meldungsart: meldungsart,
common_name: common_name,
GeraeteId: GeraeteId,
status: status,
status_message: statusMessages,
batterie_bezeichnung: Batterie
};
}
//-----------------------------------------------------------------------------------------------------
// Funktion schreibt einen Logeintrag in das Filesystem und auch in das interne Log-System (looxer)
//-----------------------------------------------------------------------------------------------------
function writelog(Name, 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 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) {
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 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`, "Fehler","extractDate",2);
return new Date(0); // Rueckgabe eines Default-Datums
}
const parts = text.split(' - '); // Trennen am Bindestrich, um das Datum zu extrahieren
if (parts.length < 5) {
LOG(`extractDate Fehler: Format stimmt nicht ueberein. Erwartet wurde 'dd.mm.yy hh:mm:ss Uhr - ...'`, "Fehler","extractDate",2);
return new Date(0); // Rueckgabe eines Default-Datums
}
const dateTime = parts[0]; // Das Datum und die Uhrzeit befinden sich vor dem ersten Bindestrich
const dateParts = dateTime.split(' ')[0].split('.'); // Datum ist im Format "dd.mm.yy"
const day = dateParts[0];
const month = dateParts[1];
let year = dateParts[2];
if (year.length === 2) year = '20' + year; // Jahr mit 2 Ziffern auf 4 Ziffern setzen
const time = dateTime.split(' ')[1]; // Uhrzeit im Format "hh:mm:ss"
const dateString = `${year}-${month}-${day} ${time}`; // Umwandlung in "yyyy-mm-dd hh:mm:ss"
return new Date(dateString); // Rueckgabe des Date-Objekts
}
//-----------------------------------------------------------------------------------------------------
// 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");
}
}