Dual-entity flashing button template for custom:button-card

Thanks for replying. I ended up making something myself to do it.

@spry-salt, what did you end up doing to achieve it?

Broadly, I created a button with three states - on, off, and changing.

For example:
If the button state is On and the button is then pressed, it changes state to Changing and stays like that until either the action succeeds or fails, then it either changes to Off or reverts to On.

It was fiddly to do but it works and it’s not as confusing as buttons that flick back and forth confusingly between states while the switching and polling is happening.

An example of that might be useful for others, if you don’t mind sharing it.

I use this technique to create a tri-state switch that lets me switch on/off internet access for a device on my home network.

When it’s switching between on and off states, to account for the delay the switch shows an intermediate state. This makes it less confusing for the user as they can see something’s actually in the process of happening.

It uses a Helper:

input_boolean.blocking_my_device)

It also uses two automations, a switch to turn internet access on/off (provided by the ControlD integration in HACS), and a toggle on a card.

      cards:
        - type: custom:button-card
          entity: input_boolean.blocking_my_device
          show_name: true
          name: "My Device"
          show_state: false
          show_label: true
          state:
            - operator: template
              value: |
                [[[
                  var e = entity.state;
                  var x = states['switch.device_my_device_blocking'].state;
                  if (e == 'on' && x == 'on') return 'on'; 
                  if (e == 'on' && x == 'off') return 'changing'; 
                  if (e == 'off' && x == 'on') return 'changing'; 
                  if (e == 'off' && x == 'off') return 'off'; 
                  else return 'Unknown';
                ]]]
              icon: |
                [[[
                  var e = entity.state;
                  var x = states['switch.device_my_device_blocking'].state;
                  if (e == 'on' && x == 'on') return 'mdi:close-network'; 
                  if (e == 'on' && x == 'off') return 'mdi:help-network'; 
                  if (e == 'off' && x == 'on') return 'mdi:help-network'; 
                  if (e == 'off' && x == 'off') return 'mdi:check-network'; 
                  else return 'Unknown';
                ]]]
          label: |
            [[[
              var e = entity.state;
              var x = states['switch.device_my_device_blocking'].state;
              if (e == 'on' && x == 'on') return 'Blocked'; 
              if (e == 'on' && x == 'off') return 'Changing'; 
              if (e == 'off' && x == 'on') return 'Changing'; 
              if (e == 'off' && x == 'off') return 'Unblocked'; 
              else return 'Unknown';
            ]]]
          styles:
            icon:
              - color: |
                  [[[
                    var e = entity.state;
                    var x = states['switch.device_my_device_blocking'].state;
                    if (e == 'on' && x == 'on') return 'red'; 
                    if (e == 'on' && x == 'off') return 'orange'; 
                    if (e == 'off' && x == 'on') return 'orange'; 
                    if (e == 'off' && x == 'off') return 'green'; 
                    else return 'lightgrey';
                  ]]]
          tap_action:
            action: call-service
            service: automation.trigger
            data: {}
            target:
              entity_id: automation.blocking_my_device_change_by_user

Automation 1 - Blocking My Device change by user - this runs when I press the button to switch internet access on/off for my device, and toggles the Helper.

alias: Blocking My_Device change by user
description: ""
trigger: []
condition: []
action:
  - if:
      - condition: template
        value_template: >-
          {{ states('switch.device_My_Device_blocking') ==
          states('input_boolean.blocking_My_Device') }} 
    then:
      - service: input_boolean.toggle
        data: {}
        target:
          entity_id: input_boolean.blocking_My_Device
      - service: switch.toggle
        data: {}
        target:
          entity_id: switch.device_My_Device_blocking
      - service: homeassistant.update_entity
        data: {}
        target:
          entity_id:
            - switch.device_My_Device_blocking
            - sensor.listalldevices_json
        enabled: false
      - if:
          - condition: template
            value_template: >-
              {{ states('switch.device_My_Device_blocking') !=
              states('input_boolean.blocking_My_Device') }} 
        then:
          - wait_for_trigger:
              - platform: template
                value_template: >-
                  {{ states('switch.device_My_Device_blocking') ==
                  states('input_boolean.blocking_My_Device') }} 
                for:
                  hours: 0
                  minutes: 0
                  seconds: 2
            continue_on_timeout: true
            timeout:
              hours: 0
              minutes: 1
              seconds: 0
              milliseconds: 0
  - if:
      - condition: template
        value_template: >-
          {{ states('switch.device_My_Device_blocking') !=
          states('input_boolean.blocking_My_Device') }} 
    then:
      - service: input_boolean.toggle
        data: {}
        target:
          entity_id: input_boolean.blocking_My_Device
