dzHomeAlarm: dzVents based alarm script

Moderator: leecollings

Post Reply
User avatar
jjnh
Posts: 33
Joined: Sunday 09 December 2018 14:06
Target OS: Linux
Domoticz version: 2023.1
Location: Netherlands
Contact:

dzHomeAlarm: dzVents based alarm script

Post by jjnh »

Hi,

for some time I've been thinking of creating my own alarm routine in Domoticz using dzVents and below you can find my first version of dzHomeAlarm.
Keep in mind: I'm not a programmer, have a full-time job and a family. So, just managing expectations :lol:

Included in the code are:
  • State machine based triggering
  • Domoticz Security Panel
  • Separate 433 MHz alarm panel (Kerui K16 Wireless with RFID scanner)
  • Z-Wave alarm siren steering (Neo Coolcam NAS-AB02Z)
  • Panic/Tamper alert handling
  • Door/Window sensor (mostly using Xiaomi Aqara MCCGQ11LM)
  • Motion sensor (mostly using Xiaomi Aqara RTCGQ11LM)
  • Smoke alarm sensor handling
You need to create one dummy selector switch called "YourAlarmState". This holds the states for the scripts:
  • 0 Disarmed
  • 10 Armed Home
  • 20 Arming
  • 30 Armed Away
  • 40 Triggered
  • 50 Triggered Delayed
  • 60 Smoke
For security reasons you can protect this selector switch so your alarm cannot be disarmed without a code.

Code: Select all

-- #####################################################
-- #                                                   #
-- #  Version 1.0 of dzHomeAlarm                       #
-- #  Target is to create a home alarm system which    #
-- #  is fully based on dzVents.                       #
-- #  Incorporated features should include             #
-- #   - External 433MHz alarm panel with RFid         #
-- #   - 2 alarm modes Home and Away                   #
-- #   - Sensors for Home and Away mode                #
-- #   - Delayed sensors for Away mode                 #
-- #                                                   #
-- #  Free to redistribute when:                       #
-- #  - Mentioning author and contributors             #
-- #                                                   #
-- # Auhor: John Huppertz                              #
-- # Contributor:                                      #
-- #                                                   #
-- # Versions:                                         #
-- #  - 0.1    Startup of framework        13-05-2021  #
-- #  - 0.2    Redoing complete framework  02-06-2023  #
-- #           with help of ChatGPT3                   #
-- #                                                   #
-- #####################################################

