Olarm YAML Package

Intro

For those interested, I went down the route of creating a YAML package for an Olarm device (sold in South Africa). Unfortunately, this device relies on the cloud, but is otherwise very easy to setup and use.

One benefit of a YAML package is that you can customise this to your own needs.

The package uses the Olarm REST API and webhooks to create an HA manual alarm panel.

I should also call out the existing integration for HA which is more feature rich than this package, but due to technological and operational constraints you might hit API limits. This makes certain features less useful (such as zone monitoring).

Note that local monitoring and automation of your alarm system doesn’t replace subscribing to a security service.

Requirements

  • An Olarm device connected to your physical alarm panel.
  • An active Olarm API.
  • Your config must support packages.
  • Your HA instance must be reachable by Olarm (i.e. be reachable via the Internet).

Installation & Configuration

Define these secrets in your secrets.yaml:

olarm_webhook_id: "<as configured under your Olarm API profile>"
olarm_bearer_token: " <as provided by Olarm>"`
olarm_device_endpoint: "https://apiv4.olarm.co/api/v4/devices/<device_id>"
olarm_actions_endpoint: "https://apiv4.olarm.co/api/v4/devices/<device_id>/actions" 
alarm_code: "<alarm pin>"

Put this in e.g. an olarm.yaml package:

Package Contents
# features:
#  * real-time alarm state updates and triggers
#  * basic alarm management: arming/disarming (advanced management via the olarm app)
#  * presence aware: ask to arm/disarm when leaving/arriving
#  * extensible with more custom automations and integration of one's own sensors
#  * area/partition aware (although not set up for multiple areas/partitions; make a copy)
#
# unsupported:
#  * zone monitoring (no real-time updates via webhooks or mqtt)
#  * bypassing zones (not implemented)
#  * alarm action history (not implemented)
#  * managing more than one area/partition (clone the package in order to do so)
#  * how or who armed/disarmed the alarm (complex)
#
# future:
#  * distinct alerts (notifications) for breach, panic, fire and emergency (currently no distinction)
#  * hoping for an mqtt integration with additional features (multiple requests made; apparently wip)
#
# known issues & bugs:
#  * arming when a zone is active (open) will not fail when calling the api (issue reported)
#
# references:
# https://community.home-assistant.io/t/olarm-integration/384560/28
# https://community.home-assistant.io/t/olarm-integration/384560/52
# https://github.com/rainepretorius/olarm-ha-integration
# https://github.com/rainepretorius/olarm-ha-integration/discussions/85

# https://www.home-assistant.io/integrations/alarm_control_panel/
# https://www.home-assistant.io/integrations/manual/
# https://www.home-assistant.io/integrations/alarm_control_panel.template/
# might switch to this once we can properly monitor zones:
# https://github.com/nielsfaber/alarmo
alarm_control_panel:
  - platform: manual
    name: "Home"
    code: !secret alarm_code
    code_arm_required: true
    arming_time: 0
    delay_time: 0
    trigger_time: 120
    disarm_after_trigger: false
    disarmed:
      trigger_time: 0
    armed_home:
      arming_time: 0
      delay_time: 0
    armed_away:
      arming_time: 0
      delay_time: 0

# https://www.home-assistant.io/integrations/rest_command/
# https://user.olarm.co/#/api
# https://user.olarm.co/#/api/documentation
rest_command:
  olarm_get_state:
    method: "GET"
    headers:
      Authorization: !secret olarm_bearer_token
    url: !secret olarm_device_endpoint
    content_type: "application/json"
  olarm_disarmed:
    method: "POST"
    headers:
      Authorization: !secret olarm_bearer_token
    url: !secret olarm_actions_endpoint
    # areas are 1-indexed (i.e. starts at 1 not 0)
    payload: >-
      {
        "actionCmd": "area-disarm",
        "actionNum": {{ area }}
      }
    content_type: "application/json"
  olarm_armed_home:
    method: "POST"
    headers:
      Authorization: !secret olarm_bearer_token
    url: !secret olarm_actions_endpoint
    payload: >-
      {
        "actionCmd": "area-stay",
        "actionNum": {{ area }}
      }
    content_type: "application/json"
  olarm_armed_away:
    method: "POST"
    headers:
      Authorization: !secret olarm_bearer_token
    url: !secret olarm_actions_endpoint
    payload: >-
      {
        "actionCmd": "area-arm",
        "actionNum": {{ area }}
      }
    content_type: "application/json"

automation:
  - alias: "Sync Alarm State"
    id: "e8f36533-e1c8-40d5-b35f-3001a159ec8e"
    initial_state: true
    trigger:
      - platform: homeassistant
        event: start
    action:
      - variables:
          state_map:
            notready: "disarm"
            disarm: "disarm"
            stay: "arm_home"
            arm: "arm_away"
      - service: rest_command.olarm_get_state
        response_variable: olarm_response
      # index 0 is area/partition 1
      # values are: stay, arm, disarm
      - service: "alarm_control_panel.alarm_{{ state_map[olarm_response.content.deviceState.areas[0]] }}"
        target:
          entity_id: alarm_control_panel.home
        data:
          code: !secret alarm_code

  # examples for state changes:
  # {'deviceId': '<uuid>', 'eventTime': 1708664464105, 'eventAction': 'area', 'eventState': 'disarm', 'eventNum': 1, 'eventMsg': 'DISARMED - Area 1 - Home', 'userFullname': ''}
  # {'deviceId': '<uuid>', 'eventTime': 1708797060201, 'eventAction': 'area', 'eventState': 'stay', 'eventNum': 1, 'eventMsg': 'STAY ARMED - Area 1 - Home', 'userFullname': ''}
  # {'deviceId': '<uuid>', 'eventTime': 1708882676603, 'eventAction': 'area', 'eventState': 'stay', 'eventNum': 1, 'eventMsg': 'STAY ARMED - Area 1 - Home', 'userFullname': 'Pieter Rautenbach'}
  # {'deviceId': '<uuid>', 'eventTime': 1709539839721, 'eventAction': 'area', 'eventState': 'arm', 'eventNum': 1, 'eventMsg': 'ARMED - Area 1 - Home', 'userFullname': ''}
  #
  # examples for emergencies
  # these are all panics. these two events seem to say that eventNum refers to the area/partition.
  # {'deviceId': '<uuid>', 'eventTime': 1709539853196, 'eventAction': 'area', 'eventState': 'emergency', 'eventNum': 1, 'eventMsg': 'EMERGENCY! - Area 1 - Home', 'userFullname': ''}
  # {'deviceId': '<uuid>', 'eventTime': 1709539853197, 'eventAction': 'area', 'eventState': 'emergency', 'eventNum': 2, 'eventMsg': 'EMERGENCY! - Area 2 - Flatlet', 'userFullname': ''}
  # but here eventNum seem to refer to the zone. thus, one needs to parse the eventMsg. seems like there are events for zones activated during a panic.
  # {'deviceId': '<uuid>', 'eventTime': 1709539853200, 'eventAction': 'area', 'eventState': 'emergency', 'eventNum': 5, 'eventMsg': 'EMERGENCY! - Area 5', 'userFullname': ''}
  # {'deviceId': '<uuid>', 'eventTime': 1709539853199, 'eventAction': 'area', 'eventState': 'emergency', 'eventNum': 4, 'eventMsg': 'EMERGENCY! - Area 4', 'userFullname': ''}
  # then there's this one (note the eventAction):
  # {'deviceId': '<uuid>', 'eventTime': 1709539853544, 'eventAction': 's_alm', 'eventState': 'panic', 'eventNum': 1, 'eventMsg': 'EMERGENCY! - Panic', 'userFullname': ''}
  - alias: "Receive Olarm Event"
    id: "fbf7afaa-c357-4a85-b50d-ba5d57b9e187"
    initial_state: true
    trigger:
      - platform: webhook
        webhook_id: !secret olarm_webhook_id
        local_only: false
        allowed_methods:
          - POST
    mode: queued
    action:
      - variables:
          state_map:
            disarm: "disarm"
            stay: "arm_home"
            arm: "arm_away"
      - service: system_log.write
        data:
          message: "{{ trigger.webhook_id ~ ': ' ~ trigger.json }}"
          level: debug
          logger: "homeassistant.components.olarm"
      - choose:
          # if triggered
          - conditions:
              - condition: template
                value_template: "{{ trigger.json.eventState in ['panic', 'alarm'] }}"
            sequence:
              - service: alarm_control_panel.alarm_trigger
                target:
                  entity_id: alarm_control_panel.home
          # elif state changed
          - conditions:
              # compare with current state (noop it if matches; means it was actioned from HA, so don't cause a loop)
              - condition: template
                value_template: >-
                  {# eventNum == '1' is the partition/area check #}
                  {% set new_state = state_map[trigger.json.eventState] %}
                  {{ trigger.json.eventNum == 1 and
                     trigger.json.eventState in ['disarm', 'stay', 'arm'] and
                     not is_state('alarm_control_panel.home', new_state) }}
            sequence:
              - service: "alarm_control_panel.alarm_{{ state_map[trigger.json.eventState] }}"
                target:
                  entity_id: alarm_control_panel.home
                data:
                  code: !secret alarm_code
        default:
          - service: system_log.write
            data:
              message: "{{ trigger.webhook_id ~ ': Unknown event received: ' ~ trigger.json.eventState }}"
              level: warning
              logger: "homeassistant.components.olarm"

  - alias: "Notify When Alarm Triggered"
    id: "45f0ac46-8bbb-42cb-a051-64ea1ca695e2"
    initial_state: true
    trigger:
      - platform: state
        entity_id: alarm_control_panel.home
        to: "triggered"
    action:
      - service: notify.mobile_app_ceres
        data:
          title: "🚨 Security"
          message: "The alarm has been triggered!"
          data:
            group: "home-security"
            url: homeassistant://navigate/lovelace/security
            push:
              sound:
                name: default
                critical: 1
                volume: 0.5
            actions:
              - action: "DISARM_ALARM"
                title: "Disarm now"
                destructive: true

  - alias: "Ask To Arm Alarm When Last Person Left"
    id: "e8a63da5-4d84-40ab-9c05-dca93ba4900d"
    initial_state: true
    trigger:
      - platform: state
        entity_id: binary_sensor.pieter_present
        to: "off"
    mode: single
    action:
      - wait_template: "{{ is_state('binary_sensor.anybody_home', 'off') }}"
        timeout: "00:02:00"
        continue_on_timeout: false
      - condition: state
        entity_id: alarm_control_panel.home
        state: "disarmed"
      - service: notify.mobile_app_ceres
        data:
          title: "🔒 Security"
          message: >-
            The last person left home. Would you like to arm the alarm?
          data:
            group: "home-security"
            url: homeassistant://navigate/lovelace/security
            actions:
              - action: "ARM_ALARM"
                title: "Arm now"
                destructive: true
            push:
              sound:
                name: default
                critical: 1
                volume: 0.5

  - alias: "Ask To Disarm Alarm When Arriving"
    id: "85753536-12f8-4a94-b292-7672ac96a4c2"
    initial_state: true
    trigger:
      - platform: state
        entity_id: binary_sensor.pieter_present
        to: "on"
    condition:
      - condition: not
        conditions:
          - condition: state
            entity_id: alarm_control_panel.home
            state: "disarmed"
    action:
      - service: notify.mobile_app_ceres
        data:
          title: "🔒 Security"
          message: >-
            You arrived home. Would you like to disarm the alarm?
          data:
            group: "home-security"
            url: homeassistant://navigate/lovelace/security
            actions:
              - action: "DISARM_ALARM"
                title: "Disarm now"
                destructive: true
            push:
              sound:
                name: default
                critical: 1
                volume: 0.5

  - alias: "Handle Alarm Notification Event"
    id: "c7a55b28-a909-4cf0-8f91-23f1d8a0e45a"
    initial_state: true
    trigger:
      - platform: event
        event_type: mobile_app_notification_action
        event_data:
          action: ARM_ALARM
        id: "arm_away"
      - platform: event
        event_type: mobile_app_notification_action
        event_data:
          action: DISARM_ALARM
        id: "disarm"
    action:
      - service: "alarm_control_panel.alarm_{{ trigger.id }}"
        target:
          entity_id: alarm_control_panel.home
        data:
          code: !secret alarm_code

  - alias: "Alarm State Changed"
    id: "c217ca42-8a3f-40c6-9945-2c77bdf16239"
    initial_state: true
    trigger:
      - platform: state
        entity_id: alarm_control_panel.home
        to:
          - "disarmed"
          - "armed_home"
          - "armed_away"
    action:
      - variables:
          state_map:
            disarmed: "disarmed"
            armed_home: "armed (stay)"
            armed_away: "armed"
          state: "{{ state_map[trigger.to_state.state] }}"
      - service: "rest_command.olarm_{{ trigger.to_state.state }}"
        data:
          area: 1
        response_variable: olarm_response
      - if: "{{ olarm_response.status == 200 and olarm_response.content.actionStatus == 'OK' }}"
        then:
          - service: notify.mobile_app_ceres
            data:
              title: "🔒 Security"
              # todo: user (can be from olarm or HA context; tricky)
              message: >-
                The alarm was {{ state }} at {{ now().strftime('%H:%M') }}.
              data:
                group: "home-security"
                url: homeassistant://navigate/lovelace/security
        else:
          - service: system_log.write
            data:
              message: "{{ olarm_response.status }}: {{ olarm_response.content }}"
              level: error
              logger: "homeassistant.components.olarm"
          - service: notify.mobile_app_ceres
            data:
              title: "⛔️ Security"
              message: >-
                The alarm couldn't be {{ state }} at {{ now().strftime('%H:%M') }}. Check the log for errors.
              data:
                group: "home-security"
                url: homeassistant://navigate/config/logs

  # my alarm only has a stay mode – not also a night mode
  # the alarm frontend card used shouldn't have night mode as a valid feature
  # also, if exposing the alarm entity to homekit, you cannot specify the features that are supported
  # (so it will allow setting the alarm to home (stay), away, night or off)
  # neither this package, olarm, my alarm panel or homekit supports the vacation arm mode or a custom bypass command
  - alias: "Set To Stay If Night Mode Activated"
    id: "651adacf-f189-48ef-814a-24a051e60bd5"
    initial_state: true
    trigger:
      - platform: state
        entity_id: alarm_control_panel.home
        to:
          - "armed_night"
    action:
      - service: "alarm_control_panel.alarm_arm_home"
        target:
          entity_id: alarm_control_panel.home
        data:
          code: !secret alarm_code

Features

  • Real-time alarm state updates and triggers
  • Basic alarm management: arming/disarming (advanced management such as bypassing must be done via the Olarm app)
  • Presence aware: ask to arm/disarm when leaving/arriving
  • Extensible with more custom automations and integration of one’s own sensors
  • Area/partition aware (although not set up for multiple areas/partitions; make a copy if you want to manage multiple partitions)

Unsupported Features

  • Zone monitoring (no real-time updates via webhooks or mqtt)
  • Bypassing zones (not implemented; not supported by the alarm integration)
  • Alarm action history (not implemented; not supported by the alarm integration)
  • Managing more than one area/partition (clone the package in order to do so)
  • How or who armed/disarmed the alarm (complex)

Future

  • Distinct alerts (notifications) for breach, panic, fire and emergency (currently no distinction)
  • Hoping for an MQTT integration with additional features (multiple requests made to Olarm; apparently it is WIP)

Known Issues & Bugs

  • Arming when a zone is active (open) will not fail when calling the API (issue reported to Olarm)

Dashboard

If you’re like me and don’t like green for an armed alarm on your UI (and you want it to match your Olarm app), you can customise this.

What this will do, is to use red for an armed alarm, yellow for armed stay/home/night and green for unarmed. It will also show the triggered state in blue (think, police) with a green disarm button.

Tile Card and History Graph
      - type: tile
        entity: alarm_control_panel.home
        name: "Home Alarm"
        features:
          - type: "alarm-modes"
            modes:
              - armed_home
              - armed_away
              - disarmed
        card_mod:
          # this works in conjunction with www/custom.js
          style:
            hui-card-features $ hui-alarm-modes-card-feature $ div ha-control-select $ div: |
              #option-armed_home {
                --control-select-color: #ffa800 !important;
              }
              #option-armed_away {
                --control-select-color: #aa0000 !important;
              }
              #option-disarmed {
                --control-select-color: #40b100 !important;
              }
            hui-card-features $ div hui-card-feature $ hui-alarm-modes-card-feature $ ha-control-button-group ha-control-button $ button: |
              .button {
                background-color: #40b100 !important;
              }

      - type: history-graph
        name: Alarm
        hours_to_show: 24
        entities:
          - entity: alarm_control_panel.home
            name: Alarm

Put this in custom.js in your www directory.

Theme Customisations
/*
Idea taken from https://gist.github.com/thomasloven/2a37152725c582fec4420ecedb65ebe3

Add this to your configuration.yaml
frontend:
  extra_module_url:
    - /local/ThemeOverride.js

And put the following into <config-dir>/www/ThemeOverride.js
*/
// setting these here means it will work for both the panel and the history graph cards
document.documentElement.style.setProperty('--state-alarm_control_panel-armed_home-color', '#ffa800');
document.documentElement.style.setProperty('--state-alarm_control_panel-armed_away-color', '#aa0000');
document.documentElement.style.setProperty('--state-alarm_control_panel-disarmed-color',   '#40b100');
document.documentElement.style.setProperty('--state-alarm_control_panel-triggered-color',  '#135dd8');

References: