Startseite » Alarmanlage und Anwesenheitssimulator mit OpenHAB

Alarmanlage und Anwesenheitssimulator mit OpenHAB

📅
•
⏱️ 20 Min. Lesezeit
•
✍️ Alexander Hacker

Es gibt verschiedene Gründe, eine Alarmanlage realisieren zu wollen. Und viele Aspekte, die daran beteiligt sind. Das nachfolgende Beispiel funktioniert gut bei mir. Natürlich kannst du es leicht erweitern und auf deine Bedürfnisse anpassen.

Das nachfolgende System hat zwei Aufgaben:

  1. Einen Alarm zu verursachen, wenn ein Eindringen entdeckt wurde
  2. Anwesenheit vorzutäuschen

Ein Alarm kann auf verschiedene Wege verursacht werden. Er kann optisch oder akustisch sein oder still. Darüber hinaus kann ein Signal versandt werden.

„Wenn ein Eindringen entdeckt wurde“ kann auch vielfältige Auslöser haben. Das kann ein aufgebrochenes oder eingeschlagenes Fenster oder Tür sein, das kann auch eine Person sein, die sich an einem Ort aufhält, wo niemand sein sollte (z.B. in deinem Garten während deiner Abwesenheit). Das können auch Geräusche sein, die nicht da sein sollten (zerbrechendes Glas oder Schritte).

Auch Anwesenheit kann auf verschiedene Wege vorgetäuscht werden. Geräusche, Licht, vielleicht bestimmte dynamische Lichtszenen, ein Gartensprinkler, abgeholte Post, sich bewegende Rollos, …

Letztlich muss des System noch gegen Ausfall und Sabotage gesichert werden. Was passiert bei ausfallenden Geräten? Was passiert, wenn notwendige Funkübertragungen nicht mehr verfügbar sind (z.B. weil sie gestört werden).

Dieses Kapitel wird mehrere Projekte wiederverwenden, die ich bereits an anderer Stelle umgesetzt habe. Und sich im laufe der Zeit sicherlich noch erweitern und verbessern.

Hardware für eine Alarmanlage

  • dein Raspberry Pi mit bereits funktionierendem OpenHAB
  • mögliche Sensoren (kabelgefunden oder per Funk)
    • Tür- / Fensterkontakte
    • Glasbruchsensoren
    • Bewegungsmelder (nur für drinnen zu empfehlen. draußen kann sich immer mal was bewegen)
    • Anwesenheitssensoren
    • Kameras (ggf. mit Objekterkennung)
  • Beleuchtung
    • smarte Leuchtmittel, die vielleicht schon in deinem Smarthome vorhanden sind
  • Akustik
    • smarte Lautsprecher (zur Alarm-Ausgabe und ggf. zur Geräuscherkennung)
  • Ausfallsicherheit
    • eine kleine USV, nur für den Pi
    • einen LTE-USB-Stick als Ausfall-Leitung für Alarm-Benachrichtigungen
      • auch hier sind Konstruktionen möglich, die einen Single-Point-of-Failure ausschließen

OpenHAB: Vorbereitungen

Deine Hardware musst du entsprechend deinen Bedrüfnissen nun in dein OpenHAB einbinden. Mein Beispiel geht davon aus, dass du smarte Leuchtmittel verwendet und dass du im besten Fall dynamische Szenen abspielen kannst. Mit den dynamischen Szenen täusche ich eine Aktivität des Fernsehers vor.

Alle Items müssen vorhanden sein. Inbesondere gehe ich davon aus, dass du

Wichtige Items und Gruppen der Alarmanlage

  • gWatchdog
    • Damit fasse ich den manuellen Modus, sowie den nächtlichen automatischen Modus zusammen. Ist einer der Beiden aktiv, ist das Alarmsystem aktiv. So können Beide zuverlässig funktionieren, ohne sich gegenseitig zu beeinflussen.
    • WatchdogManual
      • Dieses Item kannst du mittels OpenHAB GUI (oder in diesem Fall auch mittels Alexa) ein- und ausschalten. Es ist nur dafür gedacht, manuell betätigt zu werden.
      • Dieses Item wird weiter unten nicht weiter beschrieben und muss von dir auf deine Bedürfnisse eingerichtet werden!
    • WatchdogNightshift
      • Dieser Modus soll nächtlich laufen. Von dem Zeitpunkt, wo ich ins Bett gehe, bis ich aufstehe. Automatisch wird es nachts zu einem bestimmten Zeitpunkt aktiviert und später wieder deaktiviert. Das kann mittels Kalender geschehen, aber auch mittels eigenem .rules-File. In meinem Fall aktiviere ich es zusätzlich manuell. Wenn ich meinen „Haus-Aus-Knopf“ am Bettrand betätige, wird die Alarmanlage scharf. Später in der Nacht würde sie automatisch aktiviert. Das Ereignis schaltet einen Schalter von „ON“ dann nur nochmal auf „ON“.
      • Dieses Item wird weiter unten nicht weiter beschrieben und muss von dir auf deine Bedürfnisse eingerichtet werden!
    • Weitere unabhängige Modi sind denkbar. Teile als Kommentar doch bitte deine Vorschläge mit mir 😉
  • WatchdogTimestamp
    • Hier wird der Zeitpunkt der Aktivierung des WatchdogManual gespeichert. Damit kann die Alarmanlage leicht verzögert aktiviert werden. Wenn ich sie also EInschalte, habe ich noch ein paar Minuten Zeit das Haus zu verlassen. Das wird im watchdog.rules-File weiter unten berücksichtigt.
  • TestDummy
    • Dieser aktiviert einen Test-Modus, um die Routinen zu testen. Regelmäßige Alarmanlagentest sind wichtig!