return {
    active = true,
    on = {
        devices = {
            'Alarm Panel (433)',
            'Front Door Sensor',
            'Living Room Window Sensor',
            'Kitchen Motion Sensor',
            'Domoticz Security Panel',
            'Voordeur (Zigbee)',
            'Achterdeur (Zigbee)',
            'Garage Deur (ZB)',
            'Garage Poort (ZB)',
            'Gang Motion (ZB)',
            'Test Deur Sensor (ZB)',
            'Rookmelder Keuken (ZB)',
            'Rookmelder Verwarmingshok (433)',
            'Rookmelder Garage (433)',
            'Rookmelder Overloop Midden (433)'
        }
    },
    logging = {   
            level   =   domoticz.LOG_INFO,
            marker  =   "dzHomeAlarm" 
        },
    execute = function(domoticz, triggeredItem)
        
        -- #################### Ideas / To Do's #################### 
        
        -- add panic buttons
        -- integrate a blinking light so support state changes and alarm situations
        
        -- #################### Sensors Table #################### 
        
        -- Do not forget to also add your devices to the dsVents trigger devices above

        local sensors = {
            {
                name = 'Test Deur Sensor (ZB)',     -- Sensor device Domoticz name
                type = 'doorWindow',                -- Type of sensor
                triggerState = 'Open',              -- State of sensor that will trigger alarm
                triggerDelay = 0                    -- Required delay time for sensor (i.e. to enter and disable alarm)
            },
            {
                name = 'Voordeur (Zigbee)',
                type = 'doorWindow',
                triggerState = 'Open',
                triggerDelay = 20
            },
            {
                name = 'Achterdeur (Zigbee)',
                type = 'doorWindow',
                triggerState = 'Open',
                triggerDelay = 0
            },
            {
                name = 'Garage Deur (ZB)',
                type = 'doorWindow',
                triggerState = 'Open',
                triggerDelay = 0
            },
            {
                name = 'Garage Poort (ZB)',
                type = 'doorWindow',
                triggerState = 'Open',
                triggerDelay = 0
            },
            {
                name = 'Gang Motion (ZB)',
                type = 'motion',
                triggerState = 'On',
                triggerDelay = 20
            },
            {
                name = 'Rookmelder Verwarmingshok (433)',
                type = 'smoke',
                triggerState = 'On',
                triggerDelay = 0
            },
            {
                name = 'Rookmelder Overloop Midden (433)',
                type = 'smoke',
                triggerState = 'On',
                triggerDelay = 0
            },
            {
                name = 'Rookmelder Garage (433)',
                type = 'smoke',
                triggerState = 'On',
                triggerDelay = 0
            },
            {
                name = 'Rookmelder Keuken (ZB)',
                type = 'smoke',
                triggerState = 'On',
                triggerDelay = 0
            }
        }

        -- #################### Local Variables #################### 

        local currentState = domoticz.devices('YourAlarmState').state
        local isSecurityPanel = false   -- Set to true when triggering device was the Domoticz Security Panel
        local armingDelaySeconds = 20   -- Delay in seconds used to delay arming process to enable exiting the building
        local tripAlarm = false         -- Set to True when alarm is tripped
        local tripAlarmDelay = false    -- Set to true when alarm is tripped by delayed device
        local tripDelay = 0             -- Default alarm trip time in seconds for trigger devices. Is overruled by number in device table
        local tripType = ''             -- Holds type of tripped device from device table
        local maxAlarmSound = 30        -- How long should the siren sound in seconds when an alarm is triggered
        
        -- #################### Helper Functions #################### 

        -- Helper function for Z-Wave alarm siren
        local function applyzwavenodeconfig(domoticz, deviceidx, parameteridx, value)
            
            -- domoticz     is Domoticz instance (usually domoticz or dz (without quites)
            -- deviceidx    is Device index of you siren device
            -- parameteridx is Parameter to be set in the Z-Wave configuration of the siren
            -- value        is Value for the chosen parameter
            
            -- domoticz.log("Z-Wave index: " .. deviceidx, domoticz.LOG_INFO)
            -- domoticz.log("Z-Wave parameter index: " .. parameteridx, domoticz.LOG_INFO)
            -- domoticz.log("Z-Wave parameter value: " .. value, domoticz.LOG_INFO)
        	local server = domoticz.settings['Domoticz url'] -- something like "http://127.0.0.1:8080"

        	local encoded_value = domoticz.utils.urlEncode(domoticz.utils.toBase64(value))
        	local url = server .. '/json.htm?type=command&param=applyzwavenodeconfig&idx=' .. tostring(deviceidx) .. '&valuelist=' .. tostring(parameteridx) .. '_' .. encoded_value .. '_'
    
        	-- domoticz.log('Opening URL: '..url, domoticz.LOG_INFO)
        	local handle = io.popen('curl "'..url..'"')
        	local result = handle:read("*a")
        	handle:close()
        	-- domoticz.log(result, domoticz.LOG_INFO)
        end

        -- Helper function to transition states
        local function transition(state)
            domoticz.log('Transitioning to state: ' .. state, domoticz.LOG_INFO)
            domoticz.devices('YourAlarmState').switchSelector(state)
            
            if state == 'Disarmed' then
                -- setting Disarmed conditions
                domoticz.devices('YourAlarmState').cancelQueuedCommands()
                domoticz.devices('YourAlarmState').switchSelector('Disarmed')
                tripAlarm = false
                tripAlarmDelayed = false
                if triggeredItem.name == 'Alarm Panel (433)' then
                    -- if state set bij wireless panel, also set state of Domoticz Security Panel
                    domoticz.devices('Domoticz Security Panel').disarm().silent()
                end
                
                -- Chime disarm sound over siren device
                domoticz.log('Kill any running siren sounds', domoticz.LOG_INFO)
                domoticz.devices("Sirene (ZW)").switchOff()
                domoticz.log('Play signal for 1 second', domoticz.LOG_INFO)
                applyzwavenodeconfig(domoticz, 8, 1, "Low")
                applyzwavenodeconfig(domoticz, 8, 5, "Doorbell")
                domoticz.devices("Sirene (ZW)").switchOn()
                domoticz.devices("Sirene (ZW)").switchOff().afterSec(1)

            elseif state == 'Arming' then
                -- setting Arming conditions
                domoticz.devices('YourAlarmState').switchSelector('Armed Away').afterSec(armingDelaySeconds)
                if triggeredItem.name == 'Alarm Panel (433)' then
                    -- if state set bij wireless panel, also set state of Domoticz Security Panel
                    domoticz.devices('Domoticz Security Panel').armAway().afterSec(armingDelaySeconds).silent()
                end

                -- Chime arming sound over siren device
                domoticz.log('Kill any running siren sounds', domoticz.LOG_INFO)
                domoticz.devices("Sirene (ZW)").switchOff()
                domoticz.log('Play signal while arming', domoticz.LOG_INFO)
                applyzwavenodeconfig(domoticz, 8, 1, "Low")
                applyzwavenodeconfig(domoticz, 8, 5, "Alert")
                domoticz.devices("Sirene (ZW)").switchOn()
                domoticz.devices("Sirene (ZW)").switchOff().afterSec(armingDelaySeconds-1)
            
            elseif state == 'Armed Away' then
                -- setting Armed Away conditions
                if triggeredItem.name == 'Alarm Panel (433)' then
                    -- if state set bij wireless panel, also set state of Domoticz Security Panel
                    domoticz.devices('Domoticz Security Panel').armAway().silent()
                end
                
                -- TO DO: Ping alarm twice low volume
            
            elseif state == 'Armed Home' then
                -- setting Armed home conditions
                if triggeredItem.name == 'Alarm Panel (433)' then
                    -- if state set bij wireless panel, also set state of Domoticz Security Panel
                    domoticz.devices('Domoticz Security Panel').armHome().silent()
                end

                -- Chime armed home sound over siren device
                domoticz.log('Kill any running siren sounds', domoticz.LOG_INFO)
                domoticz.devices("Sirene (ZW)").switchOff()
                domoticz.log('Play signal for 1 second', domoticz.LOG_INFO)
                applyzwavenodeconfig(domoticz, 8, 1, "Low")
                applyzwavenodeconfig(domoticz, 8, 5, "Alert")
                domoticz.devices("Sirene (ZW)").switchOn()
                domoticz.devices("Sirene (ZW)").switchOff().afterSec(1)

            elseif state == 'Triggered Delayed' then
                -- actions required for Delayed triggering
                
                local fullDelay = tripDelay + maxAlarmSound
                domoticz.log('Delayed Trigger for ' .. tripDelay .. ' Seconds' , domoticz.LOG_INFO)
                domoticz.log('Play alarm signal after ' .. tripDelay .. ' for ' .. maxAlarmSound ..' seconds', domoticz.LOG_INFO)
                
                -- Play entry ping
                applyzwavenodeconfig(domoticz, 8, 1, "Low")
                applyzwavenodeconfig(domoticz, 8, 5, "Alert")
                domoticz.devices("Sirene (ZW)").switchOn()
                domoticz.devices("Sirene (ZW)").switchOff().afterSec(1)
                
                -- Sound Siren after delay time
                applyzwavenodeconfig(domoticz, 8, 1, "Low")
                applyzwavenodeconfig(domoticz, 8, 5, "Beep")
                domoticz.devices("Sirene (ZW)").switchOn().afterSec(tripDelay)
                domoticz.devices("Sirene (ZW)").switchOff().afterSec(fullDelay)

            elseif state == 'Triggered'  then
                -- actions required for Triggered state
                
                domoticz.log('### ALARM ###', domoticz.LOG_INFO)
                
                -- Chime alarm sound for set number of seconds
                domoticz.log('Kill any running siren sounds', domoticz.LOG_INFO)
                domoticz.devices("Sirene (ZW)").switchOff()
                domoticz.log('Play alarm signal for ' .. maxAlarmSound .. ' seconds', domoticz.LOG_INFO)
                applyzwavenodeconfig(domoticz, 8, 1, "High")
                applyzwavenodeconfig(domoticz, 8, 5, "Beep")
                domoticz.devices("Sirene (ZW)").switchOn()
                domoticz.devices("Sirene (ZW)").switchOff().afterSec(maxAlarmSound)
                
            elseif state == 'Smoke' then
                -- actions required for Smoke detectors
                
                domoticz.log('### SMOKE DETECTED ###', domoticz.LOG_INFO)
                domoticz.log('### From :' .. triggeredItem.name .. ' ###', domoticz.LOG_INFO)

                -- Chime alarm sound for set number of seconds
                domoticz.log('Kill any running siren sounds', domoticz.LOG_INFO)
                domoticz.devices("Sirene (ZW)").switchOff()
                domoticz.log('Play alarm signal for ' .. maxAlarmSound .. ' seconds', domoticz.LOG_INFO)
                applyzwavenodeconfig(domoticz, 8, 1, "High")
                applyzwavenodeconfig(domoticz, 8, 5, "Beep Beep")
                domoticz.devices("Sirene (ZW)").switchOn()
                domoticz.devices("Sirene (ZW)").switchOff().afterSec(maxAlarmSound)
                
            end
        end
        
        -- Helper function to set devicename because Domoticz Security Panel does not have the same device properties
        local function getDeviceName(device)
            if device.name ~= nil then
                return device.name
            elseif device.description ~= nil then
                return device.description
            else
                isSecurityPanel = true
                return 'Security Panel'
            end
        end
        
        -- #################### Main start #################### 
        
        domoticz.log('Current alarm state         : ' .. currentState, domoticz.LOG_INFO)
        domoticz.log('Current trigger device      : ' .. getDeviceName(triggeredItem), domoticz.LOG_INFO)
        domoticz.log('Current trigger device state: ' .. triggeredItem.state, domoticz.LOG_INFO)

        -- #################### Turning the alarm on and off #################### 

        -- Handle Wireless Alarm Panel
        if triggeredItem.isDevice and triggeredItem.name == 'Alarm Panel (433)' then
            local panelState = triggeredItem.state

            if panelState == 'Disarm' then
                transition('Disarmed')
            elseif panelState == 'Arm Home' then
                transition('Armed Home')
            elseif panelState == 'Arm Away' then
                transition('Arming')
            elseif panelState == 'Panic' then
                transition('Triggered')
            end
        end

        -- Handle Domoticz security panel
        if isSecurityPanel then
            local state = triggeredItem.state
            if state == 'Armed Away' then
                if currentState ~= 'Armed Away' then
                    transition('Arming')
                else
                    domoticz.log('Alarm is already armed in away mode', domoticz.LOG_INFO)
                end
            elseif state == 'Armed Home' then
                if currentState ~= 'Armed Home' then
                    transition('Armed Home')
                else
                    domoticz.log('Alarm is already armed in home mode', domoticz.LOG_INFO)
                end
            elseif state == 'Disarmed' then
                if currentState ~= 'Disarmed' then
                    transition('Disarmed')
                else
                    domoticz.log('Alarm is already disarmed', domoticz.LOG_INFO)
                end
            end
        end
    
        -- #################### Alarm triggers #################### 

        -- Check if a sensor devices triggered the script
        if (triggeredItem.isDevice and isSecurityPanel == false and triggeredItem.name ~= 'Alarm Panel (433)') then
            domoticz.log('starting trigger analysis', domoticz.LOG_INFO)
            
            -- Check all sensors in table
            for j = 1, #sensors do
                local sensorData = sensors[j]
                domoticz.log('sensor:' .. j, domoticz.LOG_INFO)
                domoticz.log('sensor:' .. sensorData.name, domoticz.LOG_INFO)
                domoticz.log('sensor:' .. sensorData.type, domoticz.LOG_INFO)
                domoticz.log('sensor:' .. sensorData.triggerState, domoticz.LOG_INFO)
                domoticz.log('sensor:' .. sensorData.triggerDelay, domoticz.LOG_INFO)
                
                -- Check if triggered device is in sensors table
                
                if getDeviceName(triggeredItem) == sensorData.name then
                    domoticz.log('Triggering item found in sensors table', domoticz.LOG_INFO)
                    if triggeredItem.state == sensorData.triggerState then
                        domoticz.log('Trigger state matches sensors', domoticz.LOG_INFO)
                        tripType = sensorData.type
                        if sensorData.triggerDelay > 0 then
                            domoticz.log('Trip Alarm Delayed after ' .. sensorData.triggerDelay .. ' Seconds' , domoticz.LOG_INFO)
                            tripAlarmDelayed = true
                            tripDelay = sensorData.triggerDelay
                        else
                            domoticz.log('Trip Alarm', domoticz.LOG_INFO)
                            tripAlarm = true
                        end
                    else
                        domoticz.log('Trigger state does not match alert level', domoticz.LOG_INFO)
                    end
                end
            end
            
            -- Trip alarm if alarm is armed and sensor matches
            domoticz.log('Current alarm state         : ' .. currentState, domoticz.LOG_INFO)
            if tripAlarm and tripType == 'smoke' then
                domoticz.log('Smoke alarm. Processing trigger', domoticz.LOG_INFO)
                transition('Smoke')
            elseif currentState == 'Armed Away' then
                domoticz.log('Alarm armed. Processing trigger', domoticz.LOG_INFO)
                if tripAlarm then
                    transition('Triggered')
                elseif tripAlarmDelayed then
                    transition('Triggered Delayed')
                end
            elseif currentState == 'Armed Home' and tripType == 'doorWindow' then
                domoticz.log('Alarm armed. Processing trigger', domoticz.LOG_INFO)
                if tripAlarm then
                    transition('Triggered')
                elseif tripAlarmDelayed then
                    transition('Triggered Delayed')
                end
                domoticz.log('Alarm not armed or trigger not matching Armed Home. Do nothing with trigger', domoticz.LOG_INFO)
            end
            
        end
    
    end
}
Hopefully this will help someone with their own setup.
If you have any ideas for extensions or tips for improvement, feel free to post 8-)

