Tracking runtime on a contact

I’m looking to add a simplistic runtime count to a existing contact sensor DTH but It’s turning out to not be that simplistic. Since I know current state and last state combined with last update time/date it shouldn’t be hard. For example each time the contact get updated (change, refresh, doesn’t matter) check if the last state was open. If so compare the last update time to the current update time and add in the difference. The logic flow is easy enough but getting the time out isn’t. I couldn’t find any decent documentation in SmartThings docs so I went out to the Groovy JDK. From there I can’t see anything that lets me do a simple (lastUpdateDate - Date) = xxx seconds as the only results are in days for math comparisons between them (from what I can tell). So based on that I think I have to:

Compare the date part from lastUpdateDate to current Date. If its the same day I then have to break out the HH:MM:SS for both and manually subtract to get a number that I can use and add that to runtime. If it’s not the same day I have to break out the same info and compare against midnight for the runtime from the day before then from there to the current time for the remainder.

I don’t need 100% accuracy so I will probably forgo the seconds part. Is this correct? Or should I store it more like:

lastUpdateDay
lastUpdateHour
lastUpdateMinute

So I can more easily do the comparisons and math? Or is there a simpler way that I’m missing completely?

If I understand you want to format the time of an event and/or duration. Bear with me as I’m traveling right now but have a look at this post I made a while back.

You can use Timecategory to calculate the number of Months, Days, Hours, minutes, etc. in a time stamp. This might get you where you need.

2 Likes

Moreover I want the duration between two time stamps but I looked at your other post and I see what you’re doing. I’ll play with it and see if it gets me what I need. Thank you.

2 Likes


Take a look at line 658 in module SHM delay child

1 Like

The key is to create 2 Date objects with your times. Then you ca use Timecategory to do the duration math if necessary, and also give the number of days, hours, etc. in the Date object. Good luck!

1 Like

So I got your code partially working. Didn’t realize the import has to go at the beginning based on your code fragment although being a VB.Net programmer I should have known so that was 30 minutes of fighting. Now the issue is I can get my lastUpdatedDate attribute to hold the date but it keeps saying it’s null and I don’t know why and maybe someone else can figure it out. Here is a snippet of the code so far:

def currentDate = new Date()
if (lastUpdatedDate == null) {sendEvent(name: 'lastUpdatedDate', value: currentDate, isStateChange: true)}   // THIS IS EXECUTING EVERY TIME SINCE IT THINKS ITS NULL
// Update runtime
if (device.latestValue("contact") == "open") {
	// If last value was open then we add to the runtime.  Doesn't matter if its still
	// open or closed as if our latestValue was closed we wouldn't do anything.
	use (groovy.time.TimeCategory) {
		//  Get the number of seconds difference
		log.debug("currentDate is: " + currentDate)
		log.debug("lastUpdatedDate is currently: " + lastUpdatedDate)  // THIS SAYS ITS NULL

		def myDuration = currentDate - lastUpdatedDate
		def mySeconds = myDuration.seconds
		// Then update the runtime
		log.debug("Current totalRuntime is " + totalRuntime + " and we are adding in " + mySeconds + " seconds")
		def myTotal = Integer.parseInt(totalRuntime) + mySeconds
		sendEvent(name: "totalRuntimeInSeconds",value: myTotal, displayed: false)
		def myTotalInMinutes = myTotal / 60
		sendEvent(name: "totalRuntimeInMinutes", value: String.valueOf(myTotalInMinutes), displayed: false)
	}
}

and here is the logging:

 9:11:05 AM: error groovy.lang.GroovyRuntimeException: Ambiguous method overloading for method java.util.Date#minus.
Cannot resolve which method to invoke for [null] due to overlapping prototypes between:
[class java.util.Date]
[class groovy.time.BaseDuration] @line 139 (doCall)
9:11:05 AM: debug lastUpdatedDate is currently: null
9:11:05 AM: debug currentDate is: Fri Mar 16 13:11:04 UTC 2018

So it’s failing because lastUpdatedDate is null which makes sense why its failing but I don’t understand why its null. The “if null then sendevent” part is executing every run. So I thought maybe it’s because I didn’t call the full “device.currentValue(‘lastUpdatedDate’)” so I tried that and it’s still null. In the device properties however it is updating:

contact: open
lastUpdated: Mar 15 at 8:11 AM
lastUpdatedDate: Fri Mar 16 13:21:04 UTC 2018

Why can’t I pull the value in?

EDIT: I started to think maybe I need to convert it to a date first so I tried:

def lastDate = new Date().parse("E MMM dd H:m:s z yyyy", device.currentValue("lastUpdatedDate"))

but the lastUpdatedDate still comes back null even though the value is properly stored in the device.

SECOND EDIT:

Now I’m super confused. I put this in for troubleshooting:

log.debug("device.currentvalue(lastUpdatedDate) using single quotes: " + device.currentValue('lastUpdatedDate'))
log.debug("device.currentvalue(lastUpdatedDate) using double quotes: " + device.currentValue("lastUpdatedDate"))
log.debug("device.latestValue(lastUpdatedDate) using single quotes: " + device.latestValue('lastUpdatedDate'))
log.debug("device.latestValue(lastUpdatedDate) using double quotes: " + device.latestValue("lastUpdatedDate"))
log.debug("device.lastUpdatedDate: " + device.lastUpdatedDate)
log.debug("lastUpdatedDate: " + lastUpdatedDate)

Every one comes back Null but device properties shows a valid date.

1 Like

So trying to figure all this out lead to lots of searching the forums and in the end I found a easier way, using the time property of a date to get the seconds since Jan 1 1970 and just comparing that. So:

def currentDate = new Date()
def myDifference = currentDate.time - device.currentValue("lastUpdatedTime")
def myTotal = device.currentValue("totalRuntimeInMilliseconds") + myDifference
sendEvent(name: "totalRuntimeInMilliseconds", value: myTotal, displayed: false, isStateChange:true)
sendEvent(name: "lastUpdatedDate", value: currentDate.time)

I then format the new totalRuntimeInMilliseconds based on a user preference for seconds, minutes, or hours to a string attribute which goes in a tile.

I’m guessing it has something to do with the fact I was storing the date as a date and pulling it back out was giving me trouble. Now I’m storing the milliseconds as a number and it seems a lot easier to retrieve. I’m sure this could be fixed but its simpler now anyway.

1 Like

And here is the DTH this was used in for anyone trying to do the same thing and searching for a answer. Hopefully it helps out someone else:

2 Likes