├── Screenshots ├── Armed-home.png ├── Arming-countdown.png ├── Disarmed-ready.png ├── Disarmed-not-ready.png └── Installation-Resource.png ├── hacs.json ├── ExampleConfig ├── scripts.yaml ├── groups.yaml ├── configuration.yaml ├── AlarmLovelaceDashboard.yaml ├── AlarmLovelaceDashboardWithAutoEnter.yaml └── automations.yaml ├── README.md └── AlarmPanel.js /Screenshots/Armed-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcooper-korg/AlarmPanel/HEAD/Screenshots/Armed-home.png -------------------------------------------------------------------------------- /Screenshots/Arming-countdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcooper-korg/AlarmPanel/HEAD/Screenshots/Arming-countdown.png -------------------------------------------------------------------------------- /Screenshots/Disarmed-ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcooper-korg/AlarmPanel/HEAD/Screenshots/Disarmed-ready.png -------------------------------------------------------------------------------- /Screenshots/Disarmed-not-ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcooper-korg/AlarmPanel/HEAD/Screenshots/Disarmed-not-ready.png -------------------------------------------------------------------------------- /Screenshots/Installation-Resource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcooper-korg/AlarmPanel/HEAD/Screenshots/Installation-Resource.png -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AlarmPanel", 3 | "content_in_root": true, 4 | "filename": "AlarmPanel.js", 5 | "homeassistant": "2024.09.0" 6 | } -------------------------------------------------------------------------------- /ExampleConfig/scripts.yaml: -------------------------------------------------------------------------------- 1 | # First disarms the alarm, and then triggers it. 2 | # Since the delay_time in the disarmed state is set to 0 3 | # (in the alarm_control_panel section in configuration.yaml), 4 | # this will cause the alarm to immediately trigger. 5 | trigger_alarm_immediately: 6 | alias: Trigger Alarm Immediately 7 | mode: single 8 | sequence: 9 | - service: alarm_control_panel.alarm_disarm 10 | data: 11 | entity_id: alarm_control_panel.house 12 | code: !secret house_alarm_code 13 | - service: alarm_control_panel.alarm_trigger 14 | entity_id: alarm_control_panel.house 15 | -------------------------------------------------------------------------------- /ExampleConfig/groups.yaml: -------------------------------------------------------------------------------- 1 | # alarm_zones_for_led is used for triggering when armed away and for the green LED state 2 | alarm_zones_for_led: 3 | name: Alarm Zones 4 | entities: 5 | - binary_sensor.hall_motion 6 | - binary_sensor.living_room_motion 7 | - binary_sensor.office_window 8 | - binary_sensor.living_room_window 9 | - binary_sensor.kitchen_sink_window 10 | - binary_sensor.kitchen_table_window 11 | - binary_sensor.dining_room_window 12 | - binary_sensor.master_bedroom_window 13 | - binary_sensor.master_bath_window 14 | - binary_sensor.bedroom1_window 15 | - binary_sensor.bedroom2_window 16 | - binary_sensor.front_door 17 | - binary_sensor.back_door 18 | - binary_sensor.garage_door 19 | - binary_sensor.side_garage_door 20 | -------------------------------------------------------------------------------- /ExampleConfig/configuration.yaml: -------------------------------------------------------------------------------- 1 | # Configure a default setup of Home Assistant (frontend, api, etc) 2 | default_config: 3 | 4 | # use input_text in the alarm automation to store the name of the source that triggered the alarm 5 | # so it can be shown in the notification automation 6 | input_text: 7 | trigger_source: 8 | name: Trigger Source 9 | 10 | # configure manual alarm 11 | alarm_control_panel: 12 | - platform: manual 13 | name: House 14 | code: !secret house_alarm_code 15 | code_arm_required: false 16 | trigger_time: 120 17 | disarmed: 18 | delay_time: 0 # immediately trigger siren by switching first to disarmed, then to trigger and since disarmed delay_time is 0, it'll immediately trigger 19 | armed_away: 20 | arming_time: 60 21 | delay_time: 30 # when triggered in away state (used for entry doors), 30 seconds pending before trigger 22 | armed_home: 23 | arming_time: 0 24 | delay_time: 0 25 | 26 | notify: 27 | - name: alarm_notification_group 28 | platform: group 29 | services: 30 | - service: mobile_app_iphone1 31 | - service: mobile_app_iphone2 32 | 33 | group: !include groups.yaml 34 | automation: !include automations.yaml 35 | script: !include scripts.yaml 36 | scene: !include scenes.yaml 37 | -------------------------------------------------------------------------------- /ExampleConfig/AlarmLovelaceDashboard.yaml: -------------------------------------------------------------------------------- 1 | custom_header: 2 | compact_mode: true 3 | exceptions: 4 | - conditions: 5 | user: Alarm Panel 6 | config: 7 | kiosk_mode: true # hide side bar and menu bar 8 | views: 9 | - badges: [] 10 | cards: 11 | - confirm_entities: 12 | - binary_sensor.office_window 13 | - binary_sensor.living_room_window 14 | - binary_sensor.kitchen_sink_window 15 | - binary_sensor.kitchen_table_window 16 | - binary_sensor.dining_room_window 17 | - binary_sensor.master_bedroom_window 18 | - binary_sensor.master_bath_window 19 | - binary_sensor.bedroom1_window 20 | - binary_sensor.bedroom2_window 21 | - binary_sensor.front_door 22 | - binary_sensor.back_door 23 | - binary_sensor.garage_door 24 | - binary_sensor.side_garage_door 25 | - binary_sensor.hall_motion 26 | - binary_sensor.living_room_motion 27 | entity: alarm_control_panel.house 28 | show_countdown_timer: true 29 | durations: 30 | arming: 60 31 | pending: 30 32 | disable_arm_if_not_ready: true 33 | show_override_if_not_ready: true 34 | labels: 35 | ui.card.alarm_control_panel.arm_away: AWAY 36 | ui.card.alarm_control_panel.arm_home: HOME 37 | ui.card.alarm_control_panel.clear_code: CLEAR 38 | ui.card.alarm_control_panel.disarm: DISARM 39 | scale: 14px 40 | states: 41 | - arm_home 42 | - arm_away 43 | type: 'custom:alarm_control_panel-card' 44 | - card: 45 | entities: 46 | - binary_sensor.office_window 47 | - binary_sensor.living_room_window 48 | - binary_sensor.kitchen_sink_window 49 | - binary_sensor.kitchen_table_window 50 | - binary_sensor.dining_room_window 51 | - binary_sensor.master_bedroom_window 52 | - binary_sensor.master_bath_window 53 | - binary_sensor.bedroom1_window 54 | - binary_sensor.bedroom2_window 55 | - binary_sensor.front_door 56 | - binary_sensor.back_door 57 | - binary_sensor.garage_door 58 | - binary_sensor.side_garage_door 59 | - binary_sensor.hall_motion 60 | - binary_sensor.living_room_motion 61 | state_filter: 62 | - 'on' 63 | type: entity-filter 64 | conditions: 65 | - entity: alarm_control_panel.house 66 | state: disarmed 67 | - entity: group.alarm_zones_for_led 68 | state: 'on' 69 | type: conditional 70 | title: Alarm 71 | 72 | -------------------------------------------------------------------------------- /ExampleConfig/AlarmLovelaceDashboardWithAutoEnter.yaml: -------------------------------------------------------------------------------- 1 | custom_header: 2 | compact_mode: true 3 | exceptions: 4 | - conditions: 5 | user: Alarm Panel 6 | config: 7 | kiosk_mode: true # hide side bar and menu bar 8 | views: 9 | - badges: [] 10 | cards: 11 | - confirm_entities: 12 | - binary_sensor.office_window 13 | - binary_sensor.living_room_window 14 | - binary_sensor.kitchen_sink_window 15 | - binary_sensor.kitchen_table_window 16 | - binary_sensor.dining_room_window 17 | - binary_sensor.master_bedroom_window 18 | - binary_sensor.master_bath_window 19 | - binary_sensor.bedroom1_window 20 | - binary_sensor.bedroom2_window 21 | - binary_sensor.front_door 22 | - binary_sensor.back_door 23 | - binary_sensor.garage_door 24 | - binary_sensor.side_garage_door 25 | - binary_sensor.hall_motion 26 | - binary_sensor.living_room_motion 27 | entity: alarm_control_panel.house 28 | show_countdown_timer: true 29 | durations: 30 | arming: 60 31 | pending: 30 32 | disable_arm_if_not_ready: true 33 | labels: 34 | ui.card.alarm_control_panel.arm_away: AWAY 35 | ui.card.alarm_control_panel.arm_home: HOME 36 | ui.card.alarm_control_panel.clear_code: CLEAR 37 | ui.card.alarm_control_panel.disarm: DISARM 38 | scale: 14px 39 | states: 40 | - arm_home 41 | - arm_away 42 | auto_enter: 43 | code_length: 4 44 | arm_action: arm_away 45 | type: 'custom:alarm_control_panel-card' 46 | - card: 47 | entities: 48 | - binary_sensor.office_window 49 | - binary_sensor.living_room_window 50 | - binary_sensor.kitchen_sink_window 51 | - binary_sensor.kitchen_table_window 52 | - binary_sensor.dining_room_window 53 | - binary_sensor.master_bedroom_window 54 | - binary_sensor.master_bath_window 55 | - binary_sensor.bedroom1_window 56 | - binary_sensor.bedroom2_window 57 | - binary_sensor.front_door 58 | - binary_sensor.back_door 59 | - binary_sensor.garage_door 60 | - binary_sensor.side_garage_door 61 | - binary_sensor.hall_motion 62 | - binary_sensor.living_room_motion 63 | state_filter: 64 | - 'on' 65 | type: entity-filter 66 | conditions: 67 | - entity: alarm_control_panel.house 68 | state: disarmed 69 | - entity: group.alarm_zones_for_led 70 | state: 'on' 71 | type: conditional 72 | title: Alarm 73 | 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Custom Alarm Panel Card 2 | 3 | This custom alarm panel card was forked in September 2020 from [Kevin Cooper's repo](https://github.com/JumpMaster/custom-lovelace) (no relation). 4 | 5 | I have included my complete working [manual alarm control panel](https://www.home-assistant.io/integrations/manual/) configuration, automations, script, lovelace dashboard, etc in the [ExampleConfig](https://github.com/jcooper-korg/AlarmPanel/tree/master/ExampleConfig) folder. 6 | 7 | ## News 8 | 9 | This card was last tested working with Home Assistant 2025.10.1. HA occasionally makes breaking changes, and I don't constantly update my system so I may not notice if it becomes broken. 10 | 11 | Previous versions of this card were named alarm_control_panel-card.js, but the card was renamed to AlarmPanel.js to match the repo name, for HACS compatibility. If you are updating, revisit the installation instructions below. 12 | 13 | Previous versions of this card included a custom version of the HA manual_alarm_panel component. This is no longer supported. If you had installed a previous version of this card, you should remove it from `/config/custom_components/manual`. Optional backstory: I had modified that component to publish a state_duration attribute for use with my countdown timer. But the HA team [rejected my pull request](https://github.com/home-assistant/core/pull/41872), so I have since removed it, and now require the durations to be specified in the card config, as detailed below. 14 | 15 | 16 | ## Card Modification Goals 17 | 18 | * improve the appearance of the arm/disarm and keypad buttons (which are too small on current Home Assistant versions) 19 | * high visibility and ergonomically sized buttons on web, iOS companion app, and wall mounted tablet 20 | * hide the key pad when disarmed (if `alarm_control_panel.code_arm_required` is off) 21 | * add a config option to show Ready / Not Ready when disarmed, monitoring a given list of entities 22 | * add a config option to show a countdown timer when arming or pending 23 | 24 | I changed the buttons from mwc-button to regular buttons. I couldn't find a reliable way to make the mwc-buttons larger (even using tools like [Thomas Loven's card mod](https://github.com/thomasloven/lovelace-card-mod)), and they were just much too small on a wall-mounted tablet. 25 | 26 | I moved the Disarm button from the top button row to the keypad, to the right of the 0 button. This allows the overall keypad area and button size to be increased, which is helpful on a phone screen or wall mounted tablet. 27 | 28 | ## Screenshots 29 | 30 | 31 | 32 | 33 | 34 | 35 | ## Installation 36 | 37 | To use this card in Home Assistant: 38 | 39 | 40 | 41 | 42 | * copy the `AlarmPanel.js` into the www folder in your config folder (create the www folder if it's missing, and restart Home Assistant) 43 | * install it as a custom Lovelace resource in Configuration > Dashboards > Resources. (Resources is under the top right ... menu). 44 | * Turn on Advanced Mode in your user profile if you can't see the Resources tab. 45 | * The Url of the file will be `/local/AlarmPanel.js`, and the type is "JavaScript Module". 46 | * If you are making local modifications to the file, you can add a version number to the end of the Url, like `/local/AlarmPanel.js?v=3` and increment the number each time you make a change, to force it to use the new version instead of your browser cached version. 47 | * add the alarm panel to your lovelace view using a Manual card, with type set as `type: 'custom:alarm_control_panel-card'` and specify your alarm\_control\_panel entity as named in your configuration.yaml (e.g. `entity: alarm_control_panel.house`). See my [example AlarmLovelaceDashboard yaml configs](https://github.com/jcooper-korg/AlarmPanel/blob/master/ExampleConfig). 48 | 49 | ## Card configuration options 50 | 51 | See the [ExampleConfig](https://github.com/jcooper-korg/AlarmPanel/tree/master/ExampleConfig) folder for my configuration files. See details on my setup in the Example Configuration section below. 52 | 53 | The card options are: 54 | 55 | * `entity`: (required string) the name of the manual `alarm_control_panel` entity 56 | * `show_countdown_timer`: (optional boolean). default false. set to true to show countdown timer, or false to hide it. If enabled, you must also configure the `durations` list, specifying a duration in seconds for the arming, and pending states. These times should match the times you specified in your manual config. 57 | * `scale`: (optional string). default is 14px. increase/decrease the size of the buttons/text/etc by changing this number 58 | * `title`: (optional string) if provided will show this title at the top of the card, and the alarm state will be below it. if not provided, will show the alarm state as the title (which saves some vertical space, if you are space constrained, like on a wall tablet) 59 | * `states`: (optional list). list of arming states to support. Default is `armed_away` and `armed_home`. If you use more than two, you may need to adjust the `.actions button` widths 60 | * `confirm_entities`: (optional list) a list of sensors which will be continuously monitored when disarmed so it can show Ready/Not ready text in the card header. If `confirm_entities` is specified, you may optionally also set `disable_arm_if_not_ready` to disable the arm buttons and auto_enter action unless all the listed sensors are ready. And if `disable_arm_if_not_ready` is set, you may also optionally set `show_override_if_not_ready` to show an override checkbox when the entities are not ready, which will re-enable the arming buttons (allowing you, for example, to arm the alarm but leave a window open). 61 | * `labels`: (optional list) list of text replacements, allowing you to customize the text that is shown for `ui.card.alarm_control_panel.arm_away`, `ui.card.alarm_control_panel.arm_home`, `ui.card.alarm_control_panel.clear_code`, `ready` and `not_ready` 62 | * `display_letters`: (optional boolean) shows letters on number pad buttons like a telephone keypad 63 | * `style`: (optional string) this text will be appended to the card css style, allowing you to override colors, etc. Also see [Thomas Loven's card mod](https://github.com/thomasloven/lovelace-card-mod) 64 | * `auto_hide`: (optional boolean) hides the keypad and action buttons. click on the badge to show/hide them. 65 | * `auto_enter`: (optional object). you must also specify `code_length` and `arm_action`. This will automatically disarm or arm with the specified arm\_action when entering the code. When a code of the correct length is entered and the alarm is currently armed, the alarm will be disarmed. If `alarm_control_panel.code_arm_required` is on and the alarm is currently disarmed, and a code of the correct length is entered, the arm_action will be triggered (e.g. 'arm\_home' or 'arm\_away'). 66 | 67 | ## My Setup 68 | 69 | My alarm setup consists of: 70 | 71 | * Home Assistant running on Hass.io, on a [Raspberry Pi 3b+](https://www.raspberrypi.org/products/raspberry-pi-3-model-b-plus/) 72 | * a wall-mounted 7" Amazon Fire Tablet, running [Fully Kiosk](https://www.fully-kiosk.com). NOTE: you may need to manually update the WebView component on older Fire tablets. Mine is a 7th Gen (2017), running FireOS 5.1.1, and the [latest compatible WebView is v110](https://amazon-system-webview.en.uptodown.com/android/versions) 73 | * the iOS [Home Assistant Companion](https://apps.apple.com/us/app/home-assistant/id1099568401) 74 | * an alarm panel kit from [Konnected.io](https://konnected.io/collections/shop-now) with various door/window/motion/smoke sensors, and a siren, beeper, and red/green LEDs 75 | 76 | ## Example Configuration 77 | My config files are in the [ExampleConfig](https://github.com/jcooper-korg/AlarmPanel/tree/master/ExampleConfig) folder. 78 | 79 | * My Alarm lovelace dashboard has two cards- the new custom alarm panel card, and a [Conditional Card](https://www.home-assistant.io/lovelace/conditional) which shows when any of the door/window sensors is opened, while disarmed. 80 | * I have configured the custom card with: 81 | * label replacements to use shorter all-caps words for the AWAY, HOME, etc. 82 | * `confirm_entities` list of sensors, so that it shows "Ready" if they're all off, or "Not ready" if any are on 83 | * `disable_arm_if_not_ready` and `show_override_if_not_ready` both set, so the arm buttons are disabled unless all the `confirm_entities` are ready, or the override checkbox is checked 84 | * countdown timer enabled and durations in seconds specified for arming (60) and pending (30) to match the `arming_time` and `delay_time` in the manual platform's armed_away config 85 | * I have set up automations to handle: 86 | * turning on/off the green/red LEDs, beeper, and siren based on sensor entity states and the manual `alarm_control_panel` armed/disarmed/triggered state 87 | * notifying our iphones when armed / disarmed or when triggered 88 | * triggering the alarm on smoke sensors, regardless of arming state 89 | * In order to include the name of the entity that triggered the alarm in the trigger notifications, I'm using an `input_text` entity in my config, which is set when the alarm trigger automation runs, and is then referenced by the notification 90 | * In order to be able to trigger the alarm immediately for some sensors, while other sensors (e.g. entry doors) are delayed, I have a script called `trigger_alarm_immediately` which first disarms the alarm, and then triggers. Requires that the the `delay_time` is set to 0 for the disarmed state in the `alarm_control_panel` configuration. 91 | * I created a separate user named Alarm Panel that I use to log in from my wall mounted tablet. I'm using [Custom Header](https://maykar.github.io/custom-header) to hide the sidebar and title bar on the wall mounted tablet for that user. 92 | 93 | ## Credits 94 | 95 | * This custom alarm panel card was forked in September 2020 from [Kevin Cooper's repo](https://github.com/JumpMaster/custom-lovelace) (no relation). 96 | * The appearance of the countdown timer was inspired by [John Schult's countdown360 project](https://github.com/johnschult/jquery.countdown360). 97 | 98 | 99 | -------------------------------------------------------------------------------- /ExampleConfig/automations.yaml: -------------------------------------------------------------------------------- 1 | - id: '1598210904880' 2 | alias: Turn on Beeper 3 | description: When entering pending or warning state, sound the beeper 4 | trigger: 5 | - entity_id: alarm_control_panel.house 6 | from: disarmed 7 | platform: state 8 | to: arming 9 | - entity_id: alarm_control_panel.house 10 | platform: state 11 | to: pending 12 | condition: [] 13 | action: 14 | - device_id: 50e2350f1b4f40cc835509d0f34d9bcc 15 | domain: switch 16 | entity_id: switch.beep 17 | type: turn_on 18 | mode: single 19 | - id: '1598211133176' 20 | alias: Alarm Triggered 21 | description: '' 22 | trigger: 23 | - entity_id: alarm_control_panel.house 24 | platform: state 25 | to: triggered 26 | condition: [] 27 | action: 28 | - device_id: cd4e154d91e749b084a3196d9371826c 29 | domain: switch 30 | entity_id: switch.siren 31 | type: turn_on 32 | - data: 33 | data: 34 | subtitle: The Alarm is Sounding 35 | message: 'Home Alarm triggered by: {{ states(''input_text.trigger_source'') 36 | }}' 37 | title: Smart Home Alert 38 | service: notify.alarm_notification_group 39 | mode: single 40 | - id: '1598211302522' 41 | alias: Alarm Disarmed 42 | description: '' 43 | trigger: 44 | - entity_id: alarm_control_panel.house 45 | platform: state 46 | to: disarmed 47 | condition: [] 48 | action: 49 | - device_id: cd4e154d91e749b084a3196d9371826c 50 | domain: switch 51 | entity_id: switch.siren 52 | type: turn_off 53 | - device_id: 50e2350f1b4f40cc835509d0f34d9bcc 54 | domain: switch 55 | entity_id: switch.beep 56 | type: turn_off 57 | - device_id: 50e2350f1b4f40cc835509d0f34d9bcc 58 | domain: switch 59 | entity_id: switch.red_led 60 | type: turn_off 61 | - service: notify.alarm_notification_group 62 | data: 63 | title: Smart Home Alert 64 | message: Alarm Disarmed 65 | mode: single 66 | - id: '1598213488706' 67 | alias: All zones clear while disarmed 68 | description: Turn on the green LED when all zones are clear while disarmed 69 | trigger: 70 | - entity_id: group.alarm_zones_for_led 71 | platform: state 72 | to: 'off' 73 | - entity_id: alarm_control_panel.house 74 | platform: state 75 | to: disarmed 76 | condition: 77 | - condition: state 78 | entity_id: alarm_control_panel.house 79 | state: disarmed 80 | - condition: state 81 | entity_id: group.alarm_zones 82 | state: 'off' 83 | action: 84 | - device_id: 50e2350f1b4f40cc835509d0f34d9bcc 85 | domain: switch 86 | entity_id: switch.green_led 87 | type: turn_on 88 | mode: single 89 | - id: '1598213553348' 90 | alias: Alarm zone opened while disarmed 91 | description: Turn off the green LED when any alarm zone is open while disarmed 92 | trigger: 93 | - entity_id: group.alarm_zones_for_led 94 | platform: state 95 | to: 'on' 96 | - entity_id: alarm_control_panel.house 97 | platform: state 98 | to: disarmed 99 | condition: 100 | - condition: state 101 | entity_id: alarm_control_panel.house 102 | state: disarmed 103 | - condition: state 104 | entity_id: group.alarm_zones 105 | state: 'on' 106 | action: 107 | - device_id: 50e2350f1b4f40cc835509d0f34d9bcc 108 | domain: switch 109 | entity_id: switch.green_led 110 | type: turn_off 111 | mode: single 112 | - id: '1598213810560' 113 | alias: Alarm Armed 114 | description: '' 115 | trigger: 116 | - entity_id: alarm_control_panel.house 117 | platform: state 118 | to: armed_home 119 | - entity_id: alarm_control_panel.house 120 | platform: state 121 | to: armed_away 122 | condition: [] 123 | action: 124 | - device_id: 50e2350f1b4f40cc835509d0f34d9bcc 125 | domain: switch 126 | entity_id: switch.beep 127 | type: turn_off 128 | - device_id: 50e2350f1b4f40cc835509d0f34d9bcc 129 | domain: switch 130 | entity_id: switch.green_led 131 | type: turn_off 132 | - device_id: 50e2350f1b4f40cc835509d0f34d9bcc 133 | domain: switch 134 | entity_id: switch.red_led 135 | type: turn_on 136 | - service: notify.alarm_notification_group 137 | data: 138 | title: Smart Home Alert 139 | message: Alarm Armed 140 | mode: single 141 | - id: '1600468597816' 142 | alias: Trigger Immediately when Arming 143 | description: disarm first, then trigger, causes immediate siren due to disarmed 144 | delay_time=0 145 | trigger: 146 | - entity_id: binary_sensor.office_window 147 | from: 'off' 148 | platform: state 149 | to: 'on' 150 | - entity_id: binary_sensor.living_room_window 151 | from: 'off' 152 | platform: state 153 | to: 'on' 154 | - entity_id: binary_sensor.kitchen_sink_window 155 | from: 'off' 156 | platform: state 157 | to: 'on' 158 | - entity_id: binary_sensor.kitchen_table_window 159 | from: 'off' 160 | platform: state 161 | to: 'on' 162 | - entity_id: binary_sensor.dining_room_window 163 | from: 'off' 164 | platform: state 165 | to: 'on' 166 | - entity_id: binary_sensor.master_bedroom_window 167 | from: 'off' 168 | platform: state 169 | to: 'on' 170 | - entity_id: binary_sensor.master_bath_window 171 | from: 'off' 172 | platform: state 173 | to: 'on' 174 | - entity_id: binary_sensor.bedroom1_window 175 | from: 'off' 176 | platform: state 177 | to: 'on' 178 | - entity_id: binary_sensor.bedroom2_window 179 | from: 'off' 180 | platform: state 181 | to: 'on' 182 | - entity_id: binary_sensor.side_garage_door 183 | from: 'off' 184 | platform: state 185 | to: 'on' 186 | condition: 187 | - condition: state 188 | entity_id: alarm_control_panel.house 189 | state: arming 190 | action: 191 | - data_template: 192 | entity_id: input_text.trigger_source 193 | value: '{{ trigger.to_state.attributes.friendly_name }}' 194 | service: input_text.set_value 195 | - data: {} 196 | service: script.trigger_alarm_immediately 197 | mode: single 198 | - id: '1600468682227' 199 | alias: Trigger Immediately when Armed Home 200 | description: '' 201 | trigger: 202 | - entity_id: binary_sensor.office_window 203 | from: 'off' 204 | platform: state 205 | to: 'on' 206 | - entity_id: binary_sensor.living_room_window 207 | from: 'off' 208 | platform: state 209 | to: 'on' 210 | - entity_id: binary_sensor.kitchen_sink_window 211 | from: 'off' 212 | platform: state 213 | to: 'on' 214 | - entity_id: binary_sensor.kitchen_table_window 215 | from: 'off' 216 | platform: state 217 | to: 'on' 218 | - entity_id: binary_sensor.dining_room_window 219 | from: 'off' 220 | platform: state 221 | to: 'on' 222 | - entity_id: binary_sensor.master_bedroom_window 223 | from: 'off' 224 | platform: state 225 | to: 'on' 226 | - entity_id: binary_sensor.master_bath_window 227 | from: 'off' 228 | platform: state 229 | to: 'on' 230 | - entity_id: binary_sensor.bedroom1_window 231 | from: 'off' 232 | platform: state 233 | to: 'on' 234 | - entity_id: binary_sensor.bedroom2_window 235 | from: 'off' 236 | platform: state 237 | to: 'on' 238 | - entity_id: binary_sensor.side_garage_door 239 | from: 'off' 240 | platform: state 241 | to: 'on' 242 | - entity_id: binary_sensor.garage_door 243 | from: 'off' 244 | platform: state 245 | to: 'on' 246 | - entity_id: binary_sensor.front_door 247 | from: 'off' 248 | platform: state 249 | to: 'on' 250 | - entity_id: binary_sensor.back_door 251 | from: 'off' 252 | platform: state 253 | to: 'on' 254 | condition: 255 | - condition: state 256 | entity_id: alarm_control_panel.house 257 | state: armed_home 258 | action: 259 | - data_template: 260 | entity_id: input_text.trigger_source 261 | value: '{{ trigger.to_state.attributes.friendly_name }}' 262 | service: input_text.set_value 263 | - data: {} 264 | entity_id: alarm_control_panel.house 265 | service: alarm_control_panel.alarm_trigger 266 | mode: single 267 | - id: '1600468738107' 268 | alias: Trigger Immediately when Armed Away 269 | description: disarm first, then trigger, causes immediate siren due to disarmed 270 | delay_time=0 271 | trigger: 272 | - entity_id: binary_sensor.office_window 273 | from: 'off' 274 | platform: state 275 | to: 'on' 276 | - entity_id: binary_sensor.living_room_window 277 | from: 'off' 278 | platform: state 279 | to: 'on' 280 | - entity_id: binary_sensor.kitchen_sink_window 281 | from: 'off' 282 | platform: state 283 | to: 'on' 284 | - entity_id: binary_sensor.kitchen_table_window 285 | from: 'off' 286 | platform: state 287 | to: 'on' 288 | - entity_id: binary_sensor.dining_room_window 289 | from: 'off' 290 | platform: state 291 | to: 'on' 292 | - entity_id: binary_sensor.master_bedroom_window 293 | from: 'off' 294 | platform: state 295 | to: 'on' 296 | - entity_id: binary_sensor.master_bath_window 297 | from: 'off' 298 | platform: state 299 | to: 'on' 300 | - entity_id: binary_sensor.bedroom1_window 301 | from: 'off' 302 | platform: state 303 | to: 'on' 304 | - entity_id: binary_sensor.bedroom2_window 305 | from: 'off' 306 | platform: state 307 | to: 'on' 308 | - entity_id: binary_sensor.side_garage_door 309 | from: 'off' 310 | platform: state 311 | to: 'on' 312 | - entity_id: binary_sensor.hall_motion 313 | from: 'off' 314 | platform: state 315 | to: 'on' 316 | - entity_id: binary_sensor.living_room_motion 317 | from: 'off' 318 | platform: state 319 | to: 'on' 320 | - entity_id: binary_sensor.glass_break_master_bath 321 | from: 'off' 322 | platform: state 323 | to: 'on' 324 | - entity_id: binary_sensor.glass_break_kitchen 325 | from: 'off' 326 | platform: state 327 | to: 'on' 328 | - entity_id: binary_sensor.glass_break_other 329 | from: 'off' 330 | platform: state 331 | to: 'on' 332 | condition: 333 | - condition: state 334 | entity_id: alarm_control_panel.house 335 | state: armed_away 336 | action: 337 | - data_template: 338 | entity_id: input_text.trigger_source 339 | value: '{{ trigger.to_state.attributes.friendly_name }}' 340 | service: input_text.set_value 341 | - data: {} 342 | service: script.trigger_alarm_immediately 343 | mode: single 344 | - id: '1600468865168' 345 | alias: Trigger Immediately on Smoke 346 | description: disarm first, then trigger, causes immediate siren due to disarmed 347 | delay_time=0 348 | trigger: 349 | - entity_id: binary_sensor.hall_smoke 350 | from: 'off' 351 | platform: state 352 | to: 'on' 353 | - entity_id: binary_sensor.carbon_monoxide_kitchen 354 | from: 'off' 355 | platform: state 356 | to: 'on' 357 | condition: [] 358 | action: 359 | - data_template: 360 | entity_id: input_text.trigger_source 361 | value: '{{ trigger.to_state.attributes.friendly_name }}' 362 | service: input_text.set_value 363 | - data: {} 364 | service: script.trigger_alarm_immediately 365 | mode: single 366 | - id: '1600472249835' 367 | alias: Trigger Pending When Armed Away 368 | description: '' 369 | trigger: 370 | - entity_id: binary_sensor.front_door 371 | from: 'off' 372 | platform: state 373 | to: 'on' 374 | - entity_id: binary_sensor.back_door 375 | from: 'off' 376 | platform: state 377 | to: 'on' 378 | - entity_id: binary_sensor.garage_door 379 | from: 'off' 380 | platform: state 381 | to: 'on' 382 | condition: 383 | - condition: state 384 | entity_id: alarm_control_panel.house 385 | state: armed_away 386 | action: 387 | - data: {} 388 | entity_id: alarm_control_panel.house 389 | service: alarm_control_panel.alarm_trigger 390 | mode: single 391 | -------------------------------------------------------------------------------- /AlarmPanel.js: -------------------------------------------------------------------------------- 1 | // Alarm Control Panel custom card 2 | // Orignally by Kevin Cooper (no relation) at https://github.com/JumpMaster/custom-lovelace 3 | // Modified by John S Cooper at https://github.com/jcooper-korg/AlarmPanel 4 | // customized button appearance and colors, hide keypad when disarmed if !code_arm_required, new confirm_entities config option, etc 5 | // See example config at https://github.com/jcooper-korg/AlarmPanel/ExampleConfig 6 | // 7 | // if confirm_entities is provided, then when disarmed, it will show "Ready" in 8 | // the status title if all those entities are off, otherwise it'll show "Not Ready". 9 | // (can customize those strings in the labels config using ready and not_ready). 10 | // also, if confirm_entities, disable_arm_if_not_ready, and show_override_if_not_ready are all set, then 11 | // it will show an Override checkbox when not ready. 12 | // 13 | // if show_countdown_timer is enabled, then the config should specify a list of durations (in seconds) for the arming and pending states 14 | // 15 | // if alarm_control_panel.code_arm_required is set, the keypad will be shown when disarmed, regardless of the 16 | // hide_keypad and auto_hide options. 17 | 18 | class AlarmControlPanelCard extends HTMLElement { 19 | constructor() { 20 | super(); 21 | this.attachShadow({ mode: 'open' }); 22 | this._icons = { 23 | 'armed_away': 'mdi:shield-lock', 24 | 'armed_custom_bypass': 'mdi:security', 25 | 'armed_home': 'mdi:shield-home', 26 | 'armed_night': 'mdi:shield-home', 27 | 'disarmed': 'mdi:shield-check', 28 | 'pending': 'mdi:shield-outline', 29 | 'triggered': 'hass:bell-ring', 30 | } 31 | this._entitiesReady = false; 32 | this._previousAlarmState = 'disarmed'; 33 | this._countdownTimerFunction = null; 34 | this._timerRadius = 30; 35 | this._timerStrokeWidth = this._timerRadius / 5; 36 | this._timerSize = 2 * (this._timerRadius + this._timerStrokeWidth); 37 | this._currentStateDuration = 0; 38 | } 39 | 40 | set hass(hass) { 41 | const entity = hass.states[this._config.entity]; 42 | 43 | if (entity) { 44 | this.myhass = hass; 45 | this.code_arm_required = entity.attributes.code_arm_required; 46 | this.has_numeric_code = !entity.attributes.code_format || entity.attributes.code_format == "number"; 47 | if(!this.shadowRoot.lastChild) { 48 | this._createCard(entity); 49 | } 50 | 51 | const updatedEntitiesReady = this._confirmEntitiesReady(); 52 | if (entity.state != this._state || this._entitiesReady != updatedEntitiesReady) { 53 | this._previousAlarmState = this._state; 54 | this._state = entity.state; 55 | this._entitiesReady = updatedEntitiesReady; 56 | this._updateCardContent(entity); 57 | } 58 | } 59 | } 60 | 61 | _createCard(entity) { 62 | const config = this._config; 63 | 64 | const card = document.createElement('ha-card'); 65 | card.innerHTML = ` 66 | ${this._iconLabel()} 67 | ${this._timerCanvas()} 68 | ${config.title ? '
' : ''} 69 | `; 70 | 71 | const content = document.createElement('div'); 72 | content.id = "content"; 73 | content.style.display = config.auto_hide ? 'none' : ''; 74 | content.innerHTML = ` 75 | ${this._actionButtons()} 76 | ${this.has_numeric_code ? 77 | `` : ''} 79 | ${this._keypad(entity)} 80 | `; 81 | 82 | card.appendChild(this._style(config.style, entity)); 83 | card.appendChild(content); 84 | this.shadowRoot.appendChild(card); 85 | this._showCountdownTimer(false); // start hidden 86 | 87 | this._setupInput(); 88 | this._setupKeypad(); 89 | this._setupActions(); 90 | } 91 | 92 | connectedCallback() { 93 | } 94 | 95 | setConfig(config) { 96 | if (!config.entity || config.entity.split(".")[0] !== "alarm_control_panel") { 97 | throw new Error('Please specify an entity from alarm_control_panel domain.'); 98 | } 99 | 100 | if (config.show_override_if_not_ready && !config.disable_arm_if_not_ready) { 101 | throw new Error('Only specify show_override_if_not_ready if disable_arm_if_not_ready is enabled.'); 102 | } 103 | 104 | if (config.disable_arm_if_not_ready && !config.confirm_entities) { 105 | throw new Error('To use disable_arm_if_not_ready, you must specify a list of confirm_entities.'); 106 | } 107 | 108 | if (config.auto_enter) { 109 | if (!config.auto_enter.code_length || !config.auto_enter.arm_action) { 110 | throw new 111 | Error('Specify both code_length and arm_action when using auto_enter.'); 112 | } 113 | this._autoarm_action = config.auto_enter.arm_action; 114 | } 115 | 116 | this._config = Object.assign({}, config); 117 | if (!this._config.states) this._config.states = ['arm_away', 'arm_home']; 118 | if (!this._config.scale) this._config.scale = '15px'; 119 | 120 | const root = this.shadowRoot; 121 | if (root.lastChild) root.removeChild(root.lastChild); 122 | } 123 | 124 | _updateCardContent(entity) { 125 | const root = this.shadowRoot; 126 | const card = root.lastChild; 127 | const config = this._config; 128 | 129 | if (config.show_countdown_timer && this._previousAlarmState != this._state) { 130 | // if changing state to arming or pending, and a duration is specified in the config, then start a countdown timer 131 | if (this._state == "arming" || this._state == "pending") 132 | { 133 | if (this._countdownTimerFunction == null) { 134 | if (this._config.durations && this._config.durations[this._state] && this._config.durations[this._state] != 0) 135 | { 136 | this._currentStateDuration = this._config.durations[this._state]; 137 | this._showCountdownTimer(true); 138 | this._doCountdownTimer(); // draw once right away 139 | this._countdownTimerFunction = setInterval( () => this._doCountdownTimer(), 1000); 140 | } 141 | } 142 | } 143 | else if (this._countdownTimerFunction) { 144 | // stop callback, hide timer 145 | this._showCountdownTimer(false); 146 | clearInterval(this._countdownTimerFunction); 147 | this._countdownTimerFunction = null; 148 | } 149 | } 150 | 151 | this._updateReady(); 152 | 153 | root.getElementById("state-icon").setAttribute("icon", 154 | this._icons[this._state] || 'mdi:shield-outline'); 155 | root.getElementById("badge-icon").className = this._state; 156 | 157 | var iconText = this._stateIconLabel(this._state); 158 | if (iconText === "") { 159 | root.getElementById("icon-label").style.display = "none"; 160 | } else { 161 | root.getElementById("icon-label").style.display = ""; 162 | if (iconText.length > 5) { 163 | root.getElementById("icon-label").className = "label big"; 164 | } else { 165 | root.getElementById("icon-label").className = "label"; 166 | } 167 | root.getElementById("icon-text").innerHTML = iconText; 168 | } 169 | 170 | const armVisible = (this._state === 'disarmed'); 171 | root.getElementById("arm-actions").style.display = armVisible ? "" : "none"; 172 | if (!config.hide_keypad && this.has_numeric_code) { 173 | root.getElementById("disarm-actions").style.display = armVisible ? "none" : ""; 174 | } 175 | 176 | if (config.auto_enter) { 177 | if (armVisible) { 178 | if (!config.confirm_entities || !config.disable_arm_if_not_ready || this._entitiesReady) 179 | this._autoarm_action = config.auto_enter.arm_action; 180 | else 181 | this._autoarm_action = "disabled"; 182 | root.querySelectorAll(".actions button").forEach(element => { 183 | element.classList.remove('autoarm'); 184 | if (element.id === this._autoarm_action) 185 | element.classList.add('autoarm'); 186 | }) 187 | root.getElementById("disarm").classList.remove('autoarm'); 188 | } 189 | else 190 | { 191 | this._autoarm_action = 'disarm'; 192 | root.getElementById("disarm").classList.add('autoarm'); 193 | } 194 | } 195 | 196 | // hide code and number pad if disarmed, if manual alarm config has code_arm_required=false 197 | if (!this.code_arm_required) { 198 | if (!config.hide_keypad) { 199 | root.getElementById("keypad").style.display = armVisible ? "none" : "flex"; 200 | } 201 | if (this.has_numeric_code) { 202 | root.getElementById("input-code").style.display = armVisible ? "none" : ""; 203 | } 204 | } 205 | } 206 | 207 | _showOverrideCheckbox(visible) { 208 | const overrideCheckbox = this.shadowRoot.getElementById("overrideCheckbox"); 209 | const overrideLabel = this.shadowRoot.getElementById("overrideLabel"); 210 | if (visible) { 211 | overrideCheckbox.style.visibility = "visible"; 212 | overrideLabel.style.visibility = "visible"; 213 | } else { 214 | overrideCheckbox.checked = false; // always clear override checkbox when hiding it 215 | overrideCheckbox.style.visibility = "hidden"; 216 | overrideLabel.style.visibility = "hidden"; 217 | } 218 | } 219 | 220 | _updateReady() { 221 | const root = this.shadowRoot; 222 | const card = root.lastChild; 223 | const config = this._config; 224 | 225 | const state_str = "state.alarm_control_panel." + this._state; 226 | status = this._label(state_str); 227 | 228 | var showOverrideCheckbox = false; 229 | if (config.confirm_entities && this._state === "disarmed") { 230 | if (this._entitiesReady) { 231 | status = status + " - " + this._label("ready"); 232 | if (config.disable_arm_if_not_ready) { 233 | root.querySelectorAll(".actions button").forEach(element => { 234 | element.removeAttribute("disabled"); 235 | }) 236 | } 237 | } else { 238 | status = status + " - " + this._label("not_ready"); 239 | if (config.disable_arm_if_not_ready) { 240 | showOverrideCheckbox = config.show_override_if_not_ready // this is the only case that shows the override checkbox 241 | const allowOverride = root.getElementById("overrideCheckbox").checked; 242 | if (allowOverride) { 243 | root.querySelectorAll(".actions button").forEach(element => { 244 | element.removeAttribute("disabled"); 245 | }) 246 | } else { 247 | root.querySelectorAll(".actions button").forEach(element => { 248 | element.setAttribute("disabled", true); 249 | }) 250 | } 251 | } 252 | } 253 | } 254 | 255 | this._showOverrideCheckbox(showOverrideCheckbox); 256 | 257 | if (config.title) { 258 | card.header = config.title; 259 | root.getElementById("state-text").innerHTML = status; 260 | root.getElementById("state-text").className = `state ${this._state}`; 261 | } else { 262 | card.header = status; 263 | } 264 | } 265 | 266 | _actionButtons() { 267 | let disarmButtonIfHideKeypad = ''; 268 | if (this._config.hide_keypad) { 269 | disarmButtonIfHideKeypad = `
${this._actionButton('disarm')}
`; 270 | } 271 | return ` 272 |
273 | ${this._config.states.map(el => `${this._actionButton(el)}`).join('')} 274 |
275 | ${disarmButtonIfHideKeypad} 276 |
` 277 | } 278 | 279 | _stateIconLabel(state) { 280 | const stateLabel = state.split("_").pop(); 281 | if (stateLabel === "disarmed" || stateLabel === "triggered" || !stateLabel) 282 | return ""; 283 | return stateLabel; 284 | } 285 | 286 | _iconLabel() { 287 | return ` 288 | 289 |
290 |
291 |
292 | 293 |
294 |
295 | 296 |
297 |
298 |
299 |
`; 300 | } 301 | 302 | _timerCanvas() { 303 | // radius 30. strokewidth = radius/4. width = 2*radius + strokewidth * 2 304 | if (this._config.show_countdown_timer) { 305 | return ` 306 | 307 | 308 | 309 | 310 | `; 311 | } 312 | return ''; 313 | } 314 | _actionButton(state) { 315 | return ``; 317 | } 318 | 319 | _doCountdownTimer() { 320 | const nowTime = new Date().getTime(); 321 | const timeMadeActive = new Date(this.myhass.states[this._config.entity].last_changed).getTime(); 322 | const elapsedSeconds = (nowTime - timeMadeActive) / 1000; 323 | const durationSeconds = this._currentStateDuration; 324 | const timeRemaining = Math.round(Math.max(durationSeconds - elapsedSeconds, 0)); 325 | const elapsedPercent = elapsedSeconds / durationSeconds; 326 | 327 | var canvas = this.shadowRoot.getElementById("timerCanvas"); 328 | var ctx = canvas.getContext("2d"); 329 | ctx.lineWidth = this._timerStrokeWidth; 330 | ctx.clearRect(0, 0, this._timerSize, this._timerSize); 331 | 332 | const centerPos = this._timerSize / 2; 333 | if (elapsedPercent > 0.75) 334 | ctx.fillStyle = 'red'; 335 | else if (elapsedPercent > 0.5) 336 | ctx.fillStyle = 'orange'; 337 | else 338 | ctx.fillStyle = '#8ac575'; 339 | 340 | // draw filled center circle 341 | ctx.beginPath(); 342 | ctx.arc(centerPos, centerPos, this._timerRadius, 0, 2*Math.PI, false); 343 | ctx.fill(); 344 | 345 | // draw arc around edges counter-clockwise from top-center 1.5*pi to 3.5*pi 346 | const endAngle = 3.5 * Math.PI - 2 * Math.PI * elapsedPercent; 347 | ctx.beginPath(); 348 | ctx.arc(centerPos, centerPos, this._timerRadius, 1.5 * Math.PI, endAngle, false); 349 | ctx.strokeStyle = '#477050'; 350 | ctx.stroke(); 351 | 352 | // draw text label 353 | const fontSize = this._timerRadius/1.2; 354 | ctx.font = "700 " + fontSize + "px sans-serif"; 355 | ctx.lineWidth = this._strokeWidth; 356 | ctx.textAlign = "center"; 357 | ctx.textBaseline = "middle"; 358 | ctx.fillStyle = "white"; 359 | ctx.fillText(timeRemaining, this._timerSize/2, this._timerSize/2); 360 | } 361 | 362 | _setupActions() { 363 | const root = this.shadowRoot; 364 | const card = this.shadowRoot.lastChild; 365 | const config = this._config; 366 | 367 | if (config.auto_hide) { 368 | root.getElementById("badge-icon").addEventListener('click', event => { 369 | var content = root.getElementById("content"); 370 | if (content.style.display === 'none') { 371 | content.style.display = ''; 372 | } else { 373 | content.style.display = 'none'; 374 | } 375 | }) 376 | } 377 | 378 | card.querySelectorAll(".actions button").forEach(element => { 379 | // note- disarm button is handled in _setupKeypad 380 | element.addEventListener('click', event => { 381 | const input = card.querySelector('ha-textfield'); 382 | const value = input ? input.value : ''; 383 | this._callService(element.id, value); 384 | }) 385 | }) 386 | 387 | this.shadowRoot.getElementById("overrideCheckbox").addEventListener("change", () => { 388 | this._updateReady(); 389 | }); 390 | } 391 | 392 | _callService(service, code) { 393 | const input = this.shadowRoot.lastChild.querySelector("ha-textfield"); 394 | this.myhass.callService('alarm_control_panel', `alarm_${service}`, { 395 | entity_id: this._config.entity, 396 | code: code, 397 | }); 398 | if (input) input.value = ''; 399 | } 400 | 401 | _showCountdownTimer(show) 402 | { 403 | if (this._config.show_countdown_timer) 404 | this.shadowRoot.getElementById("countdown").style.display = show ? '' : 'none'; 405 | else 406 | show = false; 407 | this.shadowRoot.getElementById("badge-icon").style.display = show ? 'none' : ''; 408 | } 409 | 410 | _setupInput() { 411 | if (this._config.auto_enter) { 412 | const input = this.shadowRoot.lastChild.querySelector("ha-textfield"); 413 | input.addEventListener('input', event => { this._autoEnter() }) 414 | } 415 | } 416 | 417 | _setupKeypad() { 418 | const root = this.shadowRoot; 419 | 420 | const input = root.lastChild.querySelector('ha-textfield'); 421 | root.querySelectorAll(".pad button").forEach(element => { 422 | if (element.getAttribute('value') === 423 | this._label("ui.card.alarm_control_panel.clear_code")) { 424 | element.addEventListener('click', event => { 425 | input.value = ''; 426 | }) 427 | } else if (element.id === "disarm") { 428 | element.addEventListener('click', event => { 429 | this._callService("disarm", input.value); 430 | }) 431 | } else { 432 | element.addEventListener('click', event => { 433 | input.value += element.getAttribute('value'); 434 | this._autoEnter(); 435 | }) 436 | } 437 | }); 438 | } 439 | 440 | _autoEnter() { 441 | const config = this._config; 442 | 443 | if (config.auto_enter) { 444 | const card = this.shadowRoot.lastChild; 445 | const code = card.querySelector("ha-textfield").value; 446 | if (code.length == config.auto_enter.code_length && this._autoarm_action != "disabled") { 447 | this._callService(this._autoarm_action, code); 448 | } 449 | } 450 | } 451 | 452 | _keypad(entity) { 453 | if (this._config.hide_keypad || !this.has_numeric_code) return ''; 454 | 455 | return ` 456 |
457 |
458 | ${this._keypadButton("1", "")} 459 | ${this._keypadButton("4", "GHI")} 460 | ${this._keypadButton("7", "PQRS")} 461 | ${this._keypadButton(this._label("ui.card.alarm_control_panel.clear_code"), "", "clear")} 462 |
463 |
464 | ${this._keypadButton("2", "ABC")} 465 | ${this._keypadButton("5", "JKL")} 466 | ${this._keypadButton("8", "TUV")} 467 | ${this._keypadButton("0", "")} 468 |
469 |
470 | ${this._keypadButton("3", "DEF")} 471 | ${this._keypadButton("6", "MNO")} 472 | ${this._keypadButton("9", "WXYZ")} 473 |
${this._actionButton('disarm')}
474 |
475 |
`; 476 | } 477 | 478 | _confirmEntitiesReady() { 479 | if (!this._config.confirm_entities) return true; 480 | for (var i = 0; i < this._config.confirm_entities.length; i++) { 481 | if (this.myhass.states[this._config.confirm_entities[i]].state != "off") 482 | return false; 483 | } 484 | return true; 485 | } 486 | 487 | _keypadButton(button, alpha, id='') { 488 | let letterHTML = ''; 489 | if (this._config.display_letters) { 490 | letterHTML = `
${alpha}
` 491 | } 492 | if (id == '') id = button; 493 | return ``; 494 | } 495 | 496 | _style(icon_style, entity) { 497 | const style = document.createElement('style'); 498 | style.textContent = ` 499 | ha-card { 500 | position: relative; 501 | padding-bottom: 32px; 502 | --alarm-color-disarmed: var(--label-badge-green); 503 | --alarm-color-pending: var(--label-badge-yellow); 504 | --alarm-color-triggered: var(--label-badge-red); 505 | --alarm-color-armed: var(--label-badge-red); 506 | --alarm-color-autoarm: rgba(0, 153, 255, .1); 507 | --alarm-state-color: var(--alarm-color-armed); 508 | --base-unit: ${this._config.scale}; 509 | font-size: calc(var(--base-unit)); 510 | ${icon_style} 511 | } 512 | ha-icon { 513 | color: var(--alarm-state-color); 514 | width: 24px; 515 | height: 24px; 516 | } 517 | countdown-timer { 518 | position: absolute; 519 | right: 12px; 520 | top: 12px; 521 | } 522 | ha-label-badge-icon { 523 | --ha-label-badge-color: var(--alarm-state-color); 524 | --label-badge-text-color: var(--alarm-state-color); 525 | --label-badge-background-color: var(--paper-card-background-color); 526 | color: var(--alarm-state-color); 527 | position: absolute; 528 | right: 12px; 529 | top: 12px; 530 | } 531 | .badge-container { 532 | display: inline-block; 533 | text-align: center; 534 | vertical-align: top; 535 | } 536 | .label-badge { 537 | position: relative; 538 | display: block; 539 | margin: 0 auto; 540 | width: var(--ha-label-badge-size, 2.5em); 541 | text-align: center; 542 | height: var(--ha-label-badge-size, 2.5em); 543 | line-height: var(--ha-label-badge-size, 2.5em); 544 | font-size: var(--ha-label-badge-font-size, 1.5em); 545 | border-radius: 50%; 546 | border: 0.1em solid var(--ha-label-badge-color, var(--primary-color)); 547 | color: var(--label-badge-text-color, rgb(76, 76, 76)); 548 | white-space: nowrap; 549 | background-color: var(--label-badge-background-color, white); 550 | background-size: cover; 551 | transition: border 0.3s ease-in-out; 552 | } 553 | .label-badge .value { 554 | font-size: 90%; 555 | overflow: hidden; 556 | text-overflow: ellipsis; 557 | } 558 | .label-badge .value.big { 559 | font-size: 70%; 560 | } 561 | .label-badge .label { 562 | position: absolute; 563 | bottom: -1em; 564 | /* Make the label as wide as container+border. (parent_borderwidth / font-size) */ 565 | left: -0.2em; 566 | right: -0.2em; 567 | line-height: 1em; 568 | font-size: 0.5em; 569 | } 570 | .label-badge .label span { 571 | box-sizing: border-box; 572 | max-width: 100%; 573 | display: inline-block; 574 | background-color: var(--ha-label-badge-color, var(--primary-color)); 575 | color: var(--ha-label-badge-label-color, white); 576 | border-radius: 1em; 577 | padding: 9% 16% 8% 16%; /* mostly apitalized text, not much descenders => bit more top margin */ 578 | font-weight: 500; 579 | overflow: hidden; 580 | text-transform: uppercase; 581 | text-overflow: ellipsis; 582 | transition: background-color 0.3s ease-in-out; 583 | text-transform: var(--ha-label-badge-label-text-transform, uppercase); 584 | } 585 | .label-badge .label.big span { 586 | font-size: 90%; 587 | padding: 10% 12% 7% 12%; /* push smaller text a bit down to center vertically */ 588 | } 589 | .badge-container .title { 590 | margin-top: 1em; 591 | font-size: var(--ha-label-badge-title-font-size, 0.9em); 592 | width: var(--ha-label-badge-title-width, 5em); 593 | font-weight: var(--ha-label-badge-title-font-weight, 400); 594 | overflow: hidden; 595 | text-overflow: ellipsis; 596 | line-height: normal; 597 | } 598 | .disarmed { 599 | --alarm-state-color: var(--alarm-color-disarmed); 600 | } 601 | .triggered { 602 | --alarm-state-color: var(--alarm-color-triggered); 603 | animation: pulse 1s infinite; 604 | } 605 | .arming { 606 | --alarm-state-color: var(--alarm-color-pending); 607 | animation: pulse 1s infinite; 608 | } 609 | .pending { 610 | --alarm-state-color: var(--alarm-color-pending); 611 | animation: pulse 1s infinite; 612 | } 613 | // @keyframes pulse { 614 | // 0% { 615 | // --ha-label-badge-color: var(--alarm-state-color); 616 | // } 617 | // 100% { 618 | // --ha-label-badge-color: rgba(255, 153, 0, 0.3); 619 | // } 620 | // } 621 | 622 | ha-textfield { 623 | display: block; 624 | text-align: center; 625 | margin: auto; 626 | max-width: 150px; 627 | --mdc-typography-subtitle1-font-size: 24px; 628 | } 629 | .state { 630 | margin-left: 20px; 631 | font-size: calc(var(--base-unit) * 1); 632 | position: relative; 633 | bottom: 16px; 634 | color: var(--alarm-state-color); 635 | animation: none; 636 | } 637 | .pad { 638 | display: flex; 639 | justify-content: center; 640 | } 641 | .pad div { 642 | display: flex; 643 | flex-direction: column; 644 | } 645 | .pad button { 646 | position: relative; 647 | padding: calc(var(--base-unit)*0.5); 648 | font-size: calc(var(--base-unit) * 1.6); 649 | font-weight: 700; 650 | width: calc(var(--base-unit) * 8); 651 | height: calc(var(--base-unit) * 5); 652 | margin: 8px; 653 | background-color: var(--primary-background-color); 654 | border-width: 2px; 655 | border-style: solid; 656 | border-color: var(--primary-color); 657 | border-radius: 4px; 658 | #color: var(--primary-color); 659 | color: var(--primary-text-color); 660 | } 661 | .pad .autoarm { 662 | background: var(--alarm-color-autoarm) !important; 663 | } 664 | 665 | .pad button:focus { 666 | border-color: var(--dark-primary-color); 667 | outline: none; 668 | } 669 | .pad button#disarm { 670 | border-color: var(--primary-text-color); 671 | background-color: var(--primary-color); 672 | font-size: calc(var(--base-unit) * 1.1); 673 | } 674 | .pad button#keyclear { 675 | border-color: red; 676 | font-size: calc(var(--base-unit) * 1.1); 677 | background-color: var(--primary-background-color); 678 | } 679 | .actions { 680 | margin: 0 8px; 681 | display: flex; 682 | flex-wrap: wrap; 683 | justify-content: center; 684 | font-size: calc(var(--base-unit) * 1); 685 | } 686 | .override { 687 | margin-top: 12px; 688 | text-align: center; 689 | font-size: calc(var(--base-unit) * 1.2); 690 | } 691 | .override label { 692 | padding-left: 4px; 693 | } 694 | .actions button { 695 | font-size: calc(var(--base-unit) * 1.5); 696 | font-weight: 700; 697 | width: calc(var(--base-unit) * 11); 698 | height: calc(var(--base-unit) * 6); 699 | margin-top: 0px; 700 | margin-right: 12px; 701 | margin-bottom: 0px; 702 | margin-left: 12px; 703 | border-width: 2px; 704 | border-style: solid; 705 | border-color: var(--primary-text-color); 706 | background-color: var(--primary-color); 707 | color: var(--primary-text-color); 708 | } 709 | button:disabled, 710 | button[disabled] { 711 | background-color: #cccccc; 712 | color: #666666; 713 | } 714 | .actions .autoarm { 715 | background: var(--alarm-color-autoarm) !important; 716 | } 717 | button#disarm { 718 | color: var(--google-red-500); 719 | } 720 | .alpha { 721 | position: absolute; 722 | text-align: center; 723 | bottom: calc(var(--base-unit) * 0.1); 724 | color: var(--secondary-text-color); 725 | font-size: calc(var(--base-unit) * 0.7); 726 | } 727 | `; 728 | return style; 729 | } 730 | 731 | _label(label, default_label=undefined) { 732 | // Just show "raw" label; useful when want to see underlying const 733 | // so you can define your own label. 734 | if (this._config.show_label_ids) return label; 735 | 736 | if (this._config.labels && this._config.labels[label]) 737 | return this._config.labels[label]; 738 | 739 | const lang = this.myhass.selectedLanguage || this.myhass.language; 740 | const translations = this.myhass.resources[lang]; 741 | if (translations && translations[label]) return translations[label]; 742 | 743 | if (default_label) return default_label; 744 | 745 | // If all else fails then prettify the passed in label const 746 | const last_bit = label.split('.').pop(); 747 | return last_bit.split('_').join(' ').replace(/^\w/, c => c.toUpperCase()); 748 | } 749 | 750 | getCardSize() { 751 | return 1; 752 | } 753 | } 754 | 755 | customElements.define('alarm_control_panel-card', AlarmControlPanelCard); 756 | 757 | --------------------------------------------------------------------------------