Expose NodeID on the Zwave devices

debugging Zwave loggin is not very straightforward as it is. Loggins mention a NodeID, but to find the related Device, we need to open the Zwave integration panel, and check all of the individual devices for their NodeId.

it would be so nice if we could add that in our templates, so, when confronted with a logging and a NodeID, we could immediately find the culprit device.

The nodeId is available on the devices as ID:

Please consider adding that to the available device_attributes, which now is:

      {"area_id":"zolder_woonkamer","config_entries":["62213aa5a398677a26e1a3354a861183"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"ef847e6d3729aa35d9ab7fdb66e38284","identifiers":[["zwave_js","3967834106-33-271:1538:4099"],["zwave_js","3967834106-33"]],"labels":[],"manufacturer":"Fibargroup","model":"FGWP102","model_id":null,"modified_at":"2024-08-08T09:40:48.457475+00:00","name_by_user":"Afzuigkap Zolder","name":"Metered Wall Plug Switch","primary_config_entry":"62213aa5a398677a26e1a3354a861183","serial_number":null,"sw_version":"3.2","via_device_id":"d49973230a844c7efce127d6bea7733c"},

aware it is buried here:

["zwave_js","3967834106-33"]] 

but that is not very obvious to find.

fighting with things like:

{{device_attr('ef847e6d3729aa35d9ab7fdb66e38284','identifiers')}}
{{device_attr('ef847e6d3729aa35d9ab7fdb66e38284','name_by_user')}}

{{integration_entities('zwave_js')}}

