Esphome alarm system

As I alluded to above, the templates only poll their template values with a default of 60s. You can reduce the poll rate, but this isn’t ideal.

I updated the gist with my current code. The gobal integer restore on the esp32 works well after a hard power reset. I found that the update to the global integer introduced a small but noticeable lag when trying to observe state update, so it seems to work better when it is the last action in a sequence.

I made significant changes to your project … Thanks for sharing it … As soon as possible I create a new post with everything you need to make it happen … By sharing the link in this topic. See you soon

I would love to see.

My PR to include pending in the possible states for the template alarm control panel is available in v0.108. now there is no longer an error temporarily.

I anticipate the news. Addition of led ws2812b to indicate the node status, and specifically the status LED of the on / off sensors at the passage if disarmed, while fixed if armed and in alarm to identify the sensors that have detected the presence, LED for API connection status and wifi, armed / disarmed alarm status led, triggered status led flashing and fixed in alarm. All this even in case of recovery

I’m doing something very similar to this, though not as complex. Thread here. I’m also using an ESP32 but I’m using a TTGO T-Call board with integrated SIM800L so I can text arm/disarm and get notifications if it triggers. Basically my kit is all over the house so I can’t UPS it all so I also need a self contained alarm panel in the event the perp flicks off the power to the house.

Just lately I’ve lost interest as it’s gotten way more complex than I’d hoped and I’ve run into this conundrum as well. Are you saying that it seems to reconnect to wifi OK without the board rebooting? I haven’t had the chance to test. Mine’s coded to trigger the relay for the sirens/strobes then silence the sirens after 20 minutes. The board is connected to the battery backup of the alarm panel so power outs are not an issue but obviously wifi drops are. If you’re getting reliable reconnections, why do you need to restore states/in what scenario will your board power cycle? Can’t you just set reboot_timeout to 0 and forget about it?

Thanks for sharing.

The use case is if I go on vacation and the power goes out long enough to deplete the backup battery. Then, the system reboots into the correct state when power returns.

Got it - so you’re looking for it to stay armed in this situation. Note your bell box will likely have sung itself to death in this scenario - hopefully any neighbours haven’t decided to ignore if it goes off again!

I reckon I’ll have mine restore armed/disarmed state but not triggered state and will set the reboot_timeout to something a bit longer than the default 15 mins, just to reduce the likelihood of it resetting and turning off the triggered state soon after it becoming triggered.

Mine presents template switches for armed/disarmed and triggered. I haven’t really thought about how to keep both HA and the ESP’s armed/disarmed state in sync yet.

My code restores whatever state it was on prior to power cut. The alarm siren won’t go off unless it was previously going off. I also have a timer for the alarm siren so it stops after x hrs. If my neighbors don’t call me by then…

If you don’t want it to reset into triggered, reset it into armed and it can retrigger if the disturbance is still detected. I thought about this too, and in fact, it may be what I have programmed on my live system.

You can use a template alarm control panel.

1 Like

I haven’t yet gotten my head around the template alarm control panel or why I would need it - right now I’ve got a manual alarm control panel set up.

Thanks for that - I’ve now implemented similar, however I have my strobe set up to restore state and continue indefinitely, until the system is disarmed.

So in the scenario that your panel loses AC and its battery goes dead (hence the need for restore states) your bell box won’t go off/flatten itself? Other thing - Are you UK based? If so, heads up - I’m told that bell boxes shouldn’t ring more than 20 minutes to avoid nuisance. Apparently neighbours/the council are within their rights to disconnect/smash it off the wall if it’s going off longer!

For anyone interested, my code below (it’s come on a bit since my other thread, ‘Virtual Siren/Strobe’ are temporary and just for testing).

esphome:
  name: tcall
  platform: ESP32
  board: esp32dev

wifi:
  ssid: !secret ssid
  password: !secret wpa2
  reboot_timeout: 60min

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "tcall Fallback Hotspot"
    password: !secret hotspotpass

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:
  password: !secret apipass
  reboot_timeout: 60min
  services:
  - service: send_sms
    variables:
      recipient: string
      message: string
    then:
    - sim800l.send_sms:
        recipient: !lambda 'return recipient;'
        message: !lambda 'return message;'

ota:
  password: !secret apipass
  
