LZW45 Custom Effects Script for Home Assistant

Link to Home Assistant forum post.

This script creates a custom effect.

Zwave 1.4 & Open Zwave

Script

lzw45_custom_effect:
  alias: LZW45 Custom Effect
  description: Sets the custom effects for the Inovelli LZW45 LED Strip
  mode: restart
  fields:
    service: 
      description:
        (required) The name of the service. 
          OpenZwave (Beta) -> 'ozw.set_config_parameter'
          OpenZwave (1.4) -> 'zwave.set_config_parameter'
          Zwave JS -> 'zwave_js.set_config_parameter'
      example: ozw.set_config_parameter
    lzw45: 
      description: (required) The entity_id for the lzw45 LED Strip.
      example: light.lzw45_light_strip_level
    colors:
      description: (required) A list of colors, (1 to 4 colors).  Color choices are off, 2700k, 4500k, 6500k, red, orange, yellow, yellow-green, green, spring-green, cyan, azure, blue, violet, magenta, and random.
      example: "[ 'red', 'orange', 'yellow', 'off' ]"
    effects:
      description: (required) A list of colors (1 to 4 effects).  Color choices are fade, fade-blend, flash, chase, and chase-blend.
      example: "[ 'fade', 'fade-blend', 'fade', 'chase' ]"
    brightness_pcts:
      description: (optional) A list of brightness percents, 0 - 99.  Defaults to 99 if omitted.
      example: "[ 99, 99, 99, 99 ]"
    duration_units:
      description: (required) The units of the duration.  Choices are 100ms, seconds, minutes, hours.
      example: 100ms
    durations:
      description: (required) A list of durations (1-60).
      example: "[ 10, 10, 10, 10 ]"
    finish_behavior:
      description: (optional) The behavior when the effect reaches the max number of iterations.  Choices are off, previous-color, last-color-in-program.  Default is previous-color.
      example: previous-color
    iterations:
      description: (optional) The number of times the custom effect repeats (1-254).  255 is forever.  Default is 255.
      example: 255
  variables:
    service: >
      {{ service | default('ozw.set_config_parameter') }}
    node_id: >
      {%- if lzw45 is not defined %}
        0
      {%- else %}
        {%- set node_id = state_attr(lzw45, 'node_id') %}
        {{ node_id if node_id else 0 }}
      {%- endif %}
    transitions: >
      {%- set ns = namespace(counts=[]) %}
      {%- for item in [ colors, effects, brightness_pcts, durations ] %}
        {%- set ns.counts = ns.counts + [ item | length ] %}
      {%- endfor %}
      {{ ns.counts | min }}
    levels: >
      {%- set levels = brightness_pcts | default([]) %}
      {%- if not levels %}
        {{ [ 99 ] * transitions }}
      {%- else %}
        {%- set ns = namespace(levels = []) %}
        {%- for i in range(transitions) %}
          {%- set pct = levels[i] | int %}
          {%- set pct = pct if pct >= 0 else 0 %}
          {%- set pct = pct if pct <= 99 else 99 %}
          {%- set ns.levels = ns.levels + [ pct ] %}
        {%- endfor %}
        {{ ns.levels }}
      {%- endif %}
    parameter22: >
      {%- macro byte(color, effect) %}
        {%- set colors = [
          'off',
          '2700k',
          '4500k',
          '6500k',
          'red',
          'orange',
          'yellow',
          'yellow green',
          'green',
          'spring green',
          'cyan',
          'azure',
          'blue',
          'violet',
          'magenta',
          'random'
        ] %}
        {% set effects = [
          'fade', 
          'fade-blend', 
          'flash', 
          'chase', 
          'chase-blend'
        ] %}
        {%- if color in colors and effect in effects %}
          {%- set ci = colors.index(color) %}
          {%- set ei = effects.index(effect) %}
          {{- '{0:05b}{1:03b}'.format(ci, ei) }}
        {%- else %}
          {{- '00000000' }}
        {%- endif %}
      {%- endmacro %}
      {%- set ns = namespace(bytes=[]) %}
      {%- for i in range(transitions) %}
        {%- set ns.bytes = ns.bytes + [ byte(colors[i], effects[i]) | int('', 2) * 2**(i * 8) ] %}
      {%- endfor %}
      {{ ns.bytes | sum }}
    parameter23: >
      {%- set ns = namespace(bytes=[]) %}
      {%- for i in range(transitions) %}
        {%- set ns.bytes = ns.bytes + [ '{0:08b}'.format(levels[i]) | int('', 2) * 2**(i * 8) ] %}
      {%- endfor %}
      {{ ns.bytes | sum }}
    parameter24: >
      {%- set ns = namespace(bytes=[]) %}
      {%- for i in range(transitions) %}
        {%- set duration = durations[i] | int %}
        {%- set duration = duration if duration >= 1 else 1 %}
        {%- set duration = duration if duration <= 60 else 60 %}
        {%- set ns.bytes = ns.bytes + [ '{0:08b}'.format(duration) | int('', 2) * 2**(i * 8) ] %}
      {%- endfor %}
      {{ ns.bytes | sum }}
    parameter30: >
      {%- set iterations = iterations | default(255) | int %}
      {%- set iterations = iterations if iterations >= 0 else 0 %}
      {%- set byte1 = iterations if iterations <= 255 else 255 %}
      {%- set behaviors = [ 'off', 'previous-color', 'last-color-in-program' ] %}
      {%- set units = ['100ms', 'seconds', 'minutes', 'hours'] %}
      {%- set byte2 = behaviors.index(behavior) if behavior in behaviors else 1 %}
      {%- set byte3 = units.index(duration_units) if duration_units in units else 0 %}
      {{ '{0:08b}{1:08b}{2:08b}'.format(byte3, byte2, byte1) | int('', 2) }}
  sequence:
  - condition: template
    value_template: "{{ node_id != 0 and transitions > 0 }}"
  - service: "{{ service }}"
    data:
      node_id: "{{ node_id }}"
      parameter: 22
      value: "{{ parameter22 }}"
  - service: "{{ service }}"
    data:
      node_id: "{{ node_id }}"
      parameter: 23
      value: "{{ parameter23 }}"
  - service: "{{ service }}"
    data:
      node_id: "{{ node_id }}"
      parameter: 24
      value: "{{ parameter24 }}"
  - service: "{{ service }}"
    data:
      node_id: "{{ node_id }}"
      parameter: 30
      value: "{{ parameter30 }}"

