Rule for switching two lights from sunset to midnight both half of the time

I have two groups of outdoorlamps and what I want to achieve is the following: In the time between 30 minutes after sunset and midnight the two lampgroups should be on both half of the time, except the first group should switch off ten minutes after the second group is switched on.
After reading two hours of examples, joda time and astrobinding I only have a vague idea how this rule should be written. Especially the calculation with the times isn’t really clear to me.
Eventually someone can give an example, I also have the feeling I am thinking to complicated with this.

Thanks in advance!

Nico

Look at my lighting and my time of day postings on tge following thread. Automation/Orchestration Design Patterns

Thank you very much, I got a little bit further. Can I do something like the following?

var DateTime Lampgroup1_ON

rule "SwitchTimes"
when	
	Item Sunset_Time received update
then
	 Lampgroup1_ON = new DateTime(Sunset_Time.minusHours(23).minusMinutes(59))
	
rule "Lampgroug1_ON"
when
	Time is Lampgroup1_ON
then...

So first question is: Can I make DateTimevariables which are used for trigger actions when the Astrobinding updates the Sunset_Time?

Is that the right way to set the variables like the above?
One thing is of course wrong, Sunset_Time minus 23:59 gives the negative value, that has to go the other way round, but I don’t know how to do 23:59 minus Sunset_Time. And the rule is not complete, there have to be more operations to get the right value.
But I try to find an entry in the calculation. Eventually I have a error in reasoning?

You can create a Switch Item with an Astro binding and the switch will momentarily go to ON at that time. For example, in the link I provided above (posting 11) I have:

Switch      Sunset_Event                                (Weather)  { astro="planet=sun, type=set, property=start" }

This will cause Sunset_Event to momentarily switch to ON at the start of Sunset.

You will notice that I also have a DateTime Item for sunset in the example linked above but you can’t trigger on that because the Astro binding only updates that Item once at midnight and you cannot trigger a rule on a DateTime Item like you are trying to do above.

So, your “SwitchTimes” rule above will only run once every midnight when the Astron binding calculates the new Sunset_Time for the new day.

No, this isn’t right. Per the Rule’s wiki page you can only do Time triggers based on a couple of time of day keywords (noon, midnight) or a cron formatted string.

Actually no, the concept of this is correct. There are other problems with the statement, but when working with DateTimes the “minus*” methods handle moving over the midnight barrier for you so you don’t have to worry about negative numbers and whatnot.

I highly recommend getting Designer and using it to edit and create your rules. It will have told you about all of the mistakes above. For completeness:

  • To set a value for an Item in a rule you need to call sendCommand or postUpdate. So to update the value of the Lampgroup1_ON Item you would call something like postUpdate(Lampgroup1_ON, new DateTime()) or Lampgroup1_ON.postUpdate(now).
  • To get the value of an Item you need to get its state. And if you really need the value of the state to do math or transforms you need to cast it to the right type. So your command above would actually be: Lampgroup1_ON.postUpdate((Sunset_Time.state as DateTypeType).minusMinutes(23*60+59))
  • DateTimeType represents a single point in time, including date. So your “SwitchTimes” rule above currently triggers at midnight at the start of the new day. But even if it triggered like you expect, at sunset, the time calculated for Lampgroup1_ON would be for yesterday, not today or tomorrow so your Lampgroup1_ON" rule, assuming that the trigger worked the way that you want, would never execute. When calculating times for things to happen in the future you always have to go forward (i.e. plusMinutes) not backward.

If I understand what your intent is I think what you want is an Astro switch that triggers one minute before Sunset. You can do that with an Item and a rule.

