Zwave JS Multicast Area Lights & Switches

clear your cache. Compare the script to what’s included here, i’ve been making changes. Lastly, reload the script.

I cleared the caches and restarted HA host. I copied the whole script to scripts.yaml and added my configs from above.

Still the same. Script now visible in frontend, no traces.

If I add another light to that group it also works to switch on if all are off…

  wohnzimmer:
    entities:
      - light.led_sofa
      - light.led_esstisch
      - light.led_test

Could this config disable the variable traces?

config:
counter:
frontend:
history:
image:
input_boolean:
input_button:
input_datetime:
input_number:
input_select:
input_text:
logbook:
person:
stream:
sun:
timer:
zone:
cloud:
my:

recorder:
  purge_keep_days: 365
  auto_purge: false
  include:
    domains:
      - automation
      - sensor
      - light
      - fan
      - cover
      - binary_sensor
      - input_boolean
      - climate
      - person
      - vacuum
  exclude:
    entity_globs:
      - sensor.*timestamp*
    entities:
      - sensor.uptime
      

Also, just to be sure:
It is okay to have the lights in more than one group, right?

Update:
I can’t switch on a group where the two lights are on and the third one is off.
I can only switch off all of them if all of them are on.

and that doesn’t make sense. I can’t help if you can’t provide the trace. I designed this to specifically work in that case and it works like that for me. Just tested it. I have no idea what you’re doing. Post exactly all the yaml you have, or ensure you copy exactly what I wrote and you didn’t edit it.

thanks @petro! this is working for me now for dimmers and switches (I don’t have any covers).

I made a change to brightness_pct variable. My zooz dimmers show brightness range of 0-99 (not 255) in Z2M. I left out the conversion in brightness_pct and dimming is working as expected now

variables:
    brightness_pct: >
      {%- set brightness_pct = (level | int(0)) | int %}
      {%- set brightness_pct = [ 0, brightness_pct ] | max %}
      {%- set brightness_pct = [ 99, brightness_pct] | min %}
      {{- brightness_pct }}

I just realized that the new devices are connected using S2. :man_facepalming:
So it shouldn’t work at all, right?

Situation for switching off is like that:

  • fails when both are on
  • works when only one is on

Multicast does not work at all with S2, I believe it’s in the ZwaveJS roadmap.

2 Likes

Good to know, well it works a little bit. Might be a good sign.

@petro

Apologies for reviving this old thread, but I just discovered your fantastic script. I previously created separate scripts for each of my zwave cover groups, hardcoding device ids into each script, with separate scripts for open, close, stop, and set position.

I’m trying to replace those numerous scripts with your single script, and to create template covers to interact with them. I’ve had luck editing the template light generated by your shortcut above, replacing values from the light domain with values from the cover domain–open, close, and reported state all work-- but am stuck on what to pass to the script for the set_cover_position (the very last line of the YAML below):

    shades_master_bedroom:
      unique_id: shades_master_bedroom
      friendly_name: Shades Master Bedroom
      value_template: >
        {{ is_state('group.shades_master_bedroom', 'open') }}
      position_template: >
        {%- set entities = expand('group.shades_master_bedroom') %}
        {%- set covers = entities | selectattr('domain', 'eq', 'cover') | selectattr('state', 'eq', 'open') %}
        {%- set levels = covers | map(attribute='attributes.current_position') | map('int') | list %}
        {{- levels | average if levels else 0 }}
      availability_template: >
        {{- expand('group.shades_master_bedroom') | selectattr('state','in',['unavailable','unknown']) | list | length == 0 }}
      open_cover:
      - service: script.zwave_multicast_group
        data:
          group: group.shades_master_bedroom
          level: 255
      close_cover:
      - service: script.zwave_multicast_group
        data:
          group: group.shades_master_bedroom
          level: 0
      set_cover_position:
      - service: script.zwave_multicast_group
        data:
          group: group.shades_master_bedroom
          level: >
            {{- brightness_pct }}

I’ve tried brightness_pct, value, level, and everything I can think of after evaluating your script, but nothing works (any value I pass when interacting with the template cover just results in the shades closing all the way). This service call, however, works fine:

service: script.zwave_multicast_group
data:
  group: group.shades_master_bedroom
  level: 10

with levels from 0 to 255

Any guidance on what I should put in the last line of the template to get it to accept a position value?

Also, for reference, here are my four scripts to accomplish for a single group what yours does in one. The only functionality I would lose moving to your script is the “stop” function. Any way to integrate that function into your script?

