/*
########################################################################################################################
# ALARMSYSTEM
#
# Das Skript bildet eine einfache Alarmanlage nach mit der Schaltmöglichkeit
# für intern und extern.
# Datenpunkte für Inputs und Outputs werden angelegt.
# Nähere Beschreibung siehe im ioBroker-Forum unter
# https://forum.iobroker.net/topic/32885/umfassendes-alarmanlagen-skript
# Änderungshistorie:
# 2020-05-01    Andreas Kos     Erstellt
# 2020-05-02    Andreas Kos     Schaltwunsch mit Number-Datenpunkt Input.SwitchNumber (Idee von @Homer.J.)
#                               Schaltstatus mit Number-Datenpunkt Output.ActiveNumber (Idee von @Homer.J.)
# 2020-05-03    Andreas Kos     Korrekturen, u.a. für Melderauswertung (chage: "ne") & AlarmText
# 2020-05-04    Andreas Kos     - Melder werden aus den Functions (Aufzählungen, enums) dafür geholt. Auch beim Unscharf-
#                                 schalten, dadurch ist kein Neustarten des Skripts notwendig bei
#                                 Änderungen an diesen Aufzählungen.
#                               - Eine Schaltung von einem scharf-Zustand auf einen anderen
#                                 wird verhindert. ZB von scharf intern auf scharf extern.
#                                 Es muss immer unscharf dazwischen geschaltet werden.
# 2020-05-09    Andreas Kos     Zusätzliche Objekte mit JSON-Strings für:
#                               - den auslösenden Melder
#                               - alle offenen Melder
#                               - alle offenen Melder der Außenhaut
#                               - alle offenen Melder des Innenraums
#                               Die JSON-String beinhalten das auslösende Objekt, sowie (falls vorhanden)
#                               das Parent und das ParentsParent-Objekt mit allen in ioBroker verfügbaren Eigenschaften.
#                               Kleinere Verbesserungen, z.B. bezüglich setzen der AlarmTexte.
# 2020-05-12    Andreas Kos     Setzen des Datenpunkts idReady zur Bereitschaftsanzeige neu gemacht.
# 2021-06-13    Andreas Kos     Einbau der Funktion zum Ausnehmen einzelner Melder der Aussenhülle
#                               von der Melder-Überwachung. Soll zum Kippen von Fenstern dienen u.ä.
########################################################################################################################
*/
// EINBRUCHSMELDER
// Jeder Melder muss ein State sein, der bei Auslösung true liefert und in Ruhe (geschlossen) false.
// Die Melder sind in Arrays zusammengefasst, d.h. sie müssen jeweils mit Beistrich voneinander getrennt werden.
// Die Namen der Melder sollten gut gepflegt sein für eine sinnvolle Verwendung (Attribut name bei den Objekten)
// Melder der Außenhaut
// Dies können Öffnungskontakte sein von Fenster und Türen in den Außenmauern des Objekts.
// EINGABE: In der Aufzählung "alarmanlage_aussenhaut" die States einfügen.
// Melder des Innenraums
// Dies können Bewegungsmelder sein aus dem Inneren.
// EINGABE: In der Aufzählung "alarmanlage_innenraum" die States einfügen.
// Verzögerte Melder
// Diese kommen in den Gruppen oben auch vor. Sie bewirken eine Aktivierung der Eingangsverzögerung
// bei scharf geschalteter Anlage und erlauben während der Ausgangsverzögerung nach dem
// Scharfschalten das Haus zu verlassen.
// EINGABE: In der Aufzählung "alarmanlage_verzoegert" die States einfügen.
// EINSTELLUNGEN
const entryDelay =                30;         // Eingangsverzögerung in Sekunden (sollte maximal 60s sein)
const exitDelay =                 30;         // Ausgangsverzögerung in Sekunden (sollte maximal 60s sein)
const alarmDurationAccoustical =  180;         // Dauer des akkustischen Alarms in Sekunden (ACHTUNG: in Ö sind maximal 180s erlaubt!)
const alarmDurationOptical =      -1;         // Dauer des optischen Alarm in Sekunden, -1 für unendlich
// TEXTE FÜR SCHALTZUSTAND
// Diese Text geben Auskunft über den Zustand der Anlage.
// Sie werden in den Datenpunkt "javascript.X.Output.StatusText" geschrieben.
const textStatusInactive =        "unscharf";
const textStatusActiveInternal =  "scharf intern";
const textStatusActiveExternal =  "scharf extern";
const textActiveExternalDelayed = "scharf extern verzögert";
const textEntryDelayActive =      "Eingangsverzögerung aktiv";
const textExitDelayActive =       "Ausgangsverzögerung aktiv";
// TEXTE FÜR ALARMIERUNG UND FEHLER
// Diese Text geben im unscharfen Zustand der Anlage Auskunft über die Bereitschaft
// zum Scharfschalten (nur möglich, wenn alle Melder geschlossen - in Ruhe - sind) und
// Fehler bei der Scharfschaltung bzw. bei scharfer Anlage über den Zustand Frieden oder Alarm.
// Sie werden in den Datenpunkt "javascript.X.Output.AlarmText" geschrieben.
const textAlarmInactive =         "Alles OK";
const textAlarmActive =           "Alarm!!";
const textReady =                 "Bereit";
const textNotReady =              "Nicht bereit";
const textError =                 "Fehler bei der Scharfschaltung";
// EXPERTEN-EINSTELLUNGEN
const pathToCreatedStates = "Alarmanlage";    // Beispiel: States werden erzeugt unter javascript.X.Alarmanlage
const seperator = ", ";                       // Trenn-String, der zwischen den Meldernamen verwendet wird, im Datenpunkt "OpenDetectors"
const loglevel = 2;                           // 0 bis 3. 0 ist AUS, 3 ist maximales Logging
                                            // Empfehlung für Nachvollziehbarkeit aller Handlungen ist 2 (Ereignisliste)