Group:Switch:OR(ON, OFF)	gWatchdog				
Switch						WatchdogManual			"Watchdog"	(fgPersist,gWatchdog,tgWatchdogEvents)		{ alexa="Switch" }
Switch						WatchdogNightshift					(fgPersist,gWatchdog)
DateTime					WatchdogTimestamp

Switch						TestDummy

openHAB Automatisierung: Modernes JS Scripting

In modernen openHAB-Installationen (openHAB 4 & 5) wird die gesamte Logik für Anwesenheitssimulation, Ausfallerkennung und Alarmierung über ein einziges, modulares openHAB JS Scripting Skript (ECMAScript 2021+) abgewickelt. Dadurch entfallen die unzähligen, fragmentierten Rules-Dateien und blockierenden Thread-Sleeps der alten DSL.

Die Kernfunktionen der modernen Architektur:

  • Modulare Raum-Simulation (roomTags): Räume definieren flexibel ihre Startzeiten (z. B. relativer Sonnenuntergang mit Zufallsverzögerung 0–30 Minuten) und Abschaltzeiten. Im Wohnzimmer werden dynamische Lichtszenen (TV-Simulation) und Alexa-Sprachkommandos über Echo-Lautsprecher zeitgesteuert ausgelöst.
  • Verzögerte Scharfschaltung: Nach dem Aktivieren der Alarmanlage läuft eine Karenzzeit von 5 Minuten ab, damit Bewohner das Haus ohne Fehlalarm verlassen können.
  • Sensor-Watchdog (Ausfallerkennung): Prüft minütlich, ob Sensoren unerwartet lange keine Daten mehr geliefert haben (z. B. bei Funkabriss oder Sabotage).
  • Alarmierung: Bei Detektion über Fensterkontakte (fgIntrusion) oder Kameramelder (gCamAlert) schalten alle Leuchtmittel auf Alarm-Rot und es erfolgt sofort eine Telegram-Benachrichtigung.
  • Sauberes Timer-Management: Über cache.private werden alle aktiven Simulations- und Alarm-Timer gespeichert und beim Deaktivieren der Alarmanlage restlos abgebrochen (clearAllSimTimers).
/**
 * Watchdog / Alarm System
 */
const { rules, triggers, items, time, actions } = require("openhab");

// Cache für Timer, damit wir sie bei "Disarm" abbrechen können
const CACHE = cache.private; 
if (!CACHE.get("simTimers")) CACHE.put("simTimers", {});
if (!CACHE.get("alarmTimers")) CACHE.put("alarmTimers", []);

// Konstanten
const TELEGRAM_BOT = "telegram:telegramBot:home_Bot";
const CHAT_ID = -552188908; // Long in JS ist einfach Number
const SUNSET_START_ITEM = "LokaleSonnendaten_Set_Startzeit";
const CONFIRM_IF_ITEM = "Everywhere_Interface";

const roomTags = {
	Office: {
		startAfter: {
			delay: [0, 30], // Start: Sunset + 0-30min
		},
		endAfter: {	// Ausschalten planen: 23:00 + 0-120min
			h: 23,	// optional
			m: 0,	// optional
			delay: [0,120],
		},
	},
	
	Kitchen: {
		startAfter: {
			delay: [0, 180],
		},
		endAfter: {	// nach 45-120min
			delay: [45,120],
		},
	},
	
	Livingroom: {
		startAfter: {
			delay: [30, 90],
			actions:[
				["WzBackground_Helligkeit", 50],
				["WzTisch_Lightshow", "TV-Simulation"],
				["Echo_Wohnzimmer_Volume", 50],
				["Echo_Wohnzimmer_TextCommand", "echo spiele Robin Schulz"],
			],
		},
		endAfter: { // Ende: 22:30 + 0-120min
			h: 22,
			m: 30,
			delay: [0,120],
			actions:[
				["WzBackground_Helligkeit", 0],
				["WzTisch_Lightshow", 0],
				["Echo_Wohnzimmer_TextCommand", "echo stop"],
				["Echo_Wohnzimmer_Volume", 20],
			],
		},
	},
	
	Bedroom: {
		startAfter: {
			h: 21,	// optional
			m: 0,	// optional
			delay: [0, 150], // Start: Sunset + 0-30min
		},
		endAfter: {	// Ausschalten planen: 23:00 + 0-120min
			h: 23,	// optional
			m: 0,	// optional
			delay: [5,65],
		},
	},
};

