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 Rules | openHAB), change the definition of AA and BB to:
var AA = now.plusHours(5)
var BB = new DateTime(mqtt_topic_10116ed7_MotionSensorUpLastSeen.state.toString)
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.
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.
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.
zigbee2mqtt does publish these messages as retained:
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.
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)
@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.
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