switch:
  - platform: gpio
    id: "SIM800_PWKEY"
    pin: 4
    restore_mode: ALWAYS_OFF
  - platform: gpio
    id: "SIM800_RST"
    pin: 5
    restore_mode: ALWAYS_ON
  - platform: gpio
    id: "SIM800_POWER"
    pin: 23
    restore_mode: ALWAYS_ON
  - platform: gpio
    inverted: true
    pin: GPIO33
    name: "Siren Relay"
    id: "siren"
    restore_mode: always_off
  - platform: gpio
    inverted: false
    pin: GPIO32
    name: "Strobe Relay"
    id: "strobe"
    restore_mode: restore_default_off
  - platform: template
    id: "armdisarm"
    name: "Arm/Disarm"
    restore_state: true
    optimistic: true
    on_turn_off:
      then:
      - switch.turn_off: "alarmtriggered"
      - switch.turn_off: "vstrobe" 
  - platform: template
    id: "alarmtriggered"
    name: "Alarm Triggered"
    restore_state: false
    optimistic: true
    on_turn_on:
      - if:
          condition:
          - wifi.connected:
          then:
          else:
          - sim800l.send_sms:
              recipient: !secret mobjr
              message: ALARM HAS BEEN TRIGGERED! WIFI CONNECTION UNAVAILABLE. REPLY DISARM TO STOP.
      - switch.turn_on: "vsiren"
      - switch.turn_on: "vstrobe"
      - delay: 20min
      - switch.turn_off: "alarmtriggered"
    on_turn_off:
      - switch.turn_off: "vsiren"
  - platform: template
    id: "vsiren"
    name: "Virtual Siren"
    restore_state: false
    optimistic: true
  - platform: template
    id: "vstrobe"
    name: "Virtual Strobe"
    restore_state: true
    optimistic: true
    
uart:
  baud_rate: 9600
  tx_pin: 27
  rx_pin: 26

sim800l:
  on_sms_received:
  - lambda: |-
      id(sms_sender).publish_state(sender);
      id(sms_message).publish_state(message);
      
text_sensor:
- platform: template
  id: sms_sender
  name: "SMS Sender"
- platform: template
  id: sms_message
  name: "SMS Message"
  on_value:
    - if:
        condition:
          and:
            - text_sensor.state:
                id: sms_message
                state: 'ARM'
            - text_sensor.state:
                id: sms_sender
                state: !secret mobjr
        then:
          - sim800l.send_sms:
              recipient: !secret mobjr
              message: System armed successfully.
          - switch.turn_on: "armdisarm"
          - delay: 30s
          - text_sensor.template.publish:
              id: sms_sender
              state: "Erased"
          - text_sensor.template.publish:
              id: sms_message
              state: "Erased" 
    - if:
        condition:
          and:
            - text_sensor.state:
                id: sms_message
                state: 'DISARM'
            - text_sensor.state:
                id: sms_sender
                state: !secret mobjr
        then:
          - sim800l.send_sms:
              recipient: !secret mobjr
              message: System disarmed successfully.
          - switch.turn_off: "armdisarm"
          - delay: 30s
          - text_sensor.template.publish:
              id: sms_sender
              state: "Erased"
          - text_sensor.template.publish:
              id: sms_message
              state: "Erased" 

binary_sensor:
  - platform: gpio
    name: "AC Power Detector"
    id: acpower
    pin:
      number: GPIO25
      inverted: true
    device_class: power
    on_state:
      then:
      - if:
          condition:
            and:
              - binary_sensor.is_off: "acpower"
              - switch.is_on: "armdisarm"
          then:
          - sim800l.send_sms:
              recipient: !secret mobjr
              message: AC POWER FAILURE.  ALARM STILL ARMED.  GARAGE UNPROTECTED.  TEXT DISARM TO DISARM.
      - if:
          condition:
            and:
              - binary_sensor.is_off: "acpower"
              - switch.is_off: "armdisarm"
          then:
          - sim800l.send_sms:
              recipient: !secret mobjr
              message: AC power failure.  To arm alarm in power-off state, text ARM.
      - if:
          condition:
          - binary_sensor.is_on: "acpower"
          then:
          - sim800l.send_sms:
              recipient: !secret mobjr
              message: Power restored.  Normal operation will be resumed shortly.

  - platform: gpio
    name: "Lounge PIR"
    pin:
      number: GPIO19
      mode: INPUT_PULLUP
    device_class: motion
    on_press:
      then:
      - if:
          condition:
          - switch.is_on: "armdisarm"
          then:
          - switch.turn_on: "alarmtriggered"

  - platform: gpio
    name: "Hall PIR"
    pin:
      number: GPIO13
      mode: INPUT_PULLUP
    device_class: motion
    on_press:
      then:
      - if:
          condition:
          - switch.is_on: "armdisarm"
          then:
          - switch.turn_on: "alarmtriggered"

