Table of Contents
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:
- Einen Alarm zu verursachen, wenn ein Eindringen entdeckt wurde
- 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 TestDummyOpenHAB: Rules
Die nachfolgende watchdog.rules muss in jedem Fall auf deine Bedürfnisse angepasst werden und kann nicht einfach kopiert werden.
Folgende Situationen werden dabei betrachtet:
Leuchtsimulation
- rule “Watchdog Leuchtsimulation”
- Für jeden Raum werden Timer gesetzt, die Anwesenheit simulieren. Dabei lässt sich möglichst nicht mehr unterscheiden, ob dies eine Simulation ist, oder ob tatsächlich jemand anwesend ist. Verschiedene Räume werden unterschiedlich behandelt.
Auf die Verwendung von semantischen Gruppen habe ich hier verzichtet, da die Funktionen ohnehin konkret auf deine Bedürfnisse angepasst werden müssen!
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")
//])
}
}
endIm Gegensatz zur RulesDSL verwende ich hier sehr wohl semantic items und groups. Die Anwesenheitssimulation geht von Dimmer-Items mit dem “Brightness“-Tag su, die Gruppen mit einem Raum-Tag zugeordnet sind. Die Räume müssen außerdem zur Funktionsgruppe fgPresSim gehören. Damit weiss der Raum, dass er im Watchdog-Fall simulieren soll.
Außerdem wird die Zeit des Sonnenuntergangs aus dem Astro-Binding benötigt.
Group fgPresSim // rooms, which do prensence simulation
DateTime LokaleSonnendaten_Set_Startzeit "Startzeit" (igSun) { channel="astro:sun:local:set#start" }
Group sgKitchen "Küche" <kitchen> (sgGroundFloor,fgPresSim) ["Kitchen"] { alexa="Other" }
Group sgOffice "Büro" <office> (sgGroundFloor,fgPresSim) ["Office"] { alexa="Other" }
Group sgBedroom "Schlafzimmer" <bedroom> (sgGroundFloor,fgPresSim) ["Bedroom"] { alexa="Other" }
Group sgWintergarden "Wintergarten" (sgGroundFloor,fgPresSim) ["Livingroom"] { alexa="Other" }
Dimmer WzWG_Helligkeit "Licht" <DimmableLight> (sgWintergarden) ["Control", "Brightness", "internal"] { autoupdate="false", channel="mqtt:topic:myMosquitto:HueBulbLivingRoomWinterGarden:brightness", channel="mqtt:topic:myMosquitto:HueBulbLivingRoomWinterGarden:availability", alexa="Brightness,PowerState" }
Nun zur eigentlichen Automatisierung. An den Konstanten wird konfiguriert: Telegram, Items und wie die Räume sich nun wann verhalten sollen. Hier werden die Tags angegeben und anhand einem Teitpunkt oder dem Sonnenuntergang/Laufzeit der Start- und Endzeitpunkt angegeben. Optional können weitere Aktionen für einen Raum angegeben werden, z.B. Fernseher-Simulation.
/**
* 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, Konfiguration
const TELEGRAM_BOT = "telegram:telegramBot:home_Bot";
const CHAT_ID = -000000000;
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;
}
/**
* 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("LokaleSonnendaten_Set_Startzeit").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);
});
}
}
}
});Steuermechanismen der Alarmanlage
- rule “Watchdog changed Timestamp”
- Hier wird lediglich die Aktivierungszeit der Alarmanlage gespeichert, um Alarme nicht in den ersten 5min auszulösen. Z.B. wenn du die Alarmanlage aktivierst und dann noch das Haus verlassen willst.
//Verzögerung, bis Alarm tatsächlich scharf: 5min
rule "Watchdog changed Timestamp"
when
Item gWatchdog changed to ON
then {
WatchdogTimestamp.postUpdate(new DateTimeType)
}
end// --- 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());
}
});- rule “Fault and disturbance detection”
- Ich habe Zigbee-Steckdosen dauerhaft in Betrieb, einfach nur um Störungen auf der 2,4GHz-Frequenz zu erkennen. Es wäre möglich, dass ein Angreifer versucht, dein Netz zu stören um Alarmkomponenten außer Betrieb zu setzen. Wenn diese dauerhaft plappernden Steckdosen sich nicht mehr melden, kann ein Problem vermutet werden. Dann fragt die Regel nach. Kommt dann immer noch keine Antwort, wird Alarm ausgelöst. Oder eine Wartungsmeldung versandt, wenn der Alarmmodus gerade inaktiv ist. Ich habe die hier hier angegebenen Zeiten sogar noch weiter verkürzt.
- Vergiss nicht, deinen Telegram-Bot dafür zu konfigurieren.
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 // --- 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!`);
}
}
}
});a- rule “End Watchdog Alarm”
- Wenn ein Alarm beendet wird, passieren zwei Dinge: ein laufender Alarm muss abgebrochen werden und ein regulärer Aktiv-Status wird inaktiv.
//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/**
* 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", {});
}
// --- 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);
});
// 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", []);
}
});- rule “Test Alarm start” / rule “Test Alarm end”
- Zwei Regeln um verschiedene Tests durchzuführen
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”
- Drücke ich den “Haus-Aus-Knopf”, wird auch der nächtliche Alarm aktiviert.
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// --- 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");
}
}
});- rule “End Nightshift”
- Schalte zu einem bestimmten Zeitpunkt automatisch
rule "End Nightshift"
when
Item KalenderWatchdog_CurrentEventPresence changed to OFF
then {
WatchdogNightshift.sendCommand(OFF)
}
endrules.JSRule({
name: "End Nightshift",
triggers: [triggers.ItemStateChangeTrigger("KalenderWatchdog_CurrentEventPresence", null, "OFF")],
execute: (event) => {
items.getItem("WatchdogNightshift").sendCommand("OFF");
}
});Alarme
- rule “Watchdog Alarm”
- Bei welchen Auslösern soll reagiert werden?
- Betrachte dabei, dass in den ersten 5min nach Scharfschaltung kein Alarm ausgelöst werden soll.
- Schalte alle Lichter ein und lasse sie rot blinken (Everywhere_Lightshow.sendCommand(“Alarm”) mittels dynamischer Szenen)
- Lass alle SmartSpeaker Alarm schlagen!
- Versende Meldungen an dich selbst!
- Gib ggf. eine Alarmmeldung direkt an eine dafür geschaffene Stelle weiter!
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// --- 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);
}
}
});- rule “Send telegram on object sighted (Vordertuer)” / rule “Send telegram on object sighted (Hintertuer)”
- Regeln um Meldungen an mich zu versenden, wenn eine Person auf einer Kamera entdeckt wurde
- Wenn eine Person in deiner Abwesenheit an deiner Hintertür ist, kann das ein unbefugtes Eindringen sein. Eine Person an der Vordertür hingegen kann normaler Verhalten sein (Postbote)
- Bei der Verwendung von Kameras auch im eigenen Bereich sind immer rechtliche Richtlinien zu betrachten!
- Der Code muss komplett an deine gegebenheiten angepasst werden.
// --- 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)
}
endconst { rules, triggers, items, actions } = require('openhab');
// Hilfskonstanten
const TELEGRAM_BOT = "telegram:telegramBot:home_Bot";
const CHAT_ID = -000000000;
// --- Rule 1: TensorPi Vordertür ---
rules.JSRule({
name: "Send telegram on object sighted (Vordertuer)",
description: "Sendet Telegram Foto bei Erkennung an der Vordertür",
triggers: [
triggers.ItemStateChangeTrigger('Tensorpi_Vordertuer_time')
],
execute: () => {
// Actions holen
const telegram = actions.get("telegram", TELEGRAM_BOT);
if (telegram) {
// Werte aus Items lesen
const imgState = items.getItem('Tensorpi_Vordertuer_last_img').state;
const objectName = items.getItem('Tensorpi_Vordertuer_object').state;
// String Formatting mit Template Literals (ersetzt String::format)
const caption = `${objectName} entdeckt. Kamerazugriff: http://tensorpi.rlyeh:5000/api/video/PiCamera`;
// Senden
telegram.sendTelegramPhoto(CHAT_ID, imgState, caption);
} else {
console.warn("Telegram Action nicht gefunden (Vordertür)");
}
}
});
// --- Rule 2: TensorPi Terrace (Hintertür) ---
rules.JSRule({
name: "Send telegram on object sighted (Hintertuer)",
description: "Sendet Telegram Foto bei Erkennung an der Hintertür, wenn Kameras AN sind",
triggers: [
triggers.ItemStateChangeTrigger('Tensorpi_Terrace_time')
],
execute: () => {
// Bedingung prüfen: if(Cameras.state == ON)
// Hinweis: .state gibt in JS immer einen String zurück, daher Vergleich mit 'ON'
if (items.getItem('Cameras').state === 'ON') {
const telegram = actions.get("telegram", TELEGRAM_BOT);
if (telegram) {
const imgState = items.getItem('Tensorpi_Terrace_last_img').state;
const objectName = items.getItem('Tensorpi_Terrace_object').state;
const caption = `${objectName} entdeckt. Kamerazugriff: http://tensorpi2.rlyeh:5000/api/video/PiCamera`;
telegram.sendTelegramPhoto(CHAT_ID, imgState, caption);
} else {
console.warn("Telegram Action nicht gefunden (Hintertür)");
}
}
}
});