Zwave JS Multicast Area Lights & Switches

DISCLAIMERS

  1. ZwaveJS Multicast does not work with s2 security, and it doesn’t work at all with s0. Please do not ask why this isn’t working if you are using s2 security.

  2. This will produce log warnings if you use groups with one light. The script dynamically counts lights on or off for the given command. If the filtered lights result in 1 light, you will also get a warning. You can safely ignore this warning.
    The warning:

    2021-11-14 06:13:48 WARNING (MainThread) [homeassistant.components.zwave_js] Passing the zwave_js.multicast_set_value service call to the zwave_js.set_value service since only one node was targeted
    

For everyone else not using s0 or s2…

I finally decided to use Zwave JS’s multicast service call. Using this allows you to turn on multiple lights and switches much faster than listing out the entities in a service call.

This setup creates “Light groups” but specifically designed around zwave js multicast. It will group light and switch domains into a single light entity.

Group

Make your group(s). This is the old school grouping method, only provided by yaml

inside_area:
  entities:
    - light.bonus_room_bedroom_fixture
    - light.bonus_room_bedroom_recessed
    - light.bonus_room_entrance
    - light.bonus_room_office_fixture
    - light.bonus_room_office_recessed
    - light.dining_room_chandelier
    - light.hallway
    - light.kitchen_recessed
    - light.kitchen_island
    - light.living_room
    - light.master_bathroom_recessed
    - light.master_bathroom_fixture
    - light.master_closet
    - light.master_fixture
    - switch.basement_recessed
    - switch.basement_stairs
    - switch.dining_room_hutch
    - switch.foyer
    - switch.garage_recessed
    - switch.garage_entry
    - switch.garage_workbench
    - switch.kitchen_cabinet
    - switch.kitchen_pantry
    - switch.laundry
    - switch.living_room_window
    - switch.master_nightstand_left
    - switch.master_nightstand_right

Script

This script is needed for the turnon/turnoff functionality. Place this script into your script section.

Do not change the order of the variables.

This script defaults lights to command_class=38, property='targetValue', and endpoint=0.
This script defaults switches to command_class=38, property='targetValue', and endpoint=0.

If you have any special requirements, like command_class 32 or a different endpoint for a specific set of lights, list them out individually in the settings section.

basic settings section, with no special requirements. Do not change this. Only add to it.

      light:
        command_class: 38
        property: targetValue
        endpoint: 0
        value: "{{ brightness_pct }}"
      switch:
        command_class: 37
        property: targetValue
        endpoint: 0
        value: "{{ value }}"

Advanced settings section with 2 special requirements

switch.flood_light has an endpoint of 1 and switch.sliding_door_sconce has an endpoint of 2.

      light:
        command_class: 38
        property: targetValue
        endpoint: 0
        value: "{{ brightness_pct }}"
      switch:
        command_class: 37
        property: targetValue
        endpoint: 0
        value: "{{ value }}"
      switch.flood_light:
        command_class: 32
        endpoint: 1
      switch.flood_light2:
        command_class: 32
        endpoint: 1
      switch.sliding_door_sconce:
        endpoint: 2

The script will group up and perform the necessary number of service calls for grouped commands. I.e. if 2 special settings are grouped together, this will only perform 1 multicast service call.

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 }}
    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 }}"

Template Lights (That mimic a light group)

This is a single light that can be turned on, off, and have it’s level set. It will turn on all entities inside a group

    inside_area:
      unique_id: inside_area
      friendly_name: Inside
      value_template: >
        {{ is_state('group.inside_area', 'on') }}
      level_template: >
        {%- set entities = expand('group.inside_area') %}
        {%- set lights = entities | selectattr('domain', 'eq', 'light') | selectattr('state', 'eq', 'on') %}
        {%- set levels = lights | map(attribute='attributes.brightness') | map('int') | list %}
        {{- levels | average if levels else 0 }}
      availability_template: >
        {{- expand('group.inside_area') | selectattr('state','in',['unavailable','unknown']) | list | length == 0 }}
      turn_on:
      - service: script.zwave_multicast_group
        data:
          group: group.inside_area
          level: 255
      turn_off:
      - service: script.zwave_multicast_group
        data:
          group: group.inside_area
          level: 0
      set_level:
      - service: script.zwave_multicast_group
        data:
          group: group.inside_area
          level: >
            {{- brightness }}

