// 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
//---------------------------------------------------------------------------------------------------------------------------------------------------------------
// 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 - bisher teilweise getestet - suche Tester 
//---------------------------------------------------------------------------------------------------------------------------------------------------------------
// 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.
// Vorteil ist, dass auch eine Historie und ein Log fuer Servicemeldungen geschrieben werden kann: 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
let GeraeteIDTrigger = true; // true = viele subscriptions aber praezise Historie der Servicemeldungen - false = 1 subscription - weniger praezise

//  fuer alle Spalten mit true werden die Nachrichten ueber den zugeordneten Dienst versendet
// Voraussetzung ist, dass der entsprechende Adapter installiert und konfiguriert ist
    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
const 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 und TextPfade
const PathRega = ['hm-rega.0.maintenance',];                            // Array um ggf die HCU zu konfigurieren

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
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" } },
    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"},
                                        "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'] },                  // 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:            [],
    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: 15.12.2024 Version:2.11`,`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 =[];     
                 
// 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 nicht in der Ausnahme
                alarmType.suffixes.forEach(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);
    }
});
// Aufbau MessageCollector - Beispiel
//{ "ERROR":            { "Email": [ "Error Alarm "                   ], "SMS": ["Error"         ] },
//  "SABOTAGE_ALARM":   { "Email": [ "Alarm: Eindringling im Gebaeude!"], "SMS": [ "Eindringling" ] } }
let MessageSendCollector = {};                                // alle aktuellen Servicemeldungen zum senden an messaging services
// Initialisierung des Objekts MessageCollector fuer alle Alarmtypen und Services
alarmTypes.forEach((type) => {
    const alarmKey = type.key; // Verwende die `key`-Eigenschaft als Schluessel
    MessageSendCollector[alarmKey] = {}; // Erstelle ein Objekt fuer den Alarmtyp
    services.forEach((service) => {
        MessageSendCollector[alarmKey][service] = []; // Erstelle ein leeres Array fuer jeden Service
    });
});
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 Tag
//-----------------------------------------------------------------------------------------------------
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() {
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) {
                    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) { 
                                processServiceMessage('SABOTAGE_ALARM', status, id,RunType);
                            } else {            
                                processServiceMessage(selector.name, status, id,RunType); 
                            } 
                        }
                        break;
                default:
                        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('<br>');// Formatiere alle aktuellen Servicemeldungen /  Zeilenumbruch
        let formattedMessagesKurz = ServicemeldungMessagesKurz.join('<br>');
        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
            delete MessageSendCollector['keineSM'];                                     // initialisierung Messagecollector
            addMessageToCollector('keineSM', MessageBeiKeinerSM, MessageBeiKeinerSM); 
            sendMessage('keineSM');
        } else {
            delete MessageSendCollector[meldungsart];                                   // initialisierung Messagecollector
            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 = []; // Speicher freigeben
    return;
}
//-----------------------------------------------------------------------------------------------------
//  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('<br>');
    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);
    addMessageToCollector(alarmtype, ServiceMeldungTextKurz, ServiceMeldungTextLang);
    if (!GeraeteIDTrigger && RunType === "trigger") {         // historische Meldung fuer REGA Trigger
        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
   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:
            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}".`;
    }
}
//-----------------------------------------------------------------------------------------------------
// 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");
    }
}
//-----------------------------------------------------------------------------------------------------
// addMessageToCollector  Messages werden unter beruecksichtigung der folgenden Objekte in den MessageCollector genommen
// MessengerScope, Services, AlarmTypes, TextTypeKurz
//-----------------------------------------------------------------------------------------------------
function addMessageToCollector(messageType, MessageKurz, MessageLang) {
    const isValidMessageType = alarmTypes.some((type) => type.key === messageType);         // ueberpruefung, ob der `messageType` in `alarmTypes` existiert
    let actualMessageType = isValidMessageType ? messageType : "Sonstige";                  // Wenn der messageType ungueltig ist, "Sonstige" als Fallback verwenden
    if (!isValidMessageType && debugLevel >= 1 && messageType != "keineSM") {
        LOG(`messageType ${messageType} - umgeleitet auf Sonstige`, "Ablauf", "addMessageToCollector", 3);
    }
    if (messageType === "keineSM") {                                                        // Sonderbehandlung fuer "keineSM" - Diese wird behandelt wie jeder andere messageType
        actualMessageType = "keineSM";                                                      // Setze den messageType explizit auf "keineSM"
    }
    if (!MessageSendCollector[actualMessageType]) {                                         // Sicherstellen, dass der Typ im MessageSendCollector existiert
        MessageSendCollector[actualMessageType] = {};
    }
    let messengerConfig = MessengerScope[actualMessageType];                                // ueberpruefen, ob der actualMessageType im MessengerScope vorhanden ist, falls nicht, 'Sonstige' verwenden
    if (!messengerConfig) {                                                                 // Wenn der messageType 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
    }
    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
            if (!MessageSendCollector[actualMessageType][service].includes(messageToAdd)) {  // Nachricht nur hinzufuegen, wenn sie noch nicht existiert
                MessageSendCollector[actualMessageType][service].push(messageToAdd);
            }
        }
    });
}
//-----------------------------------------------------------------------------------------------------
// 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') {
        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') {
        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('<br>') : [];  // Trennen des aktuellen Textes in Zeilen (angenommen, Zeilen sind durch <br> 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('<br>');    // Die Zeilen wieder mit <br> 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");
    }
}