Zwave JS Multicast Area Lights & Switches

Yeah, there will be some errors in the logs. I just suppress/ignore them as long as the device does what it’s told.

Well it doesn’t work.

Can you be more explicit? Can you say things like “It doesn’t physically turn on/off” instead of the dreaded unhelpful phrase “It doesn’t work”.

If you have zwavejs2mqtt, you can go to the node in question, open the command class in question, and toggle the buttons. If it doesn’t work there, it’s not going to work for multicast

It does work now. I got confused with some virtual lights.

Edit: The Fibaro Switch 1 does also work. Although this was a switch from the beginning.

I am looking forward to integrate my covers.
Thanks a lot for your help.

Could you please explain, why broadcast is faster and if I should/can use it?

Or am I correct that broadcasting just sends “turn on” and all my devices will turn on, no matter what they are? So in my case likely all lights turn on and the covers open. :smiley:

broadcast sends to all nodes on the network, not what’s in your group.

I tried fixing the covers, but I might have missed something:

mode: restart
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 }}
  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: >
    {%- set lights = expand(group) | selectattr('domain', 'eq', 'light')  %} {%-
    set lights = lights | selectattr('state','eq', 'off' if value else 'on') %}
    {%- set lights = lights | map(attribute='entity_id') | list %} {{- lights or
    none }}
  covers: >
    {%- set covers = expand(group) | selectattr('domain', 'eq', 'cover')  %} {%-
    set covers = covers | selectattr('state','eq', 'off' if value else 'on') %}
    {%- set covers = covers | map(attribute='entity_id') | list %} {{- covers or
    none }}
  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' %}
        {%- 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 }}'

I am not the residet expert but lets say you have 3 lights and want to turn them all on. Without broadcast, it does a daisy chain.

Turn off light 1
Wait for acknowledgment
Turn off light 2
Wait for acknowledgment
Turn off light 3
Wait for acknowledgment

Broadcast tells all lights to turn off at same time because its not sending a command to a specific light, its sending them to all lights. So any light that receives it will turn off.

Imagine in your city, you want to tell everyone that they will stink. You could call each person one by one and tell them, or you could broadcast it over a huge 100,000,000,000 gigawatt speaker and let everyone know at the same time! lol

No, not at all. Multicast does not cascade calls, it’s a single blast to the network. It does not wait for each one to respond, it blasts the command on the multicast address and then waits for the response on the corresponding nodes.

The broadcast is a flag that just sends it to all nodes instead of the specified nodes/entities. That’s it.

I updated the script in the original post, it should work with covers. Not able to test it as I don’t have covers.

I thought he was referring to just a normal call to turn off multiple lights, not multicast.

Thanks, there was a small mistake in your code:

  covers: >
    {%- set covers = expand(group) | selectattr('domain', 'eq', 'cover')  %} {%-
    set covers = covers | selectattr('state','eq', 'closed' if value else
    'open') %} {%- set covers = covers | map(attribute='entity_id') | list %}
    {{- lights or none }}

Should be (last line):

  covers: >
    {%- set covers = expand(group) | selectattr('domain', 'eq', 'cover')  %} {%-
    set covers = covers | selectattr('state','eq', 'closed' if value else
    'open') %} {%- set covers = covers | map(attribute='entity_id') | list %}
    {{- covers or none }}

Now it works. :slight_smile:

Thanks for all your help, appreciated.

I do have an issue that the covers only open to 20-40 %. They just stop. But this is likely unrelated to this script.

Need to use 255 as 100%

I will test that. Sounds very plausible (39%).

Edit: Yes it works. I was confused, because it didn’t consistently report 39 %, but this seems to fix it.

Thanks again.

I have another issue. I can set 255 from 0, 0 to 255 or 0 to 10 but I can’t set 255 if it is already at 10.

I’m having same issue and no luck debugging so far. Please let me know if you get it working

@davinci @Firstorlast, thanks for pointing this out, never noticed the issues but I definitely saw them at random times. Found the issue in the code, it’s been updated. Specifically the lights variable made some poor assumptions.

EDIT: I believe I fixed covers, you’ll have to test that out as I don’t have any position based covers.

Thanks, unfortunately with the new code it doesn’t work at all. Nothing in the logs, just doesn’t move at all.

It might affect dimmable lights as well. Only simple lights (on-off) and switches work.

Reverting back it works again.

My script:

zwave_multicast_group:
  mode: restart
  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 }}
    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 }}"
      light.led_test:
        endpoint: 5
    lights: >
      {% if value %}
        {% set off_lights = expand('group.office_area') 
          | selectattr('domain', 'eq', 'light')
          | selectattr('state', 'eq', 'off')
          | map(attribute='entity_id') | list %}
        {% set on_lights = expand('group.office_area') 
          | 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.office_area') 
          | selectattr('domain', 'eq', 'light')
          | selectattr('state', 'eq', 'on')
          | map(attribute='entity_id') | list or none }}
      {% endif %}
    covers: >
      {% if value %}
        {% set off_lights = expand('group.office_area') 
          | selectattr('domain', 'eq', 'cover')
          | selectattr('state', 'eq', 'closed')
          | map(attribute='entity_id') | list %}
        {% set on_lights = expand('group.office_area') 
          | 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.office_area') 
          | 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 }}"  

I am not sure why there is hardcoded group.office_area in the code. Or could it be brightness_pct?

ah, remove that hardcoded, that was me testing. That’s the problem. Ill update.