Touchpad for Manual Alarm Control Panel Supports Multiple User Codes

Several external alarm solutions exist for integration with Home Assistant. I didn’t want to use any of those, and instead I wanted to implement an alarm with Home Assistant out-of-the-box. A manual alarm platform is provided but it has one missing functionality that was a must-have requirement in my situation, multiple user codes.

In this intermediate-level configuration example, lovelace cards are used to create a multi-user touchpad interface for Home Assistant, using only Home Assistant - lovelace cards, yaml configuration, scripts and automations.

If you are new to Home Assistant, this example will present a challenge! Enjoy

DESIGN

Home Assistant is displayed on Fully Kiosk Browser from Fire HD tablets throughout my home, and I wanted a simple way to provide each person in my home a separate user code, including guests, and integrate the alarm with Apple Homekit and Alexa voice assistants (which require user codes to be disabled).

In this solution, I use a lovelace button grid to collect touches with an input_text, and evaluated touch input against a user-code map using an automation. User codes and username are stored in the secrets.yaml file.

Note: solution was built for controlling a manual alarm, but this touchpad code solution can be adapted to do basically anything. Outside this example, I use a lovelace touchpad to disarm both an intrusion and a fire/carbon monoxide alarm panel. I also have a zwave lock on the front door and use zwave event data to call the disarm script which then notifies when and who has disarmed security.

SOLUTION

First define a user code map in secrets.yaml

# add usercode-name map entry to secrets.yaml
 
disarm_user_codes:
  {
    "1234": "Guest",
    "0001": "User 1",
    "0002": "User 2",
    "0003": "User 3",
    "0004": "User 4",
    "0005": "User 5"
  }

Create a manual alarm_control_panel, input_text, and two template sensors.

Optionally exclude the entities and scripts from the recorder to prevent history tracking, otherwise there will be multiple entries created for each touchpad input.

alarm_control_panel:
  - platform: manual
    name: Intrusion alarm
    code_arm_required: false
    armed_away:
      delay_time: 0
      arming_time: 10
    armed_home:
      delay_time: 20
      arming_time: 10
    armed_night:
      delay_time: 0
      arming_time: 0
 
input_text:
  touchpad_code:
    name: Touchpad code
    #mode: password
    pattern: "[0-9]*"
    min: 0
    max: 4
 
recorder:
  exclude:
    entities:
      - input_text.touchpad_code
      - sensor.touchpad_display
      - script.touchpad_key
     
template:
  - sensor:
      - name: "Disarm user codes"
        state: Mapped in attributes
        attributes: !secret disarm_user_codes
 
      - name: "Touchpad display"
        unique_id: touchpad_display
        state: >-
          {% if is_state('input_text.touchpad_code', '') %}
              {% set mode =
                states('alarm_control_panel.intrusion_alarm') %}
              {{ {
                  'disarmed': 'READY TO ARM',
                  'armed_home': 'ARMED FOR HOME',
                  'armed_away': "ARMED FOR AWAY",
                  'armed_night': 'ARMED FOR NIGHT',
                  'armed_vacation': 'ARMED FOR VACATION',
                  'triggered': 'INTRUSION ALARM',
                  'pending': 'PENDING ALARM',
                  'arming': 'ARMING ALARM'
                } [mode] }}
          {% else %}
            {{ '****'[:(states('input_text.touchpad_code')
              | length)] }}
          {% endif %}

In lovelace, add a new card, search for the manual card, and paste the following:

# lovelace card that creates a touchpad
 
