Item Last Update

Hi,

I am trying to add generic way to update last item update time. I found two ways to do it:

  1. Write explicit rule for each item - working, but I really trying to avoid it

  2. Use last update group as follow (something I found in other discussion). The problem is that this is not working for me in Openhab2.

    rule “Update timestamp”
    when
    Item gUpdate received update
    then
    gUpdate.members.forEach[i | var Calendar cal = Calendar::getInstance() cal.setTime(i.lastUpdate(“influxdb”)) postUpdate(“Update_” + i.name, new DateTimeType(cal).toString) ]
    end

Is there any explanation why it doesn’t work? Should we use Calendar or GregorianCalendar?

Thx

You don’t say what currently doesn’t work.

See the [Type Conversion] (Type Conversions) article for examples of how to work with date times.

@rlkoshak

Thx, will take a look. Here is more detail with printouts. You can see the last time and the conversation failure.

Thx for you help!

rule "Last Update timestamp"
when
    Item Last_Update received update
then
	Last_Update.members.forEach[item |
    var lastUpdate = item.lastUpdate("influxdb");
    logInfo("LastUpdate", lastUpdate.toString)
		var Calendar cal = Calendar::getInstance()
	  cal.setTime(lastUpdate)
		//postUpdate(i.name + "_Last_Update", new DateTimeType(cal).toString)
	]
end

08:11:26.313 [INFO ] [se.smarthome.model.script.LastUpdate] - 2017-09-15T08:00:00.027+03:00
08:11:26.319 [ERROR] [.script.engine.ScriptExecutionThread] - Rule 'Last Update timestamp': Could not invoke method: java.util.Calendar.setTime(java.util.Date) on instance: java.util.GregorianCalendar[time=1505452286316,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Jerusalem",offset=7200000,dstSavings=3600000,useDaylight=true,transitions=143,lastRule=java.util.SimpleTimeZone[id=Asia/Jerusalem,offset=7200000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=23,startDayOfWeek=6,startTime=7200000,startTimeMode=0,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2017,MONTH=8,WEEK_OF_YEAR=37,WEEK_OF_MONTH=3,DAY_OF_MONTH=15,DAY_OF_YEAR=258,DAY_OF_WEEK=6,DAY_OF_WEEK_IN_MONTH=3,AM_PM=0,HOUR=8,HOUR_OF_DAY=8,MINUTE=11,SECOND=26,MILLISECOND=316,ZONE_OFFSET=7200000,DST_OFFSET=3600000]

I’ve succeed to fix the errors. WIll be happy to know if there is better / simpler way to do it.

rule "Last Update timestamp"
when
    Item Last_Update received update
then
	Last_Update.members.forEach[item |
      var DateTime lastUpdate = item.lastUpdate("influxdb").toDateTime;
	  var Calendar cal = Calendar::getInstance()
	  cal.setTimeInMillis(lastUpdate.getMillis)
	  postUpdate(item.name + "_Last_Update", new DateTimeType(cal).toString)
	]
end

Well, initially I would try:

Last_Update.members.forEach[i | postUpdate(i.name + "_Last_Update", i.lastUpdate("influxdb").toString)]

@rlkoshak

Thx, you suggestion is working and simpler

Thank you very much for all the guidance and ideas in the posts in this forum (especially @rikoshak). All the discussions and posts to design patterns helped me to find a workable solution to my requirements for “item last update”.
As i did not find a “ready to go” solution to all my requirements here so far I would like to share my thoughts also.

My requirements: Save the timestamp for each item separated by 1) last change (=timestamp of last status change of item ) and 2) last update (timestamp of last update to the item by binding without changing state). For this purpose I have defined two additional DateTime items for each item with the same name, but suffix _LCH (for last change) and _LUP (for last update).

Of course this worked with several hundred lines of repeated code that implemented inidividual rules for each item on “changed” and “received update”. But based on your other solution proposals I could now knock it down to just 12 lines of code (instead of > 1.000 lines before with more than 100 items):