// --- HELPER FUNCTIONS ---

/**
 * Fügt einen Timer zur Verwaltung hinzu
 */
function registerSimTimer(key, timerId) {
  const timers = CACHE.get("simTimers");
  // Falls schon einer existiert, vorher löschen (clean up)
  if (timers[key]) clearTimeout(timers[key]);
  timers[key] = timerId;
}

/**
 * Löscht alle Simulationstimer (bei Alarm-Ende)
 */
function clearAllSimTimers() {
  const timers = CACHE.get("simTimers");
  for (const key in timers) {
    clearTimeout(timers[key]);
  }
  CACHE.put("simTimers", {});
}

/**
 * Hilfsfunktion für zufällige Minuten (min bis max)
 */
function randomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1) + min);
}

/**
 * Berechnet die Verzögerung in Millisekunden bis zu einem ZDT
 */
function getDelayMillis(targetZDT) {
    const now = time.toZDT();
    let diff = targetZDT.toInstant().toEpochMilli() - now.toInstant().toEpochMilli();
    return Math.max(0, diff); // Keine negativen Delays
}

// --- Watchdog Leuchtsimulation ---
rules.JSRule({
  name: "Watchdog Leuchtsimulation",
  triggers: [
    triggers.ItemStateChangeTrigger("WatchdogManual", null, "ON"),
    triggers.ItemStateChangeTrigger("TestDummy", null, "ON"),
    triggers.GenericCronTrigger("0 0 6 ? * * *") // Täglich 6 Uhr Berechnung
  ],
  execute: (event) => {
    const watchdogManual = items.getItem("WatchdogManual");
    const testDummy = items.getItem("TestDummy");
	
	const triggeringItemName = event.itemName;
	
	var logPrefix = "Watchdog-Test";
	if(testDummy.state === "OFF"){
		logPrefix = "Watchdog";
	}
	
    // Prüfen ob aktiv
    if (watchdogManual.state === "ON" || testDummy.state === "ON") {
        console.info(logPrefix + ": Simulation gestartet (An)");

        // Optische Bestätigung
        if (testDummy.state === "OFF") {
            items.getItem(CONFIRM_IF_ITEM).sendCommand('{"effect": "blink"}');
        }

        // Sonnenuntergang holen
        const sunsetStr = items.getItem(SUNSET_START_ITEM).state;
        let sunset = time.toZDT(sunsetStr);
        const now = time.toZDT();

        // Falls Sonnenuntergang heute schon vorbei ist oder wir vor 6 Uhr sind, Logik prüfen.
        // Wenn sunset < now, starten Timer mit 0ms Delay sofort.
		
		for(const tag in roomTags){
			const fgPresSim = items.getItem("fgPresSim");
			const rooms = fgPresSim.members.filter(i => i.tags.includes(tag));
			
			rooms.forEach(room => {
				// Start: Sunset + n min
				let timeStart;
				const startConf = roomTags[tag]['startAfter'];
				if (typeof startConf['h'] !== 'undefined') {
					// Basis ist 'sunset', nicht 'now', um ungewollte Tagessprünge zu vermeiden
					timeStart = sunset.withHour(startConf['h']).withMinute(startConf['m'] || 0).plusMinutes(randomInt(startConf.delay[0], startConf.delay[1]));

					// if after midnight: +1 day
					if (timeStart.isBefore(sunset)) {
						timeStart = timeStart.plusDays(1);
					}
				} else {
					// Relative Endzeit basierend auf timeStart
					timeStart = sunset.plusMinutes( randomInt(startConf.delay[0], startConf.delay[1]) );
				}
				
				// ENDZEIT BERECHNEN (Vor dem Timeout!)
				let timeEnd;
				const endConf = roomTags[tag]['endAfter'];
				if (typeof endConf['h'] !== 'undefined') {
					// Basis ist 'sunset', nicht 'now', um ungewollte Tagessprünge zu vermeiden
					timeEnd = sunset.withHour(endConf['h']).withMinute(endConf['m'] || 0).plusMinutes(randomInt(endConf.delay[0], endConf.delay[1]));

					// if after midnight: +1 day
					if (timeEnd.isBefore(sunset)) {
						timeEnd = timeEnd.plusDays(1);
					}
				} else {
					// Relative Endzeit basierend auf timeStart
					const durationMinutes = randomInt(endConf.delay[0], endConf.delay[1]);
					timeEnd = timeStart.plusMinutes(durationMinutes);
				}
				// LOGIK-CHECK: Ist das geplante Ende bereits in der Vergangenheit?
				const now = time.toZDT();
				if (timeEnd.isBefore(now)) {
					console.info(`${logPrefix}: ${room.name} - Geplantes Ende (${timeEnd.toString()}) liegt in der Vergangenheit. Überspringe Einschalten.`);
					return; // Bricht den Durchlauf für diesen Raum ab
				}
				
				// EINSCHALT-TIMER SETZEN
				const delayStart = getDelayMillis(timeStart); // Sollte bei negativen Werten 0 zurückgeben!
				
				const tStart = setTimeout(() => {
					const brightness = room.members.find(m => m.tags.includes("Brightness"));

					if (testDummy.state === "OFF") {
						if (typeof startConf['actions'] !== 'undefined') {
							for(const i in startConf['actions']){
								const action = startConf['actions'][i];
								items.getItem(action[0]).sendCommand(action[1])
							}
						}
						else if (brightness) {
							brightness.sendCommand(100);
						}
						else{
							console.warn(`${logPrefix}: Fehler - ${room.name} ohne Action und Brightness-Member-Item`);
							return;
						}
					}
					console.info(`${logPrefix}: ${room.name} An (bis ca. ${timeEnd.toLocalTime().toString()})`);

					// AUSSCHALT-TIMER SETZEN
					// Die verbleibende Zeit bis zum Ende wird erst jetzt final berechnet, 
					// damit Verzögerungen beim Ausführen des Start-Timers keine Rolle spielen.
					const delayEnd = getDelayMillis(timeEnd); 
					
					registerSimTimer('end_' + room.name, setTimeout(() => {
						if (testDummy.state === "OFF") {
							if (typeof endConf['actions'] !== 'undefined') {
								for(const i in endConf['actions']){
									const action = endConf['actions'][i];
									items.getItem(action[0]).sendCommand(action[1])
								}
							}
							else {
								brightness.sendCommand(0);
							}
						}
						console.info(`${logPrefix}: ${room.name} Aus`);
					}, delayEnd));

				}, delayStart);
				registerSimTimer('begin_' + room.name, tStart);
			});
		}
    }
  }
});