type: vertical-stack
cards:
  - type: entity
    entity: sensor.touchpad_display
    name: ' '
    icon: none
  - type: grid
    cards:
      - type: button
        tap_action:
          action: call-service
          service: script.touchpad_key
          service_data:
            key: 1
        icon: mdi:numeric-1
      - type: button
        tap_action:
          action: call-service
          service: script.touchpad_key
          service_data:
            key: 2
        icon: mdi:numeric-2
      - type: button
        tap_action:
          action: call-service
          service: script.touchpad_key
          service_data:
            key: 3
        icon: mdi:numeric-3
      - type: button
        tap_action:
          action: call-service
          service: script.touchpad_key
          service_data:
            key: 4
        icon: mdi:numeric-4
      - type: button
        tap_action:
          action: call-service
          service: script.touchpad_key
          service_data:
            key: 5
        icon: mdi:numeric-5
      - type: button
        tap_action:
          action: call-service
          service: script.touchpad_key
          service_data:
            key: 6
        icon: mdi:numeric-6
      - type: button
        tap_action:
          action: call-service
          service: script.touchpad_key
          service_data:
            key: 7
        icon: mdi:numeric-7
      - type: button
        tap_action:
          action: call-service
          service: script.touchpad_key
          service_data:
            key: 8
        icon: mdi:numeric-8
      - type: button
        tap_action:
          action: call-service
          service: script.touchpad_key
          service_data:
            key: 9
        icon: mdi:numeric-9
      - type: button
        tap_action:
          action: call-service
          service: input_text.set_value
          service_data:
            value: ''
          target:
            entity_id: input_text.touchpad_code
        name: Cancel
        show_name: false
        icon: mdi:close
      - type: button
        tap_action:
          action: call-service
          service: script.touchpad_key
          service_data:
            key: 0
        icon: mdi:numeric-0
      - type: button
        tap_action:
          action: call-service
          service: script.touchpad_key
          service_data:
            key: '*'
        name: Backspace
        show_name: false
        icon: mdi:backspace-outline
    square: false
  - type: horizontal-stack
    cards:
      - type: button
        tap_action:
          action: call-service
          service: script.arm_home
        icon: mdi:home
        show_name: true
        name: Home
      - type: button
        tap_action:
          action: call-service
          service: script.arm_away
        icon: mdi:shield-lock
        name: Away
      - type: button
        tap_action:
          action: call-service
          service: script.arm_night
        icon: mdi:bed
        name: Night
  - type: history-graph
    entities:
      - entity: alarm_control_panel.intrusion_alarm
    hours_to_show: 24
    refresh_interval: 0

At this point there is a manual alarm_control_panel, two new sensors, an input_text and touchpad defined in lovelace

Each lovelace button calls a script that exposes a key variable to collect touch presses. Arming is implemented using arming scripts. When a manual alarm arming level is set while in a pending or triggered state, the triggered state is lost. Arming scripts have a condition that prevents kiosk users from effectively resetting a tripped manual alarm.

The buttons use the script’s entity_id, these can be pasted into the script ui but the entity_id must match

# scripts for button presses
touchpad_key:
  alias: Touchpad key
  sequence:
  - choose:
    - alias: asterix is backspace
      conditions:
      - condition: template
        value_template: >
          {{ key == '*' }}
      sequence:
      - service: input_text.set_value
        target:
          entity_id: input_text.touchpad_code
        data:
          value: >
            {{ states('input_text.touchpad_code')[:-1] }}
    default:
    - service: input_text.set_value
      target:
        entity_id: input_text.touchpad_code
      data:
        value: >
          {{ states('input_text.touchpad_code')[-3:] }}{{ key }}
  - delay:
      hours: 0
      minutes: 0
      seconds: 3
      milliseconds: 0
  - service: input_text.set_value
    target:
      entity_id: input_text.touchpad_code
    data:
      value: ''
  mode: restart
  fields:
    key:
      description: key character
      example: '1'
  icon: mdi:form-textbox-password
 
arm_away:
  alias: Arm away
  sequence:
  # only set away from unalarmed state, and
  # don't downgrade mode to home or night
  - condition: not
    conditions:
    - condition: state
      entity_id: alarm_control_panel.intrusion_alarm
      state: triggered
    - condition: state
      entity_id: alarm_control_panel.intrusion_alarm
      state: pending
    - condition: state
      entity_id: alarm_control_panel.intrusion_alarm
      state: armed_away
  - service: alarm_control_panel.alarm_arm_away
    target:
      entity_id: alarm_control_panel.intrusion_alarm
  mode: single
  icon: mdi:security
 
arm_home:
  alias: Arm home
  sequence:
  # only set home from disarmed unalarmed state, but
  # don't allow arming downgrade from away
  - condition: not
    conditions:
    - condition: state
      entity_id: alarm_control_panel.intrusion_alarm
      state: triggered
    - condition: state
      entity_id: alarm_control_panel.intrusion_alarm
      state: pending
    - condition: state
      entity_id: alarm_control_panel.intrusion_alarm
      state: armed_away
    - condition: state
      entity_id: alarm_control_panel.intrusion_alarm
      state: armed_home
  - service: alarm_control_panel.alarm_arm_home
    target:
      entity_id: alarm_control_panel.intrusion_alarm
  mode: single
  icon: mdi:security
 
arm_night:
  alias: Arm night
  sequence:
  # only set home from disarmed unalarmed state, and
  # don't allow arming downgrade from away  
  - condition: not
    conditions:
    - condition: state
      entity_id: alarm_control_panel.intrusion_alarm
      state: triggered
    - condition: state
      entity_id: alarm_control_panel.intrusion_alarm
      state: pending
    - condition: state
      entity_id: alarm_control_panel.intrusion_alarm
      state: armed_away
    - condition: state
      entity_id: alarm_control_panel.intrusion_alarm
      state: armed_night
  - service: alarm_control_panel.alarm_arm_night
    target:
      entity_id: alarm_control_panel.intrusion_alarm
  mode: single
  icon: mdi:security
 