multicast_shades_master_close:
  alias: Multicast Shades - Master Close
  sequence:
  - service: zwave_js.multicast_set_value
    data:
      command_class: '38'
      property: targetValue
      value: 0
    target:
      device_id:
      - 3a9b47282ac5b1a82b01dc4af7032a48
      - b3e769c15948158541755297d1f258bb
      - 0266da4a6eb09bc94f94ee77f2a0871f
      - 672be298a3d3870612f891cefa6bb744
      - 2decb1288aeec569e7c955c86ebf6667
  mode: single
  icon: mdi:window-shutter
multicast_shades_master_open:
  alias: Multicast Shades - Master Open
  sequence:
  - service: zwave_js.multicast_set_value
    data:
      command_class: '38'
      property: targetValue
      value: 99
    target:
      device_id:
      - 3a9b47282ac5b1a82b01dc4af7032a48
      - b3e769c15948158541755297d1f258bb
      - 0266da4a6eb09bc94f94ee77f2a0871f
      - 672be298a3d3870612f891cefa6bb744
      - 2decb1288aeec569e7c955c86ebf6667
  mode: single
  icon: mdi:window-shutter-open
multicast_shades_master_stop:
  alias: Multicast Shades - Master Stop
  sequence:
  - service: zwave_js.multicast_set_value
    data:
      command_class: '38'
      property: Up
      value: false
    target:
      device_id:
      - 3a9b47282ac5b1a82b01dc4af7032a48
      - b3e769c15948158541755297d1f258bb
      - 0266da4a6eb09bc94f94ee77f2a0871f
      - 672be298a3d3870612f891cefa6bb744
      - 2decb1288aeec569e7c955c86ebf6667
  mode: single
  icon: mdi:window-shutter-alert
multicast_shades_master_set:
  alias: Multicast Shades - Master Set
  fields:
    position:
      description: Target position
  sequence:
  - service: zwave_js.multicast_set_value
    data:
      command_class: '38'
      property: targetValue
      value: '{{ position }}'
    target:
      device_id:
      - 3a9b47282ac5b1a82b01dc4af7032a48
      - b3e769c15948158541755297d1f258bb
      - 0266da4a6eb09bc94f94ee77f2a0871f
      - 672be298a3d3870612f891cefa6bb744
      - 2decb1288aeec569e7c955c86ebf6667
  mode: single
  icon: mdi:window-shutter-cog

the variable for cover templates is position and you’d have to convert that from 0 to 100 to 0 to 255.

I’m not really sure if this will work with covers as I don’t own any zwave ones myself. So the rest of the script will be very hard for me to debug for you.

1 Like

Thank you! position worked, just have to figure out the conversion now, and see if I can’t tinker up a “stop” action.

Should just be {{ position * 255/100 }}

or {{ position * 2.55 }}

whatever floats your boat

Actually, I had success changing the script setting to use level instead of brightness_pct for the cover class:

      cover:
        command_class: 38
        property: targetValue
        endpoint: 0
        value: "{{ level }}"

Doing so changes the behavior of the service call to accept values only from 0 to 99:

service: script.zwave_multicast_group
data:
  group: group.shades_master_bedroom
  level: 99

Would it be better to do it the way you post (in the template device) rather than with a change to the script?

Doesn’t really matter to be honest. If you’re just using it for a group of covers, then have it as a separate script.

I do plan to update/fix this at some point. Waiting for S2 multicast. I also think there’s probably room for improvement in the script.

Well it’s working great with just changing your script to specify level for the settings of cover

When you get around to updating the script for S2, I’ll be happy to debug for covers – although I don’t have any that support the tilt commands. FYI, the stop_cover configuration variable for a template cover works well for the zwave commands if you pass a value of false and a property of Up in the data of command_class: '38' rather than a number value … just in case there’s a way to work that into the update. :slight_smile:

  - service: zwave_js.multicast_set_value
    data:
      command_class: '38'
      property: Up
      value: false

Because I can’t leave well enough alone, I updated the script for template covers with a new position variable:

