Compare an epoch with now's epoch

Hi All,

I’m trying to compare an epoch date with now to understand if an event will happen in the next hour, the rule i have is:

rule "notify ISS Pass"
when 

// run every 10 minutes
    Time cron "0 0/10 * 1/1 * ? *" then

	if timestampEpoch - now.getMillis() < 3600000 {
	sendNotification("mat@xxx.co.uk", "ISS Pass at " IssPass.state.ToString)
	}
end

timestampEpoch is a variable defined in a previous rule and should be in millis.

I’m trying to understand if it is less than an hour away by comparing with the now time in millis.

Any ideas.

What is the problem?
What error message do you get?

btw: you might want to hide your mail address. :wink:

The error in the log is

2018-06-20 22:47:46.846 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model 'Default.rules' has errors, therefore ignoring it: [162,5]: missing '(' at 'timestampEpoch'
[162,48]: missing ')' at '{'
[163,58]: missing ')' at 'IssPass'
[163,80]: extraneous input ')' expecting '}'

Right made some progress. I was a moron and forgot to put the condition in brackets.

rule "notify ISS Pass"
when 

// run every 10 minutes
    Time cron "0 0/10 * 1/1 * ? *" then

	if (timestampEpoch - now.getMillis() < 3600000) {
	sendNotification("mat@xxxx.co.uk", "ISS Pass at " IssPass.state.ToString)
	}
end

Now seems to be errors on the notification, understandable as I sort of guessed the syntax

2018-06-20 22:50:22.552 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model ‘Default.rules’ has errors, therefore ignoring it: [163,58]: missing ‘)’ at ‘IssPass’
[163,80]: extraneous input ‘)’ expecting ‘}’

Missing string concatenation operator, and method is .toString…

sendNotification("mat@xxxx.co.uk", "ISS Pass at " + IssPass.state.toString)

Cheers. Do we think the date comparison will work. I need to do some more debugging tomorrow.

Well the next pass is at 8.45, so I guess i will get a notification around 7.45 anyway

VSCode is really good at telling us when we make these sorts of mistakes.

It is also great at helping you with the syntax.

Typically we will keep the timestamps as is (i.e. don’t convert to epoch) and do something like