// --- Watchdog Aktivierungs-Verzögerung ---
rules.JSRule({
  name: "Watchdog changed Timestamp",
  triggers: [triggers.ItemStateChangeTrigger("gWatchdog", null, "ON")],
  execute: (event) => {
    // Speichert aktuellen Zeitpunkt als DateTime
    items.getItem("WatchdogTimestamp").postUpdate(time.toZDT().toString());
  }
});


// --- Fault and disturbance detection ---
rules.JSRule({
  name: "Fault and disturbance detection",
  triggers: [triggers.GenericCronTrigger("0 * * ? * * *")], // Jede Minute
  execute: (event) => {
    const now = time.toZDT();
    const fgFaultDetection = items.getItem("fgFaultDetection");
    const gWatchdog = items.getItem("gWatchdog");

    // 1. Check 5 Minuten (Device anpingen)
    // Wir suchen das erste Item, das > 5min nichts gesendet hat
    const faultItem5 = fgFaultDetection.members.find(i => {
        const lastUpdate = time.toZDT(i.state);
        return now.minusMinutes(5).isAfter(lastUpdate);
    });

    if (faultItem5) {
        // Gruppe finden
        const groupName = faultItem5.groupNames.find(g => g.startsWith("ig"));
        if (groupName) {
            const igGroup = items.getItem(groupName);
            const switchItem = igGroup.members.find(m => m.tags.includes("Switch") && m.tags.includes("Power"));
            
            if (switchItem) {
                // Refresh Trigger: Sendet den eigenen Status an sich selbst
                switchItem.sendCommand(switchItem.state);
                // console.info(`Watchdog Fault: Pinge ${switchItem.name}`);
            }
        }
    }

    // 2. Check 8 Minuten (Alarmierung)
    const faultItem8 = fgFaultDetection.members.find(i => {
        const lastUpdate = time.toZDT(i.state);
        return now.minusMinutes(8).isAfter(lastUpdate);
    });

    if (faultItem8 && gWatchdog.state !== "ON") {
        const telegram = actions.get("telegram", TELEGRAM_BOT);
        if (telegram) {
            telegram.sendTelegram(CHAT_ID, `Wartung nötig: ${faultItem8.name} hat sich länger als 8min nicht gemeldet!`);
        }
    }
  }
});


