// Beschreibung des Scriptes: https://forum.iobroker.net/topic/77816/vorlage-servicemeldungen-volume2
// Autor Looxer01 02.11.2024 Version 1.0 (initiale Version)
// Version 3.00 - 05.01.2025 Variable MessageBeiKeinerSM in die Experteneinstellungen geschoben // Problem bei der Datumsberechnung behoben // Zaehler fuer Historie hinzugefuegt
// die Datenpunkte fuer Text-Format werden jetzt optional gefuellt
// Version 3.01 - 06.01.2024 n/a Meldungen koennen auch durch andere Zeichen ersetzt werden
// Version 3.02 - 20.01.2024 bei Monatswechsel wurde die TEXT ableitung nicht korrekt durchgefuehrt // HTML Generierung als Alternative zu JSON
// Version 3.04 - 21.01.2024 weitere HTML Attribute für die Expereneinstellungen hinzugefuegt - 3.05 - Korrektur globaler CSS Einstellung
// Version 3.06 - 22.01.2024 leftover log entfernt // Batteriebezeichnung fuer Historie hinzugefuegt //HTML Tabelle äusserer Rahmen nicht notwendig(entfernt)
// Versipm 3.07 - 23.01.2024 Moeglichkeit zum manuellen Loeschen der Historie per Button hinhzugefuegt. Schedule zur Loeschung kann beibehalten oder deaktiviert werden // 3.08 Korrektur
//---------------------------------------------------------------------------------------------------------------------------------------------------------------
// Muss-Einstellungen ( HM-Classic , HMIP, Wired - hier muessen zwingend die Instanzen stimmmen )
//---------------------------------------------------------------------------------------------------------------------------------------------------------------
// Im folgenden sind die Instanzen gelistet fuer die die Selektion erfolgt bzw nicht erfolgt (Filter)
// bitte 9 Eintragen falls eine Instanz nicht relevant ist // Gruppeninstanzen sind normalerweise nicht relevant
// CuxD Instanzen duerfen nicht eingetragen werden --- ACHTUNG: bei HMIPAccessPoint-Nutzung muss GeraeteTriggerID auf true stehen
const HMClassicInstanz = 0; // HM-Classic Instanz eintragen // 9 = falls nicht relevant
const HMIPInstanz = 1; // Homematic IP instanz // 9 = falls nicht relevant
const WiredIClassicInstanz = 9; // Wired Instanz // 9 = falls nicht relevant
const GruppenInstanz = 9; // virtuelle GeraeteInstanz- 9 = nicht relevant - Die Gruppen werden i.d.R. nicht gebraucht - Empfehlung: 9
let HMIPAccessPoint = 9; // AccessPoint Servicemeldungen - (normalerweise instanz = 0) bei nicht Verwendung Instanz = 9
//---------------------------------------------------------------------------------------------------------------------------------------------------------------
// Kann-Einstellungen
//---------------------------------------------------------------------------------------------------------------------------------------------------------------
// Pfad kann angepasst werden fuer userdata pfad einfach // entfernen
const path = "javascript.0.ServicemeldungenVol2.";
//const path = "0_userdata.0.ServicemeldungenVol2."; // alternativ zum javascript pfad
// schreibt das Protokoll der Servicemeldungen in ein externes file (Excel Format)
const SMProtokoll = true;
const PathSMLog = "/opt/iobroker/log/ServicemeldungenVol2.csv"; // Pfad und Dateiname des externen Logs
//const PathSMLog = "/iobroker/log/ServicemeldungenVol2.csv"; // Pfad fuer Windows/ iobroker ist der angenommene iobroker home-pfad
//Geraete die nicht ueberwacht werden sollen. Geraete-IDs eingeben - Komma getrennt erfassen
const Ausschlussliste = ['0000D7099xxx26', '00091D8xxx7410']; // immer mit Komma trennen // Filter auf Einzel-IDs
// debug level kann eingestellt werden - wenn alles laeuft dann 0 = ruhe im log
// debug level 0 kein log // debug level 1 - nur die wichtigsten Meldungen werden gelistet // debug level 2 - mehr als nur die wichtigsten Meldungen aber ohne einzelne IDs
// debug level 3 - hier werden auch einzelne IDs gelistet (koennten lange listen werden)
const debugLevel = 1 ; // Empfehlung = 1
const SystemLog = false; // schreib das Sytemprotokoll in ein externes log (sollte normalerweise deaktviert sein nur bei Problemen verwenden)
const PathSystemLog = "/opt/iobroker/log/ServicemeldungenSystemLog.csv"; // Pfad und Dateiname des externen Logs
//const PathSystemLog = "/iobroker/log/ServicemeldungenSystemLog.csv"; // Pfad fuer Windows
// wenn GeraeteIDTrigger auf true gestellt wird, dann wird fuer jeden Datenpukt mit Relevanz fuer eine Servicemeldung eine subscription angelegt.
// Vorteile: mehr Details in der Historie von Servicemeldungen: Nachteil: bei 80 CCU Geraeten ungefaehr 300 Susbsriptions
// Wenn die variable auf false steht, dann wird auf hm.rega.0.maintenance eine subsription angelegt: Vorteil: 1 Subscription , Nachteil: erweiterte Funktion fuer nicht CCU Datenpunkte nicht verfuegbar
// bei HMIP AccessPoint Cloud HCU - funktioniert das nur bei GeraeteTrigger = true
let GeraeteIDTrigger = false; // true = viele subscriptions aber praezise Historie der Servicemeldungen - false = 1 subscription - weniger praezise Historie - Empfehlung = false
// Speicherung der Servicemeldungen im Text-Format - Empfehlung = false // davon ausgehend, dass das JSON Format in der Visualisierung gut dargestellt werden kann
const UpdateTEXT_Datenpunkte = false; // die kurz- und langen Servicemeldungen als auch die historischen werden immer im JSON Format abgelegt. Alternativ koennen diese auch in einem TEXT Format in Datenpunkten gespeichert werden
// Speicherung der Servicemeldungen im HTML-Format - Wird empfohlen zu nutzen, wenn es Problem bei der Visualisierung von JSON gibt.
const UpdateHTML_Datenpunkte = false; // die langen Servicemeldungen als auch die historischen werden immer im JSON Format abgelegt. Alternativ koennen diese auch in einem HTML Format in Datenpunkten gespeichert werden
// fuer alle Spalten mit true werden die Nachrichten ueber den zugeordneten Dienst versendet, vorausgesetzt der Messenge Adapter ist in iobroker installiert/konfiguriert
const services = ['email', 'whatsApp', 'Signal', 'Telegram', 'Pushover', 'Pushsafer'];
const MessengerScope = {
'UNREACH_ALARM': [false, false, false, false, false, false],
'LOWBAT_ALARM': [false, false, false, false, false, false],
'SABOTAGE_ALARM': [false, false, false, false, false, false],
'CONFIG_PENDING': [false, false, false, false, false, false],
'Sonstige': [false, false, false, false, false, false],
'keineSM': [false, false, false, false, false, false],
}
const MessengerInstanz = [0, 0, 0, 0, 0, 0 ]; // Instanz des Messengers
const TextTypeKurz = [false, true, true, true, true, true ]; // bei true wird der Kurztext gesendet - sonst der Langtext
// email-Einstellungen
let emailAddresse = "Vorname-Nachname@web.de"
const Headline = "ioBroker Servicemeldung"; // Ueberschrift Messages fuer email und Pushsafer
// telegram Einstellungen
const TelegramUser = "";
//-----------------------------------------------------------------------------------------------------
//Experten Einstellungen - Empfehlung: Absprache mit dem Autor
//-----------------------------------------------------------------------------------------------------
const MessageBeiKeinerSM = 'Derzeit keine Servicemeldungen' // Text der erscheinen soll, wenn keine SM vorliegen Ein Kurztext ist erforderlich
//const NichtRelevantText = 'n/a'; // statt n/a kann ein beliebiger anderer Text in den JSON verwendet werden
const NichtRelevantText = "......";
// die folgenden Einstellungen sind nur relvant bei Verwendung von HTML als Datenpunkte
// Definieren der Farbwerte für Kopfzeile, gerade und ungerade Zeilen
const headerColor = '#333333'; // Standardfarbe (Fuellfarbe) für die Kopfzeile
const evenRowColor = '#4e5049'; // Standardfarbe (Fuellfarbe) für gerade Zeilen
const oddRowColor = '#333333'; // Standardfarbe (Fuellfarbe) für ungerade Zeilen
// Definieren der Textfarben für Kopfzeile, gerade und ungerade Zeilen
const headerTextColor = 'white'; // Textfarbe für Kopfzeile
const evenRowTextColor = 'white'; // Textfarbe für gerade Zeilen
const oddRowTextColor = 'white'; // Textfarbe für ungerade Zeilen
// Definieren von Zellenumrandungseinstellungen
const useBorderAll = true; // true, wenn ein Gitternetz erzeugt werden soll
const borderColor = '#000000'; // Farbe des Gitternetzes (Zellenumrandung)
const borderWidth = '1px'; // Strichstärke der Zellenumrandung
//Schedule Zeit fuer refresh der Historie
const ScheduleAktiv = true; // Bei "false" wird der schedule nicht durchlaufen. Manuelles Löschen kann über den Datenpunkt id_Button_Refresh_Historie (Button) möglich
const scheduleTimeClearSMTexte = "2 0 1 * *"; // am 1. tag des monats um 00:02 morgens sollen alle Servicemeldungen des Monats geloescht werden
// const scheduleTimeClearSMTexte = "58 23 * * 0"; // alternative Sonntags um 23:58 Uhr sollen alle Servicemeldungen der Woche im datenpunkt der SM-Texte geloescht werden
//Batterie-Zuordnungen fuer Servicmeldungen
const batteryTypes = {
'1x CR2016': ['HM-RC-4', 'HM-RC-4-B', 'HM-RC-Key3', 'HM-RC-Key3-B', 'HM-RC-P1', 'HM-RC-Sec3', 'HM-RC-Sec3-B', 'ZEL STG RM HS 4'],
'1x CR2032': ['HM-PB-2-WM', 'HM-PB-4-WM', 'HM-PBI-4-FM', 'HM-SCI-3-FM', 'HM-Sec-TiS', 'HM-SwI-3-FM', 'HmIP-FCI1'],
'2x LR14': ['HM-Sec-Sir-WM', 'HM-OU-CFM-TW', 'HM-OU-CFM-Pl', 'HM-OU-CF-Pl'],
'2x LR44/AG13': ['HM-Sec-SC', 'HM-Sec-SC2L', 'HM-Sec-SC-2', 'HM-Sec-RHS'],
'2x LR6/AA': ['HM-CC-VD', 'HM-CC-RT-DN', 'HM-Sec-WDS', 'HM-Sec-WDS-2', 'HM-CC-TC', 'HM-Dis-TD-T', 'HB-UW-Sen-THPL-I', 'HM-WDS40-TH-I', 'HM-WDS40-TH-I-2',
'HM-WDS10-TH-O', 'HmIP-SMI', 'HMIP-eTRV', 'HM-WDS30-OT2-SM-2', 'HmIP-SMO', 'HmIP-SMO-A', 'HmIP-SPI', 'HmIP-eTRV-2',
'HmIP-SPDR', 'HmIP-STHO-A', 'HmIP-eTRV-B', 'HmIP-PCBS-BAT', 'HmIP-STHO', 'HmIP-eTRV-C',
'HmIP-WGC', 'HmIP-eTRV-C-2', 'HmIP-eTRV-E', 'HmIP-eTRV-2 I9F', 'HmIP-eTRV-E-S', 'ELV-SH-SW1-BAT'],
'3x LR6/AA': ['HmIP-SWO-PL', 'HM-Sec-MDIR', 'HM-Sec-MDIR-2', 'HM-Sec-SD', 'HM-Sec-Key', 'HM-Sec-Key-S', 'HM-Sec-Key-O', 'HM-Sen-Wa-Od', 'HM-Sen-MDIR',
'HM-Sen-MDIR-O', 'HM-Sen-MDIR-O-2', 'HM-WDS100-C6-O', 'HM-WDS100-C6-O-2', 'HmIP-ASIR', 'HmIP-SWO-B', 'HM-Sen-MDIR-O-3', 'HM-Sec-MDIR-3',
'HmIP-SWO-PR', 'HmIP-DLD', 'HmIP-ASIR-2'],
'4x LR6/AA': ['HM-CCU-1', 'HM-ES-TX-WM', 'HM-WDC7000'],
'1x LR3/AAA': ['HM-RC-4-2', 'HM-RC-4-3', 'HM-RC-Key4-2', 'HM-RC-Key4-3', 'HM-RC-Sec4-2', 'HM-RC-Sec4-3', 'HM-Sec-RHS-2', 'HM-Sec-SCo', 'HmIP-KRC4',
'HmIP-KRCA', 'HmIP-SRH', 'HMIP-SWDO', 'HmIP-DBB', 'HmIP-RCB1', 'HmIP-KRCK', 'HmIP-SWDO-2'],
'2x LR3/AAA': ['HmIP-WRCR', 'HmIP-SWD','HM-TC-IT-WM-W-EU', 'HM-Dis-WM55', 'HM-Dis-EP-WM55', 'HM-PB-2-WM55', 'HM-PB-2-WM55-2', 'HM-PB-6-WM55', 'HM-PBI-2-FM', 'HM-RC-8', 'HM-Sen-DB-PCB',
'HM-Sen-EP', 'HM-Sen-MDIR-SM', 'HM-Sen-MDIR-WM55', 'HM-WDS30-T-O', 'HM-WDS30-OT2-SM', 'HmIP-STH', 'HmIP-STHD', 'HmIP-WRC2', 'HmIP-WRC6', 'HmIP-WTH', 'HmIP-WTH-2',
'HmIP-SAM', 'HmIP-SLO', 'HMIP-SWDO-I', 'HmIP-FCI6', 'HmIP-SMI55', 'HM-PB-2-FM', 'HmIP-SWDM', 'HmIP-SCI', 'HmIP-SWDM-B2', 'HmIP-RC8', 'ALPHA-IP-RBG', 'HmIP-DSD-PCB',
'HmIP-WRCD', 'HmIP-WRC2-A', 'HmIP-WTH-B-2', 'HmIP-WTH-A', 'HmIP-STV', 'HmIP-WKP'],
'3x LR3/AAA': ['HM-PB-4Dis-WM', 'HM-PB-4Dis-WM-2', 'HM-RC-Dis-H-x-EU', 'HM-Sen-LI-O'],
'3x AAA Akkus - bitte laden': ['HM-RC-19', 'HM-RC-19-B', 'HM-RC-12', 'HM-RC-12-B', 'HM-RC-12-W'],
'3x LR14/C': ['HmIP-MP3P'],
'9Volt Block leer oder unbestimmt': ['HM-LC-Sw1-Ba-PCB', 'HM-LC-Sw4-PCB', 'HM-MOD-EM-8', 'HM-MOD-Re-8', 'HM-Sen-RD-O', 'HM-OU-CM-PCB', 'HM-LC-Sw4-WM'],
'Festbatterie leer': ['HmIP-STE2-PCB', 'HM-Sec-SD-2', 'HmIP-SWSD', 'HmIP-PCBS'],
'ohne Batterie': ['HM-LC-Sw1PBU-FM', 'HM-LC-Sw1-Pl-DN-R1', 'HM-LC-Sw1-DR', 'HM-LC-RGBW-WM', 'HM-LC-Sw1-Pl-CT-R1', 'HmIP-HEATING', 'HM-LC-Sw1-FM', 'HM-LC-Sw2-FM', 'HM-LC-Sw4-DR',
'HM-LC-Sw1-Pl', 'HM-LC-Sw1-Pl-2', 'HM-LC-Sw4-Ba-PCB', 'HM-LC-Sw1-SM', 'HM-LC-Sw4-SM', 'HM-Sys-sRP-Pl', 'HM-LC-Sw2PBU-FM', 'HM-LC-Sw1-PCB', 'HM-LC-Sw4-DR-2'],
'Akku entladen - bitte aufladen': ['HM-Sec-Win', 'HM-Sec-SFA-SM', 'HM-RC-19-SW']
};
// Rega Pfad
const PathRega = ['hm-rega.0.maintenance',]; // Array um ggf die HCU zu konfigurieren
// Pfade fuer die Speicherung aktueller und vergangener Servicemeldungen
const id_Text_ServicemeldungLang = path+'TextLangAktuelleSM'; // Objekt wo die Servicemeldung hingeschrieben werden soll (String) = path+'TextLang';
const id_Text_ServicemeldungKurz = path+'TextKurzAktuelleSM'; // Objekt wo die Servicemeldung hingeschrieben werden soll (String) - kurze Version
const id_Text_Servicemeldung_History = path+'TextLangVergangeneSM'; // Objekt wo die Servicemeldung hinzugefuegt werden soll (String) - Lange Version (es gibt nur lang)
const id_HTML_Servicemeldung_Aktuell = path+'HtmlAktuelleSM'; // Objekt wo die Servicemeldung hingeschrieben werden soll (HTML formatiere Tabelle) = path+'htmlLang';
const id_HTML_Servicemeldung_History = path+'HtmlVergangeneSM'; // Objekt wo die Servicemeldung hinzugefuegt werden soll ((HTML formatiere Tabelle) - Lange Version (es gibt nur lang)
const id_JSON_Servicemeldung_Aktuell = path+'JSONAktuelleSM'; // JSON Tabelle Datenpunkt Aktuelle SM
const id_JSON_Servicemeldung_Historie = path+'JSONVergangeneSM'; // JSON Tabelle Datenpunkt Historische SM
const id_Button_Refresh_Historie = path+'ButtonRefreshHistorie'; // Wenn DP auf true gesetzt wird, dann wird die historie refreshed (geloescht)
// Count-Pfade und counts in einer Tabelle - wird auch fuer CREATE STATES verwendet
const Zaehler = [
{ alarmtype: 'UNREACH_ALARM', count: 0, Datenpunkt: path+'Anzahl_UNREACH' },
{ alarmtype: 'STICKY_UNREACH_ALARM', count: 0, Datenpunkt: path+'Anzahl_STICKY_UNREACH' },
{ alarmtype: 'CONFIG_PENDING_ALARM', count: 0, Datenpunkt: path+'Anzahl_CONFIG_PENDING' },
{ alarmtype: 'UPDATE_PENDING_ALARM', count: 0, Datenpunkt: path+'Anzahl_Update_PENDING' },
{ alarmtype: 'LOWBAT_ALARM', count: 0, Datenpunkt: path+'Anzahl_LOWBAT' },
{ alarmtype: 'DEVICE_IN_BOOTLOADER_ALARM', count: 0, Datenpunkt: path+'Anzahl_DEVICE_IN_BOOTLOADER' },
{ alarmtype: 'ERROR', count: 0, Datenpunkt: path+'Anzahl_in_ERROR' },
{ alarmtype: 'FAULT_REPORTING', count: 0, Datenpunkt: path+'Anzahl_FAULT_REPORTING' },
{ alarmtype: 'SABOTAGE_ALARM', count: 0, Datenpunkt: path+'Anzahl_SABOTAGE' },
{ alarmtype: 'ERROR_NON_FLAT_POSITIONING_ALARM', count: 0, Datenpunkt: path+'Anzahl_NON_FLAT_POSITIONING' },
{ alarmtype: 'STICKY_SABOTAGE_ALARM', count: 0, Datenpunkt: path+'Anzahl_Sticky_SABOTAGE' },
{ alarmtype: 'SMAktuell', count: 0, Datenpunkt: path+'Anzahl_SM-Aktuell' },
{ alarmtype: 'SMHistorie', count: 0, Datenpunkt: path+'Anzahl_SM-Historie' },
{ alarmtype: 'Gesamt', count: 0, Datenpunkt: path+'Anzahl_GESAMT' }
];
// statusmessages je messagetype und adapter // Fallback ist fuer unbekannte messagetypes - z.B. alle Error Messages // FALLBACK repraesentiert Standardmessages
const statusMessages = {
UNREACH_ALARM: { "hm-rpc": { 0: "keine Kommunikationsfehler", 1: "Kommunikation gestoert", 2: "Kommunikation war gestoert" } },
STICKY_UNREACH_ALARM: { "hm-rpc": { 0: "keine Kommunikationsfehler", 1: "Sticky Kommunikation gestoert", 2: "Sticky Kommunikation war gestoert" } },
SABOTAGE_ALARM: { "hm-rpc": { 0: "Keine Sabotage", 1: "Sabotage", 2: "Sabotage aufgehoben" } },
STICKY_SABOTAGE_ALARM: { "hm-rpc": { 0: "Keine Sabotage", 1: "Sticky Sabotage", 2: "Sticky Sabotage aufgehoben" } },
LOWBAT_ALARM: { "hm-rpc": { 0: "Batterie ok", 1: "Batterie niedrig", 2: "Batterie ok" } },
LOW_BAT_ALARM: { "hm-rpc": { 0: "Batterie ok", 1: "Batterie niedrig", 2: "Batterie ok" } },
ERROR_NON_FLAT_POSITIONING_ALARM: { "hm-rpc": { 0: "Keine Meldung", 1: "Geraet wurde angehoben.", 2: "Geraet wurde angehoben: Bestaetigt" } },
CONFIG_PENDING_ALARM: { "hm-rpc": { 0: "keine Meldung", 1: "Konfigurationsdaten stehen zur Uebertragung an", 2: "Konfigurationsdaten standen zur Uebertragung an" } },
UPDATE_PENDING_ALARM: { "hm-rpc": { 0: "kein Update verfuegbar", 1: "Update verfuegbar", 2: "Update wurde eingespielt" } },
ERROR_OVERHEAT_ALARM: { "hm-rpc": { 0: "kein Overheat Alarm", 1: "Overheat gemeldet", 2: "Overheat geloest" } },
ERROR_UNDERVOLTAGE_ALARM: { "hm-rpc": { 0: "Kein Undervoltage Alarm", 1: "Undervoltage gemeldet", 2: "Undervoltage geloest" } },
DEVICE_IN_BOOTLOADER_ALARM: { "hm-rpc": { 0: "Keine Meldung", 1: "Geraet startet neu", 2: "Geraet wurde neu gestartet" } },
DUTY_CYCLE: { "hm-rpc": { false: "Geraete-Duty Cycle ok", true: "Geraete-Duty Cycle erreicht", null: "unbekannter Status (Duty_Cycle" } },
lowBat: { "hmip": { false: "Batterie ok", true: "Batterie niedrig", null: "Batterie ok" } },
unreach: { "hmip": { false: "keine Kommunikationsfehler", true: "Kommunikation gestoert", null: "Kommunikation war gestoert" } },
sabotage: { "hmip": { false: "Keine Sabotage", true: "Sabotage", null: "Sabotage aufgehoben" } },
configPending: { "hmip": { false: "Keine Meldung", true: "Konfigurationsdaten stehen zur Uebertragung an", null: "Konfigurationsdaten standen zur Uebertragung an" } },
FALLBACK: { "hm-rpc": { 0: "keine Stoerung", 1: "Stoerung", 2: "Stoerung aufgehoben",
false: "Keine Stoerung", true: "Stoerung", null: "unbekannter Status Fallback"},
"hmip": { false: "keine Stoerung", true: "Stoerung", null: "Stoerung aufgehoben" }, }
};
//ErrorMessages fuer HM-Classic Geraete - Sonderfaelle
const errorMessages = {
'HM-Sec-RHS': { 7: 'Sabotage' },
'HM-Sec-RHS-2': { 7: 'Sabotage' },
'HM-Sec-SC': { 7: 'Sabotage' },
'HM-Sec-SC-2': { 7: 'Sabotage' },
'HM-Sec-SCo': { 7: 'Sabotage' },
'HM-Sec-MD': { 7: 'Sabotage' },
'HM-Sec-MDIR': { 7: 'Sabotage' },
'HM-Sec-MDIR-2':{ 7: 'Sabotage' },
'HM-Sec-Key': { 1: 'Einkuppeln fehlgeschlagen', 2: 'Motorlauf abgebrochen' },
'HM-Sec-Key-S': { 1: 'Einkuppeln fehlgeschlagen', 2: 'Motorlauf abgebrochen' },
'HM-Sec-Key-O': { 1: 'Einkuppeln fehlgeschlagen', 2: 'Motorlauf abgebrochen' },
'HM-CC-VD': { 1: 'Ventil Antrieb blockiert', 2: 'Ventil nicht montiert', 3: 'Stellbereich zu klein', 4: 'Batteriezustand niedrig'
}
};
//ErrorMessages fuer HM-Classic Geraet (Heizung) - Sonderfaelle
const faultMessages = {
'HM-CC-RT-DN': {
0: 'keine Stoerung',
1: 'Ventil blockiert',
2: 'Einstellbereich Ventil zu gross',
3: 'Einstellbereich Ventil zu klein',
4: 'Kommunikationsfehler',
6: 'Spannung Batterien/Akkus gering',
7: 'Fehlstellung Ventil'
}
};
// hier koennen die Alarmgruppen ggf erweitert werden - Aus Alarmgruppe und Instanz wird der Selector gebastelt und in Tabelle Selectors gesammelt
const alarmTypes = [
{ key: 'UNREACH_ALARM', suffixes: ['UNREACH_ALARM','unreach' ] },//UNREACH_ALARM = HM-Classic & HMIP-CCU - unreach = HMIP Accesspoint
{ key: 'STICKY_UNREACH_ALARM', suffixes: ['STICKY_UNREACH_ALARM'] },
{ key: 'CONFIG_PENDING_ALARM', suffixes: ['CONFIG_PENDING_ALARM','configPending'] }, //configPending ist eine HMIP Meldung
{ key: 'UPDATE_PENDING_ALARM', suffixes: ['UPDATE_PENDING_ALARM'] },
{ key: 'LOWBAT_ALARM', suffixes: ['LOWBAT_ALARM', 'LOW_BAT_ALARM','lowBat'] }, //LOWBAT_ALARM = HM-Classic - LOW_BAT_ALARM = HMIP CCU - lowBat = HMIP Accesspoint
{ key: 'DEVICE_IN_BOOTLOADER_ALARM', suffixes: ['DEVICE_IN_BOOTLOADER_ALARM'] },
{ key: 'ERROR', suffixes: ['ERROR','DUTY_CYCLE'] }, // error ist ein Sammler fuer hier nicht definierte Meldungen
{ key: 'FAULT_REPORTING', suffixes: ['FAULT_REPORTING'] },
{ key: 'SABOTAGE_ALARM', suffixes: ['SABOTAGE_ALARM','sabotage'] }, // sabotage ist eine HMIP Meldung
{ key: 'STICKY_SABOTAGE_ALARM', suffixes: ['STICKY_SABOTAGE_ALARM'] },
{ key: 'ERROR_NON_FLAT_POSITIONING_ALARM', suffixes: ['ERROR_NON_FLAT_POSITIONING_ALARM'] },
{ key: 'OVERHEAT_ALARM', suffixes: ['ERROR_OVERHEAT_ALARM'] },
{ key: 'UNDERVOLTAGE_ALARM', suffixes: ['ERROR_UNDERVOLTAGE_ALARM'] },
];
// Umlaut Umwandlung und entfernung PUnkte - kann aber auch erweitert werden
const replacements = { '.': ' ', 'ä': 'ae', 'ü': 'ue', 'ö': 'oe', 'ß': 'ss' }; // Umwandlung fuer Namen der Geraete (common.name)
// Definition der Datenstrukturen fuer MetaDaten je nach Adapter, da abweichend
const StrukturDefinition = [
{ Adapter: 'hm-rpc', GeraeteID: 2, AlarmFeld: 4, nativeType: 3, common_name: 3 }, // die Ziffer ist die Positinierung des Feldes 'hm-rpc.1.00085D89B14067.0.UPDATE_PENDING_ALARM' 0=Adapter - 2 = ID 4= Feld Alarm / native Type = die ersten Strings bis zur dritten STelle fuer getObject
{ Adapter: 'hmip', GeraeteID: 3, AlarmFeld: 6, nativeType: 'hmip.xinstancex.devices.xidx.info.modelType', common_name: 'hmip.xinstancex.devices.xidx.info.label' }, // Positionierung wie bei Rm-rpc - bei hmip wird native type aber ueber den DP ausgelesen und nicht getObject "xidx" wird dann mit der geraeteiD ersetzt
]
// Moegliche Homematic Instanzen (CuxD ausgeschlossen) // Adapter hinzugefuegt um ggf HCU zu konfigurieren
const instanceIds = [
{ name: 'HMClassicInstanz', adapter: 'hm-rpc', Instanz: HMClassicInstanz },
{ name: 'HMIPInstanz', adapter: 'hm-rpc', Instanz: HMIPInstanz },
{ name: 'GruppenInstanz', adapter: 'hm-rpc', Instanz: GruppenInstanz },
{ name: 'WiredIClassicInstanz', adapter: 'hm-rpc', Instanz: WiredIClassicInstanz },
{ name: 'HMIPAccessPoint', adapter: 'hmip' , Instanz: HMIPAccessPoint },
];
// Konfigurationsobjekt fuer Ausnahmen / es koennen Arlarmtypes je Instanz ausgeschlossen werden / Filter auf AlarmTypes
const exceptions = {
HMClassicInstanz: [],
HMIPInstanz: ['DUTY_CYCLE'],
GruppenInstanz: ['ERROR_NON_FLAT_POSITIONING_ALARM'],
WiredIClassicInstanz: ['LOWBAT_ALARM', 'LOW_BAT_ALARM','ERROR_NON_FLAT_POSITIONING_ALARM'],
};
//-----------------------------------------------------------------------------------------------------
//Ende Einstellungen
//-----------------------------------------------------------------------------------------------------
LOG(`Script: Servicemeldungen Volume 2 - Autor: Looxer01 Datum: 23.01.2025 Version:3.08`,`Einstellungen`,`Start`,0 )
if(HMIPAccessPoint !== 9 && !GeraeteIDTrigger) {
GeraeteIDTrigger = true // Wenn der HMIPAccessPoint aktiv ist, dann funktioniert der REGA Trigger nicht. also setzen auf geraeteID Trigger=true
LOG(`HMIP AccessPoint bzw Cloud HCU sind aktiv - GeraeteTriggerID muss aktiviert sein - wurde aktiviert. Bitte auch manuell korrigieren und auf GeraeteTriggerID = true setzen`,`Einstellungen`,`Start`,0 )
}
LOG(`HMClassicInstanz: ${HMClassicInstanz}; HMIPInstanz: ${HMIPInstanz}; WiredIClassicInstanz: ${WiredIClassicInstanz}; GruppenInstanz: ${GruppenInstanz}`, "Einstellungen", "Start", 2);
LOG(`Inhalt instanceIds: ${JSON.stringify(instanceIds)}`, "Einstellungen", "Start", 2);
LOG(`GeraeteIDTrigger: ${GeraeteIDTrigger}`, "Einstellungen", "Start", 2);
LOG(`Inhalt Messengerscope: ${JSON.stringify(MessengerScope)}`, "Einstellungen", "Start", 2);
LOG(`Inhalt Services: ${JSON.stringify(services)}`, "Einstellungen", "Start", 2);
LOG(`Inhalt Messenger-Instanzen: ${JSON.stringify(MessengerInstanz)}`, "Einstellungen", "Start", 2);
LOG(`Inhalt TextTypeKurz: ${JSON.stringify(TextTypeKurz)}`, "Einstellungen", "Start", 2);
let HistorischeMeldungenJSON = [], AktuelleMeldungenJSON = [], MessageSendCollector = {};
// jetzt selectors fuellen anhand der Tabelle InstanceIDs, sowie der Auscchlusstabellen
let totalIdsCount = 0; // Variable fuer die Gesamtzahl der IDs
const selectors = alarmTypes.map(alarmType => {
const collectedIds = [];
instanceIds.forEach(instance => {
if (instance.Instanz !== 9) { // Umbenennung von `instance.id` zu `instance.Instanz`
const excludedAlarmTypes = exceptions[instance.name] || []; // Hole Ausnahmen oder leere Liste
if (!excludedAlarmTypes.includes(alarmType.key)) { // Wenn Alarmtype nicht in der Ausnahme
alarmType.suffixes.forEach(suffix => { // wenn suffix (meldungsart) nicht in der Ausnahme
if (!excludedAlarmTypes.includes(suffix)) {
// @ts-ignore
$(`state[id=${instance.adapter}.${instance.Instanz}.*.${suffix}]`).each(id => collectedIds.push(id));
}
});
}
}
});
const filteredIds = collectedIds.filter(id => { // IDs anhand der Geraete-IDs in der Ausschlussliste filtern
const deviceId = id.split('.')[2]; // Geraete-ID aus der vollstaendigen ID extrahieren (z. B. "0000D7099xxx26" aus "hm-rpc.1.0000D7099xxx26.0.LOW_BAT_ALARM")
return !Ausschlussliste.includes(deviceId); // Behalten, wenn nicht in der Ausschlussliste
});
totalIdsCount += filteredIds.length; // IDs zur Gesamtzahl hinzufuegen
return { name: alarmType.key, ids: filteredIds };
});
LOG(`Es wurden ${totalIdsCount} IDs fuer alle Alarmtypen gefunden. Alle drei Filter (Instanzen, Alarmtypen, Einzel-IDs) angewendet`, "Ablauf", "Start", 0);
// Ergebnisse loggen (pro Alarmtype)
selectors.forEach(selector => { // Ergebnisse loggen (pro Alarmtyp)
if(SystemLog) {
LOG(`fuer den Alarmtype: ${selector.name} wurden ${selector.ids.length} IDs gefunden${debugLevel >= 3 ? ` ${JSON.stringify(selector.ids)}` : ''}`, "Ablauf", "Start", 3);
}else{
LOG(`fuer den Alarmtype: ${selector.name} wurden ${selector.ids.length} IDs gefunden${debugLevel >= 3 ? ` ${JSON.stringify(selector.ids)}` : ''}`, "Ablauf", "Start", 0);
}
});
CreateStates(() => { Check_All("init") }); // Check_All wird aufgerufen, wenn CreateStates fertig ist (callback)
if (GeraeteIDTrigger) { // Subscription auf IDs
SubscribeGeraeteID();
}else{ // Subscription auf HMREGA (nur 1 Subscripton)
SubscribeRegaDP();
}
//-----------------------------------------------------------------------------------------------------
// Subscription - Optional kann bei push auf button "id_Button_Refresh_Historie" die Servicemeldung Histore geloescht werden
//-----------------------------------------------------------------------------------------------------
on({ id: id_Button_Refresh_Historie , val: true, ack: false }, function (obj) {
LOG(`Button zum refresh der Historie wurde ausgeloest - Historie is nun geloescht`, "Event", "id_Button_Refresh_Historie", 0);
Refresh_History()
setState(id_Button_Refresh_Historie, false, true);
});
//-----------------------------------------------------------------------------------------------------
// Schedule zum Loeschen des Datenpunktwertes der Histore einmal pro Monat oder Woche je nach schedule-Einstellung // die aktiven messages bleien erhaltebn
//-----------------------------------------------------------------------------------------------------
if(ScheduleAktiv ) {
schedule(scheduleTimeClearSMTexte, function() {
if( !ScheduleAktiv) {return };
LOG(`Schedule zum refresh der Historie wurde ausgeloest - Historie is nun geloescht`, "Event", "scheduleTimeClearSMTexte", 0);
Refresh_History()
});
}
//-----------------------------------------------------------------------------------------------------
// Funktion SubscribeRegaDP // Erstellung der Subscriptions auf REGA Datenpunkt // es werden 2 Sekunden lang vorliegende Aenderungen gesammelt
//-----------------------------------------------------------------------------------------------------
function SubscribeRegaDP() {
LOG(`Routine SubscribeRegaDP wird ausgefuehrt`, "Ablauf", "SubscribeRegaDP", 2);
let changeTimeout = null;
PathRega.forEach(path => { // Iteriere ueber das Array PathRega und abonniere jeden Pfad
on({id: path, change: 'ne'}, function (obj) {
LOG(`Subscription Datenpunkt ${path} getriggert. Neuer Wert: ${obj.state.val} alter Wert:${obj.oldState.val}`, "Ablauf", "SubscribeRegaDP", 2);
if (changeTimeout) { // Falls schon ein Timer laeuft, diesen abbrechen
clearTimeout(changeTimeout);
}
changeTimeout = setTimeout(function() { // Setze einen neuen Timer auf 2 Sekunden, um die Funktion Servicemeldung aufzurufen
Check_All("trigger",null); // Funktion wird nach 2 Sekunden ohne weitere aenderungen aufgerufen
}, 2000); // 2000 ms = 2 Sekunden
});
});
} // Ende der Funktion
//-----------------------------------------------------------------------------------------------------
// Funktion Subscribe GeraeteID// Erstellung der Subscriptions auf GeraeteID und Datenpunkt Ebene
//-----------------------------------------------------------------------------------------------------
function SubscribeGeraeteID() {
LOG(`Routine SubscribeGeraeteID wird ausgefuehrt`, "Ablauf", "SubscribeGeraeteID", 2);
let callCount = 0; // Zaehler fuer Aufrufe // maximal 25
let timeoutActive = false; // Status fuer Timeout
for (let j = 0; j < selectors.length; j++) { // Schleife ueber alle Selectoren
const selector = selectors[j];
if (selector.ids.length > 0) { // Schleife ueber IDs der Selektoren
LOG(`Erstellung der Subscriptions - Prozessiere selector: ${selector.name}`, "Ablauf", "SubscribeGeraeteID", 2);
for (let i = 0; i < selector.ids.length; i++) { // Schleife ueber alle IDs des aktuellen Selectors
const id = selector.ids[i];
LOG(`Erstellung der Subscriptions - Prozessiere Datenpunkt: ${id}`, "Ablauf", "SubscribeGeraeteID", 3);
on({ id: id, change: "ne" }, obj => {
if (obj.state.val === obj.oldState.val) {
LOG(`Datenpunkt ${obj.id} hatte keine Aenderung`, "Ablauf", "SubscribeGeraeteID", 2);
return; // Wenn der Wert nicht geaendert wurde, keine weitere Verarbeitung
}
LOG(`Subscription Datenpunkt Wert geaendert fuer ${obj.id} wird ausgefuehrt`, "Ablauf", "SubscribeGeraeteID", 1);
if (timeoutActive) {
return; // Wenn der Timeout aktiv ist, keine weitere Verarbeitung
}
callCount++;
if (callCount >= 25) { // Wenn mehr als 25 Aufrufe innerhalb von 1 Sekunde
timeoutActive = true; // Wartezeit aktivieren
LOG("Zu viele Servicemeldungen wurden generiert, warte 5 Minuten.", "WARN", "SubscribeGeraeteID", 0, "warn");
setTimeout(() => { // Timeout nach 3 Minuten
timeoutActive = false; // Timeout beenden
callCount = 0; // Zaehler zuruecksetzen
LOG("Wartezeit beendet, weitere Servicemeldungen sind jetzt moeglich.", "Ablauf", "SubscribeGeraeteID", 0);
}, 3 * 60 * 1000); // 3 Minuten in Millisekunden
}
Check_All("trigger",obj);
setTimeout(() => { // Zuruecksetzen des Zaehlers nach 1 Sekunde
callCount = Math.max(0, callCount - 1);
}, 1000);
});
} // endloop ids per selector
} else {
LOG(`No matching states found for ${selector.name}`, "Ablauf", "SubscribeGeraeteID", 2);
} // endfor loop selector
} //endif Selector
} // endfunction
//-----------------------------------------------------------------------------------------------------
// Fumktion Check_All Kompeletter Durchgang durch alle IDs des selectors - Dies ist die Kernfunktion des scriptes - alle Servicemeldungen durchzaehlen und Servicemeldungentexte speichern
//-----------------------------------------------------------------------------------------------------
function Check_All(RunType,obj) {
LOG("Routine Check_All wird ausgefuehrt", "Ablauf", "Check_All", 2);
const startTime = Date.now();
AktuelleMeldungenJSON = [];
let jsonString = getState(id_JSON_Servicemeldung_Historie).val || '[]'; // Lade den aktuellen JSON-String des Historie-Datenpunkts
HistorischeMeldungenJSON = JSON.parse(jsonString);
Zaehler.forEach(entry => { entry.count = 0; }); // Alle count-Werte im Objekt auf 0 setzen
for (let j = 0; j < selectors.length; j++) {
const selector = selectors[j];
if (selector.ids.length > 0) {
for (let i = 0; i < selector.ids.length; i++) {
let id = selector.ids[i], status = getState(id).val;
if (!existsState(id)) {
LOG(`Geraet ${obj.id} scheint zwischenzeitlich nicht mehr zu existieren - Empfehlung: script neu starten`, "WARN", "Check_All", 0, "warn");
continue;
}
switch (selector.name) { // fuer alle alarmtypes - erst die Sonderfaelle
case 'STICKY_SABOTAGE_ALARM':
if(status >= 1 ) {processServiceMessage(selector.name, status, id,RunType); }
break
case 'ERROR':
if (status >= 1 && status <= 7) {
if (status === 7) { // Sonderfall bei HM-Classic
processServiceMessage('SABOTAGE_ALARM', status, id,RunType);
} else {
processServiceMessage(selector.name, status, id,RunType);
}
}
break;
default: // Standardfall fuer alle anderen alarmtypes
if(status === 1 || status === true ) {processServiceMessage(selector.name, status, id,RunType); } // bei status = true = AccessPoint
break;
}
increaseCount('Gesamt');
} // Ende Loop der Mitglieder des Alarmtypes
} // Endif selector ist gefuellt
} // Ende Loop ueber den Selector
const AnzahlAktuellerSM = Zaehler.find(e => e.alarmtype === 'SMAktuell').count;
// Sequenz zur Speicherung der aktuellen Servicemeldungen in die JSON -Datenpunkte--------------------------
if (AnzahlAktuellerSM === 0) {
AktuelleMeldungenJSON =CreateJsonEntry([] ,func_get_datum(),'', '', '','',func_get_datum() + " - " + MessageBeiKeinerSM,MessageBeiKeinerSM,null,false ); // Keine aktuellen Meldungen -> "Keine Meldungen" hinzufuegen
let jsonString = JSON.stringify(AktuelleMeldungenJSON);
setState(id_JSON_Servicemeldung_Aktuell, jsonString); // Aktuelle SM wird in JSON-Datenpunkt als String gespeichert
} else {
let jsonString = JSON.stringify(AktuelleMeldungenJSON); // AktuelleMeldungenJSON wird in Funktion DefineServiceMessage gefuellt
setState(id_JSON_Servicemeldung_Aktuell, jsonString); // Aktuelle SM wird in JSON-Datenpunkt als String gespeichert
}
if (GeraeteIDTrigger && RunType === "trigger" && !existsState(obj.id)) { // obj.id nicht mehr in ioBroker aber noch im Selector
const GeraeteId = ExtractMetaData(obj.id, "GeraeteID");
LOG(`Geraet ${GeraeteId} scheint zwischenzeitlich nicht mehr zu existieren - Empfehlung: script neu starten`, "WARN", "Check_All", 0, "warn");
return;
}
// Speziell fuer GerateIDTrigger = true ist, dass es "aufgehoben"-Meldungen gibt, die fuer REGA Trigger nicht ausgewertet werden koennen. diese duerfen auch nicht als aktuelle meldungen in den DP gelangen
let ExtendedAktuelleMeldungenJSON = [...AktuelleMeldungenJSON]; // Erstelle eine flache Kopie
if (GeraeteIDTrigger && RunType === "trigger") {
const GeraeteId = ExtractMetaData(obj.id, "GeraeteID");
const status = obj.newState.val;
const common_name = ExtractMetaData(obj.id, "CommonName");
const native_type = ExtractMetaData(obj.id, "nativeType");
const meldungsart = ExtractMetaData(obj.id, "meldungsart");
let status_textLang = DefineServiceMessage(native_type, status, obj.id, "lang");
let status_textPure = DefineServiceMessage(native_type, status, obj.id, "pure");
if (!checkIFJSONfEntryExists(ExtendedAktuelleMeldungenJSON,func_get_datum(obj.id), GeraeteId,status) ) { // checken ob ein Eintrag schon in der Historie ist, wenn nicht dann hinzufuegen
ExtendedAktuelleMeldungenJSON = CreateJsonEntry(ExtendedAktuelleMeldungenJSON, func_get_datum(obj.id), meldungsart, common_name, GeraeteId, status, status_textLang, status_textPure, func_Batterie(native_type), true)
}
}
// Historische Meldungen hinzufuegen, wenn nicht bereits vorhanden
ExtendedAktuelleMeldungenJSON.forEach(aktuelleMeldung => { // Iteriere ueber alle aktuellen Meldungen und fuege aktuelle meldungen in die historie wenn noch nicht vorhanden
if (!checkIFJSONfEntryExists(HistorischeMeldungenJSON,aktuelleMeldung.datum_seit ,aktuelleMeldung.GeraeteId, aktuelleMeldung.status) ) { // checken ob ein Eintrag schon in der Historie ist, wenn nicht dann hinzufuegen
if(!isLatestEntryMessageBeiKeinerSM(ExtendedAktuelleMeldungenJSON)) { // nicht wenn nicht "keine Servicemeldungen" in aktuellJSON vorhanden ist
addMessageToCollector(aktuelleMeldung.meldungsart, aktuelleMeldung.status_message_Pure, aktuelleMeldung.status_message_Lang);
HistorischeMeldungenJSON = CreateJsonEntry(HistorischeMeldungenJSON, aktuelleMeldung.datum_seit, aktuelleMeldung.meldungsart, aktuelleMeldung.common_name, aktuelleMeldung.GeraeteId, aktuelleMeldung.status, aktuelleMeldung.status_message_Lang, aktuelleMeldung.status_message_Pure, aktuelleMeldung.batterie_bezeichnung, true )
writelog(aktuelleMeldung.common_name, aktuelleMeldung.GeraeteId, aktuelleMeldung.meldungsart, aktuelleMeldung.status, aktuelleMeldung.status_message_Lang); // externes log erzeugen
}
}
});
// Handling "keine aktuellen Servicemeldungen"
if (AnzahlAktuellerSM === 0 && RunType === "trigger") { // wenn keine SM vorliegen, die Meldung "keine Servicmeldungen" in die History
HistorischeMeldungenJSON =CreateJsonEntry(HistorischeMeldungenJSON ,func_get_datum(),'', '', '','',func_get_datum() + " - " + MessageBeiKeinerSM,MessageBeiKeinerSM,null,false ); // Keine aktuellen Meldungen
writelog("", "", "", "", MessageBeiKeinerSM); // log erzeugen
addMessageToCollector('keineSM', MessageBeiKeinerSM, MessageBeiKeinerSM); // Vorbereitung Messagesending
sendMessage('keineSM'); // Message senden
}
// Messages senden fuer alle Faelle mit Servicemeldungen
if (AnzahlAktuellerSM > 0 && RunType ==="trigger") { // Servicemeldungen versenden, falls servicemeldungen vorliegen
sendMessage();
}
HistorischeMeldungenJSON = Markiere_HistSM_als_Aufgehoben(HistorischeMeldungenJSON) // markiert hist messages als erledigt falls nicht mehr in aktuelle meldungen enthalten
jsonString = JSON.stringify(HistorischeMeldungenJSON); // Vorbereitung speichern
setState(id_JSON_Servicemeldung_Historie, jsonString); // hist JSON-Daten speichern
// falls so eingestellt werden jetzt die TEXTe aus den JSON (aktuell und historie) erzeugt und in die Datenpunkte geschrieben
GenerateTEXT_Datenpunkte();
generateHtmlTable(AktuelleMeldungenJSON,"AKT"); // Aktuelle SM wird in HTML Format gespeichert falls so eingestellt
generateHtmlTable(HistorischeMeldungenJSON,"HIST"); // Historische SM wird in HTML Format gespeichert falls so eingestellt
// anzahl der Eintraege in HistorischeMeldungenJSON ermitteln und alle counts in die Statistik schreiben
let entry = Zaehler.find(item => item.alarmtype === "SMHistorie");
if (entry) {entry.count = HistorischeMeldungenJSON.length; } // Wenn der Eintrag gefunden wird, aktualisiere den count-Wert
Zaehler.forEach(entry => { // JETZT: ZaehlerStaende in die ioBroker - Datenpunkte-------------------fuer aktuelle Meldungen
setState(entry.Datenpunkt, entry.count);
});
// Log Ausgabe der Ergebnisse--------------------------------------------
LOG(`Es wurden insgesamt ${Zaehler.find(e => e.alarmtype === 'Gesamt').count} IDs gecheckt - insgesamt gibt es ${AnzahlAktuellerSM} Servicemeldungen`, "Ergebnis", "Check_All", 2);
Zaehler.forEach(({ alarmtype, count }) => {
LOG(`SMAktuell fuer alarmtype: ${alarmtype} insgesamt festgestellte Servicemeldungen: ${count}`, "Ergebnis", "Check_All", 2);
});
LOG(`Zeitverbrauch fuer Routine Check_All: ${Date.now() - startTime} Millisekunden`, "Ergebnis", "Check_All", 1);
AktuelleMeldungenJSON =[]; HistorischeMeldungenJSON = []; MessageSendCollector = {}; // Speicher freigeben
}
//-----------------------------------------------------------------------------------------------------
// Function processServiceMessage // Zuordnung der richtigen Servicemeldung zum Alarmtype / Zaehlung der Faelle
//-----------------------------------------------------------------------------------------------------
function processServiceMessage(alarmtype, status, id, RunType) {
const native_type = ExtractMetaData(id, "nativeType");
const meldungsart = ExtractMetaData(id, "meldungsart");
const common_name = ReplaceString(ExtractMetaData(id, "CommonName"));
const GeraeteId = ExtractMetaData(id, "GeraeteID");
let ServiceMeldungTextLang = DefineServiceMessage(native_type, status, id, "lang"); // aktuelle Servicemeldung ableiten
let ServiceMeldungTextPure = DefineServiceMessage(native_type, status, id, "pure");
AktuelleMeldungenJSON = CreateJsonEntry(AktuelleMeldungenJSON, func_get_datum(id), meldungsart, common_name, GeraeteId, status, ServiceMeldungTextLang, ServiceMeldungTextPure, func_Batterie(native_type), true); // nur einmal unabhaengig der Version
increaseCount(alarmtype); increaseCount('SMAktuell');
}
//-----------------------------------------------------------------------------------------------------
// Function increaseCount // Diese Funktion reduziert Wiederholungen der Case abfragen in Check_ALL
//-----------------------------------------------------------------------------------------------------
function increaseCount(alarmtype) {
const entry = Zaehler.find(e => e.alarmtype === alarmtype);
if (entry) entry.count++;
}
//-----------------------------------------------------------------------------------------------------
// Funktion DefineServiceMessage Message ERmittlung
//-----------------------------------------------------------------------------------------------------
function DefineServiceMessage(native_type, status, id, version) {
const meldungsart = ExtractMetaData(id, "meldungsart");
LOG(`Routine DefineServiceMessage wird ausgefuehrt meldungsart ${meldungsart} und ID:${id}`, "Ablauf", "DefineServiceMessage", 2);
const adapter = ExtractMetaData(id, "adapter");
const GeraeteId = ExtractMetaData(id, "GeraeteID");
const common_name = ReplaceString(ExtractMetaData(id, "CommonName"));
const datum_seit = func_get_datum(id);
let matchingAlarmType = meldungsart;
let isValidMessageType = alarmTypes.some((type) => type.suffixes.includes(meldungsart));
if (isValidMessageType) { // Bestimmen des passenden Alarmtyps
matchingAlarmType = alarmTypes.find((type) => type.suffixes.includes(meldungsart)).key;
if (matchingAlarmType === "ERROR" && meldungsart !== "ERROR") {
matchingAlarmType = meldungsart;
}
}
const createServiceMessage = (message) => { // Hilfsfunktion zum Erzeugen der ServiceMessage
if (version === "lang") { // Unterschiedliche Formate je nach Version
let Status_Message_Lang = `${datum_seit} - ${meldungsart} - ${common_name} - (${GeraeteId}) - ${status} - ${message}`;
return Status_Message_Lang;
} else if (version === "pure") {
return message; // Nur die pure status Nachricht
} else {
return `${common_name} ${message}`; // Kurze Nachricht, falls kurze Nachrichten gewuenscht fuer das messaging
}
};
switch (matchingAlarmType) { // Logik je nach meldungsart
case "LOWBAT_ALARM":
const lowBatMessage = `${getStatusMessage(meldungsart, status, adapter)} - Batteriebezeichnung: ${func_Batterie(native_type)}`;
return createServiceMessage(lowBatMessage);
case "ERROR":
if (status >= 1 && status <= 7 && errorMessages[native_type] && errorMessages[native_type][status]) {
return createServiceMessage(errorMessages[native_type][status]);
}
if (status === 0) {
const fallbackMessage = getStatusMessage("FALLBACK", status, adapter);
return createServiceMessage(fallbackMessage);
}
break;
case "FAULT_REPORTING":
if (faultMessages[native_type] && faultMessages[native_type][status]) {
return createServiceMessage(faultMessages[native_type][status]);
}
break;
default:
if (isValidMessageType) {
return createServiceMessage(getStatusMessage(meldungsart, status, adapter));
} else {
const fallbackMessage = getStatusMessage("FALLBACK", status, adapter);
return createServiceMessage(fallbackMessage);
}
}
return createServiceMessage(getStatusMessage("FALLBACK", status, adapter)); // kann eigentlich nicht aufteten
}
//-----------------------------------------------------------------------------------------------------
//Funktion getStatusMessage zur Abfrage der Meldung je messageType, Adapter und Status (fuer Routine DefineServiceMessage)
//-----------------------------------------------------------------------------------------------------
function getStatusMessage(messageType, statusKey, adapter) {
if ( statusMessages[messageType] && statusMessages[messageType][adapter] &&statusMessages[messageType][adapter][statusKey] !== undefined ) { // Existenzpruefung- ggf regagieren
return statusMessages[messageType][adapter][statusKey];
} else {
return `Keine passende Meldung fuer MessageType: "${messageType}", Adapter: "${adapter}", Status: "${statusKey}".`;
}
}
//-----------------------------------------------------------------------------------------------------
// addMessageToCollector Messages werden unter beruecksichtigung der folgenden Objekte in den MessageCollector genommen
// MessengerScope, Services, AlarmTypes, TextTypeKurz
// Aufbau MessageCollector
// 1. Key AlarmType i.e. LowBat
// 2. Key Messenger i.e. email
// 3. Nachricht i.e "Batstadn nierig"
// - Beispiel
//{ "ERROR": { "Email": [ "Error Alarm " ],
// "SMS": ["Error" ] },
// "SABOTAGE_ALARM": { "Email": [ "Alarm: Eindringling im Gebaeude!"],
//-----------------------------------------------------------------------------------------------------
function addMessageToCollector(messageType, MessageKurz, MessageLang) {
let actualMessageType, matchingAlarmType;
if (messageType != "keineSM") { // keineSM ist sonderbehandlung
let isValidMessageType = alarmTypes.some((type) => type.suffixes.includes(messageType)); // ist der Messagytype in alarmtype.suffixes enthalten ?
if (isValidMessageType) { // in suffixes enthalten
matchingAlarmType = (alarmTypes.find((type) => type.suffixes.includes(messageType))).key; // wenn in alarmtype.suffixes enthalten, dann umsetzen in matchingAlarmType
actualMessageType = messageType
}else{
LOG(`MessageType: ${messageType} ist nicht konfiguriert in alarmtypes`, "WARN", "addMessageToCollector", 0); // wird weiter unten auf Sonstige gestellt
}
}
if (messageType === "keineSM") { // Sonderbehandlung fuer "keineSM" - Diese wird behandelt wie jeder andere messageType
actualMessageType = "keineSM", matchingAlarmType = "keineSM"
}
LOG(`messagetype ist ${messageType} -- aktueller messsagetype ${actualMessageType} -- matchingAlarmString = ${matchingAlarmType}`, "Ablauf", "addMessageToCollector", 2);
if (!MessageSendCollector[actualMessageType]) { // Sicherstellen, dass der Typ im MessageSendCollector existiert
MessageSendCollector[actualMessageType] = {}; // hinzufuegen des messageTypes
}
let messengerConfig = MessengerScope[matchingAlarmType]; // ueberpruefen, ob der alarmtype im MessengerScope vorhanden ist, falls nicht, 'Sonstige' verwenden
if (!messengerConfig) { // Wenn der alarmtype nicht im MessengerScope definiert ist, benutze die Konfiguration fuer 'Sonstige'
messengerConfig = MessengerScope['Sonstige'] || Array(services.length).fill(false); // false ist fallback - messengerConfig ist ein array. services=liste der messenger
}
messengerConfig.forEach((isActive, index) => { // Nachricht nur hinzufuegen, wenn die Konfiguration fuer den Service aktiv ist
if (isActive) {
const service = services[index];
if (!MessageSendCollector[actualMessageType][service]) { // Nachricht nur hinzufuegen, wenn die Konfiguration fuer den Service aktiv ist
MessageSendCollector[actualMessageType][service] = [];
}
const messageToAdd = TextTypeKurz[index] ? MessageKurz : MessageLang; // Auswahl von MessageKurz oder MessageLang basierend auf TextTypeKurz index entspricht dem service
MessageSendCollector[actualMessageType][service].push(messageToAdd + '\n'); // Messages werden nacheinander reingepusht. mehrere messages bei gleichen keys (messagtype und service)
}
});
}
//-----------------------------------------------------------------------------------------------------
// sendMessage Hier werden die Nachrichten fuer den jeweiligen Service aufbereitet
//-----------------------------------------------------------------------------------------------------
function sendMessage(messageType = null) {
LOG(`Routine sendMessage wird ausgefuehrt meldungsart ${messageType}`, "Ablauf", "sendMessage", 2);
const messageTypesToProcess = messageType ? [messageType] : Object.keys(MessageSendCollector);
const combinedMessagesByService = {}; // Kombiniere Nachrichten fuer jeden Dienst ueber alle Typen hinweg
messageTypesToProcess.forEach((type) => {
const messagesByService = MessageSendCollector[type] || {};
Object.keys(messagesByService).forEach((service) => {
if (!combinedMessagesByService[service]) {
combinedMessagesByService[service] = [];
}
combinedMessagesByService[service] = combinedMessagesByService[service].concat(messagesByService[service]);
});
delete MessageSendCollector[type]; // Loesche den Typ nach der Verarbeitung
});
Object.keys(combinedMessagesByService).forEach((service) => { // Sende kombinierte Nachrichten an die jeweiligen Dienste
const combinedMessage = combinedMessagesByService[service].join('\n');
if (combinedMessage.trim().length > 0) {
sendToService(service, combinedMessage);
}
});
} // ende Funktion
//-----------------------------------------------------------------------------------------------------
// sendToService - hier wird der Versand vorgenommen
// Reihenfolge: Email, WhatsApp, Signal, Telegram, Pushover, Pushsafer
// MessengerInstanz = [0, 0, 0, 0, 0, 0] Instanzen der Messenger-Dienste in
//-----------------------------------------------------------------------------------------------------
function sendToService(service, combinedMessage) {
LOG(`Message wird versendet mit: ${service}: ${combinedMessage}`, "Ablauf", "sendToService", 2);
switch (service) {
case "email":
sendTo(`email.${MessengerInstanz[0]}`, "send", {
text: combinedMessage,
to: emailAddresse,
subject: Headline
});
break;
case "whatsApp":
sendTo(`whatsapp-cmb.${MessengerInstanz[1]}`, "send", {
text: combinedMessage
});
break;
case "Signal":
sendTo(`signal-cmb.${MessengerInstanz[2]}`, "send", {
text: combinedMessage
});
break;
case "Telegram":
sendTo(`telegram.${MessengerInstanz[3]}`, "send", {
text: combinedMessage,
user: TelegramUser // Telegram User ID, um den Nachrichteneempfaenger festzulegen
});
break;
case "Pushover":
sendTo(`pushover.${MessengerInstanz[4]}`, "send", {
message: combinedMessage,
sound: ""
});
break;
case "Pushsafer":
sendTo(`pushsafer.${MessengerInstanz[5]}`, "send", {
message: combinedMessage,
title: Headline
});
break;
default:
LOG(`Unbekannter Service: ${service}`, "WARN", "sendToService", "warn");
}
}
//-----------------------------------------------------------------------------------------------------
// Funktion ExtractMetaData Verarbeitung des Datenpunkts zur Extraktion der Metadaten: GeraeteId, meldungsart
//-----------------------------------------------------------------------------------------------------
function ExtractMetaData(datenpunkt, ExtractType) {
LOG(`Routine ExtractMetaData wird ausgefuehrt datenpunkt fuer Metadaten ${datenpunkt} ExtractType ${ExtractType} `, "Ablauf", "ExtractMetaData", 2);
ExtractType = ExtractType.toUpperCase();
const teile = datenpunkt.split('.'); // Splitte den Datenpunkt in Teile
const adapter = teile[0]; // Der erste Teil ist immer der Adapter
const instance = teile[1]; // Der zweite Teil ist immer die Instance
const struktur = StrukturDefinition.find(s => s.Adapter === adapter); // Strukturzeile aus Tabelle StrukturDefinition fuer den Adapter aus dem uebergebenen DP
const GeraeteID = teile[struktur.GeraeteID]; // Extrahiere Felder basierend auf der Strukturdefinition
if (!struktur) {
LOG(`Die Struktur fuer MetaDaten Extract ist nicht korrekt eingestellt. Der uebergebenen datenpunkt war:$(datenpunkt)- Abbruch`, "Fehler", "ExtractMetaData", 0);
return null;
}
if(ExtractType === "ADAPTER") { return adapter; }
if(ExtractType === "GERAETEID") { return GeraeteID; }
if(ExtractType === "MELDUNGSART") {
const meldungsart = teile[struktur.AlarmFeld]; // Extrahiere Felder basierend auf der Strukturdefinition
return meldungsart;
}
if(ExtractType === "NATIVETYPE" && typeof struktur.nativeType === 'number') {
const MetaDP = datenpunkt.split('.').slice(0, struktur.nativeType).join('.'); // ergibt den Datenpunkt fuer getObject z.B. hm-rpc.1.00085D89B14067
const nativeType = getObject(MetaDP).native.TYPE;
return nativeType;
}
if(ExtractType === "NATIVETYPE" && typeof struktur.nativeType === 'string') { // bei string liegt der native-type im datenpunkt
let DPNativeTypeDP = struktur.nativeType;
DPNativeTypeDP = DPNativeTypeDP.replace('xidx', GeraeteID);
DPNativeTypeDP = DPNativeTypeDP.replace('xinstancex', instance);
let nativeType = getState(DPNativeTypeDP).val
return nativeType;
}
if(ExtractType === "COMMONNAME" && typeof struktur.common_name === 'number') {
const MetaDP = datenpunkt.split('.').slice(0, struktur.common_name).join('.'); // ergibt den Datenpunkt fuer getObject z.B. hm-rpc.1.00085D89B14067
const common_name = getObject(MetaDP).common.name;
return common_name;
}
if(ExtractType === "COMMONNAME" && typeof struktur.common_name === 'string') { // bei string liegt der common_name im datenpunkt
let DPcommmon_name = struktur.common_name;
DPcommmon_name = DPcommmon_name.replace('xidx', GeraeteID).replace('xinstancex', instance);
let common_name = getState(DPcommmon_name).val
return common_name;
}
LOG(`Der Datenpunkt/ExtractType, die erforderlich zur Ermittlung von Metadaten sind nicht bekannt Der Datenpunkt lautet ${datenpunkt} ExtractType ${ExtractType} - Abbruch`, "Fehler", "ExtractMetaData", 0);
return null
}
//-----------------------------------------------------------------------------------------------------
// ReplaceString // ersetzen entsprechend tabelle replacements
//-----------------------------------------------------------------------------------------------------
function ReplaceString(string) {
for (const [key, value] of Object.entries(replacements)) {
// Escape den Punkt (.) fuer den regulaeren Ausdruck
const escapedKey = key.replace('.', '\\.');
string = string.replace(new RegExp(escapedKey, 'g'), value);
}
return string;
}
//-----------------------------------------------------------------------------------------------------
// func_get_datum aktuelles Datum formatiert
//-----------------------------------------------------------------------------------------------------
function func_get_datum(id) {
let datum;
if (id && getState(id)) {
datum = formatDate(getState(id).lc, "TT.MM.JJ SS:mm:ss");
} else {
datum = formatDate(new Date(), "TT.MM.JJ SS:mm:ss"); // Aktuelles Datum
}
let datumDate = new Date(datum); // korrekte verwendung des datumsformates
let cutoffDate = new Date('1971-01-01T01:00:00');
return datumDate < cutoffDate ? '' : `${datum} Uhr`;
}
//-----------------------------------------------------------------------------------------------------
// func_Batterie Batterieermittlung
//-----------------------------------------------------------------------------------------------------
function func_Batterie(native_type) {
LOG(`Routine wird ausgefuehrt`, "Ablauf", "func_Batterie", 2);
const normalizedType = native_type.toUpperCase();
return Object.keys(batteryTypes).find(battery =>
batteryTypes[battery].some(device => device.toUpperCase() === normalizedType)
) || NichtRelevantText;
}
//-----------------------------------------------------------------------------------------------------
// Funktion CreateJsonEntry erzeugen einer JSON Tabelle
//-----------------------------------------------------------------------------------------------------
function CreateJsonEntry(existingJson = [], datum_seit = '', meldungsart = '', common_name = '', GeraeteId = '', status = '', Status_Lang = '', Status_Pure, Batterie = '',sortierung = false) {
let parsedJson;
if (Array.isArray(existingJson)) { // ueberpruefen, ob existingJson bereits ein Array ist
parsedJson = existingJson; // Nur das Array uebernehmen
} else {
parsedJson = []; // Falls es kein Array ist, setze es auf ein leeres Array
}
const neuerEintrag = {
datum_seit: datum_seit || func_get_datum(), // Fallback auf aktuelles Datum
meldungsart: meldungsart || NichtRelevantText,
common_name: common_name || NichtRelevantText,
GeraeteId: GeraeteId || NichtRelevantText,
status: status || NichtRelevantText, // Status entweder number oder boolean
status_message_Lang: Status_Lang || 'Keine Meldung', // Langer Text
status_message_Pure: Status_Pure || 'Keine Meldung', // nur die Status aus den Status-Tabellen
batterie_bezeichnung: Batterie || NichtRelevantText,
SM_aufgehoben_seit: "aktiv" // status ob eine message bereits aufgeboben ist. wenn aufgehoben wird ein Datum eingestellt
};
parsedJson.unshift(neuerEintrag);
if(sortierung) { sortJason(parsedJson);}
return parsedJson; // Gib das aktualisierte Array zurueck
}
//-----------------------------------------------------------------------------------------------------
// Funktion checkIfEntryExists Checken of ein Eintrag schon vorhanden ist
//-----------------------------------------------------------------------------------------------------
function checkIFJSONfEntryExists(parsedJson, datum_seit, GeraeteId, status) {
if (!Array.isArray(parsedJson)) { // ueberpruefen, ob parsedJson ein Array ist
LOG(`parsedJson ist kein Array`, "WARN","checkIFJSONfEntryExists", 1);
return false;
}
for (let entry of parsedJson) { // Durch das Array iterieren
if (entry.datum_seit === datum_seit && entry.GeraeteId === GeraeteId && entry.status === status) { // ueberpruefen, ob die Felder datum_seit, GeraeteId und status uebereinstimmen
return true; // Eintrag gefunden
}
}
return false; // Kein passender Eintrag gefunden
}
//-----------------------------------------------------------------------------------------------------
// Funktion isLatestEntryMessageBeiKeinerSM Checken ob die letzte message der variablen MessageBeiKeinerSM entspricht
//-----------------------------------------------------------------------------------------------------
function isLatestEntryMessageBeiKeinerSM(existingJson = []) {
if (!Array.isArray(existingJson) || existingJson.length === 0) { // ueberpruefen, ob existingJson ein Array ist und Eintraege enthaelt
return false;
}
const latestEntry = existingJson[0]; // Neuesten Eintrag extrahieren (erster Eintrag im Array)
if (latestEntry.status_message_Lang && latestEntry.status_message_Lang.includes(MessageBeiKeinerSM)) {
return true;
}
return false;
}
//-----------------------------------------------------------------------------------------------------
// Funktion Markiere_HistSM_als_Aufgehoben Zuordnung von historischen Messages zu "aufgehoben" mit Datum der Zuordnung
//-----------------------------------------------------------------------------------------------------
function Markiere_HistSM_als_Aufgehoben(HistorischeMeldungenJSON_transfer) {
const maxTimeDiffMs = 1000; // z.B. 1000 ms fuer eine Sekunde Unterschied
const nurKeineMeldungen = AktuelleMeldungenJSON[0].status_message_Lang.includes(MessageBeiKeinerSM); // Pruefen, ob AktuelleMeldungenJSON nur eine Meldung mit "Derzeit keine Servicemeldungen" enthaelt
if (nurKeineMeldungen) {
HistorischeMeldungenJSON_transfer.forEach(meldung => { // Alle historischen Eintraege als aufgehoben markieren
if (meldung.SM_aufgehoben_seit === "aktiv") {
meldung.SM_aufgehoben_seit = func_get_datum();
}
});
return HistorischeMeldungenJSON_transfer;
}
HistorischeMeldungenJSON_transfer.forEach(historischeMeldung => { // Iteriere ueber alle historischen Meldungen (aeussere Schleife)
if (historischeMeldung.SM_aufgehoben_seit !== "aktiv") return; // Wenn die historische Meldung bereits aufgehoben wurde, ueberspringen
let zugeordnet = false;
const historischeDatum = extractDate(historischeMeldung.datum_seit);
AktuelleMeldungenJSON.forEach(aktuelleMeldung => { // Iteriere ueber alle aktuellen Meldungen (innere Schleife)
const aktuelleDatum = extractDate(aktuelleMeldung.datum_seit);
const gleicheGeraeteID = historischeMeldung.GeraeteId === aktuelleMeldung.GeraeteId;
const gleicheStatusMessage = historischeMeldung.status_message_Pure === aktuelleMeldung.status_message_Pure;
const zeitDifferenz = Math.abs(aktuelleDatum.getTime() - historischeDatum.getTime()); // Berechne den Zeitunterschied in Millisekunden
const gleicheOderNaheZeit = zeitDifferenz <= maxTimeDiffMs; // Pruefe, ob die Zeitdifferenz innerhalb des erlaubten Bereichs liegt
if (gleicheGeraeteID && gleicheStatusMessage && gleicheOderNaheZeit) {
historischeMeldung.SM_aufgehoben_seit = "aktiv"; // Als nicht aufgehoben markieren
zugeordnet = true
}
});
if (!zugeordnet) {
historischeMeldung.SM_aufgehoben_seit = func_get_datum() // kein pendant gefunden, also muss die message obsolet sein
}
});
return HistorischeMeldungenJSON_transfer;
}
//-----------------------------------------------------------------------------------------------------
// Funktion sortJason zum Sortieren des History Jason nach datum
//-----------------------------------------------------------------------------------------------------
function sortJason(parsedJson) {
if (!Array.isArray(parsedJson)) { LOG("Die Eingabe fuer sortJason ist kein Array.", "Fehler", "sortJason", 2); return[] ;}
const parseDate = (datum) => {
try {
const [date, time] = datum.split(' '); // Datum und Zeit trennen
const [day, month, year] = date.split('.'); // Datum aufteilen in Tag, Monat, Jahr
const [hours, minutes, seconds] = time.split(':'); // Zeit aufteilen in Stunden, Minuten, Sekunden
return new Date(`20${year}-${month}-${day}T${hours}:${minutes}:${seconds}`); // ISO-Format erstellen
} catch (error) {
LOG(`Fehler beim Parsen des Datums: ${datum}`, "Fehler", "sortJason", 1);
return null; // Rueckgabe von null, wenn das Datum ungueltig ist
}
};
return parsedJson.sort((a, b) => {
const dateA = parseDate(a.datum_seit);
const dateB = parseDate(b.datum_seit);
if (dateA === null && dateB === null) return 0; // Keine aenderung bei ungueltigen Datumswerten
if (dateA === null) return 1; // Null-Werte ans Ende verschieben
if (dateB === null) return -1;
return dateB.getTime() - dateA.getTime(); // Neueste zuerst
});
}
//-----------------------------------------------------------------------------------------------------
// Funktion extractDatum // Hilfsfunktion zur Extraktion des Datums // Beispieltext: "01.12.24 12:04:55 Uhr - SABOTAGE_ALARM ..."
//-----------------------------------------------------------------------------------------------------
function extractDate(text) {
if (!text || typeof text !== "string") {
LOG(`extractDate Fehler: Ungueltiger Eingabetext: ${text}`, "Fehler", "extractDate", 2);
return new Date(0); // Fallback auf ein Default-Datum
}
const match = text.match(/^(\d{2})\.(\d{2})\.(\d{2}) (\d{2}):(\d{2}):(\d{2})/); // Regulaerer Ausdruck, um Datum und Uhrzeit zu extrahieren
if (!match) {
LOG(`extractDate Fehler: Kein gueltiges Datum gefunden: ${text}`, "Fehler", "extractDate", 2);
return new Date(0); // Fallback auf ein Default-Datum
}
const [_, day, month, year, hours, minutes, seconds] = match; // Teile aus dem Match extrahieren
const fullYear = year.length === 2 ? `20${year}` : year; // Jahr vervollstaendigen, falls noetig
const dateString = `${fullYear}-${month}-${day}T${hours}:${minutes}:${seconds}`; // Erstellen des Datums im Format "YYYY-MM-DDTHH:mm:ss"
const date = new Date(dateString); // Umwandlung in ein Date-Objekt
if (isNaN(date.getTime())) { // ueberpruefen, ob das Datum gueltig ist
LOG(`extractDate Fehler: Ungueltiges Datum erzeugt: ${dateString}`, "Fehler", "extractDate", 2);
return new Date(0);
}
return date;
}
//-----------------------------------------------------------------------------------------------------
// Funktion GenerateTEXT_Datenpunkte // Generiert TEXT format aus den JSON Arrays und speichert diese in die Datenpunkte
//-----------------------------------------------------------------------------------------------------
function GenerateTEXT_Datenpunkte() {
LOG(`Routine wird ausgefuehrt`, "Ablauf", "GenerateTEXT_Datenpunkte", 2);
if(!UpdateTEXT_Datenpunkte){return}; // wenn keine TEXT-Datenpunkte updates erfolgen sollen nicht ausfuehren
let SMAktuellMessageLangTEXT = [], SMHistMessageLangTEXT = [],SMAktuellMessageKurzTEXT = [];
HistorischeMeldungenJSON.forEach(Meldung => { // Iteriere durch HistorischeMeldungenJSON und extrahiere die status_message_Lang
SMHistMessageLangTEXT.push(Meldung.status_message_Lang + " - Meldung aufgehoben: " + Meldung.SM_aufgehoben_seit + "
"); // Status-Lang-Text extrahieren und ins Array pushen
});
setState(id_Text_Servicemeldung_History, SMHistMessageLangTEXT.join('')); // Umwandlung des Arrays in einen String und Speichern im Datenpunkt id_Text_ServicemeldungLang
AktuelleMeldungenJSON.forEach(Meldung => { // Iteriere durch AktuelleMeldungenJSON und extrahiere die status_message_Pure und status_message_Lang
if (Meldung.status_message_Pure) {
SMAktuellMessageKurzTEXT.push(Meldung.datum_seit + " " + Meldung.status_message_Pure + "
"); // Status-Kurz-Text extrahieren und ins Array pushen
}
if (Meldung.status_message_Lang) {
SMAktuellMessageLangTEXT.push(Meldung.status_message_Lang + "
"); // Status-Lang-Text extrahieren und ins Array pushen
}
});
setState(id_Text_ServicemeldungKurz, SMAktuellMessageKurzTEXT.join('')); // Umwandlung der Arrays in Strings und Speichern in den entsprechenden Datenpunkten
setState(id_Text_ServicemeldungLang, SMAktuellMessageLangTEXT.join(''));
}
//-----------------------------------------------------------------------------------------------------
// Funktion generateHtmlTable // Funktion, aus den JSON Tabellen ein HTML-Format erzeugt
//-----------------------------------------------------------------------------------------------------
function generateHtmlTable(data, HistOderAkt) {
if (!UpdateHTML_Datenpunkte) { return; } // Keine Updates, wenn HTML-Datenpunkte deaktiviert sind
const uniqueClass = "custom-table"; // Eindeutige Klasse für die Tabelle
const cellPadding = '6px';
// Baue die Tabelle mit einer eindeutigen Klasse auf
let table = `
Datum_Seit | Meldungsart | Name | GeraeteId | Status | Batterie | SM_aufgehoben_seit |
---|---|---|---|---|---|---|
${item.datum_seit} | ${item.meldungsart} | ${item.common_name} | ${item.GeraeteId} | ${item.status_message_Pure} | ${item.batterie_bezeichnung} | ${item.SM_aufgehoben_seit} |