Comparing Dates for a group of items (Zigbee devices)

I’m trying to right a rules that checks a group of items (Zigbee devices read from Zigbee2MQTT) for their last seen time. If the haven’t been seen in X amount of time send a notification. So step 1 was to get it working with a single item (below). And once that works try to put it in a loop. This is what I have so far:

rule “Test Time update”
when
Item DummyButton received update
then
val String MyStringFromJoda = now.toString //current time as string

var DateTime AA = MyStringFromJoda
var DateTime BB = mqtt_topic_10116ed7_MotionSensorUpLastSeen.state.toString
if(AA < BB){
logInfo(“Info”, “Less than works”)
}
end

This works but doesn’t allow for a buffer. So when I change it to:

if(AA.plusHours(5) < BB){

I get

An error occurred during the script execution: Could not invoke method: org.joda.time.DateTime.plusHours(int) on instance: 2020-11-16T15:00:26.440-05:00

So the big question is how do I compare the two time but adding some time to allow for buffer.

Well, for one thing, mqtt_topic_10116ed7_MotionSensorUpLastSeen.state.toString gives you a String, not a DateTime. A String doesn’t have a plusHours. And you can’t force a String to become a DateTime just by saying “you are a DateTime now!” which is what your line that defines BB does.

You need to parse the String to a DateTime. Assuming that it’s in ISO8601 format that is simple enough.

var BB = new DateTime(mqtt_topic_10116ed7_MotionSensorUpLastSeen.state.toString)

The same goes for MyStringFromJoda.

This does not work in the way that you think it is working. It’s doing a String comparison and letting you know if AA comes before BB alphabetically.

Obviously, you can’t compare a String to a range of DateTimes.

So using the above (which is documented at https://www.openhab.org/docs/configuration/rules-dsl.html#datetime-item), change the definition of AA and BB to:

var AA = now.plusHours(5)
var BB = new DateTime(mqtt_topic_10116ed7_MotionSensorUpLastSeen.state.toString)

and change your comparison to

if(AA.isBefore(BB))

Usually, zigbee2mqtt will provide an availability topic for each device that it controls.

zigbee2mqtt/yourDeviceFriendlyName/availability

When the device comes online, zigbee2mqtt will publish online to this topic. When the device goes offline (such as power has been removed) zigbee2mqtt will publish offline to the topic.

So you get instantaneous notification whether it’s online or offline - no need for rules! See this simple example for a dimming bulb.

(You will need a rule to send a notification, of course, but it’ll be very small and simple!)

There is yet another way. I can’t (and wont) look up how/if this can be done in .things files. But when you create the Thing through the UI you get three parameters on the Thing for handling the LWT.

  • Availability Topic: zigbee2mqtt/yourDeviceFriendlyName/availability
  • Payload available: online
  • Payload not available: offline

Assuming thazigbee2mqtt publishes these messages as retained than the binding will mark the thing as OFFLINE when the LWT topic says it’s OFFLINE. And all Items linked to that Thing’s Channels will be set to UNDEF similar to how it does when the MQTT Broker Thing goes OFFLINE.

Yes, good point!

I can confirm:

image

Personally I prefer to have the status in an Item - I find it a little easier to work with in rules, and of course the Sitemap. But I can see the logic behind embedding the LWT in the Thing itself!

In rules and such you would just check to see if one of the other Items linked to the Thing is UNDEF. This covers both the case where the broker reported the device as offline through the LWT and the case where the broker Thing itself goes offline.

Thanks @rlkoshak for the quick response and the assistance. I was mainly referencing DateTime Conversion (openHAB 2.x)

That was my bad, creating a DateTime variable and assigning a string as opposed to converting the string.

Is it possible to combine this into a loop to check all items in a group? I was reading Design Pattern: Working with Groups in Rules

It looks possible, this is what I thought would work reading that:

val StringBuilder sb = new StringBuilder
groupName.members.filter[ i | AA.isBefore(new DateTime(i.state.toString).plusHours(5))].forEach[i | sb.append(", " + i.name) ]

which produces:

Rule ‘Test Time update’: Invalid format: “NULL”

Is it possible to put such a command in the group loop or would it be possible to make the loop so that multiple commands can be run. I’m thinking:

val StringBuilder sb = new StringBuilder
groupName.members.filter[ i | i != NULL].forEach[i |
var BB = new DateTime(mqtt_topic_10116ed7_MotionSensorUpLastSeen.state.toString)

if(AA.isBefore(BB)) {
sb.append(", " + i.name)
}
]

@hafniumzinc thanks, thats another great way to look at it. Unfortunately I’m interested in devices losing connection not necessarily going offline. This has happened a few times with Xiaomi motion and contact sensors where they will just stop responding until I press the pairing button on the device (no changes made to Zigbee2mqtt). In this case the availability does not seem to be updated (at least for me).

You have to filter out the Items whose state is NULL or UNDEF before the forEach. You can’t initialize a DateTime with the string “NULL” or “UNDEF”. So you are on the right track but NULL and UNDEF are Item states.

groupName.members.filter[ i | i.state != NULL && i.state != UNDEF].forEach...

And yes, the [ | ] defines a lambda. You can have as many lines of code in a lambda as you want.

Have you seen Design Patterns: Generic Is Alive.

thanks again for all the assistance!

In case anyone else is interested in doing something similar below is the code.

rule “Check Zigbee Devices”
when
Item DummyButton received update
then

//Store names of all items who are not communicating in Zigbeegroup in “sb”
val StringBuilder sb = new StringBuilder
Zigbeegroup.members.filter[ i | i.state != NULL && i.state != UNDEF].forEach[ i |
var currentTime = now.plusHours(12)
var deviceTime = new DateTime(i.state.toString)
if(currentTime.isBefore(deviceTime))
{
if (sb.toString == “”)
{ sb.append(": ", i.name)
}

    else { sb.append(", " + i.name)
     }

}
]

//if devices were not communicating send message
if(sb != “” && sb.toString.contains(":"))
{ sendTelegram(“botname”, “Check Zigbee devices, may be disconnected%s”, sb.toString)
// or other notification here
}
end