I’d like to share an alarm system I’ve been working last weeks. The alarm is intended to be used mostly for wired alarms or in situations where the Wifi connection may be unstable. All the logic is contanied inside of an ESP32 which is controlled by Home Assistant using the Template Alarm Control Panel.
In first place I’d like to acknowledge that the inspiration of this project is coming from two sources that you might want to check out (I am by no means an expert in coding, so I had to grab parts from everywhere). First of all, this thread created by Matthew Flamm, which got me started and gave me the initial info I was lacking of. Second, the superb Alarmo integration, from which I took some great ideas as well. I combined this two sources in my current project, which is still in progress.
The services and states
The idea is to have an alarm system independent of Home Assistant, which can trigger allways despite the lack of wifi connection for wired alarms.
The system interacts with Home Assistant via Esphome services using the Template Alarm Control Panel. If the code provided in the panel is correct, then the ESP32 proceeds to arm or disarm the alarm system updating the value of the alarm template text sensor.
There are 5 states available: disarmed, armed_home, armed_away, arming (for leaving), pending (for arriving) and triggered.
In addition, there are 4 services: disarm, arm_home, arm_away and force_arm. Both arm home and away checks if the condition of the sensors is compatible with arming and then proceeds to execute arming or offer a force arming, which will bypass those sensors. This last service doesn’t need code confirmation because to access it you have to previously type the code in the panel (it is accessible from Home Assistant tho, and an improvement could be to add a secret data to it just to prevent an involuntary arming from the services panel). I had to add this force arm service because the Template alarm doesn’t have the option to add the Custom bypass arming as the Manual Alarm does.
If the alarm was triggered and nobody can disarm it in a period of time, the alarm will recover and force arm bypassing the sensors that where open after the siren stops. This same script is used for when you are trying to arm with an open sensor.
Boot restoration after crash or restart
There are a lot of global variables in the declaration of the esphome file, which will store the alarm state and the last alarm service called. In every boot the ESP32 will restore the last state and publish it to Home Assistant.
Mantainance free code
I’ve tried to build a code which can be accessible to almost anyone getting started in Esphome. For that reason all the first part of it consists in a series of declaration of variables that must match with your sensors.
Substitutions
There are a couple of user defined values, such as number of sensors and arming, pending and trigger times.
A lot of vectors
The idea is to define vectors that match with your binary sensors giving them some attributes like its id name, friendly name, type of sensor, and combination of watched sensors in each disarmed, armed home or armed away state.
One of the main features of the program is that can point to the binary sensor values directly from the code, not being necessary to add automations in each one like on_press -> then. This is achieved declaring 2 vectors, one with the ID names of the sensors in a vector which is of type esphome::binary_sensor::BinarySensor*, and other with the friendly names to publish in Home Assistant. You have to declare them something like this:
'{front_door, window_1, window_2, pir, tamper}'
'{"Front door", "Window 1", "Window 2", "Pir", "Tamper"}'
You then must declare the types of sensors, which are the same as Alarmo. I gave some sensors priorities over others, being the highest number the one that triggers the alarm first as it follows:
1 for Allow open sensors: Motion sensors
2 for Arm on close sensors: Front Doors, garage door, locks
3 for Immediate sensors: Windows, inner doors
4 for Allways on sensors: Tamper, gas, heat, smoke, safety
Then the sensor types vector will be something lik this:
"{2, 3, 3, 1, 4}"
Finally, you must declare the watched sensors in each alarm state, as well as some backup sensors that will serve for calculations in the code. The disarmed, armed home and armed away sensors in the example above looks like this:
"{0, 0, 0, 0, 1}"
"{1, 1, 1, 0, 1}"
"{1, 1, 1, 1, 1}"
The sensors and switches
The alarm has a siren and a buzzer, which must be declared as switches. It also needs the user to declare all the binary sensors and its IDs matching the names used in the sensors above. For example:
binary_sensor:
- platform: gpio
device_class: door
name: "Front Door"
id: front_door
pin:
number: GPIO5
mode: INPUT_PULLUP
inverted: True
Tampers and allways on type of sensors in disarmed mode
When a sensor like a tamper opens while the alarm is disarmed it will not trigger right away. Instead, it will turn on permanently the buzzer until you manually turn it off. I made this decision because sometimes it could be difficult to fix the problem right away, so the alarm will turn on the buzzer every time you try to arm or disarm just to remind you. If an allways on sensor opens when the alarm is armed it will trigger as normal.
Deep into the rabbit code
As stated before, the user does not have to mess with the code below the declarations if not needed. The alarm is intended to check the state of every sensors in each loop building a vector analyzer, and will call scripts (a.k.a. c++ functions) depending of the result.
To check the sensors the goal is to build a vector which multiplies, for each one, the active sensors, sensor types, sensor current state and bypassed vectors. Each one, excluding the sensor types, consists in values 1 or 0, so the resultant vector will have either 0’s or the sensor type detected. The algorithm chooses highest value, which will trigger depending on the alarm state. This is used in edge cases like for example when leaving the house in arming state, if someone else opens a window while you have the front door open it will trigger the alarm because the window has a higher priority than the front door.
Lovelace
My current UI has 4 elements: the alarm panel, a siren and buzzer buttons and a filter entity card thar shows only the open sensors.
In addition I have installed the Browser_mod integration by Thomas Loven (thanks!) which pops up then failing to arm offering the force arm service.
If you want to check out the code, you can do it clicking below. There you will find the esphome file as well as the lovelace and configuration ones.
I’m eager to know what you think and if you want to help optimizing the coding as I have very little experience in C++ which I’m sure makes the code a little clumpsy.
Regards!