Hi,
I recently spent a bunch of time to do something I expected to be quick and easy. (I’m sure nobody working on home automation has ever experienced this .) I thought I’d share, but I’m also curious how other people have invented this wheel.
Here’s the story (skip if you don’t care about the backstory):
I’ve got some Philips Hue bulbs that I put in my kid’s rooms. Alas, their rooms had (dumb) dimmers in them and our 4-year-old found it irresistible to dim the smart bulbs and make them flash erratically. So I quickly installed the Zooz Zen72s I’d been putting off installing elsewhere. I was able to find a blueprint (thanks IOT_Ninja!) that let me respond to presses on the switch. There was an odd hiccup where on one of the switches attribute_id
was an integer (0, 1, etc.) rather than a symbolic name (KeyPressed, KeyPressed2x, etc.), but I worked around that by looking at attribute_id_raw
value which was consistently an integer on both devices.
Since the rooms had dimmers to begin with, I wanted to make the switch gradually dim up when holding the up paddle and gradually dim down when holding the down paddle. I found a YouTube video where someone said to do something like this
description: "Sample Automation"
mode: restart
trigger:
- platform: event
event_type: zwave_js_value_notification
# details omitted for brevity - I used the blueprint and the video was using an Ikea switch
condition: []
action:
- repeat:
count: "20" # must be 100/brightness_step_pct
sequence:
- service: light.turn_on
data:
brightness_step_pct: 5
target:
entity_id: light.kids_room
- delay:
hours: 0
minutes: 0
seconds: 0
milliseconds: 250 # 20 * 250ms = 5s to go from full-off to full-on
Because the mode is restart
, when the release event first, the loop is interrupted.
This worked, but it was way slower than what was expected and you could see the “steps” pretty. (Maybe the Hue hub was too far from the lights.)
So I scratched my head and came up with this:
Use transition
to go to full-on (or full-off if dimming down) and then wait for a custom event. If you get the event, interpolate the correct brightness and set that (stopping the transition). If the light gets to full-on, just time out the event wait and don’t override the brightness. That way, you get the smooth transition effect from the bulb’s native feature and you get as much “resolution” as the underlying hardware’s latency affords
Here’s what it looks like;
fade_light:
alias: Fade Light
description: Adjust the brightness of a light
fields:
entity_id:
description: "The light to fade on/off"
example: light.kids_light
rate:
description: "Number of seconds to go from 0 -> 100%"
example: "3"
target_brightness:
description: "Desired brightness (usually 255 or 0)"
example: "255"
variables:
initial_time: "{{ now() | as_timestamp }}"
initial_brightness: >
{{ state_attr(entity_id, 'brightness') | int(0) }}
transition: >
{{ (rate * 255 / (initial_brightness - target_brightness)) | abs | int(0) if initial_brightness != target_brightness else 0 }}
sequence:
- service: light.turn_on
data:
transition: "{{ transition }}"
brightness: "{{ target_brightness }}"
target:
entity_id: "{{ entity_id }}"
- if: "{{ transition > 0 }}"
then:
- wait_for_trigger:
- platform: event
event_type: script.fade_light.abort
event_data:
entity_id: "{{ entity_id }}"
timeout:
seconds: "{{ transition }}"
- if: "{{ wait.remaining > 0 }}"
then:
- service: light.turn_on
data:
transition: 0
brightness: >
{{
target_brightness + (initial_brightness - target_brightness) * (wait.remaining) / transition | int(0)
}}
target:
entity_id: "{{ entity_id }}"
mode: parallel
max: 10
I called it from this (which was heavily based on the blueprint). It’s Zooz specific, but should be easy to adapt:
handle_light_switch:
alias: Handle Light Switch
description: Common logic for controlling a Hue group with a Zooz switch
fields:
scene_id:
description: "'Scene 001' for up, 'Scene 002' for down"
example: 'Scene 001'
attribute_id:
description: 0 -> 1x tap; 1 -> release held; 2 -> start hold; 3 -> 2x tap; 4 -> 3x tap; 5 -> 4x tap; 6 -> 5x tap
example: 0
light_entity_id:
description: Light (or group) to control
example: light.kids_room
scene_entity_id:
description: An input_select entity listing available scenes
example: input_select.kids_light_scenes
sequence:
- choose:
- conditions: "{{ scene_id == 'Scene 001' }}"
sequence:
- choose:
- conditions: "{{ attribute_id == 0 }}"
sequence:
- service: light.turn_on
data:
transition: 0
brightness: 255
target:
entity_id: "{{ light_entity_id }}"
- conditions: "{{ attribute_id == 3 }}"
sequence:
- service: input_select.select_option
data:
option: White
target:
entity_id: "{{ scene_entity_id }}"
- service: light.turn_on
data:
transition: 0
brightness: 255
target:
entity_id: "{{ light_entity_id }}"
- conditions: "{{ attribute_id == 5 }}"
sequence: []
- conditions: "{{ attribute_id == 6 }}"
sequence: []
- conditions: "{{ attribute_id == 2 }}"
sequence:
- service: script.fade_light
data:
entity_id: "{{ light_entity_id }}"
rate: 5
target_brightness: 255
- conditions: "{{ attribute_id == 1 }}"
sequence:
- event: script.fade_light.abort
event_data:
entity_id: "{{ light_entity_id }}"
- conditions: "{{ scene_id == 'Scene 002' }}"
sequence:
- choose:
- conditions: "{{ attribute_id == 0 }}"
sequence:
- service: light.turn_off
data:
transition: 0
target:
entity_id: "{{ light_entity_id }}"
- conditions: "{{ attribute_id == 3 }}"
sequence:
- service: input_select.select_next
data: {}
target:
entity_id: "{{ scene_entity_id }}"
- conditions: "{{ attribute_id == 5 }}"
sequence: []
- conditions: "{{ attribute_id == 6 }}"
sequence: []
- conditions: "{{ attribute_id == 2 }}"
sequence:
- service: script.fade_light
data:
entity_id: "{{ light_entity_id }}"
rate: 5
target_brightness: 1
- conditions: "{{ attribute_id == 1 }}"
sequence:
- event: script.fade_light.abort
event_data:
entity_id: "{{ light_entity_id }}"
mode: parallel
max: 10
Finally, I have an automation like this (really 2 since I have 2 kids):
- id: "1668406234508"
alias: Kids' Light Switch
description: ''
trigger:
- platform: event
event_type: zwave_js_value_notification
event_data:
command_class_name: Central Scene
device_id: a2846e0c7f24cec7f45e7fbb5b956cd7 # get from {{ device_id('switch.kids_room') }}
condition: []
action:
- service: script.turn_on
target:
entity_id: script.handle_light_switch
data:
variables:
scene_id: '{{ trigger.event.data.label }}'
attribute_id: '{{ trigger.event.data.value_raw }}'
light_entity_id: light.kids_room
scene_entity_id: input_select.kids_light_scenes
mode: restart
max_exceeded: silent
It seems pretty solid (and doesn’t continually send commands to the light).
What was hard:
- I’m firing off the scripts with
script.turn_on
. Otherwise, I think themode: restart
stops the script called from the automation otherwise and then the wait-for-event never got the event. - This was my first serious use of YAML and I’m having trouble with both the YAML and JINJA syntax.
- I was surprised by operator precedence in JINJA.
3 / 2 | int
is3 / int(2)
notint(3 / 2)
. - I really wish there was a “schema” reference for YAML
- I think I’m mis-indenting stuff, but it seems touchy.
- I was surprised by operator precedence in JINJA.
Anyway, I thought I’d share in case it helps anyone else. And, I’m curious if I’m doing anything the hard way.
Thanks,
Sean