This is a openhab alarm script that i started working to try to implement an alarm when none is available.
∆IT DOESNT TRY TO REPLICATE A REAL ONE but an diy one is better than no alarm.
πThe whole concept of the rule was because there was a need for an burglar alarm that doesn’t annoy the neighbors also doesn’t need an subscription. It will never replace a pro one but at least if you have also cameras you can verify for yourself.
∆Pay attention this comes with no responsibility and by no means take it as a secure implementation. (Pins are plaintext in the script and also the rules can be disabled etc)
YOU HAVE BEEN WARNED ITS YOUR RESPONSIBILITY IF IT DOESN’T WORK!!!
∆Make sure you use contacts and motion sensors that have permanent power as much as possible. Make sure you use some loud and stable and reliable system to scare the burglars. Use some kind of UPS based battery for your openhab network internet modem and sensors.
∆If someone is extremely concerned about home security, they should pay for a professionally installed and monitored solution so that humans can make informed decisions when the alarm is triggered or the power is cut.
With that out of the way please read the following steps:
For the script we need to make sure have the following items defined !!!
!!!important add group g_home and g_away to the contact items you need to be used by the script in the format for example a knx window sensor using text file configuration!!!
!!!
Contact Office_Window1 "Office left full [%s]" <window> (g_office_window, g_general_windows,g_general_windows_counting,g_home,g_away) ["OpenState"] {channel="knx:device:bridge:security:office_left_full" }
!!!!Make sure the item name not label has based on the type there included in the name "Door", "Gate", "Window", "Movement"!!!!!!!!
!!! now this items below are mandatory:
//alarm
String Alarm_State "Alarm State [%s]" // Possible values: DISARMED, ARMED_HOME, ARMED_AWAY, PENDING, TRIGGERED
String Alarm_Command // To send the commands mentioned above
String Alarm_Code "Alarm Code [%s]" // The code to arm or disarm the system
Number Alarm_Countdown "Alarm Countdown [%d]" // A countdown timer for arming the system
//zones here we have to link only contact items
Group:Contact:OR(OPEN, CLOSED) g_home "Home Zone" //only for sensor where there is someone at home
Group:Contact:OR(OPEN, CLOSED) g_away "Away Zone" //all the sensor possible nobody present at home
//zone delays
Number EntryTime_Home "Home Entry Time [%d]" //the time to enter in the house
Number ExitTime_Home "Home Exit Time [%d]" //the time to exit the house
The folowing is a widget based on the wonderfull work of the people inside this tread click here to find out more. Now the widget assumes that your windows are of type OpenState part of Window equipment same for doors type OpenState part of Door equipment then for motion detector here i had to play with it because a motion detector can have alot of setting in my case i can set delay time lux level etc so what i did i set type “Sensor”,“Presence” part of equipment MotionDetector but you can choose whatever you want. For the camera i have same thing like detectors but used “Status”,“Camera” and no equipment.
uid: alarm_control_widget
tags: []
props:
parameters:
- description: A custom widget for controlling the alarm system
label: Alarm Control Widget
name: alarmControlWidget
required: false
type: TEXT
timestamp: Aug 6, 2023, 1:24:28 AM
component: f7-card
config:
style:
--f7-card-header-border-color: transparent
border-radius: 10px
margin-left: 5px
margin-right: 5px
title: Alarm Control
slots:
default:
- component: f7-card-content
config:
style:
height: auto
slots:
default:
- component: f7-row
config:
style:
justify-content: space-between
slots:
default:
- component: f7-col
config:
style:
text-align: center
slots:
default:
- component: oh-button
config:
action: command
actionCommand: ARMED_HOME
actionItem: Alarm_Command
class:
- padding-top-half
- display-flex
- flex-direction-column
iconColor: '=(items.Alarm_State.state) == "ARMED_HOME" ? "blue" : "gray"'
iconMaterial: '=items.Alarm_State.state=="ARMED_HOME" ? "gpp_good" : "gpp_bad"'
iconSize: 60
style:
--f7-button-bg-color: transparent
--f7-button-hover-bg-color: '=vars.objVar.selectThing=="Security" ? "F8BB00" : "transparent"'
--f7-button-padding-horizontal: 0px
--f7-button-padding-vertical: 0px
--f7-button-text-color: 'themeOptions.dark==="light" ? "white" : "black"'
font-size: 14px
height: auto
text: ARMED HOME
- component: f7-col
config:
style:
text-align: center
slots:
default:
- component: oh-button
config:
action: popup
actionModal: widget:keypad
actionModalConfig:
closePopup: true
item: Alarm_Code
mask: "*"
class:
- padding-top-half
- display-flex
- flex-direction-column
iconColor: '=items.Alarm_State.state=="DISARMED" ? "green" : (items.Alarm_State.state=="PENDING" ? "blue" : (items.Alarm_State.state=="TRIGGERED" ? "red" : "orange"))'
iconMaterial: '=items.Alarm_State.state=="DISARMED" ? "gpp_good" : (items.Alarm_State.state=="PENDING" ? "gpp_maybe" : (items.Alarm_State.state=="TRIGGERED" ? "gpp_bad" : "apps"))'
iconSize: 60
style:
--f7-button-bg-color: transparent
--f7-button-hover-bg-color: '=vars.objVar.selectThing=="Security" ? "F8BB00" : "transparent"'
--f7-button-padding-horizontal: 0px
--f7-button-padding-vertical: 0px
--f7-button-text-color: 'themeOptions.dark==="light" ? "white" : "black"'
font-size: 14px
height: auto
text: '=items.Alarm_State.state=="DISARMED" ? "DISARMED" : (items.Alarm_State.state=="PENDING" ? "PENDING" : (items.Alarm_State.state=="TRIGGERED" ? "DISARM TRIGGERED" : (items.Alarm_State.state=="ARMED_HOME" ? "DISARM HOME" : (items.Alarm_State.state=="ARMED_AWAY" ? "DISARM AWAY" : "UNKNOWN"))))'
- component: f7-col
config:
style:
text-align: center
slots:
default:
- component: oh-button
config:
action: command
actionCommand: ARMED_AWAY
actionItem: Alarm_Command
class:
- padding-top-half
- display-flex
- flex-direction-column
iconColor: '=(items.Alarm_State.state) == "ARMED_AWAY" ? "yellow" : "gray"'
iconMaterial: '=items.Alarm_State.state=="ARMED_AWAY" ? "gpp_good" : "gpp_bad"'
iconSize: 60
style:
--f7-button-bg-color: transparent
--f7-button-hover-bg-color: '=vars.objVar.selectThing=="Security" ? "F8BB00" : "transparent"'
--f7-button-padding-horizontal: 0px
--f7-button-padding-vertical: 0px
--f7-button-text-color: 'themeOptions.dark==="light" ? "white" : "black"'
font-size: 14px
height: auto
text: ARMED AWAY
- component: f7-list
config:
accordionList: true
noHairlinesBetween: true
slots:
default:
- component: f7-list-item
config:
accordionItem: true
style:
font-weight: bold
fontSize: 15px
margin-top: 15px
title: Adjust Entry/Exit Times
slots:
default:
- component: f7-accordion-content
slots:
default:
- component: oh-label-card
config:
item: Alarm_Countdown
title: Entry/Exit countdown
- component: oh-slider-card
config:
item: EntryTime_Home
max: 60
min: 0
scale: true
scaleSteps: 10
scaleSubSteps: 5
step: 1
title: Home Entry Time
- component: oh-slider-card
config:
item: ExitTime_Home
max: 60
min: 0
scale: true
scaleSteps: 10
scaleSubSteps: 5
step: 1
title: Home Exit Time
- component: f7-list-item
config:
accordionItem: true
style:
font-weight: bold
fontSize: 15px
margin-top: 15px
title: Sensors & Surveillance
slots:
default:
- component: f7-accordion-content
slots:
default:
- component: f7-list
config:
accordionList: true
noHairlinesBetween: true
slots:
default:
- component: f7-list-item
config:
accordionItem: true
style:
font-weight: bold
fontSize: 15px
margin-top: 15px
title: Door Sensors
slots:
default:
- component: f7-accordion-content
config:
accordionList: true
noHairlinesBetween: true
slots:
default:
- component: oh-repeater
config:
accordionList: true
fetchMetadata: semantics,widgetOrder,uiSemantics
filter: '((loop.doorMember.category == "Door") || ((loop.doorMember.category).includes("door"))) ? true : false'
for: doorMember
fragment: true
itemTags: OpenState
sourceType: itemsWithTags
slots:
default:
- component: oh-list
config:
noHairlinesBetween: true
slots:
default:
- component: f7-list-item
config:
style:
font-weight: 800
fontSize: 14px
margin-top: -7px
title: "=loop.doorMember.metadata.uiSemantics ? loop.doorMember.metadata.uiSemantics.config.equipment + loop.doorMember.metadata.uiSemantics.config.preposition + loop.doorMember.metadata.uiSemantics.config.location : loop.doorMember.label"
slots:
inner-end:
- component: f7-chip
config:
bgColor: transparent
iconColor: '=(items[loop.doorMember.name].state == "CLOSED") ? "gray" : "red"'
iconMaterial: sensor_door
iconSize: 20px
inner-end:
- component: f7-chip
config:
bgColor: transparent
iconColor: '=(items.g_general_door.state) == "OPEN" ? "red" : "gray"'
iconMaterial: sensor_door
iconSize: 20px
- component: f7-list-item
config:
accordionItem: true
style:
font-weight: bold
fontSize: 15px
margin-top: 15px
title: Window Sensors
slots:
default:
- component: f7-accordion-content
config:
accordionList: true
noHairlinesBetween: true
slots:
default:
- component: oh-repeater
config:
accordionList: true
fetchMetadata: semantics,widgetOrder,uiSemantics
filter: '((loop.windowMember.category == "Window") || (loop.windowMember.category == "window")) ? true : false'
for: windowMember
fragment: true
itemTags: OpenState
sourceType: itemsWithTags
slots:
default:
- component: oh-list
config:
noHairlinesBetween: true
slots:
default:
- component: f7-list-item
config:
style:
font-weight: 800
fontSize: 14px
margin-top: -7px
title: "=loop.windowMember.metadata.uiSemantics ? loop.windowMember.metadata.uiSemantics.config.equipment + loop.windowMember.metadata.uiSemantics.config.preposition + loop.windowMember.metadata.uiSemantics.config.location : loop.windowMember.label"
slots:
inner-end:
- component: f7-chip
config:
bgColor: transparent
iconColor: '=items[loop.windowMember.name].state == "CLOSED" ? "gray" : "red"'
iconMaterial: sensor_window
iconSize: 20px
inner-end:
- component: f7-chip
config:
bgColor: transparent
iconColor: '=(items.g_general_windows.state) == "OPEN" ? "red" : "gray"'
iconMaterial: sensor_window
iconSize: 20px
- component: f7-list-item
config:
accordionItem: true
style:
font-weight: bold
fontSize: 15px
margin-top: 15px
title: Motion Sensors
slots:
default:
- component: f7-accordion-content
config:
accordionList: true
noHairlinesBetween: true
slots:
default:
- component: oh-repeater
config:
accordionList: true
fetchMetadata: semantics,widgetOrder,uiSemantics
filter: '((loop.motionDetector.hasTag("Sensor") && loop.motionDetector.hasTag("Presence")) && loop.motionDetector.hasCategory("MotionDetector")) ? true : false'
for: motionDetector
fragment: true
itemTags: Sensor,Presence
sourceType: itemsWithTags
slots:
default:
- component: f7-list-item
config:
style:
font-weight: 800
fontSize: 14px
margin-top: -7px
title: "=loop.motionDetector.metadata.uiSemantics ? loop.motionDetector.metadata.uiSemantics.config.equipment + loop.motionDetector.metadata.uiSemantics.config.preposition + loop.motionDetector.metadata.uiSemantics.config.location : loop.motionDetector.label"
slots:
inner-end:
- component: f7-chip
config:
bgColor: transparent
iconColor: '=items[loop.motionDetector.name].state == "CLOSED" ? "gray" : "red"'
iconMaterial: sensors
iconSize: 20px
inner-end:
- component: f7-chip
config:
bgColor: transparent
iconColor: '=(items.g_general_movement.state) == "OPEN" ? "red" : "gray"'
iconMaterial: sensors
iconSize: 20px
- component: f7-list-item
config:
accordionItem: true
style:
font-weight: bold
fontSize: 15px
margin-top: 15px
title: Surveillance
slots:
default:
- component: f7-accordion-content
config:
accordionList: true
noHairlinesBetween: true
slots:
default:
- component: oh-repeater
config:
accordionList: true
fetchMetadata: semantics,widgetOrder,uiSemantics
filter: '((loop.surveillance.hasTag("Status") && loop.surveillance.hasTag("Camera"))) ? true : false'
for: surveillance
fragment: true
itemTags: Status,Camera
sourceType: itemsWithTags
slots:
default:
- component: f7-list-item
config:
style:
font-weight: 800
fontSize: 14px
margin-top: -7px
title: "=loop.surveillance.metadata.uiSemantics ? loop.surveillance.metadata.uiSemantics.config.equipment + loop.surveillance.metadata.uiSemantics.config.preposition + loop.surveillance.metadata.uiSemantics.config.location : loop.surveillance.label"
slots:
inner-end:
- component: f7-chip
config:
bgColor: transparent
iconColor: '=items[loop.surveillance].state == "OFF" ? "red" : "gray"'
iconMaterial: video_camera_front
iconSize: 20px
inner-end:
- component: f7-chip
config:
bgColor: transparent
iconColor: '=(items.g_general_camera.state) == "OPEN" ? "red" : "gray"'
iconMaterial: video_camera_front
iconSize: 20px
Now the script assumes you have a short 1 second sound file called alarm.mp3 in your openhab sounds folder and a default sink for playing the alarm sound otherwise set to turn on or off something like a group of light or siren connected to a switch.
it has to be placed in the openhab-conf folder under automation jsr223:
'use strict';
let items, rules, log, triggers, actions;
try {
const openhab = require('openhab');
items = openhab.items;
rules = openhab.rules;
log = openhab.log;
triggers = openhab.triggers;
actions = openhab.actions;
time = openhab.time;
} catch (error) {
console.error(`Failed to load modules: ${error}`);
process.exit(1);
}
const CORRECT_PINS = ["1234", "12345"]; // Replace with your actual PINs
const RELEVANT_SENSOR_NAMES_HOME = ["Door", "door", "Gate", "gate", "Window", "window"];
const RELEVANT_SENSOR_NAMES_AWAY = ["Door", "door", "Gate", "gate", "Window", "window", "Movement", "movement"];
let alarmSoundTimeoutId = null;
let countdownTimeoutId = null;
let triggeredItems = [];
let systemStarted = false;
let elapsedTimeSinceNotification = 0;
let isInitialTrigger = true;
let detectorTimers = {};
let alarmLock = false;
// function for concurrent operation
function withSafeExecution(func) {
if (alarmLock) {
console.warn('[AlarmSystem] Another operation is in progress. Skipping...');
return;
}
try {
alarmLock = true;
func();
} catch (error) {
console.error(`[AlarmSystem] Error: ${error.message}`);
disarmSystem();
} finally {
alarmLock = false;
}
}
// function to fallback to disarm no matter what
function disarmSystem() {
if (alarmLock) {
console.warn("alarm_system_log = Alarm is currently locked by another operation. Overriding for disarm!");
}
alarmLock = true; // Lock the system for disarm
items.getItem("Alarm_State").sendCommand("DISARMED");
if (alarmSoundTimeoutId !== null) {
clearTimeout(alarmSoundTimeoutId);
alarmSoundTimeoutId = null;
}
if (countdownTimeoutId !== null) {
clearTimeout(countdownTimeoutId);
countdownTimeoutId = null;
}
alarmLock = false; // Release the lock after disarm
}
// Create a function for the alarm sound timer
function alarmSoundFunc() {
console.info("alarm_system_log = Starting alarm sound function");
actions.Audio.playSound("alarm.mp3", 1.0); // Play sound at 100% volume
if (isInitialTrigger) {
actions.NotificationAction.sendBroadcastNotification("Alarm has been triggered!");
console.info(`alarm_system_log = Alarm has been triggered!`);
isInitialTrigger = false;
}
elapsedTimeSinceNotification++;
if (elapsedTimeSinceNotification >= 600) { // 600 seconds = 10 minutes
actions.NotificationAction.sendBroadcastNotification("Alarm is still sounding after 10 minutes!");
console.info(`alarm_system_log = Alarm is still sounding after 10 minutes!`);
elapsedTimeSinceNotification = 0; // Reset the counter
}
if (alarmSoundTimeoutId !== null) {
console.info("alarm_system_log = Clearing existing alarm sound timeout");
clearTimeout(alarmSoundTimeoutId);
alarmSoundTimeoutId = null;
}
alarmSoundTimeoutId = setTimeout(alarmSoundFunc, 1000);
console.info("alarm_system_log = Alarm sound function completed");
}
//Create a function for the entry time countdown timer
function entryCountdownFunc() {
console.info("alarm_system_log = Starting entry countdown function");
let countdown = items.getItem("Alarm_Countdown").state - 1;
console.info(`alarm_system_log = Entry countdown value: ${countdown}`);
items.getItem("Alarm_Countdown").sendCommand(countdown);
if (countdown > 0) {
if (countdownTimeoutId !== null) {
clearTimeout(countdownTimeoutId);
}
countdownTimeoutId = setTimeout(entryCountdownFunc, 1000);
} else {
let alarmState = items.getItem("Alarm_State").state.toString();
if (alarmState === "ARMED_HOME" || alarmState === "ARMED_AWAY") {
console.info("alarm_system_log = Entry countdown reached zero, triggering alarm");
items.getItem("Alarm_State").sendCommand("TRIGGERED");
if (alarmSoundTimeoutId !== null) {
clearTimeout(alarmSoundTimeoutId);
}
alarmSoundTimeoutId = setTimeout(alarmSoundFunc, 3000);
}
}
console.info("alarm_system_log = Entry countdown function completed");
}
//Create a function for the exit time countdown timer
function exitCountdownFunc() {
console.info("alarm_system_log = Starting exit countdown function");
let countdown = items.getItem("Alarm_Countdown").state - 1;
console.info(`alarm_system_log = Exit countdown value: ${countdown}`);
items.getItem("Alarm_Countdown").sendCommand(countdown);
if (countdown > 0) {
if (countdownTimeoutId !== null) {
clearTimeout(countdownTimeoutId);
countdownTimeoutId = null;
}
countdownTimeoutId = setTimeout(exitCountdownFunc, 1000);
} else {
let alarmState = items.getItem("Alarm_State").state.toString();
if (alarmState === "ARMED_HOME" || alarmState === "ARMED_AWAY") {
let relevantSensorNames = alarmState === "ARMED_HOME" ? RELEVANT_SENSOR_NAMES_HOME : RELEVANT_SENSOR_NAMES_AWAY;
let groupItem = items.getItem(alarmState === "ARMED_HOME" ? "g_home" : "g_away");
let openSensors = groupItem.members.filter(item => {
return relevantSensorNames.some(sensorName => item.name.includes(sensorName)) && item.state === "OPEN";
});
if (openSensors.length > 0) {
console.info("alarm_system_log = Sensors are open after countdown, setting alarm to TRIGGERED");
items.getItem("Alarm_State").sendCommand("TRIGGERED");
if (alarmSoundTimeoutId !== null) {
clearTimeout(alarmSoundTimeoutId);
alarmSoundTimeoutId = null;
}
alarmSoundTimeoutId = setTimeout(alarmSoundFunc, 3000);
} else {
console.info(`alarm_system_log = Exit countdown reached zero, system is now ${alarmState}`);
}
}
}
console.info("alarm_system_log = Exit countdown function completed");
}
// Check for motion detectors 5 seconds
function checkDetectorOpenFor5Seconds(detectorItem, callback) {
console.info("alarm_system_log = Entering checkDetectorOpenFor5Seconds function for detector: " + detectorItem.name);
if (detectorItem.state === "OPEN") {
if (RELEVANT_SENSOR_NAMES_HOME.some(sensorName => detectorItem.name.includes(sensorName))) {
console.info(`alarm_system_log = The sensor '${detectorItem.label}' is an immediate trigger type. Triggering alarm.`);
callback(); // Execute the callback function immediately
return; // Exit the function
}
if (detectorTimers[detectorItem.name]) {
clearTimeout(detectorTimers[detectorItem.name]);
}
detectorTimers[detectorItem.name] = setTimeout(() => {
if (detectorItem.state === "OPEN") {
let relevantSensorNames = RELEVANT_SENSOR_NAMES_AWAY;
let g_awayMembers = items.getItem("g_away").members;
let openSensors = g_awayMembers.filter(item => {
try {
return relevantSensorNames.some(sensorName => item.name.includes(sensorName)) && item.state === "OPEN";
} catch (error) {
return false;
}
});
if (openSensors.length > 1) {
// Extract labels of the open sensors
let openSensorLabels = openSensors.map(sensor => sensor.label).join(", ");
console.info(`alarm_system_log = The following sensors are open together after 5 seconds: ${openSensorLabels}. Triggering alarm.`);
callback(); // Execute the callback function
} else if (openSensors.length === 1 && openSensors[0].name === detectorItem.name) {
// The detector is the only sensor open for 5 seconds
console.info(`alarm_system_log = The sensor '${detectorItem.label}' is the only one open after 5 seconds.`);
}
}
delete detectorTimers[detectorItem.name];
}, 5000);
} else {
if (detectorTimers[detectorItem.name]) {
clearTimeout(detectorTimers[detectorItem.name]);
delete detectorTimers[detectorItem.name];
}
}
}
// System start rule alarm
rules.JSRule({
uid: "system_start_rule_alarm",
name: "System start rule alarm",
description: "Set default values on system start",
tags: [],
triggers: [
triggers.SystemStartlevelTrigger(100) // Startup Complete
],
execute: (event) => {
withSafeExecution(() => {
try {
console.info("alarm_system_log = Starting System start rule alarm");
// Cancel any existing timeouts
if (alarmSoundTimeoutId !== null) {
console.info("alarm_system_log = Clearing existing alarm sound timeout");
clearTimeout(alarmSoundTimeoutId);
alarmSoundTimeoutId = null;
}
// Set default values for the alarm
if (items.getItem('Alarm_State').state !== 'UNKNOWN') {
items.getItem('Alarm_State').sendCommand('UNKNOWN');
}
if (items.getItem('Alarm_Countdown').state === 'NULL') {
items.getItem('Alarm_Countdown').sendCommand('0');
}
if (items.getItem('EntryTime_Home').state === 'NULL') {
items.getItem('EntryTime_Home').sendCommand('10');
}
if (items.getItem('ExitTime_Home').state === 'NULL') {
items.getItem('ExitTime_Home').sendCommand('10');
}
// Set the systemStarted flag to true after all other steps
cache.private.put('systemStarted', true);
console.info("alarm_system_log = System start rule alarm completed");
actions.NotificationAction.sendBroadcastNotification("System started so alarm can be started ");
} catch (error) {
console.error(`alarm_system_log = Error in System start rule alarm: ${error.message}`);
actions.NotificationAction.sendBroadcastNotification("Error in System start rule alarm.");
}
});
}
});
//Alarm system logic rule
rules.JSRule({
name: "Alarm System Rule",
description: "Monitors the state of the sensors and triggers the alarm when necessary",
triggers: [
triggers.ItemStateChangeTrigger("Alarm_State"),
triggers.GroupStateChangeTrigger("g_home"),
triggers.GroupStateChangeTrigger("g_away")
],
execute: (triggerData) => {
withSafeExecution(() => {
var systemStarted = cache.private.get('systemStarted', () => false);
if (!systemStarted) {
console.error("alarm_system_log = System is not fully started, skipping Alarm System Rule");
return;
}
console.info("alarm_system_log = Starting Alarm System Rule");
let triggeringItem = items.getItem(triggerData.itemName);
let alarmStateItem = items.getItem("Alarm_State");
if (triggeringItem.state === "NULL" || triggeringItem.state === "UNDEF" || alarmStateItem.state === "NULL" || alarmStateItem.state === "UNDEF") {
console.error("alarm_system_log = One or more items have NULL or UNDEF state in Alarm System Rule");
actions.NotificationAction.sendBroadcastNotification(`One or more items have NULL or UNDEF state in Alarm System Rule`);
return;
}
try {
let alarmState = alarmStateItem.state.toString();
console.info(`alarm_system_log = Alarm State: ${alarmState}`);
if (alarmState === "ARMED_HOME" && triggeringItem.state === "OPEN") {
let relevantSensorNames = RELEVANT_SENSOR_NAMES_HOME;
if (relevantSensorNames.some(sensorName => triggeringItem.name.includes(sensorName))) {
console.info(`alarm_system_log = Triggering item is OPEN in ${alarmState} mode: ${triggeringItem.label}`);
triggeredItems.push(triggeringItem);
let countdown = items.getItem("EntryTime_Home").state;
console.info(`alarm_system_log = Starting entry countdown with value: ${countdown}`);
items.getItem("Alarm_Countdown").sendCommand(countdown);
if (countdownTimeoutId !== null) {
clearTimeout(countdownTimeoutId);
countdownTimeoutId = null;
}
countdownTimeoutId = setTimeout(entryCountdownFunc, 1000);
}
}
if (alarmState === "ARMED_AWAY" && triggeringItem.state === "OPEN") {
console.info(`alarm_system_log = Triggering item is OPEN: ${triggeringItem.label}`);
triggeredItems.push(triggeringItem);
checkDetectorOpenFor5Seconds(triggeringItem, () => {
console.info(`alarm_system_log = Detector ${triggeringItem.label} has been open for more than 5 seconds, starting entry countdown`);
let countdown = items.getItem("EntryTime_Home").state;
console.info(`alarm_system_log = Starting entry countdown with value: ${countdown}`);
items.getItem("Alarm_Countdown").sendCommand(countdown);
if (countdownTimeoutId !== null) {
clearTimeout(countdownTimeoutId);
countdownTimeoutId = null;
}
countdownTimeoutId = setTimeout(entryCountdownFunc, 1000);
});
}
//adjust the time in the countdown item
if (triggerData.itemName === "Alarm_State" && (alarmState === "ARMED_HOME" || alarmState === "ARMED_AWAY")) {
let countdown = items.getItem("ExitTime_Home").state; // Using ExitTime_Home for both modes
console.info(`alarm_system_log = Starting exit countdown with value: ${countdown}`);
items.getItem("Alarm_Countdown").sendCommand(countdown);
if (countdownTimeoutId !== null) {
clearTimeout(countdownTimeoutId);
countdownTimeoutId = null;
}
countdownTimeoutId = setTimeout(exitCountdownFunc, 1000);
}
// Obtain the previous state of the Alarm_State item
let previousAlarmState = alarmStateItem.history.previousState().state;
// Compare the current state with the previous state
if ((alarmState !== previousAlarmState) &&
(alarmState === "ARMED_UNKNOWN" || alarmState === "ARMED_HOME" || alarmState === "ARMED_AWAY" || alarmState === "PENDING")) {
actions.NotificationAction.sendBroadcastNotification(`Alarm state changed to: ${alarmState}`);
}
console.info("alarm_system_log = Alarm System Rule completed");
}
catch (error) {
console.error(`alarm_system_log = Error in Alarm System Rule: ${error.message}`);
actions.NotificationAction.sendBroadcastNotification(`Error in Alarm System Rule: ${error.message}`);
}
});
}
});
//arm/disarm rule for alarm system
rules.JSRule({
name: "Arm/Disarm Rule",
description: "Arms or disarms the system when the correct code is entered, the gate reader changes state, or the gate receives an UP command",
triggers: [
triggers.ItemCommandTrigger("Alarm_Code"),
triggers.ItemCommandTrigger("Alarm_Command") // Listen for changes in Alarm_Command
],
execute: (triggerData) => {
withSafeExecution(() => {
var systemStarted = cache.private.get('systemStarted', () => false);
if (!systemStarted) {
console.info("alarm_system_log = System is not fully started, skipping rule");
return;
}
console.info(`alarm_system_log = Starting Arm/Disarm Rule, triggered by: ${triggerData.itemName}, new state: ${triggerData.receivedCommand}`);
try {
console.info(`alarm_system_log = Starting Arm/Disarm Rule, triggered by: ${triggerData.itemName}, new state: ${triggerData.receivedCommand }`);
// Check if the entered PIN is correct
if (triggerData.itemName === "Alarm_Code") {
let enteredPin = triggerData.receivedCommand.toString();
console.info(`alarm_system_log = PIN entered: ${enteredPin}`);
if (CORRECT_PINS.includes(enteredPin)) {
console.info("alarm_system_log = Correct PIN entered, attempting to disarm alarm");
disarmSystem();
console.info("alarm_system_log = Disarm command sent");
actions.NotificationAction.sendBroadcastNotification("Correct PIN entered");
} else {
console.warn(`alarm_system_log = Incorrect PIN entered, PIN was: ${enteredPin}`);
actions.NotificationAction.sendBroadcastNotification(`Incorrect PIN entered: ${enteredPin}`);
}
if (alarmSoundTimeoutId !== null) {
console.info("alarm_system_log = Clearing existing alarm sound timeout");
clearTimeout(alarmSoundTimeoutId);
alarmSoundTimeoutId = null;
}
if (countdownTimeoutId !== null) {
console.info("alarm_system_log = Clearing existing countdown timeout");
clearTimeout(countdownTimeoutId);
countdownTimeoutId = null;
}
//items.getItem("Alarm_Countdown").sendCommand('0');
}
// If the changed item is Alarm_Command, perform the necessary actions
if (triggerData.itemName === "Alarm_Command") {
let command = triggerData.receivedCommand.toString();
console.info(`alarm_system_log = Alarm Command: ${command}`);
if (command === "ARMED_HOME" || command === "ARMED_AWAY") {
let relevantSensorNames;
if (command === "ARMED_HOME") {
relevantSensorNames = RELEVANT_SENSOR_NAMES_HOME;
} else {
relevantSensorNames = RELEVANT_SENSOR_NAMES_AWAY;
}
let g_home = items.getItem("g_away");
let openSensors = g_home.members.filter(item => {
return relevantSensorNames.some(sensorName => item.name.includes(sensorName)) && item.state === "OPEN";
});
if (openSensors.length > 0) {
let openSensorLabels = openSensors.map(item => item.label).join(", ");
console.info(`alarm_system_log = Cannot arm the system because the following sensors are open: ${openSensorLabels}`);
actions.NotificationAction.sendBroadcastNotification(`Cannot arm the alarm because the following sensors are open: ${openSensorLabels}`);
return;
}
console.info(`alarm_system_log = Arming system with command: ${command}`);
items.getItem("Alarm_State").sendCommand(command);
let countdown = items.getItem("ExitTime_Home").state;
console.info(`alarm_system_log = Starting exit countdown with value: ${countdown}`);
items.getItem("Alarm_Countdown").sendCommand(countdown);
if (countdownTimeoutId !== null) {
console.info("alarm_system_log = Clearing existing countdown timeout");
clearTimeout(countdownTimeoutId);
countdownTimeoutId = null;
}
countdownTimeoutId = setTimeout(exitCountdownFunc, 1000);
} else if (command === "DISARM") {
console.info("alarm_system_log = Disarming system");
disarmSystem();
if (alarmSoundTimeoutId !== null) {
console.info("alarm_system_log = Clearing existing alarm sound timeout");
clearTimeout(alarmSoundTimeoutId);
alarmSoundTimeoutId = null;
}
if (countdownTimeoutId !== null) {
console.info("alarm_system_log = Clearing existing countdown timeout");
clearTimeout(countdownTimeoutId);
countdownTimeoutId = null;
}
//items.getItem("Alarm_Countdown").sendCommand('0');
}
}
console.info("alarm_system_log = Arm/Disarm Rule completed");
}
catch (error) {
console.error(`alarm_system_log = Error in Arm/Disarm Rule: ${error.message}`);
actions.NotificationAction.sendBroadcastNotification(`Error in Arm/Disarm Rule: ${error.message}`);
}
});
}
});
// Auto Arm Rule
rules.JSRule({
name: "Auto Arm Rule",
description: "Automatically arms the system to ARMED_AWAY if g_away has been CLOSED for the last 30 minutes",
triggers: [
triggers.GenericCronTrigger("0/60 * * * * ?") // This trigger runs every 30 seconds
],
execute: (triggerData) => {
withSafeExecution(() => {
var systemStarted = cache.private.get('systemStarted', () => false);
if (!systemStarted) {
console.error("alarm_system_log = System is not fully started, skipping rule");
return;
}
let alarmState = items.getItem("Alarm_State").state;
if (alarmState === "ARMED_AWAY" || alarmState === "PENDING" || alarmState === "TRIGGERED" || alarmState === "ARMED_HOME") {
console.info(`alarm_system_log = System is already: ${alarmState}`);
return;
}
try {
console.info("alarm_system_log = Starting Auto Arm Rule");
let g_away = items.getItem("g_away");
let isCurrentlyOpen = false;
let wasOpenedInLast30Minutes = false;
for (let i = 0; i < g_away.members.length; i++) {
let item = g_away.members[i];
if (item.state === "OPEN") {
isCurrentlyOpen = true;
}
if (item.history.changedSince(time.ZonedDateTime.now().minusMinutes(30))) {
wasOpenedInLast30Minutes = true;
}
if (isCurrentlyOpen || wasOpenedInLast30Minutes) {
console.info("alarm_system_log = Either a sensor is currently OPEN or there was a change in the sensors for the last 30 minutes.");
break;
}
}
if (!isCurrentlyOpen && !wasOpenedInLast30Minutes) {
items.getItem("Alarm_Command").sendCommand("ARMED_AWAY");
console.info("alarm_system_log = Sensors in g_away were not opened and are not currently open in the last 30 minutes, arming system to ARMED_AWAY");
actions.NotificationAction.sendBroadcastNotification("AWAY mode sensors have been CLOSED for the last 30 minutes, arming alarm to ARMED_AWAY");
}
console.info("alarm_system_log = Auto Arm Rule completed");
} catch (error) {
console.error(`alarm_system_log = Error in Auto Arm Rule: ${error.message}`);
actions.NotificationAction.sendBroadcastNotification(`Error in Auto Arm Rule: ${error.message}`);
}
});
}
});