// --- End Watchdog Alarm ---
rules.JSRule({
  name: "End Watchdog Alarm",
  triggers: [
    triggers.ItemStateChangeTrigger("gWatchdog", null, "OFF"),
    triggers.ItemStateChangeTrigger("TestDummy", null, "OFF")
  ],
  execute: (event) => {
	const triggeringItem = items.getItem(event.itemName);
	
	var logPrefix = "Watchdog";
	if(triggeringItem.name == "TestDummy"){
		logPrefix = "Watchdog-Test";
	}
	
	console.info(logPrefix + ": System disarmed / Reset");
    
    // Lightshow Reset
    const tgLightshow = items.getItem("tgLightshow");
    tgLightshow.members
        .filter(i => i.tags.includes("Alarm") && i.tags.includes("DynamicScene"))
        .forEach(i => {
            if (i.state === "Alarm") i.sendCommand(67); // 67 scheint ein "Normal"-Code zu sein?
        });

    // Alarm Sounds Stop
    items.getItem("gAlexa_PlayAlarmSound").members
        .filter(i => i.tags.includes("Alarm"))
        .forEach(i => i.sendCommand(""));

    // Volume Reset
    items.getItem("gAlexa_Volume").members
        .filter(i => i.tags.includes("Alarm"))
        .forEach(i => i.sendCommand(20));

    // Simulation Timer löschen
    clearAllSimTimers();
    
    // Aktive Alarm-Sequenz Timer löschen (falls man mitten im Alarm unscharf schaltet)
    const alarmTimers = CACHE.get("alarmTimers");
    alarmTimers.forEach(id => clearTimeout(id));
    CACHE.put("alarmTimers", []);
  }
});


// --- Watchdog Alarm ---
rules.JSRule({
  name: "Watchdog Alarm Execution",
  triggers: [
    triggers.GroupStateChangeTrigger("gCamAlert"),
    triggers.GroupStateChangeTrigger("fgIntrusion")
  ],
  execute: (event) => {
    const triggeringItemName = event.itemName;
    const testDummy = items.getItem("TestDummy");
    const gWatchdog = items.getItem("gWatchdog");
    const watchdogTimestamp = items.getItem("WatchdogTimestamp");

    if (testDummy.state === "ON") {
        console.info("Watchdog-Test: ALARM TRIGGERED by " + triggeringItemName);
    }

    // Grace Period Check: Ist Alarm "Scharf" und älter als 5 Minuten?
    const armedTime = time.toZDT(watchdogTimestamp.state);
    const isSystemReady = gWatchdog.state === "ON" && time.toZDT().minusMinutes(5).isAfter(armedTime);

    // Trigger Logik prüfen (gCamAlert immer, fgIntrusion nur wenn neuer Status "höher"/schlimmer ist??)
    // Vereinfachung: Wir reagieren auf Change. Im Original: (newState > previousState) für Intrusion.
    // Da wir JS Event Object nutzen, können wir newState prüfen.
    
    let isValidTrigger = false;
    if (triggeringItemName === "gCamAlert") isValidTrigger = true;
    else {
        // Bei fgIntrusion gehen wir davon aus, dass ON/OPEN der Alarmzustand ist
        const newState = event.newState;
        if (newState === "ON" || newState === "OPEN") isValidTrigger = true;
    }

    if (isSystemReady && isValidTrigger) {
        
        console.warn("Watchdog: ECHTER ALARM AUSGELÖST!");
        items.getItem("Office_Lightshow").sendCommand("Alarm");

        // Alarm-Sequenz starten (Verschachtelte Timeouts)
        // Wir speichern die Timer IDs in einer Liste, um sie bei "End Watchdog" abbrechen zu können.
        const alarmTimers = [];

        // 1. Verzögerung 60 Sekunden (Voralarm / Entry Delay)
        const t1 = setTimeout(() => {
            // Nochmals prüfen, ob Alarm noch an ist (vielleicht wurde er in den 60s deaktiviert)
            if (items.getItem("gWatchdog").state === "ON") {
                
                // Licht-Alarm
                items.getItem("tgLightshow").members
                    .filter(i => i.tags.includes("Alarm") && i.tags.includes("DynamicScene"))
                    .forEach(i => {
                        i.sendCommand(67); // Reset?
                        i.sendCommand("Alarm");
                    });

                // Alexa Sequenz starten
                const gAlexaPlay = items.getItem("gAlexa_PlayAlarmSound").members.filter(i => i.tags.includes("Alarm"));
                const gAlexaVol = items.getItem("gAlexa_Volume").members.filter(i => i.tags.includes("Alarm"));
                const gAlexaTTS = items.getItem("gAlexa_TTS").members.filter(i => i.tags.includes("Alarm"));

                // Sirene an
                gAlexaPlay.forEach(i => i.sendCommand("ECHO:system_alerts_rhythmic_02"));

                // 2. Sirene für 10 Sekunden
                const t2 = setTimeout(() => {
                    // Sirene Stop
                    gAlexaPlay.forEach(i => i.sendCommand(""));

                    if (items.getItem("gWatchdog").state === "ON") {
                        // Lautstärke hoch
                        gAlexaVol.forEach(i => i.sendCommand(80));
                        // Ansage
                        gAlexaTTS.forEach(i => i.sendCommand("Eindringling entdeckt! Notruf wurde abgesetzt. Polizei ist unterwegs."));

                        // 3. Warten auf Ansage (4s), dann Sirene wieder an
                        const t3 = setTimeout(() => {
                            if (items.getItem("gWatchdog").state === "ON") {
                                gAlexaPlay.forEach(i => i.sendCommand("ECHO:system_alerts_rhythmic_02"));
                            }
                        }, 4000);
                        alarmTimers.push(t3);
                    }
                }, 10000);
                alarmTimers.push(t2);

                // Telegram Meldung
                const fgIntrusion = items.getItem("fgIntrusion");
                fgIntrusion.members.forEach(item => {
                    // Prüfen welches Item ausgelöst hat (Im Original logic: if item.state == OFF ?? Das macht wenig Sinn bei Alarm. 
                    // Vermutlich war gemeint: Sende Info über alle Sensoren.
                    // Wir senden einfach den Auslöser.
                    if (item.name === triggeringItemName || item.state.toString() === "OPEN" || item.state.toString() === "ON") {
                        const groupLabels = item.groupNames.map(gn => items.getItem(gn).label).join(" ");
                        const telegram = actions.get("telegram", TELEGRAM_BOT);
                        if(telegram) {
                            telegram.sendTelegram(CHAT_ID, `Eindringling-Alarm wurde ausgelöst!: ${groupLabels} - ${item.label}`);
                        }
                    }
                });
            }
        }, 60000);
        
        alarmTimers.push(t1);
        CACHE.put("alarmTimers", alarmTimers);
    }
  }
});


