Openhab burglar alarm diy! not a real one!

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}`);
        }
    });
    }
});
6 Likes

Consider releasing it to the marketplace.

I agree with this so long as the user understands the limitations, which you clearly do. If you make a template or put it in the marketplace, I’d suggest adding some commentary to ensure that others see both the positives (better than nothing) and the negatives (relative to a professionally monitored security system).

that is not a bad ideea but i need to figure out the notification part also dont repeat myself in the code and do more testing. I am self learned so not a programmer by trade so … it will take time

well in its state right now people that actually use this know the risks but indeed if i finish it and ask for putting it in the marketplace i will. The most insecure part so far seems the pin code in plain text any ideea how to encrypt and decript on the fly something like ?

:partying_face:

However, if you want to create an installable rule template, you’ll need to write this as a UI rule. At least for now, only managed rules support templates.

OH doesn’t have support for avoiding that yet. But there is a PR to add a crypto-store which is the first step.

Some comments on the rule:

Overall this looks a lot like a Nashorn rule that has been lightly adjusted to JS Scripting. There are a number of simplifications that could be added if the full openhab-js library were used.

This is only needed if one has changed the default setting of the JS Scripting addon to “Do not use built in variables”.

Because for managed rules any one rule could have multiple actions and conditions, requiring an import for every one is a larg burden.

All of this is (and should be) accessed through the openhab-js library. See JavaScript Scripting - Automation | openHAB

When/if you make a template these would become properties the user fills out while instantiating the rule from the template.

Note in a managed Script Action/Condition, because the context is reused from one run to the next, you cannot use const and let in the root context of the Script. You can use them inside of functions though.

When/if this becomes a template, most of these should become debug.

In JS Scripting the state is already a String. It’s redundant to call toString() here.

Also, in OH 3.4 + you can use the shorter options:

let alarmState = items.Alarm_State.state;
let alarmState = items["Alarm_State],state;

openhab-js has a time.toZDT() function which is a factory for ZonedDateTime Objects. If you pass it no arguments it will return now.

Using the js-joda classes it’s better to use Duration. At least you don’t need to mess with ChronoUnit.

    let stateChangeTime = detectorItem.lastUpdate;
    let elapsedTime = time.Duration.between(stateChangeTime, now).abs();
    return elapsedTime.compareTo(time.Duration.ofSeconds(5)) >= 0;

There are other ways to do this too.

So for now it’s only possible to have one rule per rule template. However, looking at the triggers here it’s definitely possible to combine this all into one rule and have different paths through the rule depending on what triggered the rule. For an example of this sort of thing look at my 4.0+ rule templates (e.g. MQTT Event Bus).

For a managed rule, use the shared cache for “global” variables. Use the private cache for variables that are only used by one rule but which need to persist from one rule to the next.

If you want to prevent rules from running until runlevel 100, see Delay Start [4.0.0.0;4.9.9.9].

Debounce [4.0.0.0;4.9.9.9] could be used for this part. That might simplify this rule significantly. Essentially, with a debounce on the door Items, this rule won’t even trigger until the door has been open for five seconds, saving you from even needing to check for that with timers and such.

Threshold Alert and Open Reminder [4.0.0.0;4.9.9.9] might also be used here but that would require a whole new approach for this capability. But again, the rule wouldn’t even be called until the door has been open for longer than five seconds. The advantage here though is there is a do not disturb period which could be useful and or enhanced to take into account the armed/disarmed status.

Well then I will have to rethink all of this but after I finish it in it’s wctual state to see if the logic is there.

Here I am struggling because most of the code you see is by looking in the forums…

Interesting but what happens if someone do not use the building settings of openhab. Have to test this…

Yes I know about it but still reading again I used examples on the forums.

I forgot about that

I am so ashamed now I should not even post this :pensive:. I still have a lot to learn.

Here is what I will take into consideration what you suggested and work my way slowly up.

The first place to look should always be the docs. The JS Scripting docs are very complete and very thorough.

If someone has disabled that setting, your rule will run. If that setting hasn’t been disabled then that require line is redundant at best. The openhab-js library is pretty robust so there shouldn’t be any issues requiring it twice.

For managed rules though, the need to require everything becomes a huge burden so managed rules users will rarely if ever disable that setting. All of my rule templates assume the library is automatically imported.

Examples in what language? Because Nashorn JS and JS Scripting are very different and Nashorn JS is going to be way more complicated and way longer because there it doesn’t have a helper library.

Always follow the docs. When adopting code from the forum, always look it up in the docs. The docs should be your primary resource for how to do something in JS Scripting. Code from the forum should always be compared to the docs and adjusted accordingly.

Do not be ashamed and don’t think this is bad code. This is how we learn. The most important thing that even professional programmers go through is a code review, You’ll learn more from one good code review than you’ll learn from reading a couple books cover to cover.

I don’t think that will work. If the user has the library automatically imported, this line will overwrite all the imports. It’s probably safe to just assume the setting is enabled or disabled. If you have file based rules it’s probably going to be the case that it’s disabled so include the require. If it’s a managed rule, it’s safe to assume that the library is injected automatically so just leave that out, or if you want to be really nice to your users print a meaningful error.

if(this.items === undefined) {
  console.error('openhab-js is not imported. Make sure "Use built in variables" is enabled');
  process.exit(1); // does this work? That's something I didn't know and could come in handy
}

Given the semantic model and linking process, it is almost always going to be easier to create Items using “Create equipment from Thing” so I’m not sure creating the Items in the rule is going to save the end user that much effort.

But, having said that, one trick I use is if the rule is triggered manually I’ll do setup and configuration steps and when the rule is triggered other ways I’ll do something else. You can tell if the rule was triggered manually if this.event === undefined.

And since this rule is triggered by system startup that’s probably not necessary. But if you were to combine the rule into one that’s one way to separate the configuration checking from processing.

Wouldn’t it make sense to rely on restoreOnStartup to return the Item to what ever state it had before OH rebooted? Maybe not Alarm_Countdown but the alarm state and times could easily be restored. Then you wouldn’t lose state to a restart.

Note, null and NULL are not the same and neither is undefined and UNDEF. null means that a variable has no value. undefined means the variable doesn’t even exist. NULL is a special state that an Item can carry meaning it’s not be initialized. UNDEF means the binding cannot determine the proper state for the Item.

One other gotcha is that triggeringItem is a Java Item Object, not the JS Item Object so it’s .state is going to be a Java State Object, not a String. But you are ahead of that problem with the way you defined triggeringItem.

Rewriting this as is but corrected it would be

if(triggeringItem.state == "NULL" || triggeringItem.state == "UNDEF" || alarmStateItem.state == "NULL" || alarmStateItem.state == "UNDEF)

but, looking at the JS Scripting docs we see there is a better way.

if(triggeringItem.isUninitalized || alarmStateItem.isUninitialized)

You don’t have to prepend “alarm_system_log =” to all your logging statements. Instead you can change the name of the logger. That’s even better because it means it’s easy for you to control that logger from log4j2.xml or the karaf console.

console.loggerName = 'org.openhab.automation.alarm_system_log.'+ruleUID;

The ruleUID is a good idea but not necessary. It will help you separate logs based on the rule they come from. But of course you’d have to have that line in each rule to work. If you omit the ruleUID than you can put that line at the top of the file.

Hello i made some progress on the script i updated my first post. Its been running for a week without issues i even added an auto arm function also tried to arrange the code a little bit better. I hope i implemented all your suggestions. Do you mind having a look again into it for me its been working on openhab 4.0.2. Now i looked a little bit into the template scripting but oh my to get all that working will be a something else. The most important is that the script is in working state wich for me it is i know its not following all the documentation of the js addon but the most important is that it works in a safe manner i hope. Now i will start poking around to see if i can get it into a template and start using your library to simplify the code. Wish me luck.

See How to write a rule template. Once the code works you’ve done the hard part. The rest is really mostly just formatting and tedium.

I’ve been pushing here and there to make the experience better but it’s slow going.

At a high level, make the rule a managed rule. Add the “configDescriptions” section for all the parameters you want the end user to be able to set. See [Do not install] Rule Template Parameters Experiments and Examples for how to define parameters of various types (e.g. an Item, Thing, Channel, boolean, etc.). In the actual rule, you’ll add {{ param }} where “param” is the name of the parameter. At instantiation time those will be replaced with what the user selected/entered in the config.

All this checking is probably unnecessary. The openhab library comes with the add-on. If this code is going to be able to run at all in the first place, openhab is going to be there. So this can all safely be reduced to

const { items, rules, log, triggers, actions } = require('openhab');

As mentioned before, when/if this becomes a template, these will be populated from the parameters.

Note, a trick that has served me well in my rule templates is to assign all the parameters to constants/variables at the top of the scripts. That way you can update the template easily by just replacing everything below the definition of the variables without needing to remember where something needs to be replaced with a {{ param }}.

When moving to a rule template you’d want to store these in the cache. If you manage to merge all the rules into one rule you can use the private cache. If multiple rules you’ll want to use the shared cache.

If you use the cache, you should use openHAB timers instead of JS timeouts as then when the rule gets reloaded, the existing timers will be cancelled automatically for you instead of becoming orphaned and continuing to run in the background.

I always get a little weary when I see locks in code like this. I’m not saying it’s not appropriate here but it does raise flags.

It might be better to utilize a ReentrantLock but I’m not sure that’s possible in JS Scripting given it’s single threaded access requirements.

Because a failure to release the lock can have devastating impacts on these rules, you should always make sure the lock is released by using try/catch/finally and release the lock in the finally. NOTE: I don’t know if this is true for JS Scripting but in Rules DSL, not all errors that occur are catchable meaning even then you might end up with a lock that is forever locked and can never be unlocked until a restart.

In general, it’s better to rely on the fact that only one instance of a given rule can run at a time. That will mean all this locking stuff will come to you for free. But that requires merging the rules into one rule.

The openhab_rules_tools library has a built in countdown timer. That can free up a lot of code here. openHAB Rules Tools Announcements OHRT is available through openHABian or via npm.

I think all of this logic could be managed through Debounce [4.0.0.0;4.9.9.9] and a Group. You’d debounce the motion sensors and have them a member of a Group with a count aggregation function. You can check if Group’s state > 1 to trigger the alarm.

Be careful here. If you have a Thing that never comes online your system start levels will stall out at level 60. You wouldn’t want your alarm system to be disabled just because a Light is broken.

When pondering whether to combine these rules into one rule, you can distinguish between the various events. For example, my debounce rule template:

       if(this.event === undefined) {
          init();
        }

        else {
          switch(event.type) {
            case 'GroupItemStateChangedEvent':
            case 'ItemStateEvent':
            case 'ItemStateChangedEvent':
            case 'ItemCommandEvent':
              debounce();
              break;
            default:
              init();
          }
        }

In that case if it’s an Item event I run the rule. If it’s not an item event, I just validate the configuration of the Items and the rule.

This should never happen. The rule should only be triggered when it’s newly loaded and the start level has passed. If the rule is newly loaded it means there is nothing left over from the previous rule. If by chance alarmSoundTimeoutId isn’t null it means that one of your other rules somehow manged to run before this system started rule and that means your whole situation just got a lot more complicated.

If might be a better approach to have a rule trigger at startlevel 40 that disables the rules that should run and then only after this rule runs reenable them. That would prevent the situation sited above in almost all cases (not all). There’s a rule template for that too. :wink: Delay Start [4.0.0.0;4.9.9.9]

In a managed rule, it’s not running in a function so return doesn’t work. You’d have to use process.exit(1).

The state of an Item will never be null and never be undefined. They will be NULL or UNDEF. They are not the same. This is a bug. Use

if(triggeringItem.isUnintialized || alarmStateItem.isUninitialized)

Over all I see a good deal of duplicated code. Consider applying Design Pattern: How to Structure a Rule so some of these to avoid a lot. For example, when you have a case where you have a lot of sending of broadcasts, you should only have one line that actually sends the broadcast. The rest of the code just creates the message.

I bring up the rule templates and library not to encourage you to use them (though that would be good too) but as another example of a way to achieve the same goal.

Apreciate the time given to check. Mostly you are pointing to the same issues as mentioned in the earlier posts. I am trying every evening to read a little bit that is how I discovered the lock and all that and by trial and error I added modified etc always testing all possible scenarios. So far it passed. But I agree it’s ugly and repetitive Wich is why I want to use your library and try to merge this in one rule but I lack the brain capacity.

Well all this probably I will remove but will all the experiments I do sometimes I forget that JavaScript is not installed etc.

This I will probably implement I just started reading about cache and also openhab timers but I will probably for timers end up with your library for it.

Hmm I will have to look more into this.

The whole script is made to go in disarm state because like I said this is not a real alarm and we don’t want to annoy the neighborhood for false alarm.

You already mentioned this part but forgot to modify. So rry

Well that is the whole point now for me to start using your library to not have to deal with all that but first I need to make sure the original ideea seems good.

Anyway thank you again for the points.

Sorry for the slow reply. I’ve been away from home, and unable to reply in detail.

My previous post was actually referring to the fact that a DIY alarm system is almost never as robust as a purpose-built system. 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.

Most people don’t really need that level of security. If you just want to monitor your doors/windows and maybe scare off random burglars, DIY is fine, but it’s not going to stop someone who is determined to get into your home.

So as I said before, anyone who goes the DIY route needs to understand the limitations of their system. It’s not PIN codes and hacking that you need to worry about–that’s an entirely different security concern–it’s people cutting your power/telecom, or breaking a window and silencing the alarm before the neighbours get concerned.

I’m cautious about DIY alarm systems (including any off-the-shelf, unmonitored products) giving people an illusion of safety and security that isn’t really there. It’s important to understand that they’ve reduced some risk, but perhaps not as much as they think.

That’s just my opinion, though. If you disagree, feel free to ignore what I’ve said if/when you share your final solution.

Oh but I agree with you fully by trade I am somewhere in that field. The whole point here is to have something than nothing. Now maybe it creates the illusion of safety but I believe common sense should apply in this case.
Now based on my knowledge of pro alarm systems and electrical background I could say with confidence that even the most complex ones can be bypassed in a quiet manner no matter how well the siren is build or if you have 2 telephone lines connected and two simcard and lan and lorawan etc and the most sofisticated dual sensors and all kind of trickery with specific resistance values monitoring dual battery backup etc If there is a will and some planning you can be inside the house taking a selfie with the alarm centrale within 1 minute :wink:
But in real life it’s your friendly burglar with not prior knowledge so for that my script will be more than sufficient to scare him and me to get an notification.

Hey Rich,
I tried that one but brings up the following error message:

TypeError: (intermediate value).exit is not a function

OP’s code is the first I’ve ever seen that used so I was just assuming it worked. I never tried it myself. It may not work at all.

Until now I need to use an ugly workaround for an early exit

throw new Error;

so I was hoping that I missed anything to make process.exit work.

Less ugly work arounds include:

  • put the early exit in a rule condition (all managed rules or JS Scripting rules using rule builder and .if(fn))
  • Put the script action into a function.
(function(event) {
    // script code goes here
})(event)
  • restructure your if statements so that they test for whether to do something instead of testing for whether to not do something.

Unfortunately, common sense often isn’t as common as we tend to think it is.

I’m not questioning your understanding–like I said, it’s clear that you get it. But I caution against assuming that other people will get it. So, all I’m suggesting is that you include some commentary speaking to the weaknesses. They may not read it, but then you at least know you’ve said it and haven’t contributed to their false sense of security.

From what I can see most people here in the forums are knowledgeable enough to discern. Looking at the neighbors alarmo I don’t see any warning on the contrary they make it look like a robust alarm. But just out of curiosity do you have something similar in your setup for security or a real hardwire/wireless dedicated alarm?

Every once in awhile we get someone coming here asking how to build an alarm system from openHAB. They are not knowledgeable enough to discern, and we steer them toward professional alarm systems. :wink:

Anyway, I’ll leave it at that. I’ve just learned to not make assumptions about what other people do and do not know.

I live on an upper floor of a condo building with a controlled entrance, so all I have is a Z-Wave door lock. But it’s mostly there to automatically lock when I go to bed.