if(timestamp.isBefore(now.minusHours(1))

Then there is no need to mess with raw millis and such. Since timestampEpoch is in millis, I’m guessing you saved it using now.getMillis.

Again, VSCode is great at telling you all the methods that are available on Objects like now.

Where do you get the IssPass Information from?

Today i manually take a look on https://www.heavens-above.com/PassSummary.aspx?satid=25544&lat=0&lng=0&loc=Unspecified&alt=0 to find visibly passes, but a solution like yours would very very nice.

Greetings

rikoshak,

For some reason VSCode wasn’t highlighting it. I’ve updated to the most recent minor version and that seems to help.

TimestampEpoch came from an external JSON query, maybe I can tidy it up, thanks for the tip.

I worked out the missing concatenation. Cheers for your help.

The rule is now working nicely, it fired this morning as expected (every 10 minutes) and needs just a bit of finessing to only fire once and to fire near or after sunset. Should be straightforward.

@lubeda,

I will copy in my items and rules tonight, was meaning to put a post together for it anyway once everything is sorted.

Mat

Hi i said i would give quick breakdown of how i’ve done this notification:

Items

String IssPass "Next ISS Pass [%s]" <iss>
Number IssPass_Epoch "Next ISS Pass [%d]" { http="<[http://api.open-notify.org/iss-pass.json?lat=53.0000&lon=1.000:60000:JSONPATH($.response[:1].risetime)]" }

then I have these rules

//convert ISS pass from Epoch to human

rule "Parse ISS Epoch"	
when
   	System started or
	Item IssPass_Epoch changed then

        val timestampEpoch = (IssPass_Epoch.state as Number).longValue*1000
		val SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss' 'dd-MMM")
        val String timestampString  = sdf.format(new Date(timestampEpoch))
		IssPass.postUpdate(timestampString)

end

rule "notify ISS Pass"
when 

// run every 10 minutes
    Time cron "0 0/10 * 1/1 * ? *" then

	val timestampEpoch = (IssPass_Epoch.state as Number).longValue*1000

	if (timestampEpoch - now.getMillis() < 3600000  && timestampEpoch - now.getMillis() > 0 )  {
	sendNotification("mat@randomemail.co.uk", "ISS Pass at " + IssPass.state.toString)
	}
end

The first takes the epoch date returned to turn it into a human readable

The second needs some work, it looks every 10 minutes to see if there is a pass coming soon and sends a notification.

To Do list:

It triggers every 10 minutes when a pass is coming up, that needs stopping
It probably needs to be set to only trigger when sunset is past or near

1 Like

Oh by the way i got an icon from here for the satelite, copied SVG and PNG 32x32 to the icons folder and renames iss (careful, openhab doesn’t like upper case icons)

Much better to use:

DateTime IssPass "Next ISS Pass [%1$tH:%1$tM:%1tS %1td-%1$tm]" <iss>

Why go out of your way to build a String when you can keep it as a date and then be able to use it as such in your Rules without needing to parse the String back to a DateTime object of some sort? At a minimum it lets us do in one line what took four lines of code using SimpleDateFormat.

The label above lets you format the DateTime like you want. In this case it formats it the same as you defined in your Rule. It uses the Java String Formatter syntax

Here is the Rule:

rule "Parse ISS Epoch"
when
    System started or // why?
    Item IssPass_Epoch changed
then
    IssPass.postUpdate(new DateTime((IssPass_Epoch.state as Number).longValue).toString)
end

What this does is take the Number stored in IssPass_Epoch and converts it to a primitive long (which is what DateTime requires) then uses the toString to get an ISO 8601 formatted date time string that the openHAB DateTimeType knows how to parse.

OK, next is why poll? You already recognize this may not be the best approach. This is what Timers are great for.

You can modify the above Rule to:

val Timer issTimer =null

rule "Process ISS Time"
when
    System started or // in this case we need this
    Item IssPass_Epoch changed
then
    val issPassTime = new DateTime(Long::parse:Long(IssPass_Epoch.state.toString))
    IssPass.postUpdate(issPassTime.toString)

    issTimer?.cancel // the ? skips this line is issTimer is null
    issTimer = createTimer(issPassTime.minusHours(1), [ |
        sendNotification("mat@randomemail.co.uk", "ISS Pass at " + IssPass.state.toString)
        issTimer = null
    ])
end

This will set the IssPass Item with the date time of the pass and creates a timer to send you a notification one hour before the received time.

You can use the Design Pattern: Time Of Day and use something like:

val Timer issTimer =null

rule "Process ISS Time"
when
    System started or // in this case we need this
    Item IssPass changed
then
    val issPassTime = new DateTime(IssPass.state.toString)

    issTimer?.cancel // the ? skips this line is issTimer is null
    issTimer = createTimer(issPassTime.minusHours(1), [ |
        if(vTimeOfDay.state == "NIGHT") sendNotification("mat@randomemail.co.uk", "ISS Pass at " + IssPass.state.toString)
        issTimer = null
    ])
end

In this case the Timer will fire one hour before the ISS is going to pass but only if it is currently NIGHT. We can get a little smarter and use something like the following, assuming the same Items from the Time of Day DP:

val Timer issTimer =null

rule "Process ISS Time"
when
    System started or // in this case we need this
    Item IssPass changed
then

    // Astro calculates the sunrise and sunset times for the current day a few seconds after midnight
    // so the naive approach of check if the current time is between sunset and sunrise won't work because
    // both values are for today.
    val issPassTime = new DateTime(IssPass.state.toString)   // when the ISS will pass
    val sunset = new DateTime(vSunset_Time.state.toString)   // sunset today
    val sunrise = new DateTime(vSunrise_Time.state.toString) // sunrise today

    // Skip setting the Timer if the ISS pass time will be during the day
    if(issPassTime.isAfter(sunrise) && issPassTime.isBefore(sunset)) return; // day time today
    if(issPassTime.isAfter(sunrise.plusDays(1)) return; // day time tomorrow, sunrise from today won't be too different from sunrise tomorrow

    issTimer?.cancel // the ? skips this line is issTimer is null
    issTimer = createTimer(issPassTime.minusHours(1), [ |
        sendNotification("mat@randomemail.co.uk", "ISS Pass at " + IssPass.state.toString)
        issTimer = null
    ])
end

I just typed in the code above. It might not be completely typo free.

@rlkoshak super useful. Will look at that tonight. This lot is all a bit beyond me, just bashing stuff together till it works.