You have the alarm logic in HA then in some form. I have the alarm logic in the esp unit and the template alarm control panel is just a representation of the alarm system that allows you to interact with it, but there is no alarm logic.

I’ve programmed my system to not turn on the siren in this case. My old commercial system worked the same way, the power for the bell shared power work the panel, so when the battery died, everything is down.

I can turn my alarm on and off remotely through the wonders of home assistant!

Aaaah got it, thanks!

Wow Great Project…

I have some question, If you make it with tm1638, Pin is available to make it.

But tm1638 custom component should be made for ESPHOME.

thx again for your great project sharing.

In the initial code which I thank for sharing, an important condition has not been implemented. If a door or window is open or some PIR sensor is active because someone is still at home, and the alarm is armed, it will enter an armed state because to activate the trigger it is expected the change of state. I modified qursto by checking the state of the sensors. If these are in the on state because I use on_state and not on_press the alarm enters the delay by triggering the siren, or you can decide not to arm the system

Good point! The alarm should trigger the siren or not arm if a sensor is on.

My understanding of on_state is that it is a combination of on_press and on_release, so I’m not sure how it helps here. We also want to catch the case where a door is open during arming and stays open for example.

Edit: This case will not fire any of the on events I think.

where can I find your updated code, with the restoration of the previous state?

sorry being Italian with the help of a translator, I did not understand your request well.

on_state combined with an “if” if the binary sensor is on then the door or window is open, instead of arming it triggers or disarms.

No worries, I think I understand now. Thanks for the additional info.

The code is in the gist in the first post, and again here for convenience:

I don’t see any changes compared to the post initial code to maintain the state in case of reboot.

This is what I am using

#allarme esphome - home assistant

#ws2812b
#led 1 sensore 1
#led 2 sensore 2
#led 3 stato connessione api con home assistant
#led 4 stato connessione wifi
#led 5 stato allarme armato/disarmato
#led 6 stato allarme triggered

#GPIO4  D2 display 16x2 sda
#GPIO5  D1 display 16x2 scl
#GPIO2  D4 led onboard stato esp
#GPIO16 D0 pir/zona 1
#GPIO13 D7 pir/zona 2
#GPIO15 D8 buzzer
#GPIO12 D6 sirena
#GPIO0  D3 ws2812b led status

substitutions:
  alarm_code: "xxxxxxx"
  disarmed_id: "0"
  pending_id: "1"
  triggered_id: "2"
  armed_home_id: "3"
  
esphome:
  name: antifurto_nodemcu
  platform: ESP8266
  board: nodemcu

  # restore state
  # if trigger sequence reset to armed for now.
  #   should it retrigger sequence?
  on_boot:
    then:
      - lambda: |-
          if (id(state_int) == ${disarmed_id}) {
            id(alarm_condition).publish_state("disarmed");
          } else if (id(state_int) == ${pending_id}){
            id(alarm_condition).publish_state("armed_home");
            id(state_int) = ${armed_home_id};
          } else if (id(state_int) == ${triggered_id}){
            id(alarm_condition).publish_state("armed_home");
            id(state_int) = ${armed_home_id};
          } else if (id(state_int) == ${armed_home_id}){
            id(alarm_condition).publish_state("armed_home");
          } else{
            id(alarm_condition).publish_state("disarmed");
            id(state_int) = ${disarmed_id};
          }
      - light.turn_on: 
         id: my_light
      - delay: 3s
      - light.addressable_set:
          id: my_light
          range_from: 0
          range_to: 5
          red: 0%
          green: 0%
          blue: 0%
      - script.execute: boot_state 
     
wifi:
  ssid: "xxxxxx"
  password: "xxxxxx"
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Antifurto Nodemcu"
    password: "xxxxxx"
      
# Example configuration entry
web_server:
  port: 80
  
captive_portal:

# Enable logging
logger:

  
# Enable Home Assistant API
api:

 # add arm and disarm services to ha
 services:
    - service: arm_home
      variables:
         code: string
      then:
        - if:
            condition:
              lambda: "return code == \"${alarm_code}\";"
            then:
              - script.execute: alarm_arm
        - if:
            condition:
              lambda: "return id(pir_cucina).state;"
            then:
              - script.execute: trigger_alarm
        - if:
            condition:
              lambda: "return id(pir_corridoio).state;"
            then:
              - script.execute: trigger_alarm
              
    - service: disarm
      variables:
         code: string
      then:
        - if:
            condition:
              lambda: "return code == \"${alarm_code}\";"
            then:
              - script.execute: alarm_disarm