mode: single

Automation 2 - Blocking My Device change by REST - this runs when the internet blocking setting for my device actually changes on my DNS provider.

alias: Blocking My_Device change by REST
description: ""
trigger:
  - platform: state
    entity_id:
      - switch.device_My_Device_blocking
condition:
  - condition: template
    value_template: >-
      {{ states('switch.device_My_Device_blocking') !=
      states('input_boolean.blocking_My_Device') }}
action:
  - service: input_boolean.toggle
    data: {}
    target:
      entity_id: input_boolean.blocking_My_Device
mode: single

Sensors and switch:

- platform: rest
  resource: https://api.controld.com/devices
  name: listalldevices_json
  headers:
    content-type: "application/json"
    Authorization: "Bearer api.MY_API_HERE"
  json_attributes_path: "$.body"
  json_attributes:
    - "devices"
  value_template: "{{ value_json.success }}"

- platform: rest
  resource: https://api.controld.com/profiles
  name: listallprofiles_json
  headers:
    content-type: "application/json"
    Authorization: "Bearer api.MY_API_HERE"
  json_attributes_path: "$.body"
  json_attributes:
    - "profiles"
  value_template: "{{ value_json.success }}"



- platform: rest
  resource: https://api.controld.com/devices/MY_DEVICE_ID_HERE
  name: device_my_device_blocking
  method: put
  body_on: '{ "profile_id": "MY_PROFILE1_ID_HERE", "profile_id2": "-1" }'
  body_off: '{ "profile_id": "MY_PROFILE2_ID_HERE", "profile_id2": "-1" }'
  is_on_template: "{{ state_attr('sensor.device_my_device','profile') == 'Block access' }}"
  headers:
    content-type: "application/json"
    Authorization: "Bearer api.MY_API_HERE"


- sensor:
    - name: DeviceNames
      state: >
        {{ states('sensor.listalldevices_json') }}
      availability: >
        {{ states('sensor.listalldevices_json') }}
      attributes:
        device_names: >
          {% set theDeviceList = state_attr('sensor.listalldevices_json','devices') %}
          {{ theDeviceList | map(attribute='name') | list | default }}

    - name: ProfileNames
      state: >
        {{ states('sensor.listallprofiles_json') }}
      availability: >
        {{ states('sensor.listallprofiles_json') }}
      attributes:
        profile_names: >
          {% set theDeviceList = state_attr('sensor.listallprofiles_json','profiles') %}
          {{ theDeviceList | map(attribute='name') | list | default }}

    - name: Device_My_Device
      state: >
        {{ 'My_Device' in state_attr('sensor.DeviceNames','device_names') }}
      availability: >
        {{ (expand( 'sensor.DeviceNames','device_names' ) | rejectattr('state', 'in', ['unknown','unavailable']) | list | count > 0) 
          and ('My_Device' in state_attr('sensor.DeviceNames','device_names')) }}
      attributes:
        profile: >
          {% set theProfile = "empty" %}
          {% set theDeviceList = state_attr('sensor.listalldevices_json','devices') %}
          {% if theDeviceList is not none %}
            {% set theDevice = theDeviceList | selectattr('name', 'eq', 'My_Device') | list %}
            {% if theDevice is not none %}
              {% if 'profile' in theDevice[0] %}
                {% set theProfile = theDevice[0].profile.name %}
              {% endif %}
            {% endif %}
          {% endif %}
          {{ theProfile }}
        profile2: >
          {% set theProfile = "empty" %}
          {% set theDeviceList = state_attr('sensor.listalldevices_json','devices') %}
          {% if theDeviceList is not none %}
            {% set theDevice = theDeviceList | selectattr('name', 'eq', 'My_Device') | list %}
            {% if theDevice is not none %}
              {% if 'profile2' in theDevice[0] %}
                {% set theProfile = theDevice[0].profile2.name %}
              {% endif %}
            {% endif %}
          {% endif %}
          {{ theProfile }}
