openHAB file based home alarm, burglar/intruder alarm items, rules etc - my config

Hello fellow members, I have been trying to implement home alarm / intruder alarm in my openHAB setup for some time now. There are quite a few threads here in the community pages on this subject. Being a complete noob in coding / programming, I can only understand little bit of file-based config. With the great help from [this] thread and [this] and many other discussions, I am sharing my config here. Apologies if this is not the ideal solution one finds, though it’s working fine on my side and wanted to share this with our great openHAB community !
I do agree with other users and moderators that this DOESN’T REPLACE A REAL ALARM SYSTEM but I think it could be a good backup in general.

Three zones : doors (contact), windows (contact) and PIRs (switch, though I prefer contact for motion sensors too). A countdown timer to see how many seconds are remaining, which updates every second. Panic button behind selection to avoid accidental touch, deactivation of panic alarm is via panic button only.
Four zones for audio : buzzer1, buzzer2, indoor siren and outdoor siren). I have opted to use DFPlayer for audio notifications, but user have a choice to connect physical beeper (Zone11), buzzer (Zone12) and sirens (Zone21 & Zone22) accordingly. DFPlayer with Tasmota is what I have found quite useful to play pre-recorded audio notifications locally, without cloud, and has been working brilliant for last few years. Three MP3 files for this to replicate Zone11, Zone12 and Zone21 (Sound files ‘21’, ‘22’ and ‘23’ as in rules file).
(Optional) RFTransiver with Tasmota to receive and send 433MHz data, which can replicate to trigger corresponding command (arm, stay, disarm and panic/sos ).
I presume user already have door, window and motion sensor things and items defined in their openHAB system, which need to include in relevant group (gAlarmdoors, gAlarmwindows, gAlarmsensors) accordingly.
Arm/Stay/Disarm can be activated via sitemap obviously, as well as with RF remote if using RFTransiver and defined RF codes will replicate the same.
Arm/Stay/Disarm won’t work from any of above if Panic button is activated (either via sitemap or RF remote).
Delay timer switch to input arm, stay and entry delay times in seconds.
Mute switch to turn off audio notifications, but bypassed if alarm is triggered.
RFLink switch to activate / deactivate RF data.
items file :


Group gAlarm "Alarm" <siren> ["Equipment"]   // group for alarm items to persist

Group:Contact:OR(OPEN, CLOSED) gAlarmDoors		<doorfront>	(gHome)
Group:Contact:OR(OPEN, CLOSED) gAlarmWindows	<window>	(gHome)
Group:Switch:OR(ON,OFF) gAlarmSensors	        <man>		(gHome)

Group:Switch:OR(ON,OFF) gAlarmOutputs			            (gHome)

Group:Switch:OR(ON,OFF)   gZones  "Zones"

Contact Zone1 "Zone1 Door"    <door> 	(gAlarmDoors)	      //  { channel="mqtt:topic:TuyaDoor:TuyaDoor" }		
Contact Zone2 "Zone2 Window"  <window>  (gAlarmWindows)       //  { channel="mqtt:topic:TuyaDoor:TuyaDoor" }  
Switch  Zone3 "Zone3 PIR" 	  <motion> 	(gAlarmSensors)

Switch Zone11 "Buzzer1" <soundvolume> (gZones)  // waiting buzzer - Physical active beeper buzzer
Switch Zone12 "Buzzer2" <soundvolume> (gZones)  { expire="01s,command=OFF" }  // confirm buzzer - Physical active buzzer

Switch Zone21  "Indoor Siren"   <soundvolume>  (gAlarmOutputs, gZones)  // Physical active siren
Switch Zone22  "Outdoor Siren"  <soundvolume>  (gAlarmOutputs, gZones)  // Physical active siren

Switch Panic_Confirm "Panic Confirm" (gAlarm)    // to confirm panic button
Switch Panic_Button  "Panic Button"  (gAlarm)    // for indoor and outdoor sirens
Switch Mute          "Mute"          (gAlarm)    // enable / disable Buzzer1, Buzzer2 and Indoor Siren during arming & disarming
                                                 // But bypassed if Alarm is triggered.

String AlarmMode 	"Alarm Mode"		<alarm> 	    (gAlarm)     
String currentAlarm "Current alarm"		<alarm>         (gAlarm)	
String Countdown    "Countdown [%s]"    <time>		