ota:
# global for state restore
i2c:
  sda: GPIO4
  scl: GPIO5

  
display:
  - platform: lcd_pcf8574
    dimensions: 16x2
    address: 0x27
    id: lcd
    lambda: |-
       it.strftime( "%H:%M %d.%m.%Y", id(my_time).now());
         if (id(state_int) == ${disarmed_id}) {
            it.print(0, 1,"Stato disarmato");
          } 
         if (id(state_int) == ${armed_home_id}) {
            it.print(0, 1,"Stato armato");
          } 
         if (id(state_int) == ${pending_id}) {
            it.print(0, 1,"Stato innescato");
          } 
         if (id(state_int) == ${triggered_id}) {
            it.print(0, 1,"Stato in allarme");
          } 
  
time:
- platform: sntp
  id: my_time
  
globals:
   - id: state_int
     type: int
     restore_value: yes
     initial_value: '0'

# Example configuration entrlight:
light:
  - platform: fastled_clockless
    chipset: WS2811
    pin: D3
    num_leds: 6
    internal: true
    rgb_order: GRB
    id: my_light
    name: "FastLED WS2811 Light"
              
sensor:
  - platform: uptime
    name: Uptime Sensor
    
  - platform: wifi_signal
    name: "WiFi Signal"
    update_interval: 60s
    
status_led:
  pin: GPIO2    
    
binary_sensor:
  - platform: status
    name: "ESP ANTIFURTO"
              
  - platform: gpio
    pin:
      number: D0
      mode: INPUT_PULLUP
    name: "Pir corridoio"
    id: pir_corridoio
    device_class: door
    on_state: 
       then:
        - if:
            condition:
                   binary_sensor.is_on: pir_corridoio
            then:
            - script.execute: trigger_alarm
        - if:
            condition:
              - and:
                   binary_sensor.is_on: pir_corridoio
              - lambda: |-
                  return  id(state_int) == ${disarmed_id};
            then:
              - light.addressable_set:
                 id: my_light
                 range_from: 0
                 range_to: 0
                 red: 50%
                 green: 50%
                 blue: 0%
        - if:
            condition:
                - and:
                   binary_sensor.is_off: pir_corridoio
                - lambda: |-
                    return id(state_int) == ${disarmed_id};
            then:
              - light.addressable_set:
                 id: my_light
                 range_from: 0
                 range_to: 0
                 red: 0%
                 green: 0%
                 blue: 0%
        - if:
            condition:
                - and:
                   binary_sensor.is_on: pir_corridoio
                - lambda: |-
                    return id(state_int) == ${pending_id};
            then:
              - light.addressable_set:
                 id: my_light
                 range_from: 0
                 range_to: 0
                 red: 50%
                 green: 50%
                 blue: 0%
                 
  - platform: gpio
    pin:
      number: D7
      mode: INPUT_PULLUP
    name: "Pir cucina"
    id: pir_cucina
    device_class: door
    on_state: 
       then:
        - if:
            condition:
                   binary_sensor.is_on: pir_cucina
            then:
            - script.execute: trigger_alarm
        - if:
            condition:
              - and:
                   binary_sensor.is_on: pir_cucina
              - lambda: |-
                  return  id(state_int) == ${disarmed_id};
            then:
              - light.addressable_set:
                 id: my_light
                 range_from: 1
                 range_to: 1
                 red: 50%
                 green: 50%
                 blue: 0%
            
        - if:
            condition:
                - and:
                   binary_sensor.is_off: pir_cucina
                - lambda: |-
                    return id(state_int) == ${disarmed_id};
            then:
              - light.addressable_set:
                 id: my_light
                 range_from: 1
                 range_to: 1
                 red: 0%
                 green: 0%
                 blue: 0%
        - if:
            condition:
                - and:
                   binary_sensor.is_on: pir_cucina
                - lambda: |-
                    return id(state_int) == ${pending_id};
            then:
              - light.addressable_set:
                 id: my_light
                 range_from: 1
                 range_to: 1
                 red: 50%
                 green: 50%
                 blue: 0%