Service

service: script.lzw45_custom_effect
data:
  service: ozw.set_config_parameter
  lzw45: light.lzw45_light_strip_level
  colors:
    - red
    - orange
    - yellow
    - orange
  effects:
    - fade-blend
    - fade-blend
    - fade-blend
    - fade-blend
  brightness_pcts:
    - 99
    - 99
    - 99
    - 99
  duration_units: 100ms
  durations:
    - 10
    - 10
    - 10
    - 10
  finish_behavior: previous-color
  iterations: 255

ZwaveJS

:warning:

Currently there is a bug (Home Assistant side) with parameter 23 that is under investigation. This will error out on param 23!

EDIT: The investigation found an issue with the config file in my system, it should work for everyone else. If it does not, there may be an issue with caching in ZwaveJS2MQTT. Please report any issues that you find.

:warning:

Script

lzw45_custom_effect_zwave_js:
  alias: LZW45 Custom Effect
  description: Sets the custom effects for the Inovelli LZW45 LED Strip
  mode: restart
  fields:
    lzw45: 
      description: (required) The entity_id for the lzw45 LED Strip.
      example: light.lzw45_light_strip_level
    colors:
      description: (required) A list of colors, (1 to 4 colors).  Color choices are off, 2700k, 4500k, 6500k, red, orange, yellow, yellow-green, green, spring-green, cyan, azure, blue, violet, magenta, and random.
      example: "[ 'red', 'orange', 'yellow', 'off' ]"
    effects:
      description: (required) A list of colors (1 to 4 effects).  Color choices are fade, fade-blend, flash, chase, and chase-blend.
      example: "[ 'fade', 'fade-blend', 'fade', 'chase' ]"
    brightness_pcts:
      description: (optional) A list of brightness percents, 0 - 99.  Defaults to 99 if omitted.
      example: "[ 99, 99, 99, 99 ]"
    duration_units:
      description: (required) The units of the duration.  Choices are 100ms, seconds, minutes, hours.
      example: 100ms
    durations:
      description: (required) A list of durations (1-60).
      example: "[ 10, 10, 10, 10 ]"
    finish_behavior:
      description: (optional) The behavior when the effect reaches the max number of iterations.  Choices are off, previous-color, last-color-in-program.  Default is previous-color.
      example: previous-color
    iterations:
      description: (optional) The number of times the custom effect repeats (1-254).  255 is forever.  Default is 255.
      example: 255
  variables:
    service: >
      {{ service | default('ozw.set_config_parameter') }}
    node_id: >
      {%- if lzw45 is not defined and node is not defined %}
        0
      {%- else %}
        {%- if node is not defined %}
          {%- set node_id = state_attr(lzw45, 'node_id') %}
        {%- else %}
          {%- set node_id = node %}
        {%- endif %}
        {{ node_id if node_id else 0 }}
      {%- endif %}
    transitions: >
      {%- set ns = namespace(counts=[]) %}
      {%- for item in [ colors, effects, brightness_pcts, durations ] %}
        {%- set ns.counts = ns.counts + [ item | length ] %}
      {%- endfor %}
      {{ ns.counts | min }}
    levels: >
      {%- set levels = brightness_pcts | default([]) %}
      {%- if not levels %}
        {{ [ 99 ] * transitions }}
      {%- else %}
        {%- set ns = namespace(levels = []) %}
        {%- for i in range(transitions) %}
          {%- set pct = levels[i] | int %}
          {%- set pct = pct if pct >= 0 else 0 %}
          {%- set pct = pct if pct <= 99 else 99 %}
          {%- set ns.levels = ns.levels + [ pct ] %}
        {%- endfor %}
        {{ ns.levels }}
      {%- endif %}
    parameter22: >
      {%- macro byte(color, effect) %}
        {%- set colors = [
          'off',
          '2700k',
          '4500k',
          '6500k',
          'red',
          'orange',
          'yellow',
          'yellow green',
          'green',
          'spring green',
          'cyan',
          'azure',
          'blue',
          'violet',
          'magenta',
          'random'
        ] %}
        {% set effects = [
          'fade', 
          'fade-blend', 
          'flash', 
          'chase', 
          'chase-blend'
        ] %}
        {%- if color in colors and effect in effects %}
          {%- set ci = colors.index(color) %}
          {%- set ei = effects.index(effect) %}
          {{- '{0:05b}{1:03b}'.format(ci, ei) }}
        {%- else %}
          {{- '00000000' }}
        {%- endif %}
      {%- endmacro %}
      {%- set ns = namespace(bytes=[]) %}
      {%- for i in range(transitions) %}
        {%- set ns.bytes = ns.bytes + [ byte(colors[i], effects[i]) | int('', 2) * 2**(i * 8) ] %}
      {%- endfor %}
      {{ ns.bytes | sum }}
    parameter23: >
      {%- set ns = namespace(bytes=[]) %}
      {%- for i in range(transitions) %}
        {%- set ns.bytes = ns.bytes + [ '{0:08b}'.format(levels[i]) | int('', 2) * 2**(i * 8) ] %}
      {%- endfor %}
      {{ ns.bytes | sum }}
    parameter24: >
      {%- set ns = namespace(bytes=[]) %}
      {%- for i in range(transitions) %}
        {%- set duration = durations[i] | int %}
        {%- set duration = duration if duration >= 1 else 1 %}
        {%- set duration = duration if duration <= 60 else 60 %}
        {%- set ns.bytes = ns.bytes + [ '{0:08b}'.format(duration) | int('', 2) * 2**(i * 8) ] %}
      {%- endfor %}
      {{ ns.bytes | sum }}
    parameter30: >
      {%- set iterations = iterations | default(255) | int %}
      {%- set iterations = iterations if iterations >= 0 else 0 %}
      {%- set byte1 = iterations if iterations <= 255 else 255 %}
      {%- set behaviors = [ 'off', 'previous-color', 'last-color-in-program' ] %}
      {%- set units = ['100ms', 'seconds', 'minutes', 'hours'] %}
      {%- set byte2 = behaviors.index(behavior) if behavior in behaviors else 1 %}
      {%- set byte3 = units.index(duration_units) if duration_units in units else 0 %}
      {{ '{0:08b}{1:08b}{2:08b}'.format(byte3, byte2, byte1) | int('', 2) }}
  sequence:

  - condition: template
    value_template: "{{ lzw45 is defined and transitions > 0 }}"

  - service: zwave_js.bulk_set_partial_config_parameters
    target:
      entity_id: "{{ lzw45 }}"
    data:
      parameter: 22
      value: "{{ parameter22 }}"

  - service: zwave_js.bulk_set_partial_config_parameters
    target:
      entity_id: "{{ lzw45 }}"
    data:
      parameter: 23
      value: "{{ parameter23 }}"

  - service: zwave_js.bulk_set_partial_config_parameters
    target:
      entity_id: "{{ lzw45 }}"
    data:
      parameter: 24
      value: "{{ parameter24 }}"

  - service: zwave_js.bulk_set_partial_config_parameters
    target:
      entity_id: "{{ lzw45 }}"
    data:
      parameter: 30
      value: "{{ parameter30 }}"