Switch Arm      "Arm"  		(gAlarm)    [ "Switchable" ] //Switches for Google Home/alexa - need to define in new rules
Switch Disarm   "Disarm"    (gAlarm)  	[ "Switchable" ] //Switches for Google Home/alexa - need to define in new rules
Switch Stay     "Stay "     (gAlarm)  	[ "Switchable" ] //Switches for Google Home/alexa - need to define in new rules

Switch Delay_Timer  "Delay Timer"   (gAlarm) // for sitemap visibility of delay timers input.

Number Arm_Delay_Time        "Arm Delay Time [%.0f s]"     (gAlarm)  // exit delay timer
Number Stay_Delay_Time       "Stay Delay Time [%.0f s]"    (gAlarm)  // home armed delay timer
Number Entry_Delay_Time      "Entry Delay Time [%.0f s]"   (gAlarm)  // entry delay timer


///// BELOW ITEMS FOR RF REMOTE OF ALARM PANEL ///////////

Switch  RFLink          "RF Link"         (gAlarm)        ["Switchable"]   
String  Disarm_RfCode   "Disarm RfCode"   (gRfCodes)      ["Status"]  // for RF remote control of alarm panel
String  Arm_RfCode      "Arm RfCode"      (gRfCodes)      ["Status"]  // for RF remote control of alarm panel
String  Stay_RfCode     "Stay RfCode"     (gRfCodes)      ["Status"]  // for RF remote control of alarm panel
String  Panic_RfCode    "Panic RfCode"    (gRfCodes)      ["Status"]  // for RF remote control of alarm panel

///// BELOW ITEMS FOR RF TRANSIVER ///////////

Group   gRfTransiver1           "Rf Transiver 1"            <sensor>    (gHome)                         ["Equipment"]
String  RfTransiver1            "Rf Transiver 1"            <sensor>    (gRfTransiver1)                 ["Sensor"]          { channel="mqtt:topic:RfTransiver1:Rf_Transiver1"}
Switch	RfTransiver1_Reachable	"RfTransiver1_Reachable"	<switch>	(gRfTransiver1, gMonitor)	    ["Switch"]          { channel="mqtt:topic:RfTransiver1:reachable"}
String  RfSend1                 "RfSend 1"                  <sensor>    (gRfTransiver1)                 ["Sensor"]          { channel="mqtt:topic:RfTransiver1:RfSend1"}   


/////// BELOW ITEMS FOR SOUND /AUDIO //////////////////////

Group    gSound                     "Sound"                             <light>         (gHome)                 ["Equipment"]       
String   Sound                      "Sound"                             <light>         (gSound)                ["Control"]        {channel="mqtt:topic:Sound:Sound"}
String   SoundStop                  "Sound Stop"                        <light>         (gSound)                ["Control"]        {channel="mqtt:topic:Sound:SoundStop"}
String   SoundVolume                "Sound Volume"                      <light>         (gSensors, gSound)      ["SoundVolume"]    {channel="mqtt:topic:SoundVolume:SoundVolume"}
Number   SoundVolumeSetpoint        "Sound Volume Setpoint [%.1f]"      <soundvolume>   (gSound)                ["SoundVolume"]

rules file :


var Timer delayTimer = null
var Timer disarmTimer = null 
var Timer countdownTimer = null
var int remainingTime = 0

rule "Alarm"
when
    Item AlarmMode received command