rule "zwogup"
when
	Item zwogup received update
then
Thread::sleep(500) // give persistence a chance to catch up
    val recentUpdates = zwogup.members.filter[light|light.lastUpdate != null && light.lastUpdate.isAfter(now.minusSeconds(2).millis)] // all sensors that changed state in the last secon
 	recentUpdates.forEach[light|
 	postUpdate(light.name+"_LUP", light.lastUpdate.toString)
 	if (light.changedSince(now.minusSeconds(3))) 
 	{ 	postUpdate(light.name+"_LCH", light.lastUpdate.toString)	}
	]  
end

In short: the items are members of a persisted group and at every update to one group member the rule triggers. Then I update the timestamp for last update for all items updated in the last 2 seconds and after that check if the item state also changed by using the persistence method “changedSince”. If this is true, I also set the timestamp for last change.

Note: This only works if you set the persistence strategy to “everyUpdate”. Because only in this case the rule triggers on updates AND changes (if you have set “everyChange” in persistence configuration the same rule will only be triggered at status changes, not at updates without status change).

I am doing this because this allows me to check if my sensors and actors are still alive (by checking the update timestamps in another maintenance rule).

However, I am quite aware of the fact that my approach is quite “resource intensive” as I trigger the persistence DB very often and pile up a lot of data (on every update) and also query the DB very often (with the changedSince method).
As I have a rather powerful server for OH (Core-i7, 8 GB RAM) with mySQL as persistence I hope that this remains stable, but have to test in the long run yet.

1 Like

:+1:

Check out the Generic Is Alive and Human Readable Names in Messages DPs. They will show you how to get a message when a sensor stops reporting.

In this case you may not need the Thread:sleep at all. If you do a sleep of 10 msec may be all you need. Even on a Pi, half a second is probably too long of a sleep for this rule.

I don’t know how many items you have but I doubt your approach is very taxing in the system. I’m sure it would run comfortably on even a resource constrained machine. You can watch top or Task Manager to see if it looks like too much is being used but I wouldn’t be worried.

Thank you very much for your feedback.
How does the sleep command in a rule actually influence the system behaviour? Will it only make the one rule wait or is the complete system in stillstand for that time?

It only makes the one rule’s thread stop. But there are a limited number of threads available to run rules. Adding long sleeps (according to Kai, anything longer than 100 msec is too long, I’m a little more generous and say 300 msec) ties up that thread leaving fewer threads available for other rules to run. It is not only possible but likely that using long Thread::sleeps will cause increased latency in your rules processing at a minimum and could lead to a complete failure of OH rules as everything is tied up waiting for Thread::sleeps.

Long Thread::sleeps can work just fine, but it is risky.

I have two type of items which I want to save the last update time for them. They are Number(s) and Switch(es). The problem is that Last_Update group can be used only for one of the types, or at least, when I add switches to the group it doesn’t work. Any suggestion how to handle this case?

Thx!

This is what I want:

Group Last_Update

Number GF_Guest_Humidity “Guest Room Humidity [%.1f %%]” (Last_Update)
Switch GF_Living_Motion “Living Room PIR [MAP(motion.map):%s]” <motion_sensor> (Last_Update)

For now I have defined two groups but I am not sure it is the right way to go

Group Last_Update_Number:Number
Group Last_Update_Switch:Switch

As above, you don’t say specifically what doesn’t work.

There was a recent change (2.1 release I think) that changed the way that Groups work. They no longer generate updates when you do not give them a Type. I’ve not yet experimented with having Items of two drastically different types in the same Group. Based on what you are seeing it appears to no longer work.

So I think using two Groups, one for each type, is probably the best way to go for now. You can reuse the same Rule assuming it looks like what you posted above and just have the rule triggered by both Groups.

I do have a group item which is declared as NUMBER with function AVG:

Group:Number:AVG	zwogup	"zwogup[%.1f]"

There are switches, dimmers and thermostats items included and it works fine for my update rules:

rule "zwogup"
when
	Item zwogup received update
then
if (EX_STARTUP.state == OFF)
{
	Thread::sleep(150) // give persistence a chance to catch up
    val recentUpdates = zwogup.members.filter[light|light.lastUpdate != null && light.lastUpdate.isAfter(now.minusSeconds(1).millis)] // all sensors that changed state in the last secon
 	recentUpdates.forEach[light|
 	postUpdate(light.name+"_LUP", light.lastUpdate.toString)
 	postUpdate(OH_ZWOGUP_LCH, light.lastUpdate.toString)	
 	postUpdate(OH_ZWOGUP_LCH_TXT, light.name.toString)	

 	
 	if (light.changedSince(light.lastUpdate.toDateTime.minusSeconds(2)))
 	{
 	postUpdate(light.name+"_LCH", light.lastUpdate.toString)
	}
	]  
	
}
end

I am running Snapshot 2.2.0, Build 1036.

Apologies for reviving an old thread, but I found that the above solutions can now be significantly simplified. I think this may be due to the ‘new’ Rule features Member of and triggeringItem available in 2.3.

In a groups.items:

// Apply the Group gRecordLastUpdate to any Item you want to track the last update of
Group:Number:AVG		gRecordLastUpdate "Record Last Update" <time>

// Apply the Group gLastUpdate to virtual DateTime Items created to store those last updates
Group:DateTime			gLastUpdate		"Last Updated [%1$tH:%1$tM:%1$tS]"

and in lastupdates.rules:

// This Rule triggers when any Item in the gRecordLastUpdate Group is updated, and
// updates a virtual DateTime item with the same name as the Item but with _LUD as
// a suffix, setting it to the current date and time.
rule "Record Last Update"
when
  Member of gRecordLastUpdate received update
then
  // post an update to the item with the same name and _LUD suffix
  sendCommand( triggeringItem.name+"_LUD", now.toString)
end

Finally, any of your .items files you can add some Items and their corresponding _LUD suffix virtual DateTime Items which will be updated by the Rule whenever the real Item is changed:

Switch 		My_Test		 "Test Switch"								<smiley>	(gRecordLastUpdate)
DateTime 	My_Test_LUD	 "Test Last Update [%1$tH:%1$tM:%1$tS]" 	<time>		(gLastUpdate)
Number 		My_Test2	 "Test Number"								<smiley>	(gRecordLastUpdate)
DateTime 	My_Test2_LUD "Test Last Update [%1$tH:%1$tM:%1$tS]" 	<time>		(gLastUpdate)
6 Likes

You are right, I already changed my rules back in February this year and use since then Member of and triggering item. But I have to admit I never published an update to this thread.

Alba, thanks for the data. I have done this but I have a group of family members who I only want to update the Last Seen time frame if they are on the local network. My Daughter is away at college but with this approach, the time still updates when she is not here which defeats the purpose. I have included an image. What I need to do is find some ‘if clause’ that checks if she is on the network but I am new to this and not sure how to do that. Does this make sense?

Thanks

Craig

It’s almost unbelievable to me. In your code,
if i replace the _LUD suffix with any small capital letter in it (and on the items name)
ie.

My_Test_LUpd "Test Last Update [%1$tH:%1$tM:%1$tS]" <time> (gLastUpdate)
and
sendCommand( triggeringItem.name+"_LUpd", now.toString)

the sendCommand does not work !!! The item is not updated.

if i replace the _LUD suffix with any CAPITAL letters or numbers
ie.

My_Test_LUPD1 "Test Last Update [%1$tH:%1$tM:%1$tS]" <time> (gLastUpdate)
and
sendCommand( triggeringItem.name+"_LUPD1", now.toString)

IT WORKS !!! i see the item updated

Totally lost here… I am on OH2.3