arm_disarmed:
  alias: Arm disarmed
  sequence:
  - condition: not
    conditions:
    - condition: state
      entity_id: alarm_control_panel.intrusion_alarm
      state: disarmed
  - service: alarm_control_panel.alarm_disarm
    target:
      entity_id: alarm_control_panel.intrusion_alarm
  - service: notify.notify
    data:
      message: '{{ user }} disarmed security'
  mode: single
  fields:
    user:
      description: User or activity disarming security
      example: guest
  icon: mdi:security

Finally an automation is used to evaluate touchpad inputs

# automation to evaluate touchpad code input
alias: Touchpad code input
description: ''
trigger:
  - platform: state
    entity_id: input_text.touchpad_code
condition:
  - condition: template
    value_template: >
      {{ state_attr('sensor.disarm_user_codes',
      states('input_text.touchpad_code')) is not none }}
action:
  - service: script.turn_on
    target:
      entity_id: script.arm_disarmed
    data:
      variables:
        user: >
          {{ state_attr('sensor.disarm_user_codes',
          states('input_text.touchpad_code')) }}
  - service: input_text.set_value
    target:
      entity_id: input_text.touchpad_code
    data:
      value: ''
  - service: script.turn_off
    target:
      entity_id: script.touchpad_key
mode: single
1 Like

Wow. I was looking to do that exact same thing - except for the secrets - so I could pass whatever PIN code was entered to disarm my Qolsys alarm. Thank you, this is perfect! Can’t wait to test it out!

BTW, why not use Alarmo instead of this for your use case?

My home assistant alarm is enabled autonomously every night, elevates from night to away based on vacancy, and supports auto-disarm from night mode if conditions are met. Depending on the active security mode and situation, security sensors may trigger an alarm or disarm security; this isn’t standard alarm behavior, but it’s arguably the most ideal: hands-free, invisible, and autonomous.

My kiosk touchpads are typically used to quiet an alarm, triggered after autonomous arming to stay mode, when someone opens a perimeter door after a long nap, auto-disarm conditions are not met, and he or she forgot that there is an autonomous security system …

Other processes and scripts such as the arrival or departure of my tesla, and certain other automations, also use the disarm script above. In these non-user cases, the disarm user variable is passed a process name.

Alarmco would have slowed me down and not provided the functionality I was seeking to port to Home Assistant.

you can use lovelace to provide a stateful user experience as well of course, here’s an example touchpad built using card-mod and button-card

