I have also noticed that battery levels reported by zwave devices is often wrong. Keeping track of the last active time reported by zwavejs seems like the best workaround. I ended up writing a custom mqtt sensor that produces a markdown report and list of devices that are past a certain threshold or if the device uses a battery. Enjoy!
configuration.yaml
mqtt:
sensor:
- name: Inactive Zwave Battery Devices
state_topic: "zwave/_CLIENTS/ZWAVE_GATEWAY-zwavejs2mqtt/api/getNodes"
value_template: "{{ now() }}"
json_attributes_topic: "zwave/_CLIENTS/ZWAVE_GATEWAY-zwavejs2mqtt/api/getNodes"
json_attributes_template: |-
{%- set setting_find_older_than = timedelta(days=2) %}
{%- set settting_show_battery_only = true %}
{%- set setting_none_timestamp = 9000000000000000 %}
{%- set data = namespace(from_ha=[], node_info_list=[], markdown="") %}
{# Get HA data about the zwave devices. #}
{%- set device_ids = integration_entities('zwave_js') | map('device_id') | unique | reject('eq',None) | list %}
{%- for device_id in device_ids %}
{%- set node_id = device_attr(device_id, 'identifiers') | list | last | list | last | regex_replace("^[0-9]+-", "") | regex_replace("-.+", "") %}
{%- set data.from_ha = data.from_ha + [namespace(
device_id = device_id | string,
node_id = node_id | string
)] %}
{%- endfor %}
{%- for result in value_json.result %}
{# Find the latest activitiy time. #}
{%- set ns = namespace(times=[], device_id=None, has_battery=false) %}
{%- if "values" in result %}
{%- for key, value in result["values"].items() %}
{%- if "commandClassName" in value and value["commandClassName"] is not none and value["commandClassName"] | lower == "battery" %}
{%- set ns.has_battery = true %}
{%- endif %}
{%- if "lastUpdate" in value and value["lastUpdate"] is not none %}
{%- set ns.times = ns.times + [value["lastUpdate"] / 1000] %}
{%- endif %}
{%- endfor %}
{%- endif %}
{%- if "lastActive" in result and result["lastActive"] is not none %}
{%- set ns.times = ns.times + [result["lastActive"] / 1000] %}
{%- endif %}
{%- if ns.times | length == 0 %}
{%- set ns.times = ns.times + [setting_none_timestamp] %}
{%- endif %}
{%- if ns.times | length > 0 and ((settting_show_battery_only is false) or (settting_show_battery_only and ns.has_battery)) %}
{%- set last_activity_timestamp = ns.times | sort | last %}
{%- set last_activity_dt = as_datetime(last_activity_timestamp | string) %}
{%- if last_activity_timestamp == setting_none_timestamp or (last_activity_dt is not none and now() - last_activity_dt > setting_find_older_than) %}
{%- set name = result.name | trim %}
{%- set manufacturer = result.manufacturer | trim %}
{%- set product_description = result.productDescription | trim %}
{%- set location = result.loc | trim %}
{# Find the HA device id. #}
{%- for ha_item in data.from_ha %}
{%- if ha_item.node_id|string == result.id|string %}
{%- set ns.device_id = ha_item.device_id %}
{%- endif %}
{%- endfor %}
{%- set data.node_info_list = data.node_info_list + [{
"id": result.id,
"name": name if name | length > 0 else None,
"manufacturer": manufacturer if manufacturer | length > 0 else None,
"product_description": product_description if product_description | length > 0 else None,
"location": location if location | length > 0 else None,
"last_activity": last_activity_timestamp,
"device_id": ns.device_id
}] %}
{%- endif %}
{%- endif %}
{%- endfor %}
{# Create the output object and markdown format. #}
{%- set data.markdown = data.markdown + "| Name | Location | Last Activity |\n" %}
{%- set data.markdown = data.markdown + "| ---- | -------- | ------------- |\n" %}
{%- if data.node_info_list | length > 0 %}
{%- for node_info in data.node_info_list | sort(attribute="last_activity", reverse=True) %}
{%- set name_value = node_info.product_description if node_info.name is none else node_info.manufacturer if node_info.name is none else node_info.name %}
{%- set device_id = node_info.device_id %}
{%- set activity_dt = as_datetime(node_info.last_activity | string) %}
{%- set name_cell = name_value if device_id is none else "[" + name_value + "](/config/devices/device/" + device_id + ")" %}
{%- set location_cell = node_info.location | default("Unknown") %}
{%- set activity_cell = None if node_info.last_activity == setting_none_timestamp else relative_time(activity_dt) %}
{%- set name_cell_value = "Unknown" if name_cell is none else name_cell %}
{%- set location_cell_value = "Unknown" if location_cell is none else location_cell %}
{%- set activity_cell_value = "Unknown" if activity_cell is none else activity_cell %}
{%- set data.markdown = data.markdown + "| " + name_cell_value + " | " + location_cell_value + " | " + activity_cell_value + " |\n" %}
{%- endfor %}
{%- else %}
{%- set data.markdown = data.markdown + "| No devices |\n" %}
{%- endif %}
{{ {"markdown": data.markdown, "node_info_list": data.node_info_list } | to_json }}
Automation to refresh the data occasionally:
alias: Reload zwave node info
description: >-
Used by the custom mqtt sensor sensor.inactive_zwave_devices. See
configuration yaml for details.
trigger:
- platform: time
at: "00:00:00"
- platform: homeassistant
event: start
condition: []
action:
- service: mqtt.publish
data:
topic: zwave/_CLIENTS/ZWAVE_GATEWAY-zwavejs2mqtt/api/getNodes/set
payload: "{ \"args\": [] }"
mode: single