ZWave Switch to Dim Smart Lights

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 :wink:.) 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 the mode: 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 is 3 / int(2) not int(3 / 2).
    • I really wish there was a “schema” reference for YAML
    • I think I’m mis-indenting stuff, but it seems touchy.

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

2 Likes

Thanks for the shoutout! After seeing some of the responses in that thread, I was beginning to wonder if that was the case. I’m guessing both the value and value_raw return the integer/string corresponding to the scene value? (ie, 00 for both, vs my bp that is based on value = KeyPressed)

Right. I don’t have a good explanation. Both switches were configured the same, but one sent event.data.value as 0, 1, 2,…,5 while the other sent KeyPressed, etc. Fortunately, it was easy to use event.data.value_raw instead, even if the symbolic name is more readable.

Even more strange: I did a factory reset on the switch sending the numbers and it started sending the “correct” value. Nonetheless, it seems safer to just use event.data.value_raw.

Anyway, your blueprint was a huge help. Thank you!

Very true. Their documentation is all based on the value_raw numerical values, but aside from a few occurrences like yours, they have always used the KeyPressed, KeyHeldDown, and KeyReleased as far as I know. It’s interesting that the behavior reverted back to the symbolic naming convention on reset. I might have to go back and add an or statement in my BPs to include both the value and raw_value as trigger events just to be on the safe side.

Sorry to revive an old thread, but I’m hoping someone can ELI5 on how to set this up? I recently replaced some “dumb” LED pucks that were controlled by a Zooz dimmer with Hue ones. I’ve got the Zooz in smart bulb mode now and it’s working to turn the lights on and off via the Blueprints mentioned above. But I’d love to be able to control dimming with the Zooz switch too. I mostly understand everything above, but I’m just not sure what does what or where they’re supposed to go (script vs. automation).

Any help would be greatly appreciated!