zwave_multicast_group:
  mode: parallel
  alias: Multicast to Zwave Group
  fields:
    group:
      description: (Required) The group of lights & switches
      example: group.bonus_room_area
    level:
      description: (Optional) The brightness level, switches will be on if the level is greater than 0.
      example: 99
  variables:
    brightness_pct: >
      {%- set brightness_pct = (level | int(0) / 255 * 100) | int %}
      {%- set brightness_pct = [ 0, brightness_pct ] | max %}
      {%- set brightness_pct = [ 99, brightness_pct] | min %}
      {{- brightness_pct }}
    position: >
      {%- set position = (level) | int %}
      {%- set position = [ 0, position ] | max %}
      {%- set position = [ 99, position] | min %}
      {{- position }}      
    value: >
      {%- set value = brightness_pct > 0 %}
      {{- value }}
    settings:
      light:
        command_class: 38
        property: targetValue
        endpoint: 0
        value: "{{ brightness_pct }}"
      switch:
        command_class: 37
        property: targetValue
        endpoint: 0
        value: "{{ value }}"
      cover:
        command_class: 38
        property: targetValue
        endpoint: 0
        value: "{{ position }}"
    lights: >
      {% if value %}
        {% set off_lights = expand(group) 
          | selectattr('domain', 'eq', 'light')
          | selectattr('state', 'eq', 'off')
          | map(attribute='entity_id') | list %}
        {% set on_lights = expand(group) 
          | selectattr('domain', 'eq', 'light')
          | selectattr('state', 'eq', 'on')
          | selectattr('attributes.brightness', 'defined') 
          | rejectattr('attributes.brightness','eq', level)
          | map(attribute='entity_id') | list %}
        {{ (off_lights + on_lights) or none }}
      {% else %}
        {{ expand(group) 
          | selectattr('domain', 'eq', 'light')
          | selectattr('state', 'eq', 'on')
          | map(attribute='entity_id') | list or none }}
      {% endif %}
    covers: >
      {% if value %}
        {% set off_lights = expand(group) 
          | selectattr('domain', 'eq', 'cover')
          | selectattr('state', 'eq', 'closed')
          | map(attribute='entity_id') | list %}
        {% set on_lights = expand(group) 
          | selectattr('domain', 'eq', 'cover')
          | selectattr('state', 'eq', 'open')
          | selectattr('attributes.current_position', 'defined') 
          | rejectattr('attributes.current_position','eq', position)
          | map(attribute='entity_id') | list %}
        {{ (off_lights + on_lights) or none }}
      {% else %}
        {{ expand(group) 
          | selectattr('domain', 'eq', 'cover')
          | selectattr('state', 'eq', 'open')
          | map(attribute='entity_id') | list or none }}
      {% endif %}
    switches: >
      {%- set switches = expand(group) | selectattr('domain', 'eq', 'switch') %}
      {%- set switches = switches | selectattr('state','eq', 'off' if value else 'on') %}
      {%- set switches = switches | map(attribute='entity_id') | list %}
      {{- switches or none }}
    items: >
      {%- set ns = namespace(items={}, spool={}) %}
      {%- set fmat = "('{0}': {1})" %}
      {%- set items = (switches or []) + (lights or []) + (covers or []) %}
      {%- for item in items %}
        {%- set state_obj = expand(item) | first | default(none) %}
        {%- if state_obj and state_obj.domain in ['light','switch','cover'] %}
          {%- set domain = state_obj.domain %}
          {%- set entity_id = state_obj.entity_id %}
          {%- set entity_ids = lights if domain == 'light' else switches %}
          {%- set entity_ids = covers if domain == 'cover' else entity_ids %}
          {%- set current = settings[domain] %}
          {%- set current = dict(current, **settings[entity_id]) if entity_id in settings else current %}
          {%- set key = domain ~ '_' ~ current.items() | list | string | lower | regex_findall('[a-z0-9_]+') | join('_') %}
          {%- if key in ns.spool %}
            {%- set ns.spool = dict(ns.spool, **{key:ns.spool[key] + [entity_id]}) %}
          {%- else %}
            {%- set ns.spool = dict(ns.spool, **{key:[entity_id]}) %}
          {%- endif %}
          {%- set entity_ids = ns.spool[key] %}
          {%- set current = dict(domain=domain, **current) %}
          {%- set current = dict(current, entity_id=entity_ids) %}
          {%- set ns.items = dict(ns.items, **{key:current | string}) %}
        {%- endif %}
      {%- endfor %}
      [{{ ns.items.values() | unique | sort | list | join(', ') }}]
    execute: >
      {{ items is not none or items != [] }}
    total: >
      {{ items | length if execute else 0 }}
  sequence:
  - condition: template
    value_template: "{{ execute }}"
  - repeat:
      count: "{{ total }}"
      sequence:
      - service: zwave_js.multicast_set_value
        target:
          entity_id: "{{ items[repeat.index - 1].entity_id }}"
        data:
          command_class: "{{ items[repeat.index - 1].command_class }}"
          property: "{{ items[repeat.index - 1].property }}"
          endpoint: "{{ items[repeat.index - 1].endpoint }}"
          value: "{{ items[repeat.index - 1].value }}"