then
            
        if(delayTimer !== null) {
            delayTimer?.cancel()
            delayTimer = null
        }

            Thread::sleep (1000)  

        if(AlarmMode.state == "Disarm" && Panic_Button.state != ON) {

            if(disarmTimer !== null) {
                disarmTimer?.cancel()
                disarmTimer = null
            }
                
            if(countdownTimer !== null) {
                countdownTimer?.cancel()
                countdownTimer = null
            }

            sendCommand(Countdown, "---")

            currentAlarm.postUpdate(AlarmMode.state)
            Disarm.sendCommand(ON)
            Arm.sendCommand(OFF)
            Stay.sendCommand(OFF)
            
            
            if (RFLink.state == ON ) {
                RfSend1.sendCommand (Disarm_RfCode.state.toString)   
            }
            
            Zone11.sendCommand(OFF)
            //Zone12.sendCommand(ON)

            Zone21.sendCommand(OFF)
            Zone22.sendCommand(OFF)

            if(Mute.state == OFF || Mute.state == NULL) {
                
                Sound.sendCommand ("22")
                Zone12.sendCommand(ON)
            }

            if(Mute.state == ON) {
                SoundStop.sendCommand ("stop")
            }
            
            logInfo("Alarm", "Alarm is Disarmed.")
            sendBroadcastNotification("Alarm is Disarmed.")    

        }

        /////////////////////////////////////////////////////////////////////////////////

        if(AlarmMode.state == "Stay"  && Panic_Button.state != ON) {
            
            val armDelay = (Stay_Delay_Time.state as Number).intValue
            logInfo("Alarm", "Delayed arming started for " + armDelay + " seconds.")
            currentAlarm.postUpdate("Pending " + AlarmMode.state.toString)
                
            if(Mute.state == OFF) {
                Sound.sendCommand ("21")
                Zone11.sendCommand(ON)
            }
            
            //Zone11.sendCommand(ON)
			Thread::sleep (1000)

            remainingTime = armDelay			
            delayTimer = createTimer(now.plusSeconds(armDelay), [|

                if (gAlarmDoors.state != CLOSED || gAlarmWindows.state != CLOSED) {
                    
                    delayTimer?.cancel()
                    delayTimer = null  
                    
                    currentAlarm.postUpdate("Alarm failed to set.")
                    logInfo("Alarm", "Alarm failed to set.")
                    sendBroadcastNotification("Alarm failed to set.")

                    if(countdownTimer !== null) {
                        countdownTimer?.cancel()
                        countdownTimer = null
                    }

                    Countdown.sendCommand ("- stay failed-")
                    Sound.sendCommand ("23")
                    Zone21.sendCommand (ON)

                    if (RFLink.state == ON ) {
                        RfSend1.sendCommand (Panic_RfCode.state.toString) 
                    }

                }
                        
                else {
                    Stay.sendCommand(ON)
                    Disarm.sendCommand(OFF)
                    Arm.sendCommand(OFF)
                    currentAlarm.postUpdate(AlarmMode.state.toString)
                    //Zone11.sendCommand(OFF)
                    //Zone12.sendCommand(ON)

                    if (RFLink.state == ON ) {
                        RfSend1.sendCommand (Stay_RfCode.state.toString) 
                    }
                    
                    if(Mute.state == OFF) {
                        Sound.sendCommand ("22")
                        Zone11.sendCommand(OFF)
                        Zone12.sendCommand(ON)
                    }
                    
                    logInfo("Alarm", "Home Arm is Set")
                    sendBroadcastNotification("Home Arm is Set.")
                                
                } 
            ])

            if(countdownTimer !== null) {
                countdownTimer?.cancel()
                countdownTimer = null
            }
                    
            countdownTimer = createTimer(now.plusSeconds(1), [|
                if (remainingTime > 0) {
                    remainingTime -= 1
                    Countdown.sendCommand (remainingTime.toString + " seconds remaining")
                    countdownTimer.reschedule(now.plusSeconds(1))
                } 
                else {
                    Countdown.sendCommand ("---")
                    countdownTimer = null
                }
            ])

        }