Service

service: script.lzw45_custom_effect
data:
  lzw45: light.lzw45_light_strip_level
  colors:
    - red
    - orange
    - yellow
    - orange
  effects:
    - fade-blend
    - fade-blend
    - fade-blend
    - fade-blend
  brightness_pcts:
    - 99
    - 99
    - 99
    - 99
  duration_units: 100ms
  durations:
    - 10
    - 10
    - 10
    - 10
  finish_behavior: previous-color
  iterations: 255
1 Like

Go chiefs. Obligatory at this point (I’m from/live in KC).

Hah, that didn’t cross my mind… Just the colors I chose. I’m a Bills fan :wink:

1 Like

perto, your contributions the last couple days have made this 10x nicer to use with homeassistant. Really appreciate you sharing, hope to see this stuff make it into the official docs

1 Like

Thanks for sharing @petro
Just to confirm this wouldn’t work with zwave_js right? Since we can’t set config parameters via websocket yet?
I haven’t been able to get any of your scripts to work

The scripts won’t work until the set_config_param service is added. Next major release, 2021.3

1 Like

I may need to update the script too, if they change the name of the service.

1 Like

Looking at the latest beta release notes it looks like zwavejs should get two new services :

zwave_js.bulk_set_partial_config_parameters
zwave_js.set_value

Will this be enough to get the strip working fully with zwavejs and the native HA services? I remember reading in one of your threads either here or on HA forums that the bulk parameters was a problem.

Yeah, it’ll finally give us full functionality of the light strip with the release of the zwave_js.bulk_set_partial_config_parameters service :slight_smile: Hoping the newest beta update comes out soon, if not the full release. Apparently there’s a bug in each of the beta’s so far which make this service not work consistently. Really want to play around with it soon.

Yes, I plan on updating all these scripts after testing is complete on my end. These scripts currently work with OpenZwave, I’ll most likely update these or create new scripts for ZwaveJS.

1 Like

I have my LZW45 strips exposed to my HA via Hubitat. Is it possible to still use this script or would I need to remove my strips from hubitat and add them directly to my HA?

I’d wager that these do not work with the habitat integration. It really depends on how they were implemented in that integration.