Rule to check Zwave Battery Device LastWakeUp

I just created a rule to check for the last wake up time of my Zwave battery devices, because I had some devices which “died” for some reason and didn’t get marked as offline.

I just want to share my solution. Maybe some of you can need this.

Here ist my Rule.
Note that in my case the UIDs “:” needed to be replaced with “%3A”. You can see this in API Explorer.
You also have to create an API Key, which is needed in the shell script (see below)

rule "ZwaveThingLastWakeUp"
when
    Time cron "0 0 19 ? * * *"
then
    val String[] UIDs = newArrayList("zwave%3Adevice%3A50600ad9%3Anode30", "zwave%3Adevice%3A50600ad9%3Anode18", "zwave%3Adevice%3A50600ad9%3Anode14", "zwave%3Adevice%3A50600ad9%3Anode6", "zwave%3Adevice%3A50600ad9%3Anode3", "zwave%3Adevice%3A50600ad9%3Anode26", "zwave%3Adevice%3A50600ad9%3Anode22", "zwave%3Adevice%3A50600ad9%3Anode25", "zwave%3Adevice%3A50600ad9%3Anode2")
    UIDs.forEach [ UID |
        var String ThingAPI = executeCommandLine(Duration.ofSeconds(5),"/etc/openhab/scripts/ThingAPI2.sh", UID)
        //Split before last wakeup
        var String LastWakeUp = ThingAPI.toString.split('"zwave_lastwakeup":"').get(1)
        //Split after last wakeup
        LastWakeUp = LastWakeUp.toString.split('","zwave_neighbours').get(0)
        //logInfo("ThingInfo", "LastWakeUp " + LastWakeUp)
        val MyZonedDateTimeFromString = ZonedDateTime.parse(LastWakeUp).withZoneSameInstant(ZoneId.systemDefault())
        //logInfo("ThingInfo", "DateTime LastWakeUp: " + MyZonedDateTimeFromString)
        if(MyZonedDateTimeFromString.isBefore(now.minusHours(48))){
            logInfo("ThingInfo", "No Update from Thing UID " + UID + " since " + LastWakeUp)
            sendBroadcastNotification("Gerät " + UID + " hat sich seit über 48h nicht gemeldet, Batterie prüfen")
        }
    ]
end

And this is my Shell script which I call in the rule. Note that the $1 is used to pass the UID in the rule to this script. Maybe you have to set the script as executeable using “chmod +x your-script.sh”

 curl -X 'GET'   "http://your-oh-server:8080/rest/things/$1"   -H 'accept: application/json'   -H 'Authorization: Bearer YOUR-API-KEY'

I just came across another post which is maybe helpful.
Rule to Check LastWakeUp on ZWave battery devices - Setup, Configuration and Use / Scripts & Rules - openHAB Community

Regards
Levin

Hello @Levin1,

First of all thanks for sharing the rule.

Unfortunately I get the following error message. No idea what it means.

2022-06-03 10:53:48.563 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'ZwaveThingLastWakeUp' failed: Index 1 out of bounds for length 1

I entered the UID of the Things with : and %3A. For me in the API exloper the UID always says :

In my case the zwave device does not return zwave_lastwakeup.

Here there is only zwave_lastheal.

So I have adapted the rule like this