////////////////////////////////////////////////////////////////////////////////////////////////////////////
        
        if(AlarmMode.state == "Arm"  && Panic_Button.state != ON) {
            
            val armDelay = (Arm_Delay_Time.state as Number).intValue
            logInfo("Alarm", "Delayed arming started for " + armDelay + " seconds.")
            currentAlarm.postUpdate("Pending " + AlarmMode.state.toString)

            if(Mute.state == OFF) {
                Sound.sendCommand ("21")
                Zone11.sendCommand(ON)
            }

            //Zone11.sendCommand(ON)
			Thread::sleep (1000)

            remainingTime = armDelay
            delayTimer = createTimer(now.plusSeconds(armDelay), [|

                if (gAlarmDoors.state != CLOSED || gAlarmWindows.state != CLOSED || gAlarmSensors.state != OFF) {
                                   
                    delayTimer?.cancel()
                    delayTimer = null  
                    
                    currentAlarm.postUpdate("Alarm failed to set")
                    
                    logInfo("Alarm", "Alarm failed to set")
                    sendBroadcastNotification("Alarm failed to set.")
                    Zone21.sendCommand(ON)
                    Zone22.sendCommand(ON)
                    
                    if(countdownTimer !== null) {
                        countdownTimer?.cancel()
                        countdownTimer = null
                    }

                    Countdown.sendCommand ("-arm failed-")
                    Sound.sendCommand ("23")
                    Zone21.sendCommand (ON)
                    Zone22.sendCommand (ON)

                    if (RFLink.state == ON ) {
                        RfSend1.sendCommand (Panic_RfCode.state.toString) 
                    }

                }
                        
                else {
                    Arm.sendCommand(ON)
                    Stay.sendCommand(OFF)
                    Disarm.sendCommand(OFF)
                    currentAlarm.postUpdate(AlarmMode.state.toString)
                    // Zone11.sendCommand(OFF)
                    // Zone12.sendCommand(ON)

                    if (RFLink.state == ON ) {
                        RfSend1.sendCommand (Arm_RfCode.state.toString) 
                    }

                    if(Mute.state == OFF) {
                        Sound.sendCommand ("22")
                        Zone11.sendCommand(OFF)
                        Zone12.sendCommand(ON)
                    }

                    logInfo("Alarm", "Away Arm is Set")
                    sendBroadcastNotification("Away Arm is Set.")
                } 
            ])

                    
            if(countdownTimer !== null) {
                countdownTimer?.cancel()
                countdownTimer = null
            }
                    
            countdownTimer = createTimer(now.plusSeconds(1), [|
                if (remainingTime > 0) {
                    remainingTime -= 1
                    sendCommand(Countdown, remainingTime.toString + " seconds remaining")
                    countdownTimer.reschedule(now.plusSeconds(1))
                } 
                else {
                    Countdown.sendCommand ("---")
                    countdownTimer = null
                }
            ])    
        }
end



rule "Door or Window Opened and alarm is set to Away or stay"
when
	Member of gAlarmDoors changed to OPEN or
	Member of gAlarmWindows changed to OPEN
then

    if (currentAlarm.state  == "Arm" || currentAlarm.state == "Stay") {
        
        disarmTimer?.cancel()
        disarmTimer = null

        logInfo("Alarm", triggeringItem.label+" opened while alarm is on !!")

        val disarmDelay = (Entry_Delay_Time.state as Number).intValue
        logInfo("Alarm", "Delayed disarming started for " + disarmDelay + " seconds.")
        currentAlarm.postUpdate("Siren Pending in" + disarmDelay + " seconds.")

        Sound.sendCommand ("21")
        
        Zone11.sendCommand(ON)
        Thread::sleep (1000)

        remainingTime = disarmDelay
        disarmTimer = createTimer(now.plusSeconds(disarmDelay), [|
            Zone11.sendCommand(OFF)
            Zone21.sendCommand(ON)
            Zone22.sendCommand(ON)

            Sound.sendCommand ("23")

            if (RFLink.state == ON ) {
                RfSend1.sendCommand (Panic_RfCode.state.toString) 
            }
            
            currentAlarm.postUpdate("Alarm Sirens activated..!")
            sendBroadcastNotification("Alarm Sirens activated..!","error","high")

        ])

        if(countdownTimer !== null) {
            countdownTimer?.cancel()
            countdownTimer = null
        }
                    
        countdownTimer = createTimer(now.plusSeconds(1), [|
            if (remainingTime > 0) {
                remainingTime -= 1
                sendCommand(Countdown, remainingTime.toString + " seconds remaining")
                countdownTimer.reschedule(now.plusSeconds(1))
            } 
            else {
                Countdown.sendCommand ("---")
                countdownTimer = null
            }
        ]) 
        
    }

end

rule "Motion sensor trigered and alarm is set to arm"
when
	Member of gAlarmSensors changed to ON 
