// V1.2 vom 28.3.2020
//Script um offene Fenster pro Raum und insgesamt zu zählen. Legt pro Raum zwei Datenpunkte an, sowie zwei Datenpunkte fürs gesamte.
//Möglichkeit eine Ansage nach x Minuten einmalig oder zyklisch bis Fensterschließung anzugeben
//Dynamische erzeugung einer HTML Übersichtstabelle
//WICHTIG!!!
//Vorraussetzungen: Den Geräten müssen Räume zugewiesen sein, sowie die Funktion "Verschluss" für jeden entsprechenden Datenpunkt zugewiesen sein.
//Grundeinstellungen
const logging = false; //Erweiterte Logs ausgeben?
const praefix = "javascript.0.Benachrichtigung.TuerUeberwachung."; //Grundpfad für Script DPs
const ZeitBisNachricht = 300000 // 300000 ms = 5 Minuten
const RepeatInfoMsg = true; // Legt fest ob Ansage einmalig oder zyklisch
const InfoMsgAktiv = true; // Legt fest ob eine Infonachricht nach x Minuten ausgegeben werden soll
const WelcheFunktionVerwenden = "Tür"; // Legt fest nach welchem Begriff in Funktionen gesucht wird.
const UseTelegram = false; // Sollen Nachrichten via Telegram gesendet werden?
const UseAlexa = true; // Sollen Nachrichten via Alexa ausgegeben werden?
const AlexaId = "xyz"; // Die Alexa Seriennummer
const UseSayit = true; // Sollen Nachrichten via GOOGLE ausgegeben werden?
const UseEventLog = false; // Sollen Nachrichten ins Eventlog geschreiben werden? Authorenfunktion, sollte deaktiviert werden.
const OpenDoorListSeparator = "<br>"; //Trennzeichen für die Textausgabe der offenen Fenster pro Raum
const DoorIsOpenWhen = ["true", "offen", "gekippt", "open", "tilted", "1", "2"]; // Hier können eigene States für offen angegeben werden, immer !!! in Kleinschreibung
const DoorIsClosedWhen = ["false", "closed", "0"]; // können eigene States für geschlossen angegeben werden, immer !!! in Kleinschreibung
//Einstellungen zur Tabellenausgabe
const DoorOpenImg = "/icons-mfd-svg/fts_door_open.svg"; //Icon für Fenster offen
const DoorCloseImg = "/icons-mfd-svg/fts_door.svg"; // Icon für Fenster geschlossen
const OpenDoorColor = "red"; // Farbe für Fenster offen
const ClosedDoorColor = "green"; // Farbe für Fenster geschlossen
const HeadlessTable = false; // Tabelle mit oder ohne Kopf darstellen
//Ab hier nix mehr ändern!
let OpenDoorCount = 0; // Gesamtzahl der geöffneten Fenster
const RoomOpenDoorCount = []; // Array für offene Fenster pro Raum
let RoomsWithOpenDoors = "";
const OpenDoorMsgHandler = []; // Objektarray für timeouts pro Raum
const Sensor = []; //Sensoren als Array anlegen
const SensorVal = [];//Sensorwerte als Array anlegen
const SensorOldVal = []; //Alte Sensorwerte als Array ablegen
const Laufzeit = []; //Timer Laufzeit pro Fenster
const RoomList = []; // Raumlisten Array
const RoomStateTimeStamp = [];
let z = 0; //Zähler
let DpCount = 0; //Zähler
const States = []; // Array mit anzulegenden Datenpunkten
let Funktionen = getEnums('functions');
for (let x in Funktionen) { // loop ueber alle Functions
let Funktion = Funktionen[x].name;
if (Funktion == undefined) {
log("Keine Funktion gefunden");
}
else {
if (typeof Funktion == 'object') Funktion = Funktion.de;
let members = Funktionen[x].members;
if (Funktion == WelcheFunktionVerwenden) { //Wenn Function ist Verschluss
for (let y in members) { // Loop über alle Verschluss Members
Sensor[y] = members[y];
let room = getObject(Sensor[y], 'rooms').enumNames[0];
if (typeof room == 'object') room = room.de;
//Datenpunkte pro Raum vorbereiten
States[DpCount] = { id: praefix + room + ".RoomOpenDoorCount", initial: 0, forceCreation: false, common: { read: true, write: true, name: "Anzahl der geöffneten Tueren im Raum", type: "number", def: 0 } };
DpCount++;
States[DpCount] = { id: praefix + room + ".IsOpen", initial: false, forceCreation: false, common: { read: true, write: true, name: "Tueren offen?", type: "boolean", role: "state", def: false } }; //
DpCount++;
//log(Funktion + ': ' + room);
if (RoomList.indexOf(room) == -1) { //Raumliste ohne Raumduplikate erzeugen
RoomList[z] = room;
if (logging) log("Raum " + z + " = " + RoomList[z]);
z++;
};
RoomOpenDoorCount[y] = 0; // Array mit 0 initialisieren
Laufzeit[y] = 0; // Array mit 0 initialisieren
};
};
};
};
//Struktur anlegen in js.0 um Sollwert und Summenergebniss zu speichern
//Generische Datenpunkte vorbereiten
States[DpCount] = { id: praefix + "AlleTuerenZu", initial: true, forceCreation: false, common: { read: true, write: true, name: "Tuer zu?", type: "boolean", role: "state", def: true } }; //
DpCount++;
States[DpCount] = { id: praefix + "DoorsOpen", initial: 0, forceCreation: false, common: { read: true, write: true, name: "Anzahl der geöffneten Tuer", type: "number", def: 0 } };
DpCount++;
States[DpCount] = { id: praefix + "RoomsWithOpenDoors", initial: "Tueren in allen Räumen geschlossen", forceCreation: false, common: { read: true, write: true, name: "In welchen Räumen sind Tueren geöffnet?", type: "string", def: "Tueren in allen Räumen geschlossen" } };
DpCount++;
States[DpCount] = { id: praefix + "OverviewTable", initial: "", forceCreation: false, common: { read: true, write: true, name: "Übersicht aller Räume und geöffneten Tueren", type: "string", def: "" } };
//Alle States anlegen, Main aufrufen wenn fertig
let numStates = States.length;
States.forEach(function (state) {
createState(state.id, state.initial, state.forceCreation, state.common, function () {
numStates--;
if (numStates === 0) {
if (logging) log("CreateStates fertig!");
main();
};
});
});
function main() {
for (let x = 0; x < Sensor.length; x++) {
//setTimeout(function () { // Timeout setzt refresh status wieder zurück
SensorVal[x] = String(getState(Sensor[x]).val).toLowerCase(); // Wert von Sensor in Schleife einlesen
SimplyfyDoorStates(x);
// }, x * 100);
};
CreateTrigger();
CheckAllDoors(); //Bei Scriptstart alle Fenster einlesen
CreateOverviewTable()
}
function Meldung(msg) {
if (UseSayit) {
setState("sayit.0.tts.text",msg);
};
if (UseTelegram) {
sendTo("telegram.0", "send", {
text: msg
});
};
if (UseAlexa) {
if (AlexaId != "") setState("alexa2.0.Echo-Devices." + AlexaId + ".Commands.announcement"/*announcement*/, msg);
};
if (logging) log(msg);
}
function CreateOverviewTable() { // Erzeugt tabellarische Übersicht als HTML Tabelle
//Tabellenüberschrift und Head
let OverviewTable = "";
if (!HeadlessTable) {
OverviewTable = "<table style='width:100%; border-collapse: collapse; border: 0px solid black;'><caption><div style='height: 40px; padding-top: 10px; padding-bottom: 5px; font-size:1.4em; font-weight: bold;'>Fensterstatus</div></caption>";
OverviewTable = OverviewTable + "<thead><tr><th width='100%' style='text-align:center; height: 20px; padding-bottom: 5px;'>" + RoomsWithOpenDoors + "</th></tr></thead><tbody></tbody></table>";
};
//Tabelle der Raumdetails
OverviewTable = OverviewTable + "<div style='height: 100%; overflow-y:auto; overflow-x:hidden;'><table style='width:100%; border-collapse: collapse;'>";
OverviewTable = OverviewTable + "<thead><tr><th width='40px' style='text-align:left;'</th><th width='20px' style='text-align:center;'></th><th style='text-align:left;'></th></tr></thead><tbody>";
for (let x = 0; x < RoomList.length; x++) { //Alle Räume durchgehen
if (RoomOpenDoorCount[x] > 0) { // Räume mit offenen Fenstern
RoomStateTimeStamp[x] = formatDate(getState(praefix + RoomList[x] + ".IsOpen").lc, "SS:mm:ss TT.MM.JJJJ")
OverviewTable = OverviewTable + "<tr><td style='border: 1px solid black; background-color:" + OpenDoorColor + ";'><img height=40px src='" + DoorOpenImg + "'></td>"
OverviewTable = OverviewTable + "<td style='border: 1px solid black; padding-left: 10px; padding-right: 10px; font-size:1.1em; font-weight: bold; text-align:center;background-color:" + OpenDoorColor + ";'>" + RoomOpenDoorCount[x] + "</td>"
OverviewTable = OverviewTable + "<td style='border: 1px solid black; padding-left: 10px; padding-right: 10px; font-size:1.1em; font-weight: bold; background-color:" + OpenDoorColor + ";'>" + RoomList[x] + "<br><div style='font-size:0.8em; font-weight:bold;'>geöffnet: " + RoomStateTimeStamp[x] + "</div></td></tr>"
}
else { // Geschlossene Räume
RoomStateTimeStamp[x] = formatDate(getState(praefix + RoomList[x] + ".IsOpen").lc, "SS:mm:ss TT.MM.JJJJ")
OverviewTable = OverviewTable + "<tr><td style='border: 1px solid black; background-color:" + ClosedDoorColor + ";'><img height=40px src='" + DoorCloseImg + "'></td>"
OverviewTable = OverviewTable + "<td style='border: 1px solid black; padding-left: 10px; padding-right: 10px; font-size:1.1em; font-weight: bold; text-align:center; background-color:" + ClosedDoorColor + ";'>" + RoomOpenDoorCount[x] + "</td>"
OverviewTable = OverviewTable + "<td style='border: 1px solid black; padding-left: 10px; padding-right: 10px; font-size:1.1em; font-weight: bold; background-color:" + ClosedDoorColor + ";'>" + RoomList[x] + "<br><div style='font-size:0.7em; font-weight:normal;'>geschlossen: " + RoomStateTimeStamp[x] + "</div></td></tr>"
};
};
OverviewTable = OverviewTable + "</tbody></table></div>";
setState(praefix + "OverviewTable", OverviewTable);
//log(OverviewTable);
}
function CreateRoomsWithOpenDoorList() { //Erzeugt Textliste mit Räumen welche geöffnete Fenster haben
RoomsWithOpenDoors = ""; //Liste Initialisieren
for (let x = 0; x < RoomList.length; x++) { //Alle Räume durchgehen
if (RoomOpenDoorCount[x] > 0) { // Nur Räume mit offenen Fenstern berücksichtigen
if (RoomOpenDoorCount[x] == 1) { //Wenn 1 Fenster offen, Singular Schreibweise
RoomsWithOpenDoors = RoomsWithOpenDoors + RoomList[x] + " " + RoomOpenDoorCount[x] + " offene Tuer" + OpenDoorListSeparator;
}
else { //ansonsten Plural Schreibweise
RoomsWithOpenDoors = RoomsWithOpenDoors + RoomList[x] + " " + RoomOpenDoorCount[x] + " offene Tueren" + OpenDoorListSeparator;
};
};
};
RoomsWithOpenDoors = RoomsWithOpenDoors.substr(0, RoomsWithOpenDoors.length - OpenDoorListSeparator.length); //letzten <br> Umbruch wieder entfernen
if (RoomsWithOpenDoors == "") {
RoomsWithOpenDoors = "Alle Tueren sind geschlossen";
};
setState(praefix + "RoomsWithOpenDoors", RoomsWithOpenDoors);
if (logging) log(RoomsWithOpenDoors);
}
function GetRoom(x) { // Liefert den Raum von Sensor x
if (logging) log("Reaching GetRoom x=" + x)
let room = getObject(Sensor[x], 'rooms').enumNames[0];
if (room == undefined) {
log("Kein Raum definiert bei Sensor " + Sensor[x], 'error');
return "Kein Raum definiert";
};
if (typeof room == 'object') room = room.de;
return room;
}
function CheckDoor(x) { //Für einzelnes Fenster. Via Trigger angesteuert.
let TempRoom = GetRoom(x); //Raum des aktuellen Sensors bestimmen
let TempRoomIndex = RoomList.indexOf(TempRoom); // Raumlistenindex für aktuellen Raum bestimmen
if (logging) log("reaching CheckDoor, SensorVal[" + x + "]=" + SensorVal[x] + " SensorOldVal=" + SensorOldVal[x] + " TempRoom=" + TempRoom)
if (SensorVal[x] == "open" && SensorOldVal[x] != "open") { //Fenster war geschlossen und wurde geöffnet
OpenDoorCount++;
RoomOpenDoorCount[TempRoomIndex]++;
if (logging) log("RoomOpenDoorCount für " + TempRoom + "=" + RoomOpenDoorCount[TempRoomIndex]);
setState(praefix + "AlleTuerenZu", false);
setState(praefix + TempRoom + ".IsOpen", true);
setState(praefix + "DoorsOpen", OpenDoorCount);
setState(praefix + TempRoom + ".RoomOpenDoorCount", RoomOpenDoorCount[TempRoomIndex]);
if (logging) log(TempRoom + " Tuer geöffnet");
if (UseEventLog == true) WriteEventLog(TempRoom + " Tuer geöffnet!");
if (RoomOpenDoorCount[TempRoomIndex] == 1) {
Laufzeit[TempRoomIndex] = 0;
if (InfoMsgAktiv == true) {
if (RepeatInfoMsg == true) { // Wenn Intervallmeldung eingestellt Interval starten und Dauer bei Ansage aufaddieren
if (logging) log("Setting Interval to Room:" + TempRoom);
OpenDoorMsgHandler[TempRoomIndex] = setInterval(function () {
Laufzeit[TempRoomIndex] = Laufzeit[TempRoomIndex] + ZeitBisNachricht;
Meldung(TempRoom + "tuer seit " + Laufzeit[TempRoomIndex] / 1000 / 60 + " Minuten geöffnet!");
}, ZeitBisNachricht);
}
else { // Wenn einmalige Meldung eingestellt
if (logging) log("Setting Timeout to Room:" + TempRoom);
OpenDoorMsgHandler[TempRoomIndex] = setTimeout(function () {
Meldung(TempRoom + "tuer seit " + ZeitBisNachricht / 1000 / 60 + " Minuten geöffnet!");
}, ZeitBisNachricht);
};
};
};
}
else if (SensorVal[x] == "closed") {
if (OpenDoorCount > 0) OpenDoorCount--;
if (RoomOpenDoorCount[TempRoomIndex] > 0) RoomOpenDoorCount[TempRoomIndex]--;
setState(praefix + "DoorsOpen", OpenDoorCount);
setState(praefix + TempRoom + ".RoomOpenDoorCount", RoomOpenDoorCount[TempRoomIndex]);
log(TempRoom + " Tuer geschlossen.");
if (UseEventLog == true) WriteEventLog(TempRoom + " Tuer geschlossen!");
if (RoomOpenDoorCount[TempRoomIndex] == 0) {
setState(praefix + TempRoom + ".IsOpen", false);
if (RepeatInfoMsg == true) {
if (logging) log("reaching clearInterval - [x] = " + [x] + " TempRoomIndex= " + TempRoomIndex);
clearInterval(OpenDoorMsgHandler[TempRoomIndex]);
}
else {
if (logging) log("reaching clearTimeout");
clearTimeout(OpenDoorMsgHandler[TempRoomIndex]);
};
};
if (OpenDoorCount == 0) { //Wenn kein Fenster mehr offen
setState(praefix + "AlleTuerenZu", true);
setState(praefix + TempRoom + ".IsOpen", false);
log("Alle Tueren geschlossen.");
};
};
if (logging) log("Offene Tueren gesamt= " + OpenDoorCount);
CreateRoomsWithOpenDoorList();
CreateOverviewTable();
}
function CheckAllDoors() { //Prüft bei Programmstart alle Fenster
for (let x = 0; x < Sensor.length; x++) { //Alle Sensoren durchlaufen
let TempRoom = GetRoom(x);
let TempRoomIndex = RoomList.indexOf(TempRoom);
if (SensorVal[x] == "open") { //Fenster is offen
OpenDoorCount = OpenDoorCount + 1;
RoomOpenDoorCount[TempRoomIndex] = RoomOpenDoorCount[TempRoomIndex] + 1;
if (logging) log("Temproom= " + TempRoom + " TempRoomIndex= " + RoomList.indexOf(TempRoom) + " RoomOpenDoorcount= " + RoomOpenDoorCount[TempRoomIndex]);
setState(praefix + "AlleTuerenZu", false);
setState(praefix + "DoorsOpen", OpenDoorCount);
setState(praefix + TempRoom + ".IsOpen", true);
setState(praefix + TempRoom + ".RoomOpenDoorCount", RoomOpenDoorCount[TempRoomIndex]);
if (InfoMsgAktiv == true && RoomOpenDoorCount[RoomList.indexOf(TempRoom)] == 1) {
if (RepeatInfoMsg == true) { // Wenn Intervallmeldung eingestellt Interval starten und Dauer bei Ansage aufaddieren
if (logging) log("Setting Interval at initialization to Room: " + TempRoom);
OpenDoorMsgHandler[TempRoomIndex] = setInterval(function () {
Laufzeit[TempRoomIndex] = Laufzeit[TempRoomIndex] + ZeitBisNachricht;
Meldung(TempRoom + "tuer seit " + Laufzeit[TempRoomIndex] / 1000 / 60 + " Minuten geöffnet!");
}, ZeitBisNachricht);
}
else {
if (logging) log("Setting Timeout at initialization to Room: " + TempRoom);
OpenDoorMsgHandler[TempRoomIndex] = setTimeout(function () { // Wenn einmalige Meldung eingestellt
Meldung(TempRoom + "tuer seit " + ZeitBisNachricht / 1000 / 60 + " Minuten geöffnet!");
}, ZeitBisNachricht);
};
};
if (logging) log(TempRoom + " Tuer = geöffnet");
}
else if (SensorVal[x] == "closed") {
//RoomOpenDoorCount[TempRoomIndex] = getState(praefix + TempRoom + ".RoomOpenDoorCount").val - 1;
RoomOpenDoorCount[TempRoomIndex]--;
if (RoomOpenDoorCount[TempRoomIndex] < 0) RoomOpenDoorCount[TempRoomIndex] = 0;
setState(praefix + TempRoom + ".IsOpen", false);
setState(praefix + TempRoom + ".RoomOpenDoorCount", RoomOpenDoorCount[TempRoomIndex]);
//log(TempRoom + " Fenster = geschlossen.");
};
};
if (OpenDoorCount == 0) {
setState(praefix + "AlleTuerenZu", true);
setState(praefix + "DoorsOpen", OpenDoorCount);
log("Alle Tueren geschlossen.");
};
CreateRoomsWithOpenDoorList();
}
function SimplyfyDoorStates(x) { //Die verschiedenen Gerätestates zu open oder close vereinfachen
//log("Sensor "+Sensor[x]+" mit Wert "+ SensorVal[x]+ " hat Typ " + typeof(SensorVal[x] ));
if (DoorIsOpenWhen.indexOf(SensorVal[x]) != -1) { // Suche in Fensteroffenarray, wenn gefunden, Status auf open setzen
SensorVal[x] = "open";
}
else if (DoorIsClosedWhen.indexOf(SensorVal[x]) != -1) { // Suche in Fenstergeschlossenarray, wenn gefunden, Status auf closed setzen
SensorVal[x] = "closed";
};
if (SensorVal[x] != "open" && SensorVal[x] != "closed") { // Suche in Fensteroffenarray und Fenstergeschlossenarray, wenn nirgends gefunden, Status auf closed setzen und Logwarnung ausgeben
log("Unknown Doorstate " + SensorVal[x] + " detected at " + Sensor[x] + ", please check your configuration", "warn");
SensorVal[x] = "unknown";
};
if (DoorIsOpenWhen.indexOf(SensorOldVal[x]) != -1) {
SensorOldVal[x] = "open";
}
else if (DoorIsClosedWhen.indexOf(SensorOldVal[x]) != -1) {
SensorOldVal[x] = "closed";
};
}
function CreateTrigger() {
//Trigger für Sensoren erzeugen
for (let x = 0; x < Sensor.length; x++) { //Alle Sensoren durchlaufen
on(Sensor[x], function (dp) { //Trigger in Schleife erstellen
if (logging) log("Trigger= " + x + " Wert= " + dp.state.val + " Alter Wert= " + dp.oldState.val);
if (dp.channelId.search(praefix) == -1) { //Ausschliessen dass das Scriptverzeichnis zum Triggern verwendet wird
SensorVal[x] = String(dp.state.val).toLowerCase(); // Alles in String und Kleinschreibweise wandeln
SensorOldVal[x] = String(dp.oldState.val).toLowerCase(); // Alles in String und Kleinschreibweise wandeln
SimplyfyDoorStates(x);
CheckDoor(x);
}
else {
log("Fehler, Datenpunkt im Scriptverzeichnis als Trigger definiert", "error");
};
});
};
onStop(function () { //Bei Scriptende alle Timer löschen
for (let x = 1; x < Sensor.length; x++) {
if (RoomOpenDoorCount[x] == 0) {
if (RepeatInfoMsg == true) {
clearInterval(OpenDoorMsgHandler[x]);
}
else {
clearTimeout(OpenDoorMsgHandler[x]);
};
};
};
}, 100);
}