Everything seems to work as expected with a template cover that outputs “position” for its variable:

    shades_master_bedroom:
      unique_id: shades_master_bedroom
      friendly_name: Shades Master Bedroom
      value_template: >
        {{ is_state('group.shades_master_bedroom', 'open') }}
      position_template: >
        {%- set entities = expand('group.shades_master_bedroom') %}
        {%- set covers = entities | selectattr('domain', 'eq', 'cover') | selectattr('state', 'eq', 'open') %}
        {%- set levels = covers | map(attribute='attributes.current_position') | map('int') | list %}
        {{- levels | average if levels else 0 }}
      availability_template: >
        {{- expand('group.shades_master_bedroom') | selectattr('state','in',['unavailable','unknown']) | list | length == 0 }}
      open_cover:
      - service: script.zwave_multicast_group
        data:
          group: group.shades_master_bedroom
          level: 255
      close_cover:
      - service: script.zwave_multicast_group
        data:
          group: group.shades_master_bedroom
          level: 0
      set_cover_position:
      - service: script.zwave_multicast_group
        data:
          group: group.shades_master_bedroom
          level: >
            {{- position }}

I’ll take a crack at updating your shortcut template generator to do this for cover groups and post if I can get it working.

EDITED to update error in script (position doesn’t need to scale to 255, max targetValue sent by zwave for covers is 99)

Here’s the shortcut to paste into the template area of the developer tools for a group consisting of cover entities:

{% set entities = 'group.shades_master_bedroom' %}
{% set groups = states.group | selectattr('entity_id', 'in', entities) | list %}
{% set fmat = """
    #################################
    ### {1:^25} ###
    #################################

    {0}:
      unique_id: {0}
      friendly_name: {1}
      value_template: >
        {{{{ is_state('group.{0}', 'open') }}}}
      position_template: >
        {{%- set entities = expand('group.{0}') %}}
        {{%- set covers = entities | selectattr('domain', 'eq', 'cover') | selectattr('state', 'eq', 'open') %}}
        {{%- set levels = covers | map(attribute='attributes.current_position') | map('int') | list %}}
        {{{{- levels | average if levels else 0 }}}}
      availability_template: >
        {{{{- expand('group.{0}') | selectattr('state','in',['unavailable','unknown']) | list | length == 0 }}}}
      open_cover:
      - service: script.zwave_multicast_group
        data:
          group: group.{0}
          level: 99
      close_cover:
      - service: script.zwave_multicast_group
        data:
          group: group.{0}
          level: 0
      set_cover_position:
      - service: script.zwave_multicast_group
        data:
          group: group.{0}
          level: >
            {{{{- position }}}}
""" %}
- platform: template
  covers:
{%- for group in groups %}
{{ fmat.format(group.object_id, group.name.replace('_',' ').title()) }}
{%- endfor %}

It generates the template code that’s working for me with the updated script. Hope it helps others!

[[EDITED to change max value to 99 from 100]]

Does something need to change with the script now? I can’t seem to save it.

Here is the error:

Message malformed: extra keys not allowed @ data['zwave_multicast_group']
Script
zwave_multicast_group:
  mode: parallel
  alias: Multicast to Zwave Group
  fields:
    group:
      description: (Required) The group of lights & switches
      example: group.all_inside_zwave
    level:
      description: >-
        (Optional) The brightness level, switches will be on if the level is
        greater than 0.
      example: 99
  variables:
    brightness_pct: >
      {%- set brightness_pct = (level | int(0) / 255 * 100) | int %} {%- set
      brightness_pct = [ 0, brightness_pct ] | max %} {%- set brightness_pct = [
      99, brightness_pct] | min %} {{- brightness_pct }}
    value: |
      {%- set value = brightness_pct > 0 %} {{- value }}
    settings:
      light:
        command_class: 38
        property: targetValue
        endpoint: 0
        value: "{{ brightness_pct }}"
      switch:
        command_class: 37
        property: targetValue
        endpoint: 0
        value: "{{ value }}"
      cover:
        command_class: 38
        property: targetValue
        endpoint: 0
        value: "{{ brightness_pct }}"
    lights: |
      {% if value %}
        {% set off_lights = expand(group) 
          | selectattr('domain', 'eq', 'light')
          | selectattr('state', 'eq', 'off')
          | map(attribute='entity_id') | list %}
        {% set on_lights = expand(group) 
          | selectattr('domain', 'eq', 'light')
          | selectattr('state', 'eq', 'on')
          | selectattr('attributes.brightness', 'defined') 
          | rejectattr('attributes.brightness','eq', level)
          | map(attribute='entity_id') | list %}
        {{ (off_lights + on_lights) or none }}
      {% else %}
        {{ expand(group) 
          | selectattr('domain', 'eq', 'light')
          | selectattr('state', 'eq', 'on')
          | map(attribute='entity_id') | list or none }}
      {% endif %}
    covers: |
      {% if value %}
        {% set off_lights = expand(group) 
          | selectattr('domain', 'eq', 'cover')
          | selectattr('state', 'eq', 'closed')
          | map(attribute='entity_id') | list %}
        {% set on_lights = expand(group) 
          | selectattr('domain', 'eq', 'cover')
          | selectattr('state', 'eq', 'open')
          | selectattr('attributes.current_position', 'defined') 
          | rejectattr('attributes.current_position','eq', brightness_pct)
          | map(attribute='entity_id') | list %}
        {{ (off_lights + on_lights) or none }}
      {% else %}
        {{ expand(group) 
          | selectattr('domain', 'eq', 'cover')
          | selectattr('state', 'eq', 'open')
          | map(attribute='entity_id') | list or none }}
      {% endif %}
    switches: >
      {%- set switches = expand(group) | selectattr('domain', 'eq', 'switch') %}
      {%- set switches = switches | selectattr('state','eq', 'off' if value else
      'on') %} {%- set switches = switches | map(attribute='entity_id') | list
      %} {{- switches or none }}
    items: >
      {%- set ns = namespace(items={}, spool={}) %} {%- set fmat = "('{0}':
      {1})" %} {%- set items = (switches or []) + (lights or []) + (covers or
      []) %} {%- for item in items %}
        {%- set state_obj = expand(item) | first | default(none) %}
        {%- if state_obj and state_obj.domain in ['light','switch','cover'] %}
          {%- set domain = state_obj.domain %}
          {%- set entity_id = state_obj.entity_id %}
          {%- set entity_ids = lights if domain == 'light' else switches %}
          {%- set entity_ids = covers if domain == 'cover' else entity_ids %}
          {%- set current = settings[domain] %}
          {%- set current = dict(current, **settings[entity_id]) if entity_id in settings else current %}
          {%- set key = domain ~ '_' ~ current.items() | list | string | lower | regex_findall('[a-z0-9_]+') | join('_') %}
          {%- if key in ns.spool %}
            {%- set ns.spool = dict(ns.spool, **{key:ns.spool[key] + [entity_id]}) %}
          {%- else %}
            {%- set ns.spool = dict(ns.spool, **{key:[entity_id]}) %}
          {%- endif %}
          {%- set entity_ids = ns.spool[key] %}
          {%- set current = dict(domain=domain, **current) %}
          {%- set current = dict(current, entity_id=entity_ids) %}
          {%- set ns.items = dict(ns.items, **{key:current | string}) %}
        {%- endif %}
      {%- endfor %} [{{ ns.items.values() | unique | sort | list | join(', ')
      }}]
    execute: |
      {{ items is not none or items != [] }}
    total: |
      {{ items | length if execute else 0 }}
  sequence:
    - condition: template
      value_template: "{{ execute }}"
    - repeat:
        count: "{{ total }}"
        sequence:
          - service: zwave_js.multicast_set_value
            target:
              entity_id: "{{ items[repeat.index - 1].entity_id }}"
            data:
              command_class: "{{ items[repeat.index - 1].command_class }}"
              property: "{{ items[repeat.index - 1].property }}"
              endpoint: "{{ items[repeat.index - 1].endpoint }}"
              value: "{{ items[repeat.index - 1].value }}"

The indentation in your script is wrong. Use this:

    - repeat:
        count: "{{ total }}"
        sequence:
        - service: zwave_js.multicast_set_value
          target:
            entity_id: "{{ items[repeat.index - 1].entity_id }}"
          data:
            command_class: "{{ items[repeat.index - 1].command_class }}"
            property: "{{ items[repeat.index - 1].property }}"
            endpoint: "{{ items[repeat.index - 1].endpoint }}"
            value: "{{ items[repeat.index - 1].value }}"