// --- Start/End Nightshift ---
rules.JSRule({
  name: "Start Nightshift",
  triggers: [
    triggers.ItemStateChangeTrigger("KalenderWatchdog_CurrentEventPresence", null, "ON"),
    triggers.ItemStateChangeTrigger("SmartButton1_DimmerSwitch")
  ],
  execute: (event) => {
    const calendarWecken = items.getItem("KalenderWecken_CurrentEventPresence");
    
    // Logik aus Original: Wenn Wecken-Kalender aus ist
    if (calendarWecken.state === "OFF") {
        items.getItem("WatchdogNightshift").sendCommand("ON");
    }
  }
});

rules.JSRule({
  name: "End Nightshift",
  triggers: [triggers.ItemStateChangeTrigger("KalenderWatchdog_CurrentEventPresence", null, "OFF")],
  execute: (event) => {
    items.getItem("WatchdogNightshift").sendCommand("OFF");
  }
});

Historisches Archiv: Legacy Rules DSL (openHAB 2/3)

Für bestehende Installationen auf Basis von openHAB 2.x oder 3.x, die noch mit der alten Xtend-basierten Rules DSL arbeiten, ist nachfolgend die ursprüngliche Regelsammlung als Archiv aufgeführt.

Hinweis: Diese Regeln sind veraltet und nutzen globale Maps sowie Thread::sleep. Für Neuinstallationen wird dringend das oben stehende JS Scripting empfohlen.

import java.util.Map
import java.util.List
import org.openhab.core.model.script.ScriptServiceUtil

var Map<String, Timer> timers = newHashMap

rule "Watchdog Leuchtsimulation"
when
	Item WatchdogManual changed to ON
	or Time cron "0 0 6 ? * * *" 
then {
	if(WatchdogManual.state == ON){
		//Optische Bestätigung: Watchdog ist an!
		Everywhere_Interface.sendCommand('{"effect": "blink"}')
	
	
		val java.util.Random rand = new java.util.Random()
		var sunset = (LokaleSonnendaten_Set_Startzeit.state as DateTimeType).getZonedDateTime()
		var randMinutes = 0
		
		//if(sunset.isBefore( now )){ sunset = now; }

		//Büro Alex
		//von (Dunkel) bis (23:00 - 01:00) : Licht
		timers.put('begin_BueroAlex',createTimer(sunset, [ |
			logInfo("Watchdog", "Büro Alex: An")
			BueroAlex_Helligkeit.sendCommand(100)
			
			val endMinutes = 23*60 + rand.nextInt(120)
			timers.put('end_BueroAlex',createTimer(now.truncatedTo(java.time.temporal.ChronoUnit.DAYS).plusMinutes(endMinutes), [ |
				logInfo("Watchdog", "Büro Alex: Aus")
				BueroAlex_Helligkeit.sendCommand(0)
			]))
		]))
		
		//Küche
		//	von (Dunkel + (30 - 180min)) bis (45 - 90min lang) : Licht + Ton
		randMinutes = 30 + rand.nextInt(150)
		timers.put('begin_Kueche',createTimer(sunset.plusMinutes(randMinutes), [ |
			logInfo("Watchdog", "Küche: An")
			//TODO: Licht
			
			val endMinutes = 45 + rand.nextInt(45)
			timers.put('end_Kueche',createTimer(now.plusMinutes(endMinutes), [ |
				logInfo("Watchdog", "Küche: Aus")
				//TODO: Licht aus
			]))
		]))

		//Wohnzimmer
		//	von (Dunkel + (30 - 90min)) bis (22:30 - 00:30) : TV-Simulation + Ton
		randMinutes = 30 + rand.nextInt(60)
		timers.put('begin_Wohnzimmer',createTimer(sunset.plusMinutes(randMinutes), [ |
			//Licht
			logInfo("Watchdog", "Wohnzimmer Tisch: An")
			WzTisch_Helligkeit.sendCommand(100)
			
			logInfo("Watchdog", "Wohnzimmer Couch: TV-Simulation")
			WzCouch_Lightshow.sendCommand("TV-Simulation") //TV-Simluation
			
			//Ton
			logInfo("Watchdog", "Wohnzimmer Echo: Musik")
			Echo_Wohnzimmer_TextCommand.sendCommand("echo spiele Robin Schulz")
			
			val endMinutes = 22*60 + 30 + rand.nextInt(120)
			timers.put('end_Wohnzimmer',createTimer(now.truncatedTo(java.time.temporal.ChronoUnit.DAYS).plusMinutes(endMinutes), [ |
				//TODO: Licht und Ton aus
				logInfo("Watchdog", "Wohnzimmer (Tisch, Couch, Echo): Aus")
				WzTisch_Helligkeit.sendCommand(0)
				WzCouch_Helligkeit.sendCommand(0) //TV-Simluation
				Echo_Wohnzimmer_TextCommand.sendCommand("echo stop")
			]))
		]))

		//Schlafzimmer
		//	von (21:00 - 23:30) bis (23:35 - 00:30) : Licht
		randMinutes = 21*60 + rand.nextInt(150)
		timers.put('begin_Schlafzimmer',createTimer(now.truncatedTo(java.time.temporal.ChronoUnit.DAYS).plusMinutes(randMinutes), [ |
			//Licht
			logInfo("Watchdog", "Schlafzimmer : An")
			SchlafzimmerRoom_Helligkeit.sendCommand(100)
			
			val endMinutes = 5 + rand.nextInt(60)
			timers.put('end_Schlafzimmer',createTimer(now.plusMinutes(endMinutes), [ |
				//Licht aus
				logInfo("Watchdog", "Schlafzimmer : Aus")
				SchlafzimmerRoom_Helligkeit.sendCommand(0)
			]))
		]))
		
		
		//Musiksimulation
		//createTimer(now, [ |
		//	Echo_BueroAlex_TextCommand.sendCommand("echo spiele Robin Schulz")
		//	while(gWatchdog.state == ON){
		//		Thread::sleep(500)
		//	}
		//	Echo_BueroAlex_TextCommand.sendCommand("echo stop")
		//])
	}
}
end

// --------------------------------------------------

//Verzögerung, bis Alarm tatsächlich scharf: 5min
rule "Watchdog changed Timestamp"
when
	Item gWatchdog changed to ON
then {
	WatchdogTimestamp.postUpdate(new DateTimeType)
}
end

// --------------------------------------------------

rule "Fault and disturbance detection"
when
	Time cron "0 * * ? * * *" 
then {
	//if 5min nothing received: ask
	if(now.minusMinutes(5).isAfter((fgFaultDetection.state as DateTimeType).getZonedDateTime())){
		val GenericItem faultItem = fgFaultDetection.members.findFirst [ GenericItem i | now.minusMinutes(5).isAfter((i.state as DateTimeType).getZonedDateTime()) ]
		val String faultIGName = faultItem.getGroupNames.findFirst[ String groupName | groupName.startsWith("ig") ]
		val GroupItem faultIG = ScriptServiceUtil.getItemRegistry.getItem(faultIGName) as GroupItem
		val SwitchItem switchItem = faultIG.members.findFirst[ a | a.tags.contains("Switch") && a.tags.contains("Power")]
		switchItem.sendCommand(switchItem.state)
	}
	
	//if 8min nothing received: alert
	if(now.minusMinutes(8).isAfter((fgFaultDetection.state as DateTimeType).getZonedDateTime())){
		val GenericItem faultItem = fgFaultDetection.members.findFirst [ GenericItem i | now.minusMinutes(8).isAfter((i.state as DateTimeType).getZonedDateTime()) ]
		//TODO: WatchdogDummyAlertContact.sendCommand(ON)
		if(gWatchdog.state != ON){
			getActions("telegram","telegram:telegramBot:home_Bot").sendTelegram(-000000000L,
				"Wartung nötig: %s hat sich länger als 8min nicht gemeldet!",
				faultItem.name
			)
		}
	}
}
end	

// --------------------------------------------------

//To end an Alarm, but also just to end the Night
rule "End Watchdog Alarm"
when
	Item gWatchdog changed to OFF
then {
	if(Everywhere_Lightshow.state.toString == "Alarm"){
		Everywhere_Farbtemperatur.sendCommand(67)
	}
	
	//Everywhere_Helligkeit.sendCommand(0)
	gAlexa_PlayAlarmSound.members.forEach[ StringItem item |
		item.sendCommand('') //...PlayAlarmSound wird mit '' unterbrochen
	]
	gAlexa_Volume.members.forEach[ DimmerItem item |
		item.sendCommand(20)
	]
	
	timers.keySet().forEach[String key |
		if(key.startsWith("begin")){
			timers.get(key).cancel()
		}
		if(key.startsWith("end")){
			timers.get(key).reschedule(now)
		}
	]
}
end