In the end, you will have a single light entity that will behave like a light and all lights under it will respond extremely fast.


For the lazy

If you’re lazy like me and don’t feel like copy/pasting hundreds of group names, you can use the template editor with this template. List out your groups and copy the results into your light section of configuration.yaml.

{% set entities = 'group.inside_area', 'group.outside_area' %}
{% 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}', 'on') }}}}
      level_template: >
        {{%- set entities = expand('group.{0}') %}}
        {{%- set lights = entities | selectattr('domain', 'eq', 'light') | selectattr('state', 'eq', 'on') %}}
        {{%- set levels = lights | map(attribute='attributes.brightness') | map('int') | list %}}
        {{{{- levels | average if levels else 0 }}}}
      availability_template: >
        {{{{- expand('group.{0}') | selectattr('state','in',['unavailable','unknown']) | list | length == 0 }}}}
      turn_on:
      - service: script.zwave_multicast_group
        data:
          group: group.{0}
          level: 255
      turn_off:
      - service: script.zwave_multicast_group
        data:
          group: group.{0}
          level: 0
      set_level:
      - service: script.zwave_multicast_group
        data:
          group: group.{0}
          level: >
            {{{{- brightness }}}}
""" %}
- platform: template
  lights:
{%- for group in groups %}
{{ fmat.format(group.object_id, group.name.replace('_',' ').title()) }}
{%- endfor %}

2 Likes

Please stop posting in this and post in the other thread.

1 Like

That sounds promising. Can I do this for covers as well (position and on/off)?

How do I have to modify the script?

Yes you can, but you’ll need my help most likely. You’ll need to look at the zwave js logs and see what command classes are being used to open/close the devices you plan to group. Once you have that, you just need to add it to the config. I recommend posting that info in that thread so I can update the script.

Covers position should be exactly the same CC as lights dimmer level.
Multilevel CC or something like that.

interesting, if that’s true then we’d just need to duplicate that for covers. It would be a 4 line addition to my script. 1 if I make it an anchor from dimmers

Sure, I can try that. :slight_smile:

I guess I should add, the one’s I’ve seen used that command class…

I am using Fibaro Roller modules (different versions).

just watch the logs when you open and close the cover, and post them here. The zwave logs, not the home assitant logs.

Pressing the download diagnostics on the device will dump all the command classes too.

Command class is 37 (binary switch), 38 as multilevel switch.

37 should be full open/close while 38 adjusts your open/close amount

Perfect, I now made four groups containing lights, switches and covers using the “old method for groups”.

What do I have to do here:

  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

Like this:

  fields:
    group:
      value: group.bonus_room_area -- can I add more or is it one script per group???
    level:
      value: 99 -- what does it do???

What needs to be added for supporting covers?

Also, if I paste your script into scripts, I can save it, but it never shows up in the list of my scripts.

I’ll need to update the script. I just looked at what I wrote and it’s not as generic as I remember.

Thanks.

I am struggling a bit with the groups, scripts and templates. I am not used in doing this “the old way”. I can’t see the groups in HA, the script is invisible and I don’t know where to put the template. The code looks different.

all you do is copy these items into your configuration, as is. There’s nothing extra other than using the script or group.

so group will go under the group: section. And the script just gets copy/pasted into your scripts.

just post what you’re struggling with if copying/pasting doesn’t work.

Ok, got it now.

What should I add to the fields though:

  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

Let’s say I have these groups:
group.test1
group.test2
group.test3

When do I have to set a level and how?

you just call the script:

turn on

service: script.zwave_multicast_group
data:
  group: group.test1
  level: 255

turn off

service: script.zwave_multicast_group
data:
  group: group.test1
  level: 0

if you want, you can make a template light out of it, which is what I did with mine. Otherwise, you just use the service calls.