2 Likes

Love this, but struggling a little bit to understand how best to setup. I want to have this active for multiple sensors I have running eg motion, and water.

Any guidance on what to do / copy etc is appreciated.

@CJF077, it was designed for a sensor and a switch but it might work for two sensors. If it doesn’t work as-is, it might need some small adjustments to react correctly to sensor states if they aren’t “off” and “on”. I recommend experimenting with it and reading documentation for the custom button card. The more you learn about how it works the more powerful it will be for you :slight_smile:

1 Like

I realise this conversation has been stale for some time. However, I have been using the alerter-toggle yaml and have an issue that someone may be able to help with,

I have implemented this for door sensors and window sensors. The door that was open flashed correctly. However, all the other buttons that had this applied were fading in and out in what seemed to be a random flash pattern. SUbtle but very offputting. I have attached a gif.

11-11-2024 (21-27-38)

Code I am using is as follows:

Button_cards_template.yaml:

alerter-door: 
  !include /config/www/lovelace/all-dashboards/reusable_code/lovelace-alerter-toggle.yaml
alerter-window: 
  !include /config/www/lovelace/all-dashboards/reusable_code/lovelace-alerter-toggle.yaml

Lovelace-alerter-toggle-yaml (copied from the op at the top of the discussion):

# alerter-toggle.yaml
# Keith Townsend, 3/31/2022
# Based on prior work I did around October 2020, which was inspired by someone's background flash animation. This complete refactor is MUCH cleaner.

variables:
  toggle_entity: null   # optional entity to toggle and watch states
  toggle_state_on: 'on' # optional "on" state to watch for on toggle_entity
  sensor_state_on: 'on' # optional "on" state to watch for on primary entity
  delay_color: null     # optional color to use after delay_seconds
  delay_seconds: 60     # optional seconds to wait before using delay_color

template: standard      # this is my default styles for all my buttons, and includes hold_action to show more_info
color_type: card        # this lets user specify background color by overriding color property on states
show_last_changed: true # user can override this if they want
triggers_update: all    # ensure switch state is detected becuase the card won't auto-monitor entities in variables

extra_styles: |
  [[[ 
    let basecolor = null
    if (variables.delay_color)
      basecolor = Date.parse(entity.last_updated) - new Date() + (variables.delay_seconds * 1000) > 0 ? null : variables.delay_color;
    return `
    @keyframes sharp {
      0% {
        background-color: ${basecolor};
      }
      95% {
        background-color: var(--paper-card-background-color);
        color: var(--paper-card-color);
      }
      100% {
        background-color: ${basecolor};
      }
    }
  `]]]

tap_action:
  action: '[[[ return variables.toggle_entity ? "call-service" : "more-info" ]]]'
  service: homeassistant.toggle
  service_data:
    entity_id: "[[[ return variables.toggle_entity ]]]"

