Thanks for replying. I ended up making something myself to do 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 }}
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
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.
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.