then
        
	if (currentAlarm.state  == "Arm") {
        
        disarmTimer?.cancel()
        disarmTimer = null
        
        logInfo("Alarm", triggeringItem.label+" motion alarm is triggered !!")

        val disarmDelay = (Entry_Delay_Time.state as Number).intValue
        logInfo("Alarm", "Delayed disarming started for " + disarmDelay + " seconds.")
        currentAlarm.postUpdate("Siren Pending in" + disarmDelay + " seconds.")

        Sound.sendCommand ("21")
        
        Zone11.sendCommand(ON)
		Thread::sleep (1000)

        remainingTime = disarmDelay
        disarmTimer = createTimer(now.plusSeconds(disarmDelay), [|
            Zone11.sendCommand(OFF)
            Zone21.sendCommand(ON)
            Zone22.sendCommand(ON)

            Sound.sendCommand ("23")

            if (RFLink.state == ON ) {
                RfSend1.sendCommand (Panic_RfCode.state.toString) 
            }
           
            currentAlarm.postUpdate("Alarm Sirens activated..!")
            sendBroadcastNotification("Alarm Sirens activated..!","error","high")
        ])

        if(countdownTimer !== null) {
            countdownTimer?.cancel()
            countdownTimer = null
        }
                    
        countdownTimer = createTimer(now.plusSeconds(1), [|
            if (remainingTime > 0) {
                remainingTime -= 1
                sendCommand(Countdown, remainingTime.toString + " seconds remaining")
                countdownTimer.reschedule(now.plusSeconds(1))
            } 
            else {
                Countdown.sendCommand ("---")
                countdownTimer = null
            }
        ]) 
        
    }

end


rule "Panic"
when
    Item Panic_Button received update
then

    if (Panic_Button.state == (ON)) {

            if(countdownTimer !== null) {
                countdownTimer?.cancel()
                countdownTimer = null
            }
            
            if(delayTimer !== null) {
                delayTimer?.cancel()
                delayTimer = null
            }

            if(disarmTimer !== null) {
                disarmTimer?.cancel()
                disarmTimer = null
            }


            Countdown.sendCommand ("---")

            if(Panic_Confirm.state != (ON)) {  
                Panic_Confirm.sendCommand (ON)
            }

            Zone21.sendCommand(ON)
            Zone22.sendCommand(ON)

            SoundStop.sendCommand ("stop")
            Thread::sleep (500)
            Sound.sendCommand ("23")

            if (RFLink.state == ON ) {
                RfSend1.sendCommand (Panic_RfCode.state.toString) 
            }

            currentAlarm.postUpdate("Alarm Sirens activated..!")
            sendBroadcastNotification("Alarm Sirens activated..!","error","high")
    }

    
    if (Panic_Button.state == (OFF)) {    
        
        AlarmMode.sendCommand ("Disarm")
        Panic_Confirm.sendCommand (OFF)
    
    }
end

rule "Panic confirm "
when
    Item Panic_Confirm received update
then
    if (Panic_Confirm.state == OFF && Panic_Button.state == ON) {

        Panic_Confirm.postUpdate (ON)
        
        logInfo("Alarm", "Panic Button is still ON")

    } 

end


rule "RFLink down"
when
    Item RFLink received update
then 
    
    if (RFLink.state == ON && RfTransiver1_Reachable.state != ON) {

        RFLink.postUpdate (OFF)

        sendBroadcastNotification("..RF Link Down..!!!","error","high")
        logInfo("Alarm", "..RF Link Down..!!!")

    }

end

///////////// BELOW RULE FOR RF REMOTE AND ALARM PANEL ////////////////////////////////////


rule "Rf Receiver1"
    when
        Item RfTransiver1 received update // Need received update if Data has not changed but button still pressed
    then
        //  Thread::sleep(100) // Need the sleep to allow for the state to change


        if(RFLink.state == ON ) {

            if (RfTransiver1.state == Stay_RfCode.state && Panic_Button.state != ON ) {
                AlarmMode.postUpdate ("Stay") 
            }

            if (RfTransiver1.state == Arm_RfCode.state && Panic_Button.state != ON) {
                AlarmMode.postUpdate ("Arm") 
            }

            if (RfTransiver1.state == Disarm_RfCode.state && Panic_Button.state != ON) {
                AlarmMode.postUpdate ("Disarm") 
            } 

            if (RfTransiver1.state == Panic_RfCode.state ) {
            
                if(Panic_Button.state != (ON)) {
                    Panic_Button.postUpdate (ON) 
                }
            
                if(Panic_Button.state != (OFF)) {
                    Panic_Button.postUpdate (OFF) 
                }
            }

        }

end

other rules for RF & Audio :


rule "Set Default Values WHEN SYSTEM STARTS"
    when
        System started
    then
        Thread::sleep(10 * 1000) // let persistance finish restoring a few seconds

        Disarm_RfCode.postUpdate('0xAAAAAA')
        Arm_RfCode.postUpdate('0xBBBBBB')
        Stay_RfCode.postUpdate('0xCCCCCC')
        Panic_RfCode.postUpdate('0xDDDDDD')  

        SoundVolumeSetpoint.sendCommand(50)

end


rule "Sound Volume setpoint"
    when
        Item SoundVolumeSetpoint received update  
    then
        
            sendCommand(SoundVolume, SoundVolumeSetpoint.state.toString)
                 
end

things file for RF & Audio :


Thing mqtt:topic:RfTransiver1 " RfTransiver1 " (mqtt:broker:myMqtt) @ "Porch"  
    {
        Channels:
            Type switch : reachable "Reachable"         [ stateTopic="tele/RfTransiver1/LWT", transformationPattern="MAP:reachable.map" ]
            Type string : Rf_Transiver1 "RfTransiver1"  [ stateTopic="tele/RfTransiver1/RESULT" , transformationPattern="JSONPATH:$.RfReceived.Data" ]          
            Type string : RfSend1 "RfSend1"             [ commandTopic="cmnd/RfTransiver1/rfsend"  ]

    }


 Thing mqtt:topic:Sound " Sound " (mqtt:broker:myMqtt) @ "Porch"  
    {
         Channels:
            Type switch : reachable "Reachable"        [ stateTopic="tele/Sound/LWT", transformationPattern="MAP:reachable.map" ]
            Type string : Sound "Sound"                [ commandTopic="cmnd/Sound/mp3track"  ]
            Type string : SoundStop "Sound Stop"       [ commandTopic="cmnd/Sound/mp3stop"  ]
            Type string : SoundVolume "Sound Volume"   [ commandTopic="cmnd/Sound/mp3volume" ]
            
   }

sitemap file :

sitemap Smarthome label="Smarthome" {


        Frame  {
                    
            Text label="Alarm" icon="siren" {
                
                Selection item=Panic_Confirm label="Panic"         icon="error"  labelcolor=[=="OFF"="orange",=="ON"="red"]  valuecolor=[=="OFF"="orange",=="ON"="red"]  mappings=[ON="YES", OFF="NO"]
                Switch    item=Panic_Button  label="Panic Button"  icon="sun"    iconcolor=[=="OFF"="green",!="OFF"="red"]  labelcolor=[=="OFF"="green",!="OFF"="red"]   visibility=[Panic_Confirm == ON]
                
                Text item=gAlarmOutputs label="Siren [%s]" icon="siren"	 valuecolor=[=="OFF"="green",!="OFF"="red"]
                Text item=currentAlarm 	label="Alarm State[%s]" icon="shield" iconcolor=[currentAlarm == "Disarm"="green",currentAlarm != "Disarm"="red"] valuecolor=[currentAlarm == "Disarm"="green",currentAlarm!="Disarm"="red"] labelcolor=[currentAlarm=="Disarm"="green",currentAlarm!="Disarm"="red"]
		        Text item=Countdown label="Countdown [%s]"  icon="time"  valuecolor=[=="---"="green",!="---"="red"]

                Switch item=AlarmMode label="" 	icon="none"		mappings=[Arm="Arm",Stay="Stay",Disarm="Disarm"]
		    
                Group item=gAlarmDoors   label="Doors [%s]" icon="door"  valuecolor=[=="CLOSED"="green",!="CLOSED"="red"]  labelcolor=[=="CLOSED"="green",!="CLOSED"="red"]
                Group item=gAlarmWindows label="Windows [%s]" icon="window"  valuecolor=[=="CLOSED"="green",!="CLOSED"="red"]  labelcolor=[=="CLOSED"="green",!="CLOSED"="red"]
                Group item=gAlarmSensors label="PIRs [%s]" icon="motion"  valuecolor=[=="OFF"="green",!="OFF"="red"]  labelcolor=[=="OFF"="green",!="OFF"="red"]
            
                Group item=gZones label="Audio Zones" icon="pump"
                 
                Switch item=Mute label="Mute"
                Setpoint item=SoundVolumeSetpoint label="Alarm Volume" minValue=5 maxValue=95 step=5
                Switch item=Delay_Timer label="Delay Timer" 
                Setpoint item=Arm_Delay_Time label="Arm Delay Time" minValue=0 maxValue=300 step=1  visibility=[Delay_Timer==ON]
                Setpoint item=Stay_Delay_Time label="Stay Delay Time" minValue=0 maxValue=300 step=1  visibility=[Delay_Timer==ON]
                Setpoint item=Entry_Delay_Time label="Entry Delay Time" minValue=0 maxValue=300 step=1  visibility=[Delay_Timer==ON]

                Switch item=RFLink label="RF Link"
        
            }
        }

}