// --------------------------------------------------

rule "Test Alarm start"
when
	Item TestDummy changed to ON
then {
	//Everywhere_Lightshow.sendCommand("Alarm")
	BueroAlex_Lightshow.sendCommand("Alarm")
}
end	

rule "Test Alarm end"
when
	Item TestDummy changed to OFF
then {
	//if(Everywhere_Lightshow.state.toString == "Alarm"){
	//	Everywhere_Farbtemperatur.sendCommand(67)
	//}
	if(BueroAlex_Lightshow.state.toString == "Alarm"){
		BueroAlex_Farbtemperatur.sendCommand(67)
	}
}
end

// --------------------------------------------------

rule "Smart Button - Start Nightshift"
when
	//Channel "mqtt:topic:99a3e7ba:SmartButton1:action" triggered
	Item SmartButton1_DimmerSwitch changed
then {
	if(
		KalenderWecken_CurrentEventPresence.state == OFF
		|| SmartButton1_DimmerSwitch == NULL
	){
		WatchdogNightshift.sendCommand(ON)
	}
}
end

// --------------------------------------------------


rule "End Nightshift"
when
	Item KalenderWatchdog_CurrentEventPresence changed to OFF
then {
	WatchdogNightshift.sendCommand(OFF)
}
end

// --------------------------------------------------

rule "Watchdog Alarm"
when
	Item gCamAlert changed
	or Item fgIntrusion changed
then {
	if( gWatchdog.state == ON && now.minusMinutes(5).isAfter((WatchdogTimestamp.state as DateTimeType).getZonedDateTime())){
		
		logInfo("Test", "triggeringItemName: {}", triggeringItemName)
		//Licht-Alarm
		Everywhere_Lightshow.sendCommand("Alarm")
		
		//Alexa-Warnrufe
		createTimer(now, [ |
			gAlexa_PlayAlarmSound.members.forEach[ StringItem item |
				item.sendCommand('ECHO:system_alerts_rhythmic_02')
			]
			
			Thread::sleep(10000)
			
			gAlexa_PlayAlarmSound.members.forEach[ StringItem item |
				item.sendCommand('')
			]
			
			if(gWatchdog.state == ON){
				gAlexa_Volume.members.forEach[ DimmerItem item |
					item.sendCommand(80)
				]
				gAlexa_TTS.members.forEach[ StringItem item |
					item.sendCommand("Eindringling entdeckt! Notruf wurde abgesetzt. Polizei ist unterwegs.")
				]
			
				Thread::sleep(4000)
			}
			
			if(gWatchdog.state == ON){
				gAlexa_PlayAlarmSound.members.forEach[ StringItem item |
					item.sendCommand('ECHO:system_alerts_rhythmic_02')
				]
			}
		])
		
		//Telegram-Meldungen
		fgIntrusion.members.forEach[ GenericItem item |
			if(item.state == OFF){
				var groupLabels = ""
				item.groupNames.forEach[ String groupName |
					var groupLabel = ScriptServiceUtil.getItemRegistry.getItem(groupName).label
					groupLabels = groupLabels + " " + groupLabel
				]
				getActions("telegram","telegram:telegramBot:home_Bot").sendTelegram(-000000000L,
					"Eindringling-Alarm wurde ausgelöst!: %s - %s",
					groupLabels,
					item.label
				)
			}
		]
	}
}
end

// --------------------------------------------------

// --- TensorPi ---
rule "Send telegram on object sighted (Vordertuer)"
when
	Item Tensorpi_Vordertuer_time changed
then
	getActions("telegram","telegram:telegramBot:home_Bot").sendTelegramPhoto(-000000000L,
		Tensorpi_Vordertuer_last_img.state.toString,
		Tensorpi_Vordertuer_object.state.toString)
end

// --- TensorPi2 ---
rule "Send telegram on object sighted (Hintertuer)"
when
	Item Tensorpi_Hintertuer_time changed
then
	if(gWatchdog.state == ON){
		getActions("telegram","telegram:telegramBot:home_Bot").sendTelegramPhoto(-000000000L,
			Tensorpi_Hintertuer_last_img.state.toString,
			Tensorpi_Hintertuer_object.state.toString)
	}
end
0
0
Alexander Hacker
Autor & Smart-Home-Entwickler

Alexander Hacker

Dokumentiert auf SmartHome Node praxisnahe Do-It-Yourself-Projekte, offene Architekturen und Automatisierungen mit openHAB 5, Zigbee2MQTT, ESPHome und Linux – ohne Abhängigkeit von Hersteller-Clouds.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre, wie deine Kommentardaten verarbeitet werden.

Translate »