Cheers,
John
Odroid N2+ 4GB, with an RfxTrx433E, Zigbee with Slaesh's CC2652RB, Z-Wave, BroadLink IR using RM mini 3 and P1 connected.
Kedi
Posts: 564
Joined: Monday 20 March 2023 14:41
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: Somewhere in NL
Contact:

Re: dzHomeAlarm: dzVents based alarm script

Post by Kedi »

Nice script. :D
With what hardware device do you receive info from the 433 MHz alarm panel (Kerui K16 Wireless with RFID scanner)?
Logic will get you from A to B. Imagination will take you everywhere.
WouterO
Posts: 47
Joined: Sunday 14 May 2023 19:23
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: dzHomeAlarm: dzVents based alarm script

Post by WouterO »

Impressive script, thanks for sharing.
My only surprise was the bunch of different wireless standards you’re using: both 433MHz, Z-Wave ánd Zigbee. Guess your hubs/coordinators are powered by an USB hub?
User avatar
jjnh
Posts: 33
Joined: Sunday 09 December 2018 14:06
Target OS: Linux
Domoticz version: 2023.1
Location: Netherlands
Contact:

Re: dzHomeAlarm: dzVents based alarm script

Post by jjnh »

Thanks!!

To answer both questions:

To receive the 433MHz I use an Rfxtrx433E.

