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