DISCLAIMERS
-
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.
-
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 %}