state:
  - id: sensor_off_toggle_off
    operator: default

  - id: sensor_off
    operator: template
    value: '[[[ return entity.state !== variables.sensor_state_on && !variables.toggle_entity ]]]'

  - id: sensor_on
    operator: template
    value: '[[[ return entity.state === variables.sensor_state_on && !variables.toggle_entity ]]]'
    styles:
      card:
        - animation: sharp ease-in-out 1s infinite

  - id: sensor_on_toggle_off
    operator: template
    value: '[[[ return entity.state === variables.sensor_state_on && variables.toggle_entity && states[variables.toggle_entity].state !== variables.toggle_state_on ]]]'
    styles:
      card:
        - animation: sharp ease-in-out 1s infinite

  - id: sensor_off_toggle_on
    operator: template
    value: '[[[ return entity.state !== variables.sensor_state_on && variables.toggle_entity && states[variables.toggle_entity].state === variables.toggle_state_on ]]]'

  - id: sensor_on_toggle_on
    operator: template
    value: '[[[ return entity.state === variables.sensor_state_on && variables.toggle_entity && states[variables.toggle_entity].state === variables.toggle_state_on ]]]'
    styles:
      card:
        - animation: sharp ease-in-out 1s infinite

Decluttering templates (where the actual button formats are defined):

window_sensor_button_template:
  card:
    type: custom:button-card
    template: alerter-window
    show_entity_picture: true
    name: '[[name]]'
    entity: '[[entity]]'
    styles:
      name:
        - text-overflow: unset
        - white-space: unset
        - word-break: break-word
        - font-size: 10px
      card:
        - box-shadow: 0px 0px 10px 3px lightblue
    state:
      - value: 'closed'
        icon: mdi:window-closed-variant
        color: grey
      - value: 'open'
        icon: mdi:window-open-variant
        color: yellow
      - value: 'unavailable'
        icon: mdi:window-closed-variant
        color: red
      - value: 'unknown'
        icon: mdi:window-closed-variant
        color: red
    show_state: false
    show_label: false
    show_icon: true
    show_name: true
    size: 20%

door_sensor_button_template:
  card:
    type: custom:button-card
    template: alerter-door
    show_entity_picture: true
    name: '[[name]]'
    entity: '[[entity]]'
    styles:
      name:
        - text-overflow: unset
        - white-space: unset
        - word-break: break-word
        - font-size: 10px
      card:
        - box-shadow: 0px 0px 10px 3px lightblue
    state:
      - id: sensor_on
        icon: mdi:door-closed
        color: grey
      - value: 'open'
        icon: mdi:door-open
        color: yellow
      - value: 'unavailable'
        icon: mdi:door-closed
        color: red
      - value: 'unknown'
        icon: mdi:door-closed
        color: red
    show_state: false
    show_label: false
    show_icon: true
    show_name: true
    size: 20%


The actual yaml that builds the page and uses these buttons:

      - type: vertical-stack
        cards:
          - type: custom:mod-card
            style: |
              ha-card {
                border: 4px solid white;
                background: white;
                opacity: 90%;
                box-shadow: none;
              }
            card:
              type: custom:vertical-stack-in-card
              cards:
                - type: horizontal-stack
                  cards:
                    - type: vertical-stack
                      cards:
                        - type: custom:button-card
                          template: title
                          name: Door Contact Sensors
                          variables:
                            background_color: black
                            color: dark-grey
                          icon: mdi:door-open
                        - type: horizontal-stack
                          cards:
                            - type: custom:decluttering-card
                              template: door_sensor_button_template
                              variables:
                                - entity: binary_sensor.front_door_contact_sensor_ring
                                - name: <br/>Front
                                - sensor_state_on: 'open' 
#                                - delay_color: lightblue
                                - delay_seconds: 15
                            - type: custom:decluttering-card
                              template: door_sensor_button_template
                              variables:
                                - entity: binary_sensor.kitchen_door_contact_sensor_ring
                                - name: <br/>Kitchen
                                - sensor_state_on: 'open' 
#                                - delay_color: lightblue
                                - delay_seconds: 15
                            - type: custom:decluttering-card
                              template: door_sensor_button_template
                              variables:
                                - entity: binary_sensor.conservatory_door_contact_sensor_ring
                                - name: <br/>Conservatory
                                - sensor_state_on: 'open' 
#                                - delay_color: lightblue
                                - delay_seconds: 15
                            - type: custom:button-card
                              color_type: blank-card

Hopefully, someone can help.