OH3 error in rule to show me "time since last state update"

Hi,
i have a sitemap where i can see, how long in minutes the last update of an item was ago.

So with cron i get a new calculation every minute and i can see: last update: 5 minutes, 6 minutes and so on

Here is my rule:

rule "Update Xiaomi Temp time since"
when
    Time cron "0 * * * * ?"
then
	if(SystemStarting.state == OFF) {
	gXiaomiZeit.members.forEach[ DateTimeItem lastTime |
	logInfo("temp-anz-5 rule","lastTime : "+lastTime)
		val lastMillis = (lastTime.state as DateTimeType).getZonedDateTime().toInstant().toEpochMilli()
        val mins = (now.toInstant().toEpochMilli() - lastMillis) / 60000
        //val split = lastTime.split("_") // so we can reconstruct the time_since Item name
		val split = lastTime.name.split('_')
        (split.get(0)+"_"+split.get(1)+"_"+split.get(2)+"_time_since").postUpdate(mins.toString)
    ]
	}
end

loginfo output and error in openhab log:

2022-12-20 14:32:00.519 [INFO ] [ab.core.model.script.temp-anz-5 rule] - lastTime : Xiaomi_Temp_1_last_connection (Type=DateTimeItem, State=NULL, Label=Temp 1, Category=clock, Groups=[gXiaomiZeit])

2022-12-20 14:32:00.520 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'temperatur_anzeigen-5' failed: Could not cast NULL to org.openhab.core.library.types.DateTimeType; line 100, column 21, length 30 in temperatur_anzeigen

2022-12-20 14:32:06.520 [WARN ] [b.core.model.script.actions.BusEvent] - Cannot convert '2022-12-20T14:32:06.520505+01:00[Europe/Berlin]' to a state type which item 'Xiaomi_Temp_10_last_connection' accepts: [DateTimeType, UnDefType].

Any ideas how to solve this?

Do you need more information?

Now I understand it! :slight_smile: :slight_smile:

As I said before, you need to check whether lastTime.state is a DateTimeType before casting it.

Maybe something like this:

gXiaomiZeit.members.forEach[ DateTimeItem lastTime |
    var status = ""
    if (lastTime.state instanceof DateTimeType) {
      val lastMillis = (lastTime.state as DateTimeType).getZonedDateTime().toInstant().toEpochMilli()
      val mins = (now.toInstant().toEpochMilli() - lastMillis) / 60000
      status = mins.toString
    }
    val split = lastTime.name.split('_')
    (split.get(0)+"_"+split.get(1)+"_"+split.get(2)+"_time_since").postUpdate(status)
]
}

Updated… I REALLY hate rulesdsl now… so tedious

And using Duration

val mins = java.time.Duration.between(now, (lastTime.state as DateTimeType).zonedDateTime).toMinutes

How does this work? Is that something new added to the language? It should be:

postUpdate(split.get(0)+"_"+split.get(1)+"_"+split.get(2)+"_time_since", mins)

You don’t show your Item naming pattern but I bet something like: lastTime.name.replace('sensor', 'time_since') would make more sense here than splitting and reassembling the name like this.

1 Like

That rule worked for years with OH2.

And my naming can be seen in the Log-Output.

I have Xiaomi Zigbee Temperature Sensors.

Items:
number: Xiaomi_Temp_1
datetime: Xiaomi_Temp_1_last_connection


By the way… With the new rule errors are gone, but now i get warnings:

2022-12-21 14:27:01.471 [WARN ] [b.core.model.script.actions.BusEvent] - Cannot convert '' to a state type which item 'Xiaomi_Temp_1_time_since' accepts: [DecimalType, QuantityType, UnDefType].

It seems, that i get no value to my datetime item. I will wait some time until the Temp Sensors give me new values.

This did not ever work in OH2.

Maybe your Item got updated by some other method that you’ve overlooked - another rule, a timestamp profile, say.

Ahh your “time_since” item is a Numeric item! I thought it was a String item.

If I were you I would either change it to a DateTime item (and do the formatting of time / duration using state description format thingy), or a String. But assuming you want to keep things as is, what would you want the “time_since” to be updated to, if the lastTime is NULL? That’s what you need to do in the “else” clause.

GOOD catch! I didn’t even pay attention to that!

Incorporating the excellent suggestions from @rlkoshak:

gXiaomiZeit.members.forEach[ DateTimeItem lastTime |
    val timeSinceItemName = lastTime.name.replace('sensor', 'time_since')
    if (lastTime.state instanceof DateTimeType) {
      val mins = java.time.Duration.between((lastTime.state as DateTimeType).zonedDateTime, now).toMinutes
      postUpdate(timeSinceItemName, mins.toString)
    } else {
      postUpdate(timeSinceItemName, NULL)
    }
]

Note I switched the order for Duration.between. I think it should be Duration.between(older, newer) to get a positive duration. See Duration (Java Platform SE 8 )

1 Like