type: vertical-stack
cards:
  - type: entity
    entity: sensor.touchpad_display
    name: ' '
    icon: none
    card_mod:
      style: |
        ha-card {
          {% if 'ALARM' in states('sensor.touchpad_display') %}
            background: red
          {% endif %} ;
        }
  - type: conditional
    conditions:
      - entity: binary_sensor.touchpad_visible
        state: 'on'
    card:
      type: grid
      cards:
        - type: custom:button-card
          tap_action:
            haptic: heavy
            action: call-service
            service: script.touchpad_key
            service_data:
              key: 1
          icon: mdi:numeric-1
          styles:
            card:
              - height: 65px
        - type: custom:button-card
          tap_action:
            haptic: heavy
            action: call-service
            service: script.touchpad_key
            service_data:
              key: 2
          icon: mdi:numeric-2
          styles:
            card:
              - height: 65px
        - type: custom:button-card
          tap_action:
            haptic: heavy
            action: call-service
            service: script.touchpad_key
            service_data:
              key: 3
          icon: mdi:numeric-3
          styles:
            card:
              - height: 65px
        - type: custom:button-card
          tap_action:
            haptic: heavy
            action: call-service
            service: script.touchpad_key
            service_data:
              key: 4
          icon: mdi:numeric-4
          styles:
            card:
              - height: 65px
        - type: custom:button-card
          tap_action:
            haptic: heavy
            action: call-service
            service: script.touchpad_key
            service_data:
              key: 5
          icon: mdi:numeric-5
          styles:
            card:
              - height: 65px
        - type: custom:button-card
          tap_action:
            haptic: heavy
            action: call-service
            service: script.touchpad_key
            service_data:
              key: 6
          icon: mdi:numeric-6
          styles:
            card:
              - height: 65px
        - type: custom:button-card
          tap_action:
            haptic: heavy
            action: call-service
            service: script.touchpad_key
            service_data:
              key: 7
          icon: mdi:numeric-7
          styles:
            card:
              - height: 65px
        - type: custom:button-card
          tap_action:
            haptic: heavy
            action: call-service
            service: script.touchpad_key
            service_data:
              key: 8
          icon: mdi:numeric-8
          styles:
            card:
              - height: 65px
        - type: custom:button-card
          tap_action:
            haptic: heavy
            action: call-service
            service: script.touchpad_key
            service_data:
              key: 9
          icon: mdi:numeric-9
          styles:
            card:
              - height: 65px
        - type: button
          tap_action:
            haptic: heavy
            action: call-service
            service: input_text.set_value
            service_data:
              value: ''
            target:
              entity_id: input_text.touchpad_code
          name: Cancel
          show_icon: false
          styles:
            card:
              - height: 65px
        - type: custom:button-card
          tap_action:
            haptic: heavy
            action: call-service
            service: script.touchpad_key
            service_data:
              key: 0
          icon: mdi:numeric-0
          styles:
            card:
              - height: 65px
        - type: custom:button-card
          tap_action:
            haptic: heavy
            action: call-service
            service: script.touchpad_key
            service_data:
              key: '*'
          icon: mdi:backspace
          styles:
            card:
              - height: 65px
            icon:
              - height: 25px
      square: false
  - type: horizontal-stack
    cards:
      - type: custom:button-card
        entity: alarm_control_panel.intrusion_alarm
        styles:
          card:
            - height: 100px
        state:
          - value: armed_home
            color: red
            styles:
              card:
                - border-style: solid
                - border-width: 2px
                - border-color: red
              icon:
                - color: red
              name:
                - color: red
          - value: triggered
            color: var(--disabled-text-color)
            styles:
              name:
                - color: var(--disabled-text-color)
          - value: pending
            color: var(--disabled-text-color)
            styles:
              name:
                - color: var(--disabled-text-color)
          - value: armed_away
            color: var(--disabled-text-color)
            styles:
              name:
                - color: var(--disabled-text-color)
        tap_action:
          haptic: heavy
          action: call-service
          service: script.arm_stay
          service_data: {}
          target: {}
        icon: mdi:home
        show_name: true
        name: Stay
      - type: custom:button-card
        entity: alarm_control_panel.intrusion_alarm
        styles:
          card:
            - height: 100px
        state:
          - value: armed_away
            color: red
            styles:
              card:
                - border-style: solid
                - border-width: 2px
                - border-color: red
              icon:
                - color: red
              name:
                - color: red
          - value: triggered
            color: var(--disabled-text-color)
            styles:
              name:
                - color: var(--disabled-text-color)
          - value: pending
            color: var(--disabled-text-color)
            styles:
              name:
                - color: var(--disabled-text-color)
        tap_action:
          haptic: heavy
          action: call-service
          service: script.arm_away
          service_data: {}
          target: {}
        icon: mdi:door-open
        name: Away
      - type: custom:button-card
        entity: alarm_control_panel.intrusion_alarm
        styles:
          card:
            - height: 100px
        state:
          - value: armed_night
            color: red
            styles:
              card:
                - border-style: solid
                - border-width: 2px
                - border-color: red
              icon:
                - color: red
              name:
                - color: red
          - value: triggered
            color: var(--disabled-text-color)
            styles:
              name:
                - color: var(--disabled-text-color)
          - value: pending
            color: var(--disabled-text-color)
            styles:
              name:
                - color: var(--disabled-text-color)
          - value: armed_away
            color: var(--disabled-text-color)
            styles:
              name:
                - color: var(--disabled-text-color)
        tap_action:
          haptic: heavy
          action: call-service
          service: script.arm_night
          service_data: {}
          target: {}
        icon: mdi:bed
        name: Night
  - type: history-graph
    entities:
      - entity: alarm_control_panel.intrusion_alarm
    hours_to_show: 24
    refresh_interval: 0

the touchpad key grid can be shown or hidden based on a template binary sensor also, which makes for a more compact card situational card for mobile phone use

- binary_sensor:
    - name: "Touchpad Visible"
      unique_id: touchpad_visible
      state: >
        {% if is_state('input_text.touchpad_code', '') %}
          {% if states('input_boolean.fire_and_co_alarm') == 'on' %}
            true
          {% elif states('input_boolean.intrusion_alarm') == 'on' %}
            true
          {% else %}  
            {% set mode = states('alarm_control_panel.intrusion_alarm') %}
            {% if mode == 'pending' %}
              true
            {% else %}
              {{ {'disarmed': false, 'armed_home': true, 'armed_away': true, 'armed_night': true, 'triggered': true, 'pending': true}[mode] }}  
            {% endif %} 
          {% endif %}
        {% else %}
          true
        {% endif %}

displays equally well on an iphone or tablet

1 Like