const functionOuterSkin = "alarmanlage_aussenhaut";
const functionIndoor = "alarmanlage_innenraum";
const functionDelayedDetectors = "alarmanlage_verzoegert";
/*
   ###############################################################################
                    DO NOT CHANGE ANYTHING BELOW THIS LINE
                         AB HIER NICHTS MEHR ÄNDERN
   ###############################################################################
*/
// ===============================================================================
// Variablen
// ===============================================================================
// Arrays für die Melder
var detectorsOuterSkin = [];
var detectorsIndoor = [];
var detectorsDelayed = [];
// Array für die IgnoreOpen-Melder (beinhaltet nur Flags).
// Das Array ist inital deckungsgleich mit detectorsOuterSkin, da diese
// nur aus dieser Function kommen können.
// Dieses Array ist 2-Dimensional: [Melder-ID, IgnoreOpen-Zustands-ID]
var detectorsIgnoreOpen = [];
// Javascript-Instanz mit der das Alarmanlagen-Skript ausgeführt wird
var javascriptInstance = instance;
// States, die erzeugt werden für Status-Ausgaben
var idActive = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.Active";
var idActiveExternal = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.ActiveExternal";
var idActiveInternal = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.ActiveInternal";
var idActiveNumber = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.ActiveNumber";
var idAlarm = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.Alarm";
var idAlarmAccoustical = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.AlarmAccoustical";
var idAlarmOptical = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.AlarmOptical";
var idReady = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.Ready";
var idEntryDelayActive = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.EntryDelayActive";
var idExitDelayActive = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.ExitDelayActive";
var idAlarmingDetector = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.AlarmingDetector";
var idAlarmingDetectorJSON = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.AlarmingDetectorJSON";
var idOpenDetectors = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.OpenDetectors";
var idOpenDetectorsJSON = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.OpenDetectorsJSON";
var idOpenDetectorsOuterSkinJSON = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.OpenDetectorsOuterSkinJSON";
var idOpenDetectorsIndoorJSON = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.OpenDetectorsIndoorJSON";
var idOpenDetectorsWithIgnoreOpenFlagSet = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.OpenDetectorsIgnoreOpen";
var idStatusText = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.StatusText";
var idAlarmText = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.AlarmText";
var idError = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Output.Error";
// States, die erzeugt werden für Eingaben
var idSwitchExternal = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Input.SwitchExternal";
var idSwitchInternal = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Input.SwitchInternal";
var idSwitchExternalDelayed = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Input.SwitchExternalDelayed";
var idSwitchNumber = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Input.SwitchNumber";
// Sonstige globale Variablen, die gebraucht werden
var timerExitDelay = null;
var timerTexts = null;
// ===============================================================================
// Precode
// ===============================================================================
// Logging
if (loglevel >= 1) log ("Alarmsystem started.");
getAllDetectors();
// States erzeugen
myCreateState(idActive, "boolean", false, "Switch Status Total", false, "info.status");
myCreateState(idActiveExternal, "boolean", false, "Switch Status External", false, "info.status");
myCreateState(idActiveInternal, "boolean", false, "Switch Status Internal", false, "info.status");
myCreateState(idAlarm, "boolean", false, "Alarm Status", false, "sensor.alarm");
myCreateState(idAlarmAccoustical, "boolean", false, "Accoustical Alarm Status", false, "sensor.alarm");
myCreateState(idAlarmOptical, "boolean", false, "Optical Alarm Status", false, "sensor.alarm");
myCreateState(idReady, "boolean", false, "Alarmsystem Ready", false, "info.status");
myCreateState(idError, "boolean", false, "Error Switching Active", false, "info.status");
myCreateState(idEntryDelayActive, "boolean", false, "Entry Delay Active Status", false, "info.status");
myCreateState(idExitDelayActive, "boolean", false, "Exit Delay Active Status", false, "info.status");
myCreateState(idAlarmingDetector, "string", "", "Alarming Detector", false, "text");
myCreateState(idAlarmingDetectorJSON, "string", "", "Alarming Detector JSON", false, "json");
myCreateState(idOpenDetectors, "string", "", "Open Detectors", false, "info.name");
myCreateState(idOpenDetectorsJSON, "string", "", "Open Detectors JSON", false, "json");
myCreateState(idOpenDetectorsOuterSkinJSON, "string", "", "Open Detectors Outer Skin JSON", false, "json");
myCreateState(idOpenDetectorsIndoorJSON, "string", "", "Open Detectors Indoor JSON", false, "json");
myCreateState(idOpenDetectorsWithIgnoreOpenFlagSet, "string", "", "Open Detectors with IgnoreOpen-Flag set", false, "text");
myCreateState(idStatusText, "string", "", "Status Text", false, "text");
myCreateState(idAlarmText, "string", "", "Alarm Text", false, "text");
myCreateState(idSwitchExternal, "boolean", false, "Enable Surveillance External", true, "switch");
myCreateState(idSwitchInternal, "boolean", false, "Enable Surveillance Internal", true, "switch");
myCreateState(idSwitchExternalDelayed, "boolean", false, "Enable Surveillance External Delayed", true, "switch");
myCreateMultiState (idActiveNumber, "number", 0, "Switch Status Number", false, 0, 4,"0:"+textStatusInactive+"; 1:"+textStatusActiveInternal+"; 2:"+textStatusActiveExternal+"; 3:"+textExitDelayActive+"; 4:"+textEntryDelayActive);
myCreateMultiState (idSwitchNumber, "number", 0, "Switch by Number", true, 0, 3, "0:"+textStatusInactive+"; 1:"+textStatusActiveInternal+" ; 2:"+textStatusActiveExternal+" ; 3:"+textActiveExternalDelayed);
// Erzeugen der Datenpunkte für die IgnoreOpen-Einstellung
myCreateIgnoreOpenDPs();
// Melder nach dem Starten checken
checkDetectors(detectorsOuterSkin.concat(detectorsIndoor));
// ===============================================================================
// ON-Subscribtions
// ===============================================================================
// Auf Schaltstellen EXTERN verzögert reagieren (schalten EIN/AUS)
on ({id: idSwitchExternalDelayed, change: "any"}, function(obj){
    if (loglevel >= 3) log ("Switching External Delayed, Value: " + getState(obj.id).val);
    if (getState(obj.id).val) { // Einschalten, scharf extern VERZÖGERT
        if (loglevel >= 2) log ("Switching required: Delayed External Active");
            if (getState(idActive).val) {
                if (loglevel >= 3) log ("Alarmsystem already active, switch to inactive first!");
                setState(idError, true);
            } else {
                setState(idExitDelayActive, true);
                if (timerExitDelay) clearTimeout(timerExitDelay);
                timerExitDelay = setTimeout(switchActiveExternal, exitDelay * 1000);
            }
    }
    else { // Ausschalten, unscharf SOFORT
        if (loglevel >= 2) log ("Switching required: Inactive");
        switchInactive();
    }
});
// Auf Schaltstellen EXTERN sofort reagieren (schalten EIN/AUS)
on ({id: idSwitchExternal, change: "any"}, function(obj){
    if (loglevel >= 3) log ("Switching External Immediately, Value: " + getState(obj.id).val);
    if (getState(obj.id).val) { // Einschalten, scharf extern
        if (loglevel >= 2) log ("Switching required: External Active");
        if (getState(idActive).val) {
            if (loglevel >= 3) log ("Alarmsystem already active, switch to inactive first!");
            setState(idError, true);
        } else {
            switchActiveExternal();
        }
    }
    else { // Ausschalten, unscharf
        if (loglevel >= 2) log ("Switching required: Inactive");
        switchInactive();
    }
});
// Auf Schaltstellen INTERN sofort reagieren (schalten EIN/AUS)
on ({id: idSwitchInternal, change: "any"}, function(obj){
    if (loglevel >= 3) log ("Switching Internal, Value: " + getState(obj.id).val);
    if (getState(obj.id).val) { // Einschalten, scharf intern
        if (loglevel >= 2) log ("Switching required: Internal Active");
        if (getState(idActive).val) {
            if (loglevel >= 3) log ("Alarmsystem already active, switch to inactive first!");
            setState(idError, true);
        } else {
            switchActiveInternal();
        }
    }
    else { // Ausschalten, unscharf
        switchInactive();
        if (loglevel >= 2) log ("Switching required: Inactive");
    }
});
// Auf Schaltstelle mit Datenpunkt SwitchNumber reagieren
// Folgende Reaktionen:
//      0 ... unscharf schalten
//      1 ... scharf intern schalten
//      2 ... scharf extern schalten
//      3 ... verzögert scharf extern schalten
on ({id: idSwitchNumber, change: "any"}, function(obj){
    if (loglevel >= 3) log ("Switching Number, Value: " + obj.state.val);
    switch (obj.state.val) {
        case 0:
            if (loglevel >= 2) log ("Switching required: Inactive");
            switchInactive();
            break;
        case 1:
            if (loglevel >= 2) log ("Switching required: Internal Active");
            if (getState(idActive).val) {
                if (loglevel >= 3) log ("Alarmsystem already active, switch to inactive first!");
                setState(idError, true);
            } else {
                switchActiveInternal();
            }
            break;
        case 2:
            if (loglevel >= 2) log ("Switching required: External Active");
            if (getState(idActive).val) {
                if (loglevel >= 3) log ("Alarmsystem already active, switch to inactive first!");
                setState(idError, true);
            } else {
                switchActiveExternal();
            }
            break;
        case 3:
            if (loglevel >= 2) log ("Switching required: Delayed External Active");
            if (getState(idActive).val) {
                if (loglevel >= 3) log ("Alarmsystem already active, switch to inactive first!");
                setState(idError, true);
            } else {
                setState(idExitDelayActive, true);
                if (timerExitDelay) clearTimeout(timerExitDelay);
                timerExitDelay = setTimeout(switchActiveExternal, exitDelay * 1000);
            }
            break;
        default:
            if (loglevel >= 3) log ("idSwitchNumber has unknown number!");
    }
});
// Auf Fehler bei der Scharfschaltung reagieren
on ({id: idError, val: true, change: "any"}, function(obj){
    if (loglevel >= 1) log ("Error when switching to active.");
    setState(idAlarmText, textError);
});
// Auf Eingangsverzögerung reagieren
on({id: idEntryDelayActive, val: true, change: "ne"}, function (obj) {
    if (loglevel >= 2) log ("Entry Delay is active (" + entryDelay + " seconds)");
    setState(idStatusText, textEntryDelayActive);
    setStateDelayed(idAlarmAccoustical, true, entryDelay * 1000);
    setStateDelayed(idAlarmOptical, true, entryDelay * 1000);
});
// Auf Ausgangsverzögerung reagieren
on({id: idExitDelayActive, val: true, change: "ne"}, function (obj) {
    if (loglevel >= 2) log ("Exit Delay is active (" + exitDelay + " seconds)");
    setState(idStatusText, textExitDelayActive);
});
// Auf Akustischen Alarm reagieren
on ({id: idAlarmAccoustical, val: true, change: "ne"}, function(obj){
    if (loglevel >= 1) log ("ALARM is active!");
    setState(idEntryDelayActive, false);
    setState(idAlarmText, textAlarmActive);
    setState(idAlarm, true);
    setStateDelayed(idAlarmAccoustical, false, alarmDurationAccoustical * 1000);
    if (getState(idActiveExternal).val) setState(idStatusText, textStatusActiveExternal);
    if (getState(idActiveInternal).val) setState(idStatusText, textStatusActiveInternal);
});
// Auf Optischen Alarm reagieren
on ({id: idAlarmOptical, val: true, change: "ne"}, function(obj){
    if (alarmDurationOptical >= 0)
        setStateDelayed(idAlarmOptical, false, alarmDurationOptical * 1000);
});
// Melderauswertung
on( {id: detectorsOuterSkin.concat(detectorsIndoor), change: "ne"}, function(obj){
    detectorSurveillance(obj);
});
// Status in Active.Number schreiben
on ({id: [idActiveInternal, idActiveExternal, idEntryDelayActive, idExitDelayActive], change: "ne"}, function(obj){
    if (loglevel >= 3) log ("on for writing ActiveNumber, Trigger: " + obj.id);
    setActiveNumber();
});
// Texte korrekt setzen
on ({id: [idAlarm, idActive, idOpenDetectors], change: "any"}, function (obj) {
    // Das Timeout ggf. abbrechen
    if (timerTexts) clearTimeout(timerTexts);  
    // Nach einem Timeout den Check anfangen
    timerTexts = setTimeout(setTexts, 200);
});
// ===============================================================================
// Funktionen
// ===============================================================================
// Funktion     checkIfIgnoreOpenFlagSet
// =====================================
// Parameter:       Prüft, ob das IgnoreOpen-Flag für einen Melder gesetzt ist.
// Übergabewert:    Melder-ID
// Rückgabewert:    Flag-Zustand
function checkIfIgnoreOpenFlagSet(id){
    var i=0;
    while (i<detectorsIgnoreOpen.length){
        if ( detectorsIgnoreOpen[i][0] == id )
            if (getState(detectorsIgnoreOpen[i][1]).val)
                return true;
        i++;
    }
    return false;
}
// Function setTexts
// =================
// Parameter:       keiner
// Funktion:        Abhängig von den Zuständen scharf/unscharf und
//                  Alarm / Kein Alarm werden Texte für
//                  den AlarmText richtig gesetzt. Auch der Datenpunkt idReady wird hier gesetzt.
// Rückgabewert:    keiner
function setTexts(){    
    var textListOfDetectors = getState(idOpenDetectors).val;
    if (textListOfDetectors.length > 0) {
        if (!getState(idActive).val)
            // Offene Melder gefunden und unscharf
            setState(idAlarmText, textNotReady);
            setState(idReady, false);
        return false;
    } else {
        if (getState(idActive).val && !getState(idAlarm).val)
            // kein offener Melder gefunden und scharf und kein Alarm
            setState(idAlarmText, textAlarmInactive);
        else if (!getState(idActive).val)
            // kein offener Melder gefunden und unscharf
            setState(idAlarmText, textReady);
            setState(idReady, true);
        return true;
    }
}
// Function getAllDetectors
// ========================
// Parameter:       keiner
// Funktion:        Über die Funktion getDetectorsFromFunction werden
//                  alle Melder von den Functions geholt und in die
//                  globalen Variablen dafür geschrieben.
// Rückgabewert:    keiner
function getAllDetectors(){
    var i=0;
    detectorsOuterSkin = getDetectorsFromFunction(functionOuterSkin);
    detectorsIgnoreOpen = [];
    while (i<detectorsOuterSkin.length) {
        detectorsIgnoreOpen.push([detectorsOuterSkin[i++]]);
    }
    detectorsIndoor = getDetectorsFromFunction(functionIndoor);
    detectorsDelayed = getDetectorsFromFunction(functionDelayedDetectors);
}
// Function getDetectorsFromFunction
// =================================
// Parameter:       functionString
//                  Name der Function, in der die Melder enthalen sind.
// Funktion:        Alle Teilnehmer der übergebenen Function werden
//                  in ein Array geschrieben.
// Rückgabewert:    Array der Melder-IDs
function getDetectorsFromFunction( functionString ) {
    if (loglevel >= 3) log ("Function getDetectorsFromFunction");
    var detectors = [];
    $('state(functions='+functionString+')').each(function(id, i) {
        detectors.push(id);
        if (loglevel >= 3) log ("This detector was added to surveillance from function "+functionString+": " + id);
    });
    return detectors;
}
// Function detectorSurveillance
// =============================
// Parameter:       obj
//                  Objekt des auslösenden Melders
// Funktion:        Abhängig vom Schaltzustand werden die Melder überprüft.
//                  Bei unscharf wird nur die Liste der offenen Melder und die
//                  Bereitschaft der Anlage zum Scharfschalten gepfelgt.
//                  Bei scharf geschalteter Anlage ist es anders:
//                  Es wird geprüft, ob der auslösende Melder in den Außenhaut- oder
//                  Innenraum-Meldern enthalten ist und ob dieser ein verzögerter Melder ist.
//                  Abhängig davon wird entweder sofort oder verzögert Alarm ausgelöst.
// Rückgabewert:    keiner
function detectorSurveillance (obj) {
    var active = getState(idActive).val;
    var activeExternal = getState(idActiveExternal).val;
    var activeInternal = getState(idActiveInternal).val;
    var ready;
    var ignoreOpenSet;
    
    if (loglevel >= 2) log ("Surveillance of detectors started, triggering detector: " + obj.common.name);
    // Alle offenen Melder feststellen
    ready = checkDetectors(detectorsOuterSkin.concat(detectorsIndoor));
    // Auslösenden Melder schreiben
    setState(idAlarmingDetector, obj.common.name);
    setState(idAlarmingDetectorJSON, JSON.stringify(  getDetectorObject(obj.id)  ));
    // Prüfen, wie der der Schaltzustand ist
    // bei unscharf
    if (!active) {
        if (loglevel >= 2) log ("Alarmsystem is Inactive");
    }
    // bei scharf intern
    if (activeInternal) {
        if (loglevel >= 2) log ("Alarmsystem is Internal Active");
        if (detectorsOuterSkin.indexOf(obj.id) != -1) {
            if (loglevel >= 3) log ("Detector is part of Outer Skin");
            if (detectorsDelayed.indexOf(obj.id) != -1) {
                if (loglevel >= 3) log ("Detector is part of Delayed Detectors");
                if(!getState(idAlarm).val) setState(idEntryDelayActive, true);
                else {
                    if (loglevel >= 3) log ("EntryDelay was already active, alarming now");
                    setState(idAlarmAccoustical, true);
                    setState(idAlarmOptical, true);
                }
            } else {
                if (loglevel >= 3) log ("Detector is not delayed");
                setState(idAlarmAccoustical, true);
                setState(idAlarmOptical, true);
            }
        }
    }
    // bei scharf extern
    if (activeExternal) {
        if (loglevel >= 2) log ("Alarmsystem is External Active");
        // Prüfen, ob er der Melder das IgnoreOpen-Flag gesetzt hat.
        ignoreOpenSet = checkIfIgnoreOpenFlagSet(obj.id);
        if (loglevel >= 2) log ("Detector has IgnoreOpen-Flag set true!");
        if (ignoreOpenSet)  return;
        if (detectorsOuterSkin.concat(detectorsIndoor).indexOf(obj.id) != -1) {
            if (loglevel >= 3) log ("Detector is part of Outer Skin or Indoor");
            if (detectorsDelayed.indexOf(obj.id) != -1) {
                if (loglevel >= 3) log ("Detector is part of Delayed Detectors");
                if(!getState(idAlarm).val) setState(idEntryDelayActive, true);
                else {
                    if (loglevel >= 3) log ("EntryDelay was already active, alarming now");
                    setState(idAlarmAccoustical, true);
                    setState(idAlarmOptical, true);
                }
            } else {
                if (loglevel >= 3) log ("Detector is not delayed");
                if (loglevel >= 2) log ("System is ALARMING now!");
                setState(idAlarmAccoustical, true);
                setState(idAlarmOptical, true);
            }
        }
    }
}
// Function myCreateState
// =======================
// Parameter:       id      ... id des neu anzulegenden Datenpunkts
//                  typ     ... typ des anzulegenden Datenpunkts ("boolean", "string", "number", etc.)
//                  val     ... Wert, den der Datenpunkt nach dem Anlegen haben soll
//                  descr   ... Name als String
//                  writeaccess Schreibrechte true oder false
// Funktion:        Mit der Funktion createState wird der neue Datenpunkt mit den übergebenen
//                  Parametern angelegt.
// Rückgabewert:    keiner
function myCreateState(id, typ, val, descr, writeaccess, role) {
    if (loglevel >= 3) log ("Function myCreateState for " + id);
    createState(id, val,    {read: !writeaccess, 
                            write: writeaccess, 
                            name: descr,
                            type: typ,
                            def: val,
                            role: role ? role : "state"
    });
}
// Function myCreateMultiState
// ===========================
// Parameter:       id      ... id des neu anzulegenden Datenpunkts
//                  typ     ... typ des anzulegenden Datenpunkts ("boolean", "string", "number", etc.)
//                  val     ... Wert, den der Datenpunkt nach dem Anlegen haben soll
//                  descr   ... Name als String
//                  min     ... Minimalwert
//                  max     ... Maximalwert
//                  list    ... Liste mit Werten und Bezeichnern im Format "0:Text0; 1:Text1; 2:Text2"
//                  writeaccess Schreibrechte true oder false
// Funktion:        Mit der Funktion createState wird der neue Datenpunkt mit den übergebenen
//                  Parametern angelegt.
// Rückgabewert:    keiner
function myCreateMultiState(id, typ, val, descr, writeaccess, minimum, maximum, list, role) {
    if (loglevel >= 3) log ("Function myCreateMultiState for " + id);
    createState(id, val, {  read: true, 
                            write: writeaccess, 
                            name: descr,
                            type: typ,
                            def: val,
                            min: minimum, 
                            max: maximum, 
                            states: list,
                            role: role ? role : "state"
    });
}
// Funktion myCreateIgnoreOpenDPs
// ==============================
// Parameter:       Keine
// Funktion:        Erzeugt die Datenpunkte für die Ignoge-Open-Melder.
//                  Dafür werden die Namen aus dem detectorsOuterSkin-Array geholt.
//                  Die IDs der Ignore-Open Datenpunkte für jeden Melder werden auch in
//                  das Array detectorsIgnoreOpen geschrieben. Dieses wird damit 3-Dimensional.
// Rückgabewert:    Keiner.
function myCreateIgnoreOpenDPs(){
    var detectorName;
    var idBase = "javascript." + javascriptInstance + "." + pathToCreatedStates + ".Input.IgnoreOpen.";
    var i=0;
    while (i<detectorsOuterSkin.length) {
        detectorName = getObject(detectorsOuterSkin[i]).common.name.replace(/\./g,"_");
        myCreateState(idBase + detectorName, "boolean", false, "Ignore Open for " + detectorName, false, "switch");
        detectorsIgnoreOpen[i].push(idBase + detectorName);
        i++;
    }
}
// Function switchActiveExternal
// =============================
// Parameter:       keiner
// Funktion:        Nach Prüfung auf Bereitschaft zum externen Scharfschalten, wird 
//                  scharf geschaltet oder ein Fehler angezeigt.
// Rückgabewert:    keiner
function switchActiveExternal () {
    if (loglevel >= 3) log ("Function switchActiveExternal");
    setState(idExitDelayActive, false);
    var ok = checkDetectors(detectorsOuterSkin.concat(detectorsIndoor));
    if (ok) {
        setState(idActiveExternal, true);
        setState(idActive, true);
        setState(idStatusText, textStatusActiveExternal);
        setState(idAlarmText, textAlarmInactive);
        setState(idAlarmingDetector,"");
        setState(idError, false);
        if (loglevel >= 2) log ("Switched to External Active");
    } else {
        if (loglevel >= 2) log ("NOT ready to switch to External Active!");
        setState(idError, true);
    }
}
// Function switchActiveInternal
// =============================
// Parameter:       keiner
// Funktion:        Nach Prüfung auf Bereitschaft zum internen Scharfschalten, wird 
//                  scharf geschaltet oder ein Fehler angezeigt.
// Rückgabewert:    keiner
function switchActiveInternal () {
    if (loglevel >= 3) log ("Function switchActiveInternal");
    var ok = checkDetectors(detectorsOuterSkin);
    if (ok) {
        setState(idActiveInternal, true);
        setState(idActive, true);
        setState(idStatusText, textStatusActiveInternal);
        setState(idAlarmText, textAlarmInactive);
        setState(idAlarmingDetector,"");
        setState(idError, false);
        if (loglevel >= 2) log ("Switched to Internal Active");
    } else {
        if (loglevel >= 2) log ("NOT ready to switch to Internal Active!");
        setState(idError, true);
    }
;}
// Function switchInactive
// =============================
// Parameter:       keiner
// Funktion:        Es wird unscharf geschaltet und die ganze Anlage resetiert.
// Rückgabewert:    keiner
function switchInactive () {
    if (loglevel >= 3) log ("Function switchInactive");
    if (timerExitDelay) clearTimeout(timerExitDelay);
    setState(idEntryDelayActive, false);
    setState(idExitDelayActive, false);
    setState(idActiveExternal, false);
    setState(idActiveInternal, false);
    setState(idActive, false);
    setState(idError, false);
    clearStateDelayed(idAlarmAccoustical);
    clearStateDelayed(idAlarmOptical);
    setState(idAlarmAccoustical, false);
    setState(idAlarmOptical, false);
    setState(idAlarm, false);
    setState(idAlarmText, textAlarmInactive);
    setState(idStatusText, textStatusInactive);
    checkDetectors(detectorsOuterSkin.concat(detectorsIndoor));
    if (loglevel >= 2) log ("Switched to Inactive");
    getAllDetectors();
}
// Function setActiveNumber
// =======================
// Parameter:       keine
// Funktion:        Prüft ob intern scharf, extern scharf oder unscharf ist und
//                  ob jeweils Eingangs- oder Ausgangsverzögerung aktiv ist.
//                  Abhängig davon wird der Datenpunt "Output.ActiveNumber"
//                  wie folgt gesetzt:
//                    0 ... unscharf
//                    1 ... intern scharf
//                    2 ... extern scharf
//                    3 ... Eingangsverzögerung aktiv
//                    4 ... Ausgangsverzögerung aktiv
// Rückgabewert:    keiner
function setActiveNumber () {
    if (loglevel >= 3) log ("Function setActiveNumber");
    var internal = getState(idActiveInternal).val;
    var external = getState(idActiveExternal).val;
    var entry = getState(idEntryDelayActive).val;
    var exit = getState(idExitDelayActive).val;
    if (!external && !internal) {
        if (exit)
            setState(idActiveNumber, 4);
        else
            setState(idActiveNumber, 0);
    }
    if (internal) setState(idActiveNumber, 1);
    if (external){
        if (entry)
            setState(idActiveNumber, 3);
        else
            setState(idActiveNumber, 2);
    }
}
// Function checkDetectors
// =======================
// Parameter:       detectors
//                  Array mit Melder-IDs
// Funktion:        Alle Melder aus dem Array werden geprüft und alle offenen
//                  Melder werden in einen Datenpunkt geschrieben als String.
//                  Das Trennzeichen zwischen den Meldernamen ist die globale
//                  Variable "seperator".
// Rückgabewert:    true, wenn alle Melder in Ruhe sind
//                  false, wenn ein oder mehr Melder ausgelöst sind
function checkDetectors ( detectors ) {
    if (loglevel >= 3) log ("Function checkDetectors");
    var i=0;
    var textListOfDetectors="";
    var textListOfDetectorsIgnoreOpen="";
    var objOpenDetectors = {
        title: "All open detectors",
        zone: null,
        listOfDetectors:    []
    };
    var objOpenDetectorsOuterSkin = {
        title: "Open detectors outer skin",
        zone: functionOuterSkin,
        listOfDetectors:    []
    };
    var objOpenDetectorsIndoor = {
        title: "Open detectors indoor",
        zone: functionIndoor,
        listOfDetectors:    []
    };
    while(i < detectors.length) {
        if (getState(detectors[i]).val) {
            if (checkIfIgnoreOpenFlagSet(detectors[i])) {
                if (textListOfDetectorsIgnoreOpen.length > 0) textListOfDetectorsIgnoreOpen += seperator;
                textListOfDetectorsIgnoreOpen += getObject(detectors[i]).common.name;
            }
            else {
                if (textListOfDetectors.length > 0) textListOfDetectors += seperator;
                textListOfDetectors += getObject(detectors[i]).common.name;
            }
            var detObj = getDetectorObject(detectors[i]);
            objOpenDetectors.listOfDetectors.push(detObj);
            if (detectorsOuterSkin.indexOf(detectors[i]) != -1) objOpenDetectorsOuterSkin.listOfDetectors.push(detObj);
            if (detectorsIndoor.indexOf(detectors[i]) != -1) objOpenDetectorsIndoor.listOfDetectors.push(detObj);
        }
        i++;
    }
    if (loglevel >= 3) log ("Open Detectors found: " + (textListOfDetectors.length ? textListOfDetectors : "none"));
    
    // Datenpunkte schreiben
    setState(idOpenDetectors, textListOfDetectors);
    setState(idOpenDetectorsWithIgnoreOpenFlagSet, textListOfDetectorsIgnoreOpen);
    setState(idOpenDetectorsJSON, JSON.stringify(objOpenDetectors));
    setState(idOpenDetectorsOuterSkinJSON, JSON.stringify(objOpenDetectorsOuterSkin));
    setState(idOpenDetectorsIndoorJSON, JSON.stringify(objOpenDetectorsIndoor));
    if (textListOfDetectors.length > 0) {
        return false;
    } else {
        return true;
    }
}
// Function getDetectorObject
// ==========================
// Parameter:       id
//                  id eines Melder-States
// Funktion:        Vom Melder mit der id wird das Objekt über getObjekt(id) geholt,
//                  sowie von seinem Parent und ParentsParent.
//                  Alle Objekte kommen in ein großes Objekt und werden zurück gegeben.
// Rückgabewert:    Das Objekt des Melders samt Parent und ParentsParent (sofern es welche gibt)
function getDetectorObject (id) {
    if (loglevel >= 3) log ("Function getDetectorObject for id: " + id);
    // ioBroker Parent-Datenpunkt (z.B. Kanal), kann auch null sein (zB bei KNX)
    var idParent = id.substr(0, id.lastIndexOf("."));
    // ioBroker ParentsParent-Datenpunkt (z.B. Gerät), kann auch null sein (zB bei KNX)
    var idParentsParent = idParent.substr(0, idParent.lastIndexOf("."));
    // Objekte dazu holen
    var obj    = getObject(id);
    var objParent = getObject(idParent);
    var objParentsParent = getObject(idParentsParent);
    // Alle Objekte in ein großes Objekt sammeln
    var detectorsObj = {
        id:             id,
        self:           obj,
        parent:         objParent,
        parentsparent:  objParentsParent
    };
    // Rückgeben
    return detectorsObj;
}