Skip to content

Instantly share code, notes, and snippets.

@brucefoster
Last active January 20, 2025 17:55
Show Gist options
  • Select an option

  • Save brucefoster/31a7ba46466eb182403d903b31d1f598 to your computer and use it in GitHub Desktop.

Select an option

Save brucefoster/31a7ba46466eb182403d903b31d1f598 to your computer and use it in GitHub Desktop.
Philips Hue Switch Light Controller for Home Assistant
blueprint:
name: Control Lights with Philips Hue Switch (ZHA)
author: "Sascha B."
description: >
### Bind your light or a group of lights and use the full power of a Hue Switch v2.
«On/off» button:
- click to turn the light or on off,
- hold to turn on with minimal brightness,
- click twice to turn on with max brightness or to exit a scene.
«Dim Up»/«Dim Down» buttons:
- click to increase or decrease brightness,
- keep holding to seamlessly adjust brightness,
- click twice to jump to the max or min brightness.
Hue button (configurable):
- cycle through scenes, scripts or colors presets,
- temporary change the behavior of «Dim Up»/«Dim Down» buttons (control CCT or color),
- switch between CCT and color modes or activate favorite scene or script.
<details>
<summary><b>🔴 Prerequisites</b></summary>
Install a blueprint [to propagate «button down» event faster than ZHA's native handler](https://community.home-assistant.io/).
</details>
domain: automation
input:
hue_switch:
name: Hue Switch
description: >
Which remote will control the particular lights?
selector:
device:
filter:
integration: zha
manufacturer: Signify Netherlands B.V.
model: RWL022
target_light:
name: Lights
description: Which light or group will be controlled by the remote?
selector:
entity:
domain: light
storage:
name: Text Helper entity
description: >
This automation requires a text helper to store runtime data.
Pick the option «Create a new Text helper» inside the dropdown and proceed with default options. Struggle with the name? Try «Hue Switch + Lights Storage».
selector:
entity:
domain: input_text
# Optional controls section ==============================================================
optional_controls:
name: "Optional settings"
icon: mdi:cog
collapsed: true
input:
brightness_step:
name: "Adjust brightness by..."
description: Controls how fast the brightness changes when you hold «Dim Up»/«Dim Down» buttons. Pick 1...3 for slow adjustment and 4...9 for rapid adjustment.
default: 6
selector:
number:
min: 2
max: 12
step: 1
unit_of_measurement: '%'
transition_speed:
name: "Transition speed"
description: Controls the speed of a fade animation when turning on or switching modes and scenes.
default: 0.25
selector:
number:
min: 0
max: 2
step: 0.05
unit_of_measurement: seconds
min_cct:
name: "Minimal color temperate (°K)"
description: Sets the lowest temperature you'll be able to set through the adjustment.
default: 2200
selector:
number:
min: 1000
max: 5000
step: 100
unit_of_measurement: K
max_cct:
name: "Maximal color temperate (°K)"
description: Sets the highest temperature you'll be able to set through the adjustment.
default: 4000
selector:
number:
min: 3000
max: 9000
step: 100
unit_of_measurement: K
default_sat:
name: "Colors saturation"
description: Sets the saturation for colors when using color adjust mode.
default: 80
selector:
number:
min: 1
max: 100
step: 1
unit_of_measurement: '%'
# Hue button behavior section ==============================================================
hue_button:
name: "«Hue» button behavior"
icon: mdi:numeric-4-box
collapsed: true
input:
click_action:
name: "When clicking «Hue» button..."
default: "none"
selector:
select:
mode: dropdown
options:
- label: Do nothing
value: "none"
- label: Cycle through scenes
value: "cycle_scenes"
- label: Cycle through presets
value: "cycle_presets"
- label: Temporary adjust CCT or hue with «Dim Up»/«Dim Down» buttons
value: "temp_mode_change"
# hold_action:
# name: "When holding «Hue» button..."
# default: "none"
# selector:
# select:
# mode: dropdown
# options:
# - label: Do nothing
# value: "none"
# - label: Cycle through scenes
# value: "cycle_scenes"
# - label: Cycle through presets
# value: "cycle_presets"
double_click_action:
name: "When clicking «Hue» button twice..."
description: Switching between controlling CCT and hue will temporarily change the behavior of the «Dim Up»/«Dim Down» buttons, allowing you to shift CCT or hue accordingly.
default: "change_mode"
selector:
select:
mode: dropdown
options:
- label: Do nothing
value: "none"
- label: Switch between controlling CCT and hue
value: "change_mode"
- label: Activate favorite scene
value: "favorite_scene"
scenes:
name: "Scenes list"
description: Pick scenes (or scripts) to cycle through. Hold «Hue» to exit a scene and sync all lights.
default: []
selector:
entity:
multiple: true
filter:
domain:
- scene
- script
favorite_scene:
name: "Fast-access favorite scene (or script)"
description: Hold «Hue» to exit a scene and sync all lights.
default: []
selector:
entity:
filter:
domain:
- scene
- script
scope:
name: "When activating a scene..."
description: >
Press Power twice to take control over all lights and sync them with last used color temperature.
When controlling lights used in scenes only brightness adjustment is available.
default: "within_scene"
selector:
select:
mode: dropdown
options:
- label: Control lights used in a scene only
value: "within_scene"
- label: Control all lights enlisted in automation (may work weird)
value: "all"
has_tradfri:
name: "Do you use IKEA bulbs with color support in scenes?"
description: Tradfri bulbs can't animate changes in brightness and color simultaneously. If you use them in your scenes where both brightness and color change, automation will apply scenes without transition to lower glitches.
default: false
selector:
boolean:
presets:
name: "CCT/hue presets (comma-separated)"
description: >
Specify a set of color temperatues (°K) and [hue values](https://www.selecolor.com/en/hsv-color-picker/) to cycle through.
When specifying a color temperature, include a trailing «K» (e. g. «2500K»), and use just a number when specifying a hue value.
Examples:
- 2600K, 3000K, 3600K
- 2600K, 120, 160, 3500K
default: ""
selector:
text:
# ===============================================================================
variables:
storage: !input storage
target_light: !input target_light
target_light_data:
power: |
{{ states(target_light, 'is_on') == 'on' }}
supported_modes:
cct: |
{{ 'color_temp' in state_attr(target_light, 'supported_color_modes') }}
color: |
{{ 'xy' in state_attr(target_light, 'supported_color_modes') }}
mode: |
{{ state_attr(target_light, 'color_mode') }}
hue_click_action: !input click_action
hue_double_click_action: !input double_click_action
scenes_list: !input scenes
presets: !input presets
dim_speed: 200
brightness_step: !input brightness_step
transition_speed: !input transition_speed
min_cct: !input min_cct
max_cct: !input max_cct
default_sat: !input default_sat
cct_step: |
{{ ((max_cct - min_cct) * 0.075) | int }}
timestamp_reset_seconds: 15
loop_data: |
{% set ns = namespace(breaker=false, estimated_brightness=0) %}
{{ ns }}
supported_flows:
# Power button (1)
on_press: power
on_hold: |
{{ 'min' if target_light_data.power == false else 'ignore' }}
on_double_press: |
{{ 'max' if target_light_data.power == false else 'reset' }}
# Dim Up button (2)
up_press: up
up_hold: up
up_double_press: max
# Dim Down button (3)
down_press: down
down_hold: down
down_double_press: min
# Hue button (4)
off_press: custom
off_hold: reset
off_double_press: |
{{ 'change_mode' if hue_double_click_action == 'change_mode' else 'ignore' }}
on_short_release: abort
on_long_release: abort
up_short_release: abort
up_long_release: abort
down_short_release: abort
down_long_release: abort
off_short_release: abort
off_long_release: abort
flow: >
{{ supported_flows[trigger.event.data.command] if trigger.event.data.command
in supported_flows else 'ignore' }}
next_variant: 0
# m int mode
# c bool continue automation (breaker)
# rts int reset mode at specific timestamp
# lc int last remembered CCT
# lh int last remembered hue
# cs int current (last) scene ID
current_settings: |
{% set _structure = {"m": "1", "c": true, "rts": false, "lc": 0, "lh": 0, "cs": -1} %}
{% set _data = _structure if states(storage) in ['', 'unknown'] else states(storage) | from_json %}
{% if (
_data['rts']|int > 1000 and
_data['rts']|int < as_timestamp(now())
) or flow == 'power' %}
{% set _data = dict(_data, **{"m": 1, "rts": false}) %}
{% endif %}
{% if flow == 'reset' %}
{% set _data = dict(_data, **{"cs": -1}) %}
{% endif %}
{{ _data }}
scene_lights: |
{% set scene_id = current_settings['cs'] | int %}
{% if scene_id != -1 %}
{{ expand(state_attr(scenes_list[scene_id], 'entity_id')) | selectattr('domain', 'eq', 'light') | map(attribute='entity_id') | list }}
{% else %}
{{ false }}
{% endif %}
mode: parallel
# ===============================================================================
# ZHA emits lots of events. This narrow targeting reduces number of automation runs
triggers:
- trigger: event
event_type: zha_event
event_data:
device_id: !input hue_switch
args:
command_id: 0
duration: 0
- trigger: event
event_type: zha_event
event_data:
device_id: !input hue_switch
args:
command_id: 0
duration: 1
- trigger: event
event_type: zha_event
event_data:
device_id: !input hue_switch
args:
command_id: 0
duration: 8
- trigger: event
event_type: zha_event
event_data:
device_id: !input hue_switch
args:
command_id: 0
press_type: long_release
actions:
- choose:
# Because of the parallel mode of this automation, this option ensures that new runs will quit as soon as possible
- conditions:
- "{{ current_settings['c'] == true and flow != 'abort' }}"
sequence:
# =========================================================
# Dim Up/Dim Down buttons handler
# =========================================================
- conditions:
- "{{ flow in ['down', 'up'] }}"
sequence:
# Saving current state
- variables:
current_settings: |
{{ dict(current_settings, **{"c": true}) }}
- action: input_text.set_value
entity_id: !input storage
data:
value: |
{{ current_settings | to_json }}
- repeat:
sequence:
- if:
- "{{ scene_lights != false }}"
then:
- action: light.turn_on
data:
brightness_step_pct: |
{{ brightness_step if flow == "up" else (0 - brightness_step) }}
transition: 0.3
target:
entity_id: |
{{ scene_lights }}
else:
- action: light.turn_on
data_template: >-
{% set hue = state_attr(target_light, 'hs_color')[0]|int
if state_attr(target_light, 'hs_color') != None
else 0 %}
{% set sat = state_attr(target_light, 'hs_color')[1]|float
if state_attr(target_light, 'hs_color') != None
else 0 %}
{% set cct = state_attr(target_light, 'color_temp_kelvin')|int(0) %}
{% set brightness = state_attr(target_light, 'brightness')|int(0) %}
{#
===============================
Brigtness adjust mode
===============================
#}
{% if current_settings["m"]|int == 1 %}
{
"brightness_step_pct": {{ brightness_step if flow == "up" else (0 - brightness_step) }},
"transition": 0.4
}
{#
===============================
Color temperature adjust mode
===============================
#}
{% elif current_settings["m"]|int == 2 %}
{
"color_temp_kelvin": {{ min(max_cct, cct + cct_step) if flow == "up" else max(min_cct, cct - cct_step) }},
"transition": 0.2
}
{#
===============================
Hue temperature adjust mode
===============================
#}
{% elif current_settings["m"]|int == 3 %}
{
"hs_color": {{ [ hue + (4 if flow == "up" else -4), default_sat ] }},
"transition": 0.2
}
{% endif %}
target:
entity_id: !input target_light
- delay:
hours: 0
minutes: 0
seconds: 0
milliseconds: |
{{ dim_speed }}
while:
- condition: template
value_template: >-
{{
(states(storage)|from_json)['c']
== true }}
- condition: template
value_template: >-
{% set hue = state_attr(target_light, 'hs_color')[0]|int %}
{% set cct = state_attr(target_light, 'color_temp_kelvin')|int(0) %}
{% set brightness = state_attr(target_light, 'brightness')|int %}
{% set brightness_change = 255 / 100 * brightness_step %}
{% if current_settings["m"]|int == 1 %}
{{ brightness - brightness_change > 0 if flow == "down" else brightness + brightness_change < 255 }}
{% elif current_settings["m"]|int == 2 %}
{{ cct > min_cct if flow == "down" else cct < max_cct }}
{% elif current_settings["m"]|int == 3 %}
{{ hue > 0 if flow == "down" else hue < 360 }}
{% endif %}
# =========================================================
# Handler for:
# — Dim Up/Dim Down buttons double-click
# — Power button double-click/hold
# =========================================================
- conditions:
- "{{ flow in ['max', 'min'] }}"
sequence:
- action: light.turn_on
data_template: >-
{% if current_settings["m"]|int == 1 %}
{
"brightness_pct": {{ 100 if flow == "max" else 1 }},
"transition": {{ transition_speed }}
}
{% elif current_settings["m"]|int == 2 %}
{
"color_temp_kelvin": {{ max_cct if flow == "max" else min_cct }},
"transition": {{ transition_speed }}
}
{% endif %}
target:
entity_id: !input target_light
# =========================================================
# Power button click handler
# =========================================================
- conditions:
- "{{ flow == 'power' }}"
sequence:
- action: light.toggle
target:
entity_id: !input target_light
data: {}
- action: input_text.set_value
entity_id: !input storage
data:
value: |
{{ current_settings | to_json }}
# =========================================================
# Hue button click handler
# =========================================================
- conditions:
- "{{ flow == 'custom' }}"
sequence:
- choose:
# Temporary behavior change mode
- conditions:
- "{{ hue_click_action == 'temp_mode_change' }}"
- "{{ scene_lights == false }}"
- "{{ target_light_data.supported_modes.values() | list | max == true }}"
sequence:
- variables:
current_cct: |
{{ state_attr(target_light, 'color_temp_kelvin')|int(0) }}
current_hue: |
{{ state_attr(target_light, 'hs_color')[0]|int }}
current_brightness: |
{{ state_attr(target_light, 'brightness')|int }}
current_settings: |
{{ dict(current_settings, **{
"m": 2 if current_cct > 0 else 3,
"rts": (as_timestamp(now())|int + timestamp_reset_seconds)
}) }}
- repeat:
for_each: |
{% set signal_brightness = current_brightness + 60 if (current_brightness - 60) < 5 else current_brightness - 60 %}
{{ [signal_brightness, current_brightness] }}
sequence:
- action: light.turn_on
data:
brightness: "{{ repeat.item }}"
transition: 0.1
target:
entity_id: !input target_light
- delay:
hours: 0
minutes: 0
seconds: 0.1
- action: input_text.set_value
entity_id: !input storage
data:
value: |
{{ current_settings | to_json }}
# Cycle through scenes/presets mode
- conditions:
condition: template
value_template: |
{{ hue_click_action in ["cycle_scenes", "cycle_presets"] }}
sequence:
- variables:
variants: |
{{ scenes_list if hue_click_action == "cycle_scenes" else presets.split(",") | map('trim') | list }}
next_variant: |
{{ (current_settings["cs"] + 1) if (current_settings["cs"] + 1) < (variants | length) else 0 }}
current_settings: |
{{ dict(current_settings, **{
"cs": next_variant
}) }}
- action: input_text.set_value
entity_id: !input storage
data:
value: |
{{ current_settings | to_json }}
- if:
- "{{ hue_click_action == 'cycle_scenes' }}"
then:
- if:
- "{{ variants[current_settings['cs']].startswith('script') }}"
then:
- action: script.turn_on
target:
entity_id: |
{{ variants[current_settings["cs"]] }}
else:
- action: scene.turn_on
target:
entity_id: |
{{ variants[current_settings["cs"]] }}
data:
transition: |
{{ transition_speed if has_tradfri == false else 0 }}
else:
- action: light.turn_on
data_template: >-
{% if variants[next_variant].endswith('K') %}
{
"color_temp_kelvin": {{ variants[next_variant][:-1] | int }},
"transition": {{ transition_speed }}
}
{% else %}
{
"hs_color": {{ [ variants[next_variant] | int, default_sat ] }},
"transition": {{ transition_speed }}
}
{% endif %}
target:
entity_id: !input target_light
# Saving current state
# - variables:
# current_settings: |
# {{ dict(current_settings, **{"c": true}) }}
# - action: input_text.set_value
# entity_id: !input storage
# data:
# value: |
# {{ current_settings | to_json }}
# - variables:
# variants: |
# {{ scenes_list if hue_click_action == "cycle_scenes" else presets.split(",") | map('trim') | list }}
# next_variant: |
# {{ (current_settings["cs"] + 1) if (current_settings["cs"] + 1) < (variants | length) else 0 }}
# current_settings: |
# {{ dict(current_settings, **{
# "cs": next_variant
# }) }}
# - repeat:
# sequence:
# - variables:
# next_variant: |
# {{ (current_settings["cs"] + 1) if (current_settings["cs"] + 1) < (variants | length) else 0 }}
# current_settings: |
# {{ dict(current_settings, **{
# "cs": next_variant
# }) }}
# - if:
# - "{{ hue_click_action == 'cycle_scenes' }}"
# then:
# - action: scene.turn_on
# target:
# entity_id: |
# {{ variants[current_settings["cs"]] }}
# data:
# transition: |
# {{ transition_speed }}
# else:
# - action: light.turn_on
# data_template: >-
# {% if variants[next_variant].endswith('K') %}
# {
# "color_temp_kelvin": {{ variants[next_variant][:-1] | int }},
# "transition": {{ transition_speed }}
# }
# {% else %}
# {
# "hs_color": {{ [ variants[next_variant] | int, default_sat ] }},
# "transition": {{ transition_speed }}
# }
# {% endif %}
# target:
# entity_id: !input target_light
# - delay:
# hours: 0
# minutes: 0
# seconds: 0
# milliseconds: 500
# while:
# - "{{ (states(storage)|from_json)['c'] == true }}"
# =========================================================
# Hue button double-click handler
# =========================================================
# Modes description:
# 1. Adjust brightness (default)
# 2. Adjust color temperature
# 3. Adjust hue color (hs_color)
# =========================================================
- conditions:
# Mode change is available if both CCT and color modes are supported
- "{{ flow == 'change_mode' and target_light_data.supported_modes.values() | list | min == true }}"
sequence:
- variables:
current_settings: |
{% set hue = state_attr(target_light, 'hs_color')[0]|int %}
{% set cct = state_attr(target_light, 'color_temp_kelvin')|int(0) %}
{% set current_mode = 2 if target_light_data.mode == 'color_temp' else 3 %}
{% set next_mode = 2 if current_mode == 3 else 3 %}
{{ dict(current_settings, **{
"m": next_mode,
"rts": (as_timestamp(now())|int + timestamp_reset_seconds),
"lc": cct if current_mode == 2 else current_settings["lc"],
"lh": hue if current_mode == 3 else current_settings["lh"],
}) }}
- if:
- condition: template
value_template: >
{{ current_settings["m"] == 2 }}
then:
- action: light.turn_on
data:
color_temp_kelvin: |
{{ current_settings["lc"] if current_settings["lc"] > 0 else 2400 }}
transition: |
{{ transition_speed }}
target:
entity_id: !input target_light
else:
- action: light.turn_on
data:
hs_color: |
{{ [ current_settings["lh"], default_sat ] }}
transition: |
{{ transition_speed }}
target:
entity_id: !input target_light
- action: input_text.set_value
entity_id: !input storage
data:
value: |
{{ current_settings | to_json }}
# =========================================================
# Reset scene handler
# =========================================================
- conditions:
- "{{ flow == 'reset' }}"
sequence:
- action: input_text.set_value
entity_id: !input storage
data:
value: |
{{ current_settings | to_json }}
# =========================================================
# All buttons release handler
# =========================================================
- conditions:
- "{{ flow == 'abort' }}"
sequence:
- variables:
current_settings: |
{{ dict(current_settings, **{"c": false}) }}
- action: input_text.set_value
entity_id: !input storage
data:
value: |
{{ current_settings | to_json }}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment