Lovelace: Alarm card

Thanks for maintaining this card! One question, how do you get the alarm code to show asterisks when entering the code? regardless of using the keypad that’s part of the alarm, the keypad on my android phone or on my iPad the numbers is always showing when disarming.

I’m feeling rather stupid here. I cannot seem to get the keypad to appear even after reading and making changes based on this thread. Here is my code:

  • entity: alarm_control_panel.43155
    show_keypad: true
    code: ‘0000’
    states:
    - arm_home
    - arm_away
    title: Alarm
    type: ‘custom:alarm_control_panel-card’

Any help would be greatly appreciated.

Try: hide_keypad: false

Sadly, no change.

try indenting the states 2 spaces in (arm_home & arm_away) so the dashes are under the letter a of the word states. It would help as well if you formatted your bits with 3 ```.

I figured it out and it had more to do with how I initially added my alarm system (SimpliSafe) than my code. When I originally added it, I did it through the auto-discovery system. Well, that never added it to the configuration.yaml file so this had nothing to reference. I added it through the configuration.yaml file and it now works. Thank you for attempting to help.

Try this one, it works you just need to change the entity to your alarm panel name.

entity: alarm_control_panel.ha_alarm
show_keypad: true
title: My Alarm
states:
  - arm_home
  - arm_away

glad to hear you fix it.

My card looks different than the examples. I replaced “paper-button” with “mwc-button” to get the square buttons rather than just numbers, and left everything else alone. The “Arm Home” and “Arm Away” buttons are touching, and the status icon at the top right never loads, no matter the status. I disabled the theme thinking that might be it, but it made no difference.

I’ve dug through the js file and saw that it is calling icons from mdi, but nothing is showing. Anyone have any advice on what is going on here?

Thanks to all of you who shared their work. This is why Home Assistant is the best Home-Automation-System out there; it’s the community!

I did a one more change to the last commit:

  • Changed input-type to “password” to not display the code at input

alarm_control_panel-card.js

class AlarmControlPanelCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this._icons = {
      'armed_away': 'mdi:shield-lock',
      'armed_custom_bypass': 'mdi:security',
      'armed_home': 'mdi:shield-home',
      'armed_night': 'mdi:shield-home',
      'disarmed': 'mdi:shield-check',
      'pending': 'mdi:shield-outline',
      'triggered': 'hass:bell-ring',
    }
  }

  set hass(hass) {
    const entity = hass.states[this._config.entity];

    if (entity) {
      this.myhass = hass;
      if(!this.shadowRoot.lastChild) {
        this._createCard(entity);
      }
      if (entity.state != this._state) {
        this._state = entity.state;
        this._updateCardContent(entity);
      }
    }
  }

  _createCard(entity) {
    const config = this._config;

    const card = document.createElement('ha-card');
    card.innerHTML = `
      ${this._iconLabel()}
      ${config.title ? '<div id="state-text"></div>' : ''}
    `;
    const content = document.createElement('div');
    content.id = "content";
    content.style.display = config.auto_hide ? 'none' : '';
    //`<paper-input label='${this._label("ui.card.alarm_control_panel.code")}' type="password"></paper-input>` : ''}
    content.innerHTML = `
      ${this._actionButtons()}
      ${entity.attributes.code_format ?
          `<paper-input label='${this._label("ui.card.alarm_control_panel.code")}' type="password"></paper-input>` : ''}
      ${this._keypad(entity)}
    `;
    card.appendChild(this._style(config.style, entity));
    card.appendChild(content);
    this.shadowRoot.appendChild(card);

    this._setupInput();
    this._setupKeypad();
    this._setupActions();
  }

  connectedCallback() {
  }

  setConfig(config) {
    if (!config.entity || config.entity.split(".")[0] !== "alarm_control_panel") {
      throw new Error('Please specify an entity from alarm_control_panel domain.');
    }
    if (config.auto_enter) {
      if (!config.auto_enter.code_length || !config.auto_enter.arm_action) {
        throw new
          Error('Specify both code_length and arm_action when using auto_enter.');
      }
      this._arm_action = config.auto_enter.arm_action;
    }
    if (!config.states) config.states = ['arm_away', 'arm_home'];
    if (!config.scale) config.scale = '15px';
    this._config = Object.assign({}, config);

    const root = this.shadowRoot;
    if (root.lastChild) root.removeChild(root.lastChild);
  }

  _updateCardContent(entity) {
    const root = this.shadowRoot;
    const card = root.lastChild;
    const config = this._config;

    const state_str = "state.alarm_control_panel." + this._state;
    if (config.title) {
      card.header = config.title;
      root.getElementById("state-text").innerHTML = this._label(state_str);
      root.getElementById("state-text").className = `state ${this._state}`;
    } else {
      card.header = this._label(state_str);
    }

    root.getElementById("state-icon").setAttribute("icon",
      this._icons[this._state] || 'mdi:shield-outline');
    root.getElementById("badge-icon").className = this._state;

    var iconText = this._stateIconLabel(this._state);
    if (iconText === "") {
      root.getElementById("icon-label").style.display = "none";
    } else {
      root.getElementById("icon-label").style.display = "";
      if (iconText.length > 5) {
        root.getElementById("icon-label").className = "label big";
      } else {
	root.getElementById("icon-label").className = "label";
      }
      root.getElementById("icon-text").innerHTML = iconText;
    }

    const armVisible = (this._state === 'disarmed');
    root.getElementById("arm-actions").style.display = armVisible ? "" : "none";
    root.getElementById("disarm-actions").style.display = armVisible ? "none" : "";
  }

  _actionButtons() {
    const armVisible = (this._state === 'disarmed');
    return `
      <div id="arm-actions" class="actions">
        ${this._config.states.map(el => `${this._actionButton(el)}`).join('')}
      </div>
      <div id="disarm-actions" class="actions">
        ${this._actionButton('disarm')}
      </div>`;
  }

  _stateIconLabel(state) {
    const stateLabel = state.split("_").pop();
    return stateLabel === "disarmed" ||
      stateLabel === "triggered" ||
      !stateLabel
      ? ""
      : stateLabel;
  }

  _iconLabel() {
    return `
      <ha-label-badge-icon id="badge-icon">
        <div class="badge-container" id="badge-container">
          <div class="label-badge" id="badge">
            <div class="value">
              <ha-icon id="state-icon"/>
            </div>
            <div class="label" id="icon-label">
              <span id="icon-text"/>
            </div>
          </div>
        </div>
    </ha-label-badge-icon>`;	  
  }

  _actionButton(state) {
    return `<mwc-button outlined id="${state}">
      ${this._label("ui.card.alarm_control_panel." + state)}</mwc-button>`;
  }

  _setupActions() {
    const root = this.shadowRoot;
    const card = this.shadowRoot.lastChild;
    const config = this._config;

    if (config.auto_hide) {
      root.getElementById("badge-icon").addEventListener('click', event => {
        var content = root.getElementById("content");
        if (content.style.display === 'none') {
          content.style.display = '';
        } else {
          content.style.display = 'none';
        }
      })
    }

    if (config.auto_enter) {
      card.querySelectorAll(".actions mwc-button").forEach(element => {
        element.classList.remove('autoarm');
        if (element.id === this._arm_action || element.id === 'disarm') {
          element.classList.add('autoarm');
        }
        element.addEventListener('click', event => {
          card.querySelectorAll(".actions mwc-button").forEach(element => {
            element.classList.remove('autoarm');
          })
          element.classList.add('autoarm');
          if (element.id !== 'disarm') this._arm_action = element.id;
        })
      })
    } else {
      card.querySelectorAll(".actions mwc-button").forEach(element => {
        element.addEventListener('click', event => {
          const input = card.querySelector('paper-input');
          const value = input ? input.value : '';
          this._callService(element.id, value);
        })
      })
    }
  }

  _callService(service, code) {
    const input = this.shadowRoot.lastChild.querySelector("paper-input");
    this.myhass.callService('alarm_control_panel', `alarm_${service}`, {
      entity_id: this._config.entity,
      code: code,
    });
    if (input) input.value = '';
  }

  _setupInput() {
    if (this._config.auto_enter) {
      const input = this.shadowRoot.lastChild.querySelector("paper-input");
      input.addEventListener('input', event => { this._autoEnter() })
    }
  }

  _setupKeypad() {
    const root = this.shadowRoot;

    const input = root.lastChild.querySelector('paper-input');
    root.querySelectorAll(".pad button").forEach(element => {
      if (element.getAttribute('value') === 
        this._label("ui.card.alarm_control_panel.clear_code")) {
        element.addEventListener('click', event => {
          input.value = '';
        })
      } else {
        element.addEventListener('click', event => {
          input.value += element.getAttribute('value');
          this._autoEnter();
        })
      }
    });
  }

  _autoEnter() {
    const config = this._config;

    if (config.auto_enter) {
      const card = this.shadowRoot.lastChild;
      const code = card.querySelector("paper-input").value;
      if (code.length == config.auto_enter.code_length) {
        const service = card.querySelector(".actions .autoarm").id;
        this._callService(service, code);
      }
    }
  }

  _keypad(entity) {
    if (this._config.hide_keypad || !entity.attributes.code_format) return '';

    return `
      <div class="pad">
        <div>
          ${this._keypadButton("1", "")}
          ${this._keypadButton("4", "GHI")}
          ${this._keypadButton("7", "PQRS")}
        </div>
        <div>
          ${this._keypadButton("2", "ABC")}
          ${this._keypadButton("5", "JKL")}
          ${this._keypadButton("8", "TUV")}
          ${this._keypadButton("0", "")}
        </div>
        <div>
          ${this._keypadButton("3", "DEF")}
          ${this._keypadButton("6", "MNO")}
          ${this._keypadButton("9", "WXYZ")}
          ${this._keypadButton(this._label("ui.card.alarm_control_panel.clear_code"), "")}
        </div>
      </div>`
  }

  _keypadButton(button, alpha) {
    let letterHTML = '';
    if (this._config.display_letters) {
      letterHTML = `<div class='alpha'>${alpha}</div>`
    }
    return `<button value="${button}">${button}${letterHTML}</button>`;
  }

  _style(icon_style, entity) {
    const style = document.createElement('style');
    style.textContent = `
      ha-card {
        ${(this._config.hide_keypad ||
	   !entity.attributes.code_format) ? 'padding-bottom: 16px;' : '' }
        position: relative;
        --alarm-color-disarmed: var(--label-badge-green);
        --alarm-color-pending: var(--label-badge-yellow);
        --alarm-color-triggered: var(--label-badge-red);
        --alarm-color-armed: var(--label-badge-red);
        --alarm-color-autoarm: rgba(0, 153, 255, .1);
        --alarm-state-color: var(--alarm-color-armed);
        --base-unit: ${this._config.scale};
        font-size: calc(var(--base-unit));
        ${icon_style}
      }
      ha-icon {
        color: var(--alarm-state-color);
	    width: 24px;
	    height: 24px;
      }
      ha-label-badge-icon {
        --ha-label-badge-color: var(--alarm-state-color);
        --label-badge-text-color: var(--alarm-state-color);
        --label-badge-background-color: var(--paper-card-background-color);
        color: var(--alarm-state-color);
        position: absolute;
        right: 12px;
        top: 12px;
      }
      .badge-container {
        display: inline-block;
        text-align: center;
        vertical-align: top;
      }
      .label-badge {
        position: relative;
        display: block;
        margin: 0 auto;
        width: var(--ha-label-badge-size, 2.5em);
        text-align: center;
        height: var(--ha-label-badge-size, 2.5em);
        line-height: var(--ha-label-badge-size, 2.5em);
        font-size: var(--ha-label-badge-font-size, 1.5em);
        border-radius: 50%;
        border: 0.1em solid var(--ha-label-badge-color, var(--primary-color));
        color: var(--label-badge-text-color, rgb(76, 76, 76));
        white-space: nowrap;
        background-color: var(--label-badge-background-color, white);
        background-size: cover;
        transition: border 0.3s ease-in-out;
      }
      .label-badge .value {
        font-size: 90%;
        overflow: hidden;
        text-overflow: ellipsis;
      }
      .label-badge .value.big {
        font-size: 70%;
      }
      .label-badge .label {
        position: absolute;
        bottom: -1em;
        /* Make the label as wide as container+border. (parent_borderwidth / font-size) */
        left: -0.2em;
        right: -0.2em;
        line-height: 1em;
        font-size: 0.5em;
      }
      .label-badge .label span {
        box-sizing: border-box;
        max-width: 100%;
        display: inline-block;
        background-color: var(--ha-label-badge-color, var(--primary-color));
        color: var(--ha-label-badge-label-color, white);
        border-radius: 1em;
        padding: 9% 16% 8% 16%; /* mostly apitalized text, not much descenders => bit more top margin */
        font-weight: 500;
        overflow: hidden;
        text-transform: uppercase;
        text-overflow: ellipsis;
        transition: background-color 0.3s ease-in-out;
        text-transform: var(--ha-label-badge-label-text-transform, uppercase);
      }
      .label-badge .label.big span {
        font-size: 90%;
        padding: 10% 12% 7% 12%; /* push smaller text a bit down to center vertically */
      }
      .badge-container .title {
        margin-top: 1em;
        font-size: var(--ha-label-badge-title-font-size, 0.9em);
        width: var(--ha-label-badge-title-width, 5em);
        font-weight: var(--ha-label-badge-title-font-weight, 400);
        overflow: hidden;
        text-overflow: ellipsis;
        line-height: normal;
      }
      .disarmed {
        --alarm-state-color: var(--alarm-color-disarmed);
      }
      .triggered {
        --alarm-state-color: var(--alarm-color-triggered);
        animation: pulse 1s infinite;
      }
      .arming {
        --alarm-state-color: var(--alarm-color-pending);
        animation: pulse 1s infinite;
      }
      .pending {
        --alarm-state-color: var(--alarm-color-pending);
        animation: pulse 1s infinite;
      }
      @keyframes pulse {
        0% {
          --ha-label-badge-color: var(--alarm-state-color);
        }
        100% {
          --ha-label-badge-color: rgba(255, 153, 0, 0.3);
        }
      }
      paper-input {
        margin: auto;
        max-width: 200px;
        font-size: calc(var(--base-unit));
      }
      .state {
        margin-left: 20px;
        font-size: calc(var(--base-unit) * 0.9);
        position: relative;
        bottom: 16px;
        color: var(--alarm-state-color);
        animation: none;
      }
      .pad {
        display: flex;
        justify-content: center;
      }
      .pad div {
        display: flex;
        flex-direction: column;
      }
      .pad button {
        position: relative;
        padding: calc(var(--base-unit)*0.5);
        font-size: calc(var(--base-unit) * 1.6);
        width: calc(var(--base-unit) * 6);
        margin: 8px;
        #background-color: var(--primary-background-color);
        background-color: var(--primary-color);
        border-width: 2px;
        border-style: solid;
        border-color: var(--primary-color);
        border-radius: 4px;
        #color: var(--primary-color);
        color: var(--text-primary-color);
      }
      .pad button:focus {
        background-color: var(--dark-primary-color);
        border-color: var(--primary-color);
        border-width: 2px;
        outline: none;
      }
      .actions {
        margin: 0 8px;
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        font-size: calc(var(--base-unit) * 1);
      }
      .actions mwc-button {
        min-width: calc(var(--base-unit) * 9);
        color: var(--primary-color);
		margin-top: 0px;
		margin-right: 4px;
		margin-bottom: 0px;
		margin-left: 4px;
      }
      .actions .autoarm {
        background: var(--alarm-color-autoarm);
      }
      mwc-button#disarm {
        color: var(--google-red-500);
      }
      .alpha {
        position: absolute;
        text-align: center;
        bottom: calc(var(--base-unit) * 0.1);
        color: var(--secondary-text-color);
        font-size: calc(var(--base-unit) * 0.7);
      }
    `;
    return style;
  }

  _label(label, default_label=undefined) {
    // Just show "raw" label; useful when want to see underlying const
    // so you can define your own label.
    if (this._config.show_label_ids) return label;

    if (this._config.labels && this._config.labels[label])
      return this._config.labels[label];

    const lang = this.myhass.selectedLanguage || this.myhass.language;
    const translations = this.myhass.resources[lang];
    if (translations && translations[label]) return translations[label];

    if (default_label) return default_label;

    // If all else fails then pretify the passed in label const
    const last_bit = label.split('.').pop();
    return last_bit.split('_').join(' ').replace(/^\w/, c => c.toUpperCase());
  }

  getCardSize() {
    return 1;
  }
}

customElements.define('alarm_control_panel-card', AlarmControlPanelCard);

In the Lovelace-UI:

entity: alarm_control_panel.alarmanlage
labels:
  ui.card.alarm_control_panel.arm_away: Unterwegs
  ui.card.alarm_control_panel.arm_home: Zuhause
  ui.card.alarm_control_panel.arm_night: Nachtmodus
  ui.card.alarm_control_panel.clear_code: ←
  ui.card.alarm_control_panel.code: Code
states:
  - arm_home
  - arm_away
  - arm_night
title: Alarmanlage
type: 'custom:alarm_control_panel-card'

My (german) output (with the midnight-theme):

I would like to change the font-size (e.g. clear-button) to match bigger words. Anyone knows how to do this for just one keypad-button?

Kind regards!

1 Like

Don’t suppose there is a way to change the color of the buttons to red if the alarm gets armed? Just to make it a little more apparent the system is armed.

the badge next to the buttons is red when armed…

Yes, it is. I have a fire8 tablet on the wall as my HA dashboard and the badge is not very visible from a distance. In any case, I just created another theme with red buttons and set up an automation to switch themes when armed/disarmed.


I set up my abode integration through the HA GUI, and this card seems to be working for me, but when I ‘arm away’ the icon does not blink pending while the countdown is counting down. The command is executing because the alarm starts doing its countdown, but through the card its hard to tell if the command executed because the icon isn’t blinking. Is there a way to fix this?

I have my code like that

title: Alarm
icon: mdi: alarm-light
cards:

  • type: ‘custom: alarm_control_panel-card’
    entity: alarm_control_panel.house
    show_keypad: true
    title: House Alarm
    tyle: ‘–alarm-color-disarmed: var (- label-badge-blue);’
    labels:
    ui.card.alarm_control_panel.arm_away: Outside
    ui.card.alarm_control_panel.arm_home: House
    ui.card.alarm_control_panel.arm_night: Summer
    ui.card.alarm_control_panel.clear_code: ←
    ui.card.alarm_control_panel.code: Code
    states:
    • arm_home
    • arm_away
    • arm_night

and I don’t get the keyboard how can I fix it?

Hi,
i’ve used the above code to install custom alarm card.
Unfortunately cards ha an error, could you help me please?

This is card code:

Cattura

…and this is resource:

Cattura2
Js file is in www directory.

Thank again for your help.
Regards,

Alessandro

try to remove quotes around type string and also a space between custom: and the rest as per this post

Thank you so much guys,
now it works!

Regards,

Alessandro

1 Like

Is it possible to be able to arm without entering a code?

I believe it depends on your alarm’s settings - if it requires code to ar, you’ll see the keypad, otherwise just buttons to arm.