map file for reachable items :

Online=ON
Offline=OFF

persist file :

Strategies
{   
    everyMinute : "0 * * * * ?"
	everyHour : "0 0 * * * ?"
	everyDay : "0 0 0 * * ?" 
    default = everyUpdate
}

Items {
        
            gRfCodes*, gAlarm* : strategy = everyChange, restoreOnStartup
              
}

I think that’s about it…
cheers :slight_smile:

1 Like

Thatnks for posting. There are a few things you do in your rules which are a little dangerous (e.g. long Thread.sleeps) and which could cause some issues (e.g forcing the type of all your variables).

Some improvements you could make:

Don’t force the type. Especially avoid primitives in Rules DSL except where absolutely necessary. This can add minutes to how long it takes OH to load and parse the rule.

var remainingTime = 0

The ? is redundant here. You’ve already confirmed that dayTimer isn’t null by the if statement. An alternative approach that does the same thing is

        delayTimer?.cancel()
        delayTimer = null

That’s what the ? does. It only calls cancel if delayTimer is not null. And it’s no problem to set delayTimer to null if it’s already null.

It can be really problematic to use long sleeps in rules. OH is designed to have rules that trigger and exit quickly (< a second). It is better to use Timers where possible. The danger is basically if for some reason AlarmMode receives a bunch of commands all at once or within a few seconds, each of those triggers gets queued up and worked off in order. You could end up with a denaila of service type problem which is definitely not something you want to have happen for somethign like an alarm.

Also, what’s the purpose of this sleep? If it’s just to make sure the AlarmMode.state has changed in response to the command, use receivedCommand instead of AlarmMode.state. You can never rely on the Item becoming the state it is destined to in response to the command that triggered the rule. The Item may never change states. The Item may have changed states again to soemthing else while the rule was sleeping. But receivedCommand will always be the exact command that triggered the rule so that’s what you want to use in the rule for AlarmMode.state.

In Rules DSL it is always better to use the sendCommand function on the Item instead of the action.

Countdown.sendCommand("---")

The function does all sorts of error checking and conversion based on the Item type that the action doesn’t do.

Fail fast instead. You only do something in this rule when Panic_Button.state != ON so just exit the rule if it happens to be ON.

        delayTimer?.cancel()
        delayTimer = null

        if(Panic_Button.state == ON) return;

        if(AlarmMode.state == "Disarm") {

Definitely use else if for the subsequent if statements on AlarmMode.state but a switch statement might be even easier to follow.

switch(AlarmMode.state) {
    case "Disarm": {
        ...
    }
    case "Stay": {
        ...
    }
    case "Arm": {
        ...
    }
}

There is a lot of code in each of the cases which makes the code a little hard to follow. Even though Rules DSL seems to actively work against this (one reason why I no longer recommend Rules DSL for new development), breaking this up into separate parts could help. For example, if we move the body in the “Disarm” case to a procedure you can isolate that code into a separate unit. The following would go at the top of the .rules file as a global variable

val disarm = [ disarmTimer, countdownTimer |
    disarmTimer?.cancel()
    disarmTimer = null
                
    countdownTimer?.cancel()
    countdownTimer = null

    Countdown.sendCommand("---")

    currentAlarm.postUpdate(AlarmMode.state)
    Disarm.sendCommand(ON)
    Arm.sendCommand(OFF)
    Stay.sendCommand(OFF)

    ...
]

and the rule would call it

switch(AlarmMode.state){
    case "Disarm": disarm.apply(disarmTimer, countdownTimer)
    case "Stay": stay.apply(...
}

Little things like moving code to a procedure or function can greatly improve the readability and long term maintainability of your rules in the long run.

Apply these to the rest of your rules too where appropriate and I think you’ll end up with them becoming a bit shorter and easier to underastand and maintain. Have pitty on future @K4thv4d1! That poor person has to maintain this code.

Use a timer and this will likely no longer be necessary in future versions of OH. There is a PR open to add persistence to the startup runlevels so you won’t reach runlevel 100 until persistence has loaded. This means you would no longer need to wait. Also, if you are using restoreOnStartup, your Rf codes likely do not need to be set here.

thank you for taking valuable time to go through, big fan… will work on this to improve my knowledge further.