Indeed have a powered USB hub connected to my RPi3B.
Odroid N2+ 4GB, with an RfxTrx433E, Zigbee with Slaesh's CC2652RB, Z-Wave, BroadLink IR using RM mini 3 and P1 connected.
Kedi
Posts: 564
Joined: Monday 20 March 2023 14:41
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: Somewhere in NL
Contact:

Re: dzHomeAlarm: dzVents based alarm script

Post by Kedi »

Tnx. for the answer.
Is it the Ext, Ext2, Pro1 or Pro2 firmware?
And which mode is used?
Logic will get you from A to B. Imagination will take you everywhere.
User avatar
jjnh
Posts: 33
Joined: Sunday 09 December 2018 14:06
Target OS: Linux
Domoticz version: 2023.1
Location: Netherlands
Contact:

Re: dzHomeAlarm: dzVents based alarm script

Post by jjnh »

Kedi wrote: Sunday 18 June 2023 8:23 Tnx. for the answer.
Is it the Ext, Ext2, Pro1 or Pro2 firmware?
And which mode is used?
It's on the EXT firmware as I also have some legacy X10 stuff hanging around which is being replaced step bij step.
Odroid N2+ 4GB, with an RfxTrx433E, Zigbee with Slaesh's CC2652RB, Z-Wave, BroadLink IR using RM mini 3 and P1 connected.
Kedi
Posts: 564
Joined: Monday 20 March 2023 14:41
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: Somewhere in NL
Contact:

