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

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
- 0 Disarmed
- 10 Armed Home
- 20 Arming
- 30 Armed Away
- 40 Triggered
- 50 Triggered Delayed
- 60 Smoke
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¶m=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
}
If you have any ideas for extensions or tips for improvement, feel free to post

Cheers,
John