Inside ESP32 template alarm system for Home Assistant

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!

11 Likes

This sounds great! One thing I seem to have missed is how is the esp getting status of window, door, etc sensors? You mention wanting this to be able to operate independently of HA which I agree is an important availability and stability requirement so I assume these are hard wired to the esp in the case of home run door and windows reed switches or is there a dependency on HA to provide the sensor statuses?

Joni

1 Like

Great work, very impressive code. I can see room for improvement here :smiley:. Commercial alarms are using and ADC type sensor to calculate sensor state based on resistance returned from sensors. One of the most crucial is in a setup like yours where direct esp pins are used an intruder can cut the sensor cable to bypass the sensor and the alarm won’t be able to notice that the sensor is not functional. ESP pins tend to suffer ghost trigger which is not good for an alarm system, this can be fixed by using a ADC boards like ADS1115 and EOL resistors at the sensors end. See this project how it was done. Another idea is to include a chime type sensor which can be used to sound a buzzer when a front or back doors are opened during the day when alarm is not set.

Very good point about the ghost trigger. In my particular case I had the luck to wire all myself when the contractor was building the house. It would be very difficult to cut anything because (almost) all the wiring is inside the wall.

As for the hardware, my dad is an electric engineer, and he made me a little circuit that isolates the ESP32 from the rest of the alarm via octocouples. The whole circuit is charged with 12v using fairly thick cables which doesn’t seem to loose voltage. In addition, I’ve mounted a DIY UPS so in case of power loss I’ll still have my alarm and siren working. I also will use a dedicated raspberry to track the alarm, as my main system is in a NUC. It’s not perfect, but it’s getting close to I need.

Left to right: UPS, backup RPI, alarm system, DIY watering relays.

2 Likes

All is hard wired and isolated, I posted a picture to show how it works.

Hello
first try on an ESP32s board didn’t work.
the ESPHome compiler reports various errors.
am I making a mistake?
Does the code work on an ESP32s?
I’m going to try on an ESP8266

I’m not sure what could be happening, perhaps de declaration of “NodeMCU 32”? I’m using a plain ESP32 and it works just fine. Esp8266 may have to little gpios to work with…

Eduardo !.
The truth is that I tried it in a hurry because I liked the idea. I have to take a good look at the issue with the ESP32s and ESPHome. I also have to read the code well. Surely it is my mistake. Any advice will be welcome!
No. The ESP8266 was dedicated to something else.
Thanks