needs a lot of manual editing (and doesnt yet come close to finding the device or name_by_user based on the available NodeId in the logging.

A mapping would be very welcome

please consider

(btw, purposely not adding to Display NodeID as a column in the ZWaveJS Integration Devices page, because that, even though essentially the same quest, explicitly mentions a solution, which Id rather leave to the devs to consider.


in the zwave_js.legacy_zwave_migration NodeID are listed like:

            "sensor.afzuigkap_zolder_totaal": {
                "node_id": 33,
                "endpoint_index": 0,
                "command_class": 50,
                "value_property_name": "value",
                "value_property_key_name": "Electric_kWh_Consumed",
                "value_id": "33-50-0-value-65537",
                "device_id": "ef847e6d3729aa35d9ab7fdb66e38284",
                "domain": "sensor",
                "entity_id": "sensor.afzuigkap_zolder_totaal",
                "unique_id": "3967834106.33-50-0-value-65537",
                "unit_of_measurement": "kwh"
            },

which make it available without an issue, on any of the entities listed. Cant use that file anylonger, just showing it for reference

This code will provide two dictionaries, one with device names as the keys and the other with entity_idā€™s as the keys. The values will be the node.

{% set ns = namespace(device_dict=dict(), entity_dict=dict()) %}
{% for entity in integration_entities('zwave_js') %}

  {% set name = entity | device_id | device_attr('name') %}
  {% set id1, id2 = entity | device_id | device_attr('identifiers') %}
  {% set node = id1[1].split('-')[1] %}
  {% set ns.device_dict = dict.from_keys([(name, node)], **ns.device_dict) %}
  {% set ns.entity_dict = dict.from_keys([(entity, node)], **ns.entity_dict) %}
  
{% endfor %}
{{ ns.device_dict }}
{{ ns.entity_dict }}

You can set those to variable to use elsewhere, then you can do something like this to look up the node for an entity:

{{ entity_dict.['light.living_room_dimmer'] }}
1 Like

Nice indeed.

Using your other template:

{% set entities = integration_entities('zwave_js') %}
{% set names = entities | map('device_id') | map('device_attr', 'name_by_user') | list %}
{% set ns = namespace(nodes=[]) %}
{% for x,y in entities | map('device_id') | map('device_attr', 'identifiers') | list %}
  {% set ns.nodes = ns.nodes + [x[1].split('-')[1]] %}
{% endfor %}
{{ zip(names, ns.nodes) | unique | list }}

with name replaced by name_by_user returns exactly what I was hoping for:

[(None, '1'), ('Stookhok', '4'), ('Lamp Library', '6'), ('Freezer Garage', '13'), ('Attic flood sensor', '23'), ('Achter deur', '27'), ('Gang deur', '28'), ('Stookhok deur', '30'), ('Afzuigkap Zolder', '33'), ('Garage deur', '37'), ('Netwerk Library', '38'), ('Combi magnetron Keuken', '39'), ('Slaapkamer google', '40'), ('Subwoofer Auditorium', '44'), ('Voor deur', '50'), ('Garage binnen deur', '52'), ('Vijverpompen', '59'), ('Tv tuner Badkamer', '60'), ('Garage flood sensor', '62'), ('Attic heater flood sensor', '63'),  etc etc]```

even thought the ā€˜nameā€™ also has its use, to see the actual device names:

[('Zā€Stick Gen5 USB Controller', '1'), ('Switch Meter Plugin', '4'), ('Switch Meter Plugin', '6'), ('Switch Meter Plugin', '13'), ('Flood Sensor', '23'), ('Strips-MaZw', '27'), ('Strips-MaZw', '28'), ('Strips-MaZw', '30'), ('Metered Wall Plug Switch', '33'), ('Strips-MaZw', '37'), ('Smart Plug', '38'), ('Smart Plug', '39'), ('Metered Wall Plug Switch', '40'), ('Smart Plug', '44'), ('Strips-MaZw', '50'), ('Strips-MaZw', '52'), ('Double Switch 2', '59'), ('Metered Wall Plug Switch', '60'), ('Flood Sensor', '62'), ('Flood Sensor', '63'), ('Indoor Siren 6', '65'), ('Switch Meter Plugin', '66'), ('Single-Socket PowerNode', '67'), ('Smart Plug', '68'), ('Single-Socket PowerNode', '69'), ('Single-Socket PowerNode', '70'), ('Metered Wall Plug Switch', '71'), ('Flood Sensor', '73'), ('Fibaro Walli Outlet Type E/F', '74'), ('Fibaro Walli Outlet Type E/F', '75'), ('Metered Wall Plug Switch', '77'), ('Metered Wall Plug Switch', '78'), ('Metered Wall Plug Switch', '79'), ('Metered Wall Plug Switch', '80'), ('Metered Wall Plug Switch', '81'), ('Metered Wall Plug Switch', '82'), ('Metered Wall Plug Switch', '83'), ('Metered Wall Plug Switch', '85'), ('Metered Wall Plug Switch', '86'), ('Metered Wall Plug Switch', '87'), ('Smart Plug', '88'), ('Smart Plug', '89'), ('Single-Socket PowerNode', '90'), ('Single-Socket PowerNode', '91'), ('Smart Plug', '93'), ('Single-Socket PowerNode', '94'), ('Single-Socket PowerNode', '95'), ('CO2 Monitor Air Quality Detector', '96'), ('Switch Meter Plugin', '101'), ('Metered Wall Plug Switch', '102'), ('Switch Meter Plugin', '103'), ('Strips Guard 700', '104')]

Your template above:

{% set ns = namespace(device_dict=dict(), entity_dict=dict()) %}
{% for entity in integration_entities('zwave_js') %}

  {% set name = entity | device_id | device_attr('name') %}
  {% set id1, id2 = entity | device_id | device_attr('identifiers') %}
  {% set node = id1[1].split('-')[1] %}
  {% set ns.device_dict = dict.from_keys([(name, node)], **ns.device_dict) %}
  {% set ns.entity_dict = dict.from_keys([(entity, node)], **ns.entity_dict) %}
  
{% endfor %}
{{ ns.device_dict }}

returns something different, and it is not complete:

{
  "Indoor Siren 6": "65",
  "Double Switch 2": "59",
  "Flood Sensor": "23",
  "Strips Guard 700": "104",
  "Metered Wall Plug Switch": "33",
  "Smart Plug": "38",
  "CO2 Monitor Air Quality Detector": "96",
  "Strips-MaZw": "27",
  "Switch Meter Plugin": "4",
  "Single-Socket PowerNode": "67",
  "Fibaro Walli Outlet Type E/F": "74",
  "Zā€Stick Gen5 USB Controller": "1"
}

and I need to check why that is.

Ill add the template with the name_by_user to a markdown for my Zwave dashboard to never have to think twice again :wink:

As long as the FR wont be acknowledged, this will have to do.
Again, a huge thanks

result (need to format a bit, and do away with all of the () ):

Sep-15-2024 15-27-59

      - type: entities
        title: Device - NodeID
        card_mod:
          class: class-header-margin
          style: !include /config/dashboard/card_mods/scroll_card-content.yaml
        entities:
          - type: custom:hui-element
            card_type: markdown
            card_mod: !include /config/dashboard/card_mods/box_margin_0_16_16_16_markdown.yaml
            content: >
              {% set entities = integration_entities('zwave_js') %}
              {% set names = entities | map('device_id') | map('device_attr', 'name_by_user') | list %}
              {% set ns = namespace(nodes=[]) %}
              {% for x,y in entities | map('device_id') | map('device_attr', 'identifiers') | list %}
                {% set ns.nodes = ns.nodes + [x[1].split('-')[1]] %}
              {% endfor %}
              {{ zip(names, ns.nodes) | unique |join(',\n') }}
1 Like

Thenks for information :hugs:

Thatā€™s a really cool workaround for something that Iā€™ve also wanted for a while!

1 Like

Hereā€™s what I did with my markdown:

type: markdown
content: >
  {% set area = 'Theater' %}
  {% set ns = namespace(device_dict=dict(), entity_dict=dict()) %}
  {% for entity in integration_entities('zwave_js') %}
    {% if area_name(entity) == area %}
      {% set name = entity | device_id | device_attr('name') %}
      {% set id1, id2 = entity | device_id | device_attr('identifiers') %}
      {% set node = id1[1].split('-')[1] %}
      {% set ns.device_dict = dict.from_keys([(name, node)], **ns.device_dict) %}
      {% set ns.entity_dict = dict.from_keys([(entity, node)], **ns.entity_dict) %}
    {% endif %}
  {% endfor %}

  |Device||Z-Wave ID|

  |:----|--|:----|

  {% for item in ns.device_dict -%}

  |{{ item
  }}||{{ ns.device_dict[item] }}|

  {% endfor %}
title: Room Z-Wave Devices

image

nice!
if I try this (taking out the area because I want them all, and taking out the entity_dict because it does not seem to do anything in the output?):

  {% set ns = namespace(device_dict=dict()) %}
  {% for entity in integration_entities('zwave_js') %}
      {% set name = entity | device_id | device_attr('name') %}
      {% set id1, id2 = entity | device_id | device_attr('identifiers') %}
      {% set node = id1[1].split('-')[1] %}
      {% set ns.device_dict = dict.from_keys([(name, node)], **ns.device_dict) %}
      {% endfor %}

  |Device||Z-Wave ID|

  |:----|--|:----|

  {% for item in ns.device_dict -%}

  |{{ item
  }}||{{ ns.device_dict[item] }}|

  {% endfor %}

I only get a handful of devices.

also, when I try the device_attr('name_by_user') the following error is displayed

TypeError: keywords must be strings

edit

see post below: the list is abridged because unique is used on the device names.

the type error was caused by the fact 2 of my devices had no name_by_user setā€¦ Ive fixed that, and now the Markdown is beautiful in a, sorted, table

No more use of Zip though :wink:

          - type: custom:hui-element
            card_type: markdown
            card_mod: !include /config/dashboard/card_mods/box_margin_0_16_16_16_markdown.yaml
            content: >
              {% set ns = namespace(device_dict=dict()) %}
              {% for entity in integration_entities('zwave_js') %}
              {% set name = entity|device_id|device_attr('name_by_user') %}
              {% set id1, id2 = entity|device_id|device_attr('identifiers') %}
              {% set node = id1[1].split('-')[1] %}
              {% set ns.device_dict = dict.from_keys([(name,node)], **ns.device_dict) %}
              {% endfor %}

              |Device||Z-Wave ID|

              |:----|--|:----|

              {% for item in ns.device_dict|sort -%}
              |{{item}}||{{ns.device_dict[item]}}|

              {% endfor %}

1 Like

I suppose it is the ā€˜uniqueā€™ that does it hereā€¦ as these are many identical types of devices, thereā€™s but 1 of them listed per type, and so that is not the complete list of NodeIdā€™s.

Since I name them uniquely, that is the beter way of using in this template

The code that creates a list of tuples (and uses zip and unique) will generate a list of every single node, no matter what. That is because the unique filter is being applied to the entire tuple, which is both the name and the node together. Therefore even if you name devices the same, they will have different node numbers, and therefore they will each be included in the output.

The code that generates the dictionary does not use the unique filter because a dictionary cannot have duplicate keys. So if you have multiple nodes named the same, the dictionary will only contain that name once and the value will be the node number of the last one that was attempted to be added.

If you have uniquely named items, it doesnā€™t matter which code you use. If you want to create a dictionary to look up nodes programmatically by device, you (by definition) need uniquely-named devices and then youā€™ll need the dictionary code.

Hereā€™s your card code modified to make a list so that every node is displayed, regardless of overlapping names. For your use case without overlapping names the output should be the same.

- type: custom:hui-element
            card_type: markdown
            card_mod: !include /config/dashboard/card_mods/box_margin_0_16_16_16_markdown.yaml
            content: >
              {% set ns = namespace(device_list=[]) %}
              {% for entity in integration_entities('zwave_js') %}
              {% set name = entity|device_id|device_attr('name_by_user') %}
              {% set id1, id2 = entity|device_id|device_attr('identifiers') %}
              {% set node = id1[1].split('-')[1] %}
              {% set ns.device_list = ns.device_list + [(name, node)] %}
              
              {% endfor %}

              |Device||Z-Wave ID|

              |:----|--|:----|

              {% for name, node in ns.device_list|unique|sort -%}
              |{{name}}||{{node}}|

              {% endfor %}
1 Like

Hereā€™s a Markdown card Iā€™ve been using for awhile now. I took inspiration from another post (which I canā€™t recall).

Markdown card YAML
type: markdown
content: >
  ##
  [Configuration](/config/zwave_js/dashboard?historyBack=1&config_entry=ef37a31dab38dcfee3fd3d1ecd1f3692)


  ## [Nodes](/config/devices/dashboard?historyBack=1&domain=zwave_js)


  {%- set ns = namespace(nodes=[]) %}

  {%- for dev_id in integration_entities('zwave_js') | map('device_id') | unique
  %}

  {%- set node_id = (device_attr(dev_id, 'identifiers') | first |
  last).split('-')[1] %}

  {%- set ns.nodes = ns.nodes + [(node_id | int, dev_id)] %}

  {%- endfor %}

  ID  | Manufacturer | Product / ZUI Custom Name | Product code | HA Custom Name
  | Location / Area | FW

  ---:|:-------------|:--------------------------|:-------------|:---------------|:----------------|---:

  {%- for node_id, dev_id in ns.nodes | sort(attribute='0') %}

  {{ ["[**%d**](/config/devices/device/%s)" | format(node_id, dev_id),
      device_attr(dev_id, 'manufacturer'),
      device_attr(dev_id, 'name'),
      device_attr(dev_id, 'model'),
      device_attr(dev_id, 'name_by_user') or "",
      area_name(dev_id) or "",
      device_attr(dev_id, 'sw_version')] | join("|") }}
  {%- endfor %}

The ā€œConfigurationā€ link goes straight to the integration configuration page. This is faster than going through Settings for me.

The ā€œNodesā€ link goes straight to the device listing page filtered by integration.

Notes about the node table:

  1. Devices are sorted by Node ID, and the ID links to the device page. Number is formatted to support Long Range node IDs, but practically speaking I doubt Iā€™ll ever go past 3 digits.
  2. I print both name attributes, because I use Z-Wave JS UI and wanted to make sure the names were synchronized.

The Configuration link and the table would need to be updated if you have multiple integrations (Z-Wave networks) by taking consider of the Home ID. The Home ID is the first part of the device identifier split by -.

2 Likes

wow this is amazing really.
Should be builtin in Home Assistant :wink:
perfect, and thank you very much, it might make implementing this Feature request a lot simpler for the dev team. they got to love it.

is there some extra formatting in your card? if I c&p into the contents of a markdown, it looks like this in my dashboard. This is a type: sections view so I probably need to give it the full width if possibleā€¦

I updated the YAML. I just copied and pasted from the editor. Iā€™m currently using a ā€œPanel (1-card)ā€ view because this was before Sections were introduced and it was the easiest way to get a wide card.

(Looks like the ā€œcontentā€ text from the visual editor compresses empty lines, but those are required to render the table properly)

A right, panel seems straightforward, thanks will try after updating to 2024.9.2 :wink:

update

seems to do better, but still your columns look is much nicer (this is a UI view):

yes, thats it, creating the identical in a yaml view is perfect:

changing the nodeid to

[**%01d**]

takes away those ugly leading 00ā€™s

Beauty is in the eye of the beholder! :stuck_out_tongue:

I originally did that with the intent of keeping the IDs aligned, but with right column alignment itā€™s superfluous. Just updated my code above to remove them and the image shows a LR node with a 3-digit node ID.

Where can one find a list of attributes to call? Example: I think it would be helpful to show data to identify ā€œsickā€ devices (dropped packets, RSSI value, etc). I just donā€™t know enough to be able to pull these from my head? Thanks!

Thatā€™s not so easy. Those values are not part of the device attributes, but are separate sensors. You would need to write a template to select the corresponding entities for the device.

For example, hereā€™s a template to display the node status (awake, asleep, dead, etc.) that @Mariusthvdb wrote:

device_entities(dev_id)|select('search','_node_status')|map('states')|list|join('|') or '' ]|join('|') }}

It searches for the node status by name. Youā€™d need to repeat this same logic for RSSI, etc. sensors, or come up with some similar that finds the desired entities.