interval:
  - interval: 10s
    then:
     - if:
        condition:  
           wifi.connected:
        then:
           - light.addressable_set:
              id: my_light
              range_from: 3
              range_to: 3
              red: 0%
              green: 0%
              blue: 100%
        else:
           - light.addressable_set:
              id: my_light
              range_from: 3
              range_to: 3
              red: 0%
              green: 0%
              blue: 0%
     - if:
        condition:
          api.connected:
        then:
         - light.addressable_set:
            id: my_light
            range_from: 2
            range_to: 2
            red: 0%
            green: 0%
            blue: 100%
        else:
         - light.addressable_set:
            id: my_light
            range_from: 2
            range_to: 2
            red: 0%
            green: 0%
            blue: 0%
          
output:
  - platform: gpio
    pin: D8
    id: output_buzzer_front_door
    
  - platform: gpio
    pin: D6
    id: output_sirena

# warning chirp when pending
switch:
  - platform: template
    turn_on_action:
      - switch.template.publish:
          id: buzzer_front_door
          state: ON
      - while:
          condition:
            switch.is_on: buzzer_front_door
          then:
            - light.addressable_set:
                 id: my_light
                 range_from: 5
                 range_to: 5
                 red: 100%
                 green: 0%
                 blue: 0%
            - output.turn_on: output_buzzer_front_door
            - delay: 10ms
            - light.addressable_set:
                 id: my_light
                 range_from: 5 
                 range_to: 5
                 red: 0%
                 green: 0%
                 blue: 0%
            - output.turn_off: output_buzzer_front_door
            - delay: 990ms
    turn_off_action:
      - switch.template.publish:
          id: buzzer_front_door
          state: OFF
      - output.turn_off: output_buzzer_front_door
    id: buzzer_front_door

# holds alarm condition for arm/disarm/pending/triggered
text_sensor:
  - platform: template
    name: "Alarm Condition"
    id: "alarm_condition"
    
script:
  # when arming and disarming, turn off all sirens and buzzers
  # also, cancel any trigger sequence
  - id: alarm_arm
    then:
      - text_sensor.template.publish:
          id: alarm_condition
          state: "armed_home"
      - script.stop: trigger_alarm_execute
      - switch.turn_off: buzzer_front_door
      - output.turn_off: output_sirena
      - light.addressable_set:
         id: my_light
         range_from: 5 
         range_to: 5
         red: 0%
         green: 0%
         blue: 0%
      - light.addressable_set:
         id: my_light
         range_from: 4 
         range_to: 4
         red: 100%
         green: 0%
         blue: 0%
      - lambda: |-
          id(state_int) = ${armed_home_id};
          
  - id: alarm_disarm
    then:
      - text_sensor.template.publish:
          id: alarm_condition
          state: "disarmed"
      - script.stop: trigger_alarm_execute
      - switch.turn_off: buzzer_front_door
      - output.turn_off: output_sirena
      - lambda: |-
          id(state_int) = ${disarmed_id};
      - light.addressable_set:
         id: my_light
         range_from: 5 
         range_to: 5
         red: 0%
         green: 0%
         blue: 0%
      - light.addressable_set:
         id: my_light
         range_from: 4 
         range_to: 4
         red: 0%
         green: 100%
         blue: 0%
  # triggering sequence
  - id: trigger_alarm_execute
    then:
      - text_sensor.template.publish:
          id: alarm_condition
          state: "pending"
      - switch.turn_on: buzzer_front_door
      - lambda: |-
          id(state_int) = ${pending_id};
      - delay: 30s
      - text_sensor.template.publish:
          id: alarm_condition
          state: "triggered"
      - switch.turn_off: buzzer_front_door
      - output.turn_on: output_sirena
      - light.addressable_set:
          id: my_light
          range_from: 5
          range_to: 5
          red: 100%
          green: 0%
          blue: 0%
      - lambda: |-
          id(state_int) = ${triggered_id};
      - delay: 3600s
      # TODO: turn on siren
  # only execute triggering if armed and not already running
  - id: trigger_alarm
    then:
      if:
        condition:
          and:
            - not:
                script.is_running: trigger_alarm_execute
            - lambda: |-
                return id(state_int) == ${armed_home_id};
        then:
            script.execute: trigger_alarm_execute
            
  - id: boot_state        
    then:
       - if:
            condition:
             or:
              - lambda: |-
                 return id(state_int) == ${pending_id};
              - lambda: |-
                 return id(state_int) == ${triggered_id};
              - lambda: |-
                 return id(state_int) == ${armed_home_id};
            then:
              - light.addressable_set:
                 id: my_light
                 range_from: 4 
                 range_to: 4
                 red: 100%
                 green: 0%
                 blue: 0%
            else:
              - light.addressable_set:
                 id: my_light
                 range_from: 4 
                 range_to: 4
                 red: 0%
                 green: 100%
                 blue: 0%