UIDs.forEach [ UID |
  var String ThingAPI = executeCommandLine(Duration.ofSeconds(5),"/etc/openhab/scripts/ZwaveThingLastWakeUp.sh", UID)
  // Split before last wakeup
  var String lastWakeUp = ThingAPI.toString.split('"zwave_lastheal":"').get(1)
  // Split after last wakeup
  lastWakeUp = lastWakeUp.toString.split('","').get(0)
  logInfo("ThingInfo", "Thing UID " + UID + ", LastWakeUp " + lastWakeUp)
  val myZonedDateTimeFromString = ZonedDateTime.parse(lastWakeUp).withZoneSameInstant(ZoneId.systemDefault());
  ...

Hi Jeff

Maybe you have to Split using another String.
You can run the .sh script directly on your OH Server (if it is Linux :slight_smile: )
There you can see the hole output and if it is even working. You should get the same output as in the API Explorer. There you should see “zwave_lastwakeup”.

You should also see the value in the UI in the Things properties:

I’m running OH 3.2 on openhabian.

I’m on OH3.3.0.M5.

@Levin1 I did the test with a radiator thermostat which also has a battery but does not provide zwave_lastwakeup.

Anyway, I built the whole thing in ECMAScript_2021. In this version we just go through a number of devices and read out the information via JSON.

var notificationAction = org.openhab.io.openhabcloud.NotificationAction;    

console.info('Start======================================================================= ');

for (let nodeNumber = 3; nodeNumber < 25; nodeNumber++) {
 
  // Build UID 
  var UID = 'zwave:device:422e9dc4c4:node' + nodeNumber;
  console.info('Test Thing UID ' + UID);

  // Execute command line with timeout.
  let duration = Java.type('java.time.Duration');
  let thingAPI = actions.Exec.executeCommandLine(duration.ofSeconds(10), '/etc/openhab/scripts/ZwaveThingLastWakeUp.sh', UID);

  let index = thingAPI.indexOf('channels');
  
  if (index > 1) {
    // Split the JSON data
    var result = thingAPI.substring(index - 2);

    // Removing characters from the command line in longer Z-Wave JSON files
    //   100  7412    0  7412    0     0   278k      0 --:--:-- --:--:-- --:--:--  278k
    let start = '\r';    //CR (character : \r
    let end   = '\n';    //LF (character : \n 
    let startIndex = result.indexOf(start);
    let endIndex   = result.lastIndexOf(end);
    result = result.replace(result.substring(startIndex, endIndex + end.length), '');
    //console.info("There were " + result);

    // Split before last wakeup
    let thingJSON = JSON.parse(result);
        
    // Get last wakeup value
    var lastWakeUpRaw = thingJSON.properties.zwave_lastwakeup;
    
    // Check if battery device!!
    if (lastWakeUpRaw !== undefined) {
      console.info('Thing UID ' + UID + ', LastWakeUp ' + lastWakeUpRaw);

      let zonedDateTimeCheck = Java.type('java.time.ZonedDateTime');
      let lastWakeUp         = Java.type('java.time.ZonedDateTime');
      let zoneId             = Java.type('java.time.ZoneId');
      
      lastWakeUp             = zonedDateTimeCheck.parse(lastWakeUpRaw).withZoneSameInstant(zoneId.systemDefault());
      zonedDateTimeCheck     = zonedDateTimeCheck.now().minusHours(48);
      
      console.info('lastWakeUp    ' + lastWakeUp.toString());
      console.info('zonedDateTime ' + zonedDateTimeCheck.toString());
      
      if (lastWakeUp.isBefore(zonedDateTimeCheck)) {
        console.info('No Update from Thing UID ' + UID + ' since ' + lastWakeUp.toString());
        notificationAction.sendBroadcastNotification('Device ' + UID + ' has not reported for over 48h, check battery');
      }
    }
    else {
      console.warn('Thing UID ' + UID + ' no battery device');
    }
  }
  else {
    console.warn('Thing UID ' + UID + ' no Z-Wave device');
  }
}

I did one as a shell script that uses the REST API. It lists the last wake-up time for all zwave devices that support wake-up.

#!/bin/bash

if [ $# -ne 0 ] ; then
    echo "Usage: z-lastwakeup"
    exit 1
fi

AUTH="Authorization: Bearer <<<your auth token goes here>>>"
HOST="<<<your host name or IP goes here>>>"
PORT="8080"

URL="http://${HOST}:${PORT}/rest/things"

THINGS=`curl --silent -X GET --header "Accept: application/json" --header "${AUTH}" ${URL}`

(for row in $(echo "${THINGS}" | jq -r '.[] | @base64'); do
    _jq() {
        echo ${row} | base64 --decode | jq -r ${1}
    }
    THINGTYPEUID=`echo $(_jq '.thingTypeUID')`
    LISTENING=`echo $(_jq '.properties.zwave_listening')`
    FREQUENT=`echo $(_jq '.properties.zwave_frequent')`
    if [[ $THINGTYPEUID == zwave* ]] && [[ $LISTENING == false ]]  && [[ $FREQUENT == false ]] ; then
        localDateTime=`date -d $(_jq '.properties.zwave_lastwakeup') "+%Y-%m-%d %H:%M:%S"`
        printf "%s  -  %s\n" "$localDateTime" "$(_jq '.label')"
    fi
done) | sort

Edit: I should mention it uses jq, which is normally not installed by default. And also base64, which may not be installed either.

1 Like

It is possible to get the thing properties without using the external script to make the API calls. In ECMAScript_2021, using the helper libraries, this is in fact fairly compact.

var ThingRegistry = osgi.getService('org.openhab.core.thing.ThingRegistry');
var ThingUID = Java.type('org.openhab.core.thing.ThingUID');
var myThing = ThingRegistry.get(new ThingUID('DEVICE UID HERE'));

logger.info(myThing.getProperties()['zwave_lastwakeup']);
3 Likes

Justin wins! :+1:

Wins what? :rofl::rofl::rofl:

Hi all

Thank you for your responses, this is great :clap:
I didn’t know how to realise this using ECMA Script 2021.
I will definitely try JustingG’s solution.

Does anybody know why batterie devices won’t show offline if they don’t report back during the configured wake up intervall?

Kinda; I believe two wakeup intervals need to pass. However, if the device isn’t fully initialized it will never show offline. For example lets say you pull the battery out and restart OH before two periods have passed it will never show offline. One way to check is in the Userdata/Zwave folder the time stamp of the Device XML needs to be after (later) than the controller time stamp for the offline timer to work

Not as slick as the solutions above, but I have a DateTime item linked to the Battery channel and have a page of label cards (13 devices) with the batteries % and have the footer as the DateTime. At a glance I can check the currency of the readings. I use a daily (86400) wakeup for a battery check/poll, so really just look at the day.

Bob

Same for me Bob, I use some DateTime items and have a rule that updates then whenever something changes on the thing. Then I have a page that shows all the last check ins, and I have a few scripts that kick off on some devices when they don’t check in on time. The scripts restart an OH service or kick something off the wifi so it can re-join. Ping back here if anyone needs some samples.

hmm, I’m using many different battery devices for example a “Popp mold detector” for over one year.
It works great but two weeks ago I removed the battery for another device and it didn’t show offline. But I think it should be fully initialized after one year of operating? However, the “node XML” time stamp is older than the last wakeup time stamp… (also daily wake up)

I’m not an expert, but I believe the device polling is the only section with a timer that can set a battery node as offline. If other commands to a battery device are not answered, the binding just assumes the device to be asleep and subsequent messages are queued. No commands are sent to a device that is asleep. Since the device itself sends the “I’m awake” message, a dead device can’t be discerned from a sleeping one.

There are three main ways a device will be uninitialized. Obviously while first being included is one. However, with every OH, binding or Controller restart all Zwave nodes are reinitialized from the XML files. For mains powered device this happens pretty quickly. For battery devices this happens when they send the “I’m awake” message. Since a dead device will never send that message, it will not be reinitialized after a restart, and polling will be deferred indefinitely, so will not be marked as offline. The third uninitialized state is if a “Heal” is requested but not completed. I do not use the Heal command enough to know if polling is suspended, but I suspect it is as well. My example was related to a OH restart that I have confirmed with a test.

As to the Device XML, when it is older than the controller XML, it is a clue the controller has been restarted (see above), but the device has not been reinitialized from the file yet because a new XML is created after that happens. However, after this reinitialization is complete, subsequent “awakes” will not generate a new XML, so the wakeup time stamp should always be younger or equal to the node XML time stamp.

Lastly another clue I use to determine if a node is fully initialized is if there are five lines at the bottom of the UI page for the device. If “reinitialize device” is not visible, then the device is not initialized, you can’t reinitialize an uninitialized device.
Five Lines of configured node

Hope this helps in some way

Bob

Where is the “controller XML” located?

I think the “heal” is also causing some trouble.
If I add a new device, (after repeated wake up), I can see the “Reinitialise the Device” option. However the next day (after heal during night) the option is not there anymore. Before OH 3.x, this was not the case I think.

So I think i will continue to query the last wake up value to be notified if a node died for some reason. In my experience the battery status value is very inaccurate too.

The length of the “heal” gets longer the more devices you have. Recently some battery device optimization enhancements were merged (in OH3 M6) that should help both initialization and heal to complete in one wake-up. However, even with this, I still check the battery wake-up time, so I think that is still a good idea

It should be where the others are userdata/zwave folder. Mine is Node 1.

Bob