The HA scripting language is kicking my butt. Would someone be kind enough to get me going on writing a script that runs every night that pings each of my non-battery z-wave devices? I’ll explain why I want this in a follow-up message. I’m running the current version of Home Assistant and using the Z-wave JS integration.
I have figured out that I can get a list of Z-wave entities using integration_entities(‘zwave_js’) and I can define a non-battery device as one that doesn’t have an *_batterylevel entity (I renamed all of the devices to be consistent). I also know there’s an *_ping button entity that can be “pushed” to execute a ping.
The reason I want this is to detect dead or malfunctioning devices. I have a dashboard card that displays any unavailable Z-wave devices or those that haven’t been seen in the last 24 hours based on the *_last_seen values. But non-battery devices (e.g. light switch) don’t talk to the controller every day if they’re not used (like the one in my crawlspace). But a ping updates *_last_seen. That’s why I’d to ping them regularly.
if you just want a sensor that tells you if you have any failed zwave nodes you don’t need to ping them or use the last seen data. You just need to look for any nodes that have the node_status sensor as ‘dead’. Every zwave device has that sensor available.
here is the sensor I use to give that info:
sensor:
- platform: template
sensors:
zwave_failed_nodes:
friendly_name: "ZwaveJS Failed Nodes"
value_template: >
{% set dead_count = states | selectattr("entity_id", "search", "node_status") |
selectattr('state', 'in', 'dead, unavailable, unknown') |
map(attribute='entity_id') | list | count %}
{% set total = states | selectattr("entity_id", "search", "node_status") |
map(attribute='entity_id') | list | count %}
{{ dead_count }}/{{ total }}
attribute_templates:
dead nodes: >
{% set dead_list = states | selectattr("entity_id", "search", "node_status") |
selectattr('state', 'in', 'dead, unavailable, unknown') |
map(attribute='name') | list %}
{{ dead_list }}
it gives me a count of dead nodes as it’s state and lists the dead nodes in it’s attributes. But you can move the attribute template into the state if that’s what you want.
Thank you finity! But I’m afraid that approach isn’t working for me.
The node_status sensors aren’t a reliable indicator (on my network at least). I have about 43 zwave devices and I know 3 of them are dead. One because it hasn’t had a battery in it for a year, and two because I’m pretty sure the batteries died, but I haven’t checked yet because I wanted to get this working first.
The device that hasn’t had a battery for a year shows “dead” if I inspect it, but the other two show “asleep” even though they haven’t broadcast a battery level or anything else in many weeks. I also just tried opening the air gap on one of my dimmers to simulate a failure and at this point at least it still shows “alive” even though it definitely isn’t. If I ping it, then it will show dead.
This is why I was thinking a combination of last_seen and pinging would be necessary.
I don’t really have any working code to show at this point, more like snippets of stuff. Is there documentation somewhere for the templating language that I’ve missed? For example, the ‘search’ option in the selectattr() call in finity’s code. I never ran across that documented in Template Designer Documentation — Jinja Documentation (3.2.x)
To start, I added a markdown card to a lovelace dashboard and simply had it list the zwave entities:
type: markdown
content: |-
{%- for entity in integration_entities(‘zwave_js’) -%}
{{ entity }}{%- if not loop.last -%}{{ ‘\n’ }}{%- endif -%}
{%- endfor -%}
This works and shows a bunch of entities. But how would I select out only entities that have an entity_id that ends in ‘_ping’? I’ve found no syntax for SelectAttr() that accomplishes that. Having found those entities, how would I reject any entity where there exists a similarly named entity ending in ‘_batterylevel’?
I suspect I’m taking the completely wrong approach here.
What i do is refresh the switch value 2x a hour. This keeps the node active, the routes up to date, and detects the switch being offline, and makes sure the switch value in HA is correct. For example if the switch fails to send its state due to some transient issue (HA restart, RF interference), HA retains a bad state, by refreshing it you get the right state.
Summary: I like what you are doing, I have health monitoring on everything, consider using a refresh instead of a ping as it has added benefits, and consider doing it more frequently as exercising the zwave network is good for it (just don’t over exercise it)
And most important stagger the pings/refreshes in time. At least 30 seconds in between. I have mine staggered by 60 seconds and have been running like that for 2 years.
Yeah, that’s strange. I have three zwave mains powered devices (out of 40 total mixed mains and battery powered) paired but not plugged in to power and they show as dead. But I don’t know if a dead battery will change a battery powered device to dead or not. I’ve never tried it. I have another automation to monitor and tell me if any battery devices need their batteries changed so I usually catch all of those before they completely die.
I thought you were looking for a solution for your mains-powered (non-battery) devices? In that case, there should be no reliability issues with the “node_status” sensor, as long as you are causing them to report as dead (either control them, or ping).
Battery devices will never show “dead”, and pinging them won’t work. Not sure if that factors into your solution.
That’s interesting - but what is this refreshing that you’re talking about? It’s not a term I’ve run across before and searching hasn’t turned up anything pertinent. That’s basically what I’m intending to use the Ping for - force an action to update values in HA. Does this refresh work for battery operated devices (which will generally be asleep 99+% of the time)?
I had been thinking about 3 times a day with pauses between pings of 15 seconds or so to avoid putting too much traffic on the Z-wave network. But I’ve nothing against making it longer, those (untested) numbers just felt about right.
Apparently it will eventually mark them as dead, but it’s not a fast process (either for battery powered or AC powered). Your system shows that it will catch AC powered devices eventually and my system shows that it will catch battery powered devices eventually. But the AC powered device I disconnected 24 hours ago still shows as alive and well. It appears AC powered devices don’t talk to the network unless something happens so HA apparently just assumes they’re still working unless they fail to respond.
I’ve been doing this as well and have an entire dashboard page dedicated to batteries. The problem is that the *_batterylevel entities for the two dead devices are showing 100%. Those values haven’t changed in weeks. I only realized the devices weren’t working while I was testing some changes to my dashboard UI and wanted to see the door sensors change state but they didn’t.
That’s why for battery powered devices I wanted to use *_lastseen to monitor activity. All my battery operated zwave devices report battery levels regularly and I’d never buy a battery powered smart device that didn’t report battery level. Unfortunately this doesn’t help for AC powered devices, thus my intent to ping them to force a response.
Exactly! That’s what lead to this topic in the first place. I can’t use *_node_status reliably (as I explained above), but I can use *_last_seen to track the last time a device responded. For battery powered devices, this work well because they’re regularly broadcasting their battery status. But it does’t work well for AC powered devices because they don’t broadcast anything unless something happens (and I don’t turn on the light in my crawlspace all that often). But if I ping an AC-powered device and it doesn’t respond, HA will notice that.
So my thought is to monitor *_last_seen to look for unresponsive devices. But for that work with AC powered devices, I need to ping them regularly and writing that script has my stumped at the moment.
I’m not sure that you need to create a *_last_updated entity manually any longer. There’s now a built-in *_last_seen entity on all Z-wave devices that I think accomplishes the same thing.
I think I’m getting closer to my goal. I’ve tried to create a template sensor that generates a list of the non-battery z-wave device ping entities. Testing the logic on the Developer Tools | Template tab works, but the actual template sensor doesn’t work (it just returns “unknown” on the Developer Tools | States tab) .
Can someone please take a look at this and tell me what I’m doing wrong?
template:
- sensor:
- name: "zwave_ping_entities"
state: >-
{% set ns = namespace(items=[]) %}
{% for entity in integration_entities('zwave_js') %}
{% if entity.endswith('_ping') %}
{% if states(entity.replace('button.','sensor.').replace('_ping','_battery_level'))=='unknown' %}
{% set ns.items = ns.items + [entity] %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.items }}
I basically grabbed bits and pieces from lots of other forum posts and I obviously don’t understand all the nuances. I’m guessing it’s maybe a problem with how I’m returning the list. But why does the logic work on the Developers Tools | Template tab then?
I couldn’t figured out why the template I showed above doesn’t work, but the same logic in an automation worked fine and that’s all I needed anyway. I’m including it here for any people in the future that can use it.
This automation runs 3 times a day and pings all z-wave devices that do not have a _Battery_Level sensor (pinging a battery operated device is worthless since they’re almost always asleep and won’t respond anyway). There’s a 5 second pause between pings to not saturate the z-wave network.
alias: Ping Z-wave Devices
description: ""
trigger:
- platform: time
at: "06:05:00"
- platform: time
at: "14:05:00"
- platform: time
at: "22:05:00"
action:
- repeat:
for_each: >-
{% set ns = namespace(items=[]) %} {% for entity in
integration_entities('zwave_js') %}
{% if entity.endswith('_ping') %}
{% if states(entity.replace('button.','sensor.').replace('_ping','_battery_level'))=='unknown' %}
{% set ns.items = ns.items + [entity] %}
{% endif %}
{% endif %}
{% endfor %} {{ ns.items }}
sequence:
- delay: 5
- service: button.press
target:
entity_id: "{{ repeat.item }}"
mode: single
This is used in conjunction with a card that lists all z-wave devices that have a state of unknown or a _last_seen sensor more than 24 hours old. This basic approach could be changed to send a notification or whatever you choose to do. I used the auto-entries card from HACS.