Re: dzHomeAlarm: dzVents based alarm script

Post by Kedi »

So this panel uses the X10 protocol?
I use the Pro1 firmware which also has the X10.
Have to find out if enabling X10 it does not interfere with my other settings.

Edit.
I downloaded the RFXCOM manual, and now I see the Kerui mentioned. ;)
Logic will get you from A to B. Imagination will take you everywhere.
User avatar
jjnh
Posts: 33
Joined: Sunday 09 December 2018 14:06
Target OS: Linux
Domoticz version: 2023.1
Location: Netherlands
Contact:

Re: dzHomeAlarm: dzVents based alarm script

Post by jjnh »

Kedi wrote: Sunday 18 June 2023 10:37 So this panel uses the X10 protocol?
I use the Pro1 firmware which also has the X10.
Have to find out if enabling X10 it does not interfere with my other settings.

Edit.
I downloaded the RFXCOM manual, and now I see the Kerui mentioned. ;)
No the panel doesn't use X10. It is indeed directly supported by the RfxTrx and Domoticz. But if you don't want to use a separate panel, the built-in Domoticz Security Panel is also integrated in the code.
Odroid N2+ 4GB, with an RfxTrx433E, Zigbee with Slaesh's CC2652RB, Z-Wave, BroadLink IR using RM mini 3 and P1 connected.
Post Reply

Who is online

Users browsing this forum: No registered users and 0 guests