Switch Lampgroup1_ON { astro="planet=sun, type=set, property=start", offset=-90, offset=-1" }

rule "Lampgroup1_ON"
when
    Item Lampgroup1_ON received command ON
then
    ...

In general, I strongly recommend reading the Rules wiki page, particularly the Time triggers section, install and use Designer, and read my example. These three things should take you far.

First of all thank you again for your support and especially your patience!
I have already the designer installed but I thin I just have some misunterstandings of the concepts…
My thought was when I define variables in he rule they can be reused when openhab is restarted - but as of writing these lines I realize that the variables aren’t in any persistence, so they also have no effect on restarting openhab. So let’s just forget about this bad idea.
I had found that on some things like noon can be triggered, so I wondered if it is possible to trigger on DateTime-Variables, but OK, that is not the case.

So for this calculated times I think the right way is to use a timer?
If i understand correct the way to go for my Idea is to use the principle of the Alarm clock I Example here.

I’ll try to describe what I like to Switch is this:

sunset_to_midnight: difference sunset_time - midnight
Lamgroup1_ON: Sunset_Time
Lampgroup1_OFF: Sunset_Time + (sunset_to_midnight/2) + 10min
Lampgroup2_ON: Sunset_Time + (sunset_to_midnight/2)
Lamgroup2_OFF: Midnight

The info that DateTime hase alway the complete timestamp in it is very helpful!
What I don’t know is where to get the basics of the language how to make this statements like this
var DateTime alarmTime = parse(now.getYear() + "-" + now.getMonthOfYear() + "-" + now.getDayOfMonth() + "T00:00")

I can read this, but where is the right documentation for the basics of this? I tried to find something at the xtext-docu but I think I’ve not got the right pages. And at the openhabwiki I found also nothing about the basics how to put these things together. So I am just trying to put some things from the examples together at the designer…

I think I can now get the most together, but wat isn’t clear to me is how I have to write the calculation of the difference of the sunset_time to midnight and get this results in minutes.

Update:
I tried to find out some thing and ended up in something like that (also not right and finished)
Lampgroup1_off = Lampgroup1_off.minus(Duration(Sunset_Time, now.plusDays(1).withTimeAtStartOfDay().getMinutes())
But with things like minutesBetween of joda time I also couldn’t get further. I found this thread but also using that last exampe in that thread
var DateTimeType dt = MyDateTimeItem.state as DateTimeType var int m = Minutes::minutesBetween(new DateTime(dt.calendar.timeInMillis), now).getMinutes()
throws errors, the first at DateTimeType.
This rule is driving me nuts at the moment…

I think the key is “now”. The “now” variable is one that you get for free in the rules and it is a Joda DateTime object that represents the time right now. Also, the key to working with time in openHAB is the Joda library, particularly the “now” variable.

Note, if you were able to accept something a little more static (e.g. shutting off the lamp at a fixed time of night rather than half way between sunset and midnight) below could be WAY simpler, implemented without even needing the rules.

I would implement your rules as follows.

Items:

Switch Lampgroup1_ON { astro="planet=sun, type=set, property=start" }
Switch Lampgroup1_OFF
Switch Lampgroup2_ON
Switch Lampgroup2_OFF

Rules:

var Timer Lampgroup1_OFF_Timer = null
var Timer Lampgroup2_ON_Timer = null

rule "Sunset"
when
    Item Lampgroup1_ON received command
then
    val long midnight = now.plusHours(23).withTimeAtStartOfDay.millis // get time at midnight tomorrow in milliseconds epoc
    val long halfway = (midnight - now.millis) / 2 // get number of milliseconds to halfway between now and midnight

    Lampgroup1_OFF_Timer = createTimer(now.plusMillis(halfway + (10*60*1000)), [ | Lampgroup1_OFF.sendCommand(OFF) ]) // Timer to send OFF command at Sunset + ((Sunset-midnight)/2) + 10 mins
    Lampgroup2_ON_Timer = createTimer(now.plusMillis(halfway), [| Lampgroup2_ON.sendCommand(ON) ]
end

rule "Midnight"
when
    Time midnight
then
    Lampgroup2_OFF.sendCommand(OFF)
    Lampgroup1_OFF_Timer = null
    Lampgroup2_ON_Timer = null
end

Actually I sort of misspoke as I would actually implement your rules following the design pattern I describe here. In short, I would have some time of day switches that get turned on and off based on certain times of day. Then I would trigger events or check what time I’m at by checking those switches. I’ve sort of done that here with how I’m triggering the Lampgroup switches, but I would make them generic (i.e. EarlyEvening, Evening, Night, Morning, etc) instead of having them tied to the lamps directly.

Ok, so I will more look after the now-variable and try to use this.
Of course, with the Astrobinding and some static values it would be easier, but half of the fun :wink:

So, I try to understand the statements:
val long midnight - create variable midnight with type long
now - get the actual time
.plushours(23) - shift over to the next day. Question here: would .plusDays(1) work also here?
.withTimeAtStartOfDay - now it returns the time of midnight at the shifted date
.millis the value is returned in milliseconds

(midnight - now-millis) / 2 - so simple calculationoperations are just done this way if i have the variables in the right and compatible format? That’s nice.

The installation where I have the Lampgroups have until now only this two Lampgroups, a third which is manually switched and another with static times. Therefore I tied it directly at the moment. But you are right, as the system can grow it should be more generic to have it more clearly arranged.

Thanks again, I’ll try to play around some more with the now-variable. The right documentation for the joda time and operations around now seems to be this.

The Designer was complaining about now.plusMillis(halfway) uses int but gets long.
Therefore I changed it to now.plus(halfway) as plus uses long instead of int. The designer shows no error any more, I’ll watch, if it is working this evening.

So, it seems to work till some point.

what my rule looks like now:

var Timer Lampgroup1_OFF_Timer = null
var Timer Lampgroup2_ON_Timer = null

rule "Sunset"
when
	Item Sunset_Event_plus30min received update ON
then
	val long midnight1 = now.plusHours(23).withTimeAtStartOfDay.millis // get time at midnight tomorrow in milliseconds epoc
    val long halfway = (midnight1 - now.millis) / 2 // get number of milliseconds to halfway between now and midnight

	sendCommand(Switch_Lights_Frontgarden_1, ON)
    Lampgroup1_OFF_Timer = createTimer(now.plus(halfway + (10*60*1000)), [ | Switch_Lights_Frontgarden_1.sendCommand(OFF) ]) // Timer to send OFF command at Sunset + ((Sunset-midnight)/2) + 10 minutes
    Lampgroup2_ON_Timer = createTimer(now.plus(halfway), [| Switch_Lights_Frontgarden_2.sendCommand(ON) ])
end

The first trigger works, but I think there is a problem with the timers, because I got the following errors in the log:

2016-01-03 20:32:31.046 [ERROR] [org.quartz.core.JobRunShell   ] - Job DEFAULT.2016-01-03T20:32:31.023+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: org.eclipse.xtex
t.xbase.impl.XClosureImplCustom@2bb5085 (explicitSyntax: true) threw an unhandled Exception:
java.lang.RuntimeException: The name '<unkown>' cannot be resolved to an item or type.
        at org.openhab.model.script.interpreter.ScriptInterpreter.internalFeatureCallDispatch(ScriptInterpreter.java:67) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._evaluateAbstractFeatureCall(XbaseInterpreter.java:658) ~[na:na]
        at sun.reflect.GeneratedMethodAccessor61.invoke(Unknown Source) ~[na:na]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_66]
        at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_66]
        at org.eclipse.xtext.util.PolymorphicDispatcher.invoke(PolymorphicDispatcher.java:291) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:218) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._evaluateMemberFeatureCall(XbaseInterpreter.java:545) ~[na:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_66]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_66]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_66]
        at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_66]
        at org.eclipse.xtext.util.PolymorphicDispatcher.invoke(PolymorphicDispatcher.java:291) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:218) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._evaluateBlockExpression(XbaseInterpreter.java:321) ~[na:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_66]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_66]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_66]
        at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_66]
        at org.eclipse.xtext.util.PolymorphicDispatcher.invoke(PolymorphicDispatcher.java:291) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:218) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:204) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:28) ~[na:na]
        at com.sun.proxy.$Proxy89.apply(Unknown Source) ~[na:na]
        at org.openhab.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:44) ~[na:na]
        at org.quartz.core.JobRunShell.run(JobRunShell.java:213) ~[quartz-all-2.1.7.jar:na]
        at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:557) [quartz-all-2.1.7.jar:na]
2016-01-03 20:32:31.053 [ERROR] [org.quartz.core.ErrorLogger   ] - Job (DEFAULT.2016-01-03T20:32:31.023+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: org.eclipse.xte
xt.xbase.impl.XClosureImplCustom@2bb5085 (explicitSyntax: true) threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception.
        at org.quartz.core.JobRunShell.run(JobRunShell.java:224) ~[quartz-all-2.1.7.jar:na]
        at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:557) [quartz-all-2.1.7.jar:na]
Caused by: java.lang.RuntimeException: The name '<unkown>' cannot be resolved to an item or type.
        at org.openhab.model.script.interpreter.ScriptInterpreter.internalFeatureCallDispatch(ScriptInterpreter.java:67) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._evaluateAbstractFeatureCall(XbaseInterpreter.java:658) ~[na:na]
        at sun.reflect.GeneratedMethodAccessor61.invoke(Unknown Source) ~[na:na]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_66]
        at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_66]
        at org.eclipse.xtext.util.PolymorphicDispatcher.invoke(PolymorphicDispatcher.java:291) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:218) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._evaluateMemberFeatureCall(XbaseInterpreter.java:545) ~[na:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_66]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_66]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_66]
        at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_66]
        at org.eclipse.xtext.util.PolymorphicDispatcher.invoke(PolymorphicDispatcher.java:291) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:218) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._evaluateBlockExpression(XbaseInterpreter.java:321) ~[na:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_66]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_66]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_66]
        at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_66]
        at org.eclipse.xtext.util.PolymorphicDispatcher.invoke(PolymorphicDispatcher.java:291) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:218) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:204) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:28) ~[na:na]
        at com.sun.proxy.$Proxy89.apply(Unknown Source) ~[na:na]
        at org.openhab.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:44) ~[na:na]
        at org.quartz.core.JobRunShell.run(JobRunShell.java:213) ~[quartz-all-2.1.7.jar:na]
        ... 1 common frames omitted

So I don’t know exactly what this should mean, eventually the timer has a problem with the variabletype?

The same 2 errors appear 10 minutes later (so both timers seem to work so far).

Yes but you are missing one key point. midnight will contain the number of milliseconds between midnight January 1st, 1970 (often called epoch) and midnight tomorrow.

now is Joda DateTime that represents the current time so yes, it gets the actual current time.

.plusHours(23) returns a new Joda DateTime to 23 hours from now. Using plusDays(1) would probably work too.

.withTimeAtStartOfDay returns a new Joda DateTime set to midnight of the day of the .plusHours(23) DateTime.

As you hopefully surmise all of this was to get a DateTime for midnight tomorrow.

And as with the above, the .millis returns the number of milliseconds between epoch and midnight tomorrow.

Both midnight and no-millis are both just big numbers so yes, simple math is easy. The (midnight-now-millis) will return the number of milliseconds between now and midnight tomorrow and the “/2” get the midpoint.

The error is complaining that it cannot resolve Switch_Lights_Frontgarden_1 and Switch_Lights_Frontgarden_2. Are you sure these Items exist and appear in your Items file just as typed here? A Timer should have access to everything that existed at the time of its creation which includes a reference to all your Items so something is going wrong there.

You can try switching to sendCommand and see if that makes a difference but I doubt it will.

sendCommand(Switch_Lights_Frontgarden_1, ON)

Ok, so some kind of strange now.
I edited with the designer and I am sure, this item exists, as they are statet the same in the midnight rule, which powers them off.

I changed the rule a little bit to this:

rule "Sunset"
when
	Item Sunset_Event_plus30min received update ON
then
	// val long midnight1 = now.plusHours(23).withTimeAtStartOfDay.millis // get time at midnight tomorrow in milliseconds epoc
    // val long halfway = (midnight1 - now.millis) / 2 // get number of milliseconds to halfway between now and midnight
    val int midnight1 = now.plusHours(23).withTimeAtStartOfDay.millis.intValue // get time at midnight tomorrow in milliseconds epoc
    val int halfway = (midnight1 - now.millis.intValue) / 2 // get number of milliseconds to halfway between now and midnight

	sendCommand(Switch_Lights_Frontgarden_1, ON)
    Lampgroup1_OFF_Timer = createTimer(now.plus(halfway + (10*60*1000)), [ | Switch_Lights_Frontgarden_1.sendCommand(OFF) ]) // Timer to send OFF command at Sunset + ((Sunset-midnight)/2) + 10 minutes
    Lampgroup2_ON_Timer = createTimer(now.plus(halfway), [| Switch_Lights_Frontgarden_2.sendCommand(ON) ])
end

and the rule is working now. I am running 1.8 Snapshot at the moment.
Is it really a problem with the type?
Is it a problem which come up sporadically?

I don’t see the actual change made but if it works great!

I don’t know what the problem is but you seem to have cleared it up with whatever you changed. I doubt it will reoccur until you change something.

I changed from long to integer:

val long midnight1 = now.plusHours(23).withTimeAtStartOfDay.millis -> val int midnight1 = now.plusHours(23).withTimeAtStartOfDay.millis .intValue
and
val long halfway = (midnight1 - now.millis) / 2 -> val int halfway = (midnight1 - now.millis.intValue) / 2

That’s all I changed. I thought so becaus of “java.lang.RuntimeException: The name ‘’ cannot be resolved to an item or type.” and because it can’t be the item which exists I thought it was the time. The most examples with createtimer where with int, haven’t found really something with long. So I hoped this could work which seems so at the moment.

It has to be long. int isn’t big enough to hold the number. The current epoch as of this writing is around 1,451,938,795,000. An int can hold nothing larger than 2,147,483,647.

If you want to use an int we can do some math on it to convert it to seconds but this really is not necessarily related to the error.

To do seconds instead of milliseconds (which seems to fit in an int, at least for another 700,000 seconds or so) change the code as follows:

val int midnight = (now.plusHours(23).withTimeAtStartOfDay.millis / 1000).intValue
val int halfway = ((midnight - (now.millis / 1000)) / 2).intValue

...
... createTimer(now.plusSeconds(halfway + 600), [ | ...

However, it might be that the following will work too. There might be some weirdness going on with the way the rules engine likes to change everything into a BigNumber object without telling you.

val long midnight = (now.plusDays(1).withTimeAtStartOfDay.millis).longValue
val long halfway = ((midnight - now.millis.longValue) / 2).longValue

...
... createTimer(now.plus(halfway + 600000), [| ...

I thought too int is to small, but however it was working a short while ago…

When the rulesengine likes to change everything into BigNumber there might be a reason for the problem.
I’ll try it with the longValue tomorrow and see how it is working, hopefully it will do the trick and be a clean solution. Thank you, I’ll tell you as soon as I tested it!

With LongValue it is working as expected! Thank you!