When Astro triggers an event it toggles the switch ON and then immediately OFF. Try changing the Astro event triggers to received update ON or (I’ll update the code above accordingly.
I also see a typo in the code above. There should be no “Twilight” period.
This is what happens when I try to distill my more complicated code into simpler examples. I’ll also correct it above.
Unfortunately this doesn’t explain what you are seeing. My working rule which has an extra time period and an extra Item I use to easily keep track of the previous time of day. I’ve pasted it below. It has been working for me reliably.
rule "Get time period for right now"
when
System started or
Time cron "0 0 6 * * ? *" or // Morning start
Item Sunrise_Event received update ON or // Day start
Item Twilight_Event received update ON or // Twilight start
Item Sunset_Event received update ON or // Evening start
Time cron "0 0 23 * * ? *" // Night start
then
Thread::sleep(50) // lets make sure we are just a little past the time transition
val morning = now.withTimeAtStartOfDay.plusHours(6).millis
val sunrise = new DateTime((Sunrise_Time.state as DateTimeType).calendar.timeInMillis)
val twilight = new DateTime((Twilight_Time.state as DateTimeType).calendar.timeInMillis)
val evening = new DateTime((Sunset_Time.state as DateTimeType).calendar.timeInMillis)
val night = now.withTimeAtStartOfDay.plusHours(23).millis
var currPeriod = "Night"
if(now.isAfter(morning) && now.isBefore(sunrise)) currPeriod = "Morning"
else if(now.isAfter(sunrise) && now.isBefore(twilight)) currPeriod = "Day"
else if(now.isAfter(twilight) && now.isBefore(evening)) currPeriod = "Twilight"
else if(now.isAfter(evening) && now.isBefore(night)) currPeriod = "Evening"
if(TimeOfDay.state.toString != currPeriod) {
logInfo(logNameWeather, "Updating Time of Day {}, Previous Time of Day {}", TimeOfDay.state.toString, currPeriod)
PreviousTimeOfDay.sendCommand(TimeOfDay.state.toString)
TimeOfDay.sendCommand(currPeriod)
}
end
Hey @rlkoshak, I really like this solution. I just feel it does however not fall into the category of Design Patterns. Would you agree? I would call it a snippet, similar to the two solutions already posted here. In comparison your solution is quite neat!
I put it as a Design Pattern because it can be augmented beyond just Time of Day tracking. It could also be used for Day tracking, it can be used to develop a rule cascade (e.g. something that can be used for controlling one’s irrigation zones), and I’m currently working through using this approach to develop a generic state machine (one that isn’t as much work or more than just custom coding for one’s particular problem).
Perhaps if I illustrate a different non-time based example or added more text to show its generic nature.
2016-10-23 05:08:00.211 [INFO ] [openhab.model.script.ToD.rule1] - Setting TimeOfDay to "Day"
2016-10-23 05:08:00.221 [INFO ] [openhab.model.script.ToD.rule1] - Setting TimeOfDay to "Day"
2016-10-23 16:31:01.625 [INFO ] [openhab.model.script.ToD.rule1] - Updating Time of Day Day, Previous Time of Day Twilight
2016-10-23 18:01:00.999 [INFO ] [openhab.model.script.ToD.rule1] - Updating Time of Day Day, Previous Time of Day Evening
2016-10-23 22:00:00.218 [INFO ] [openhab.model.script.ToD.rule1] - Updating Time of Day Day, Previous Time of Day Evening
2016-10-24 16:32:01.522 [INFO ] [openhab.model.script.ToD.rule1] - Updating Time of Day to Day, Previous Time of Day was Twilight
2016-10-24 18:02:01.077 [INFO ] [openhab.model.script.ToD.rule1] - Updating Time of Day to Day, Previous Time of Day was Evening
It is always Day… took your code verbatim, and only changed the wording of the log info…
What I have noticed is that sunrise is before morning… morning and night come up as millis, while the others are date/time stamps. Can OH compare these without casting them first?
I have noticed one error in my setup now that I’ve been home to notice (it was a busy week this past week) in that it is not transitioning to Night for me. I do need to look into that but that does not address what you are seeing. Here is my Time Of Day logs for the past few days:
2016-10-21 06:00:00.053 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Evening, Previous Time of Day Morning
2016-10-21 07:17:00.060 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Morning, Previous Time of Day Day
2016-10-21 16:40:00.079 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Day, Previous Time of Day Twilight
2016-10-21 18:10:00.058 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Twilight, Previous Time of Day Evening
2016-10-22 06:52:52.439 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Evening, Previous Time of Day Morning
2016-10-22 07:18:00.062 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Morning, Previous Time of Day Day
2016-10-22 16:39:00.063 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Day, Previous Time of Day Twilight
2016-10-22 18:09:00.058 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Twilight, Previous Time of Day Evening
2016-10-23 06:00:00.093 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Evening, Previous Time of Day Morning
2016-10-23 07:19:00.116 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Morning, Previous Time of Day Day
2016-10-23 16:38:00.060 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Day, Previous Time of Day Twilight
2016-10-23 18:08:00.060 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Twilight, Previous Time of Day Evening
2016-10-24 06:00:00.059 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Evening, Previous Time of Day Morning
2016-10-24 07:20:00.086 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Morning, Previous Time of Day Day
2016-10-24 16:36:00.064 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Day, Previous Time of Day Twilight
2016-10-24 18:06:00.057 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Twilight, Previous Time of Day Evening
2016-10-25 06:00:00.056 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Evening, Previous Time of Day Morning
2016-10-25 07:21:00.058 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Morning, Previous Time of Day Day
That can happen if your sunrise is before 06:00 which it is indeed for me some parts of the year for me, but not at the moment. There might be a bug there, though I as pretty sure I had accounted for that in this rule. Since the actual time the sun comes up moves around (not to mention the daylight savings nonsense) the rule should account for this. It should just skip over Morning entirely if Sunrise is before Morning starts.
That is indeed an inconsistency. They should all be the same. However it works as is because the Joda DateTime class has two versions on .isAfter and .isBefore, one that takes a long (i.e. milliseconds) and one that takes a DateTime (really a parent of DateTime but let’s not bring OO programming and inheritance into this right now). It really isn’t OH that is doing the comparison, it’s the Joda DateTime class.
Updates to make them consistent.
val morning = new DateTime(now.withTimeAtStartOfDay.plusHours(6).millis)
val sunrise = new DateTime((Sunrise_Time.state as DateTimeType).calendar.timeInMillis)
val twilight = new DateTime((Twilight_Time.state as DateTimeType).calendar.timeInMillis)
val evening = new DateTime((Sunset_Time.state as DateTimeType).calendar.timeInMillis)
val night = new DateTime(now.withTimeAtStartOfDay.plusHours(23).millis)
I need to think some more on why when I run I’m failing to transition to night and for you it is stuck as Day. What is weird is the “Previous Time of Day” does seem to be changing some.
Are you certain the rule is copied in verbatim? It almost looks like PreviousTimeOfDay is being updated twice and TimeOfDay is not or something like that.
if(TimeOfDay.state.toString != currPeriod) {
logInfo(logNameWeather, "Updating Time of Day {}, Previous Time of Day {}", TimeOfDay.state.toString, currPeriod)
PreviousTimeOfDay.sendCommand(TimeOfDay.state.toString)
TimeOfDay.sendCommand(currPeriod)
}
EDIT: Wait, I am seeing weirdness in the timestamps in my logs, though the behavior of my other rules seems to be correct. Investigating…
EDIT 2: My bad, I had the arguments passed to the log statement backward. doh! It should be:L
logInfo(logNameWeather, "Updating Time of Day {}, Previous Time of Day {}", currPeriod, TimeOfDay.state.toString)
So in your logs, “Previous Time of Day” is actually the current and visa versa.
EDIT 3: I do see that indeed you should never see a morning since your sunrise is well before 06:00. And based on the error I see in my log statement I see that it is your PreviousTimeOfDay that is stuck at Day and your TimeOfDay is flipping between Twilight and Evening. That is still very wrong but another data point.
EDIT 4: The lack of the transition to Night may have something to do with the problem described here:
I’ve changed my trigger to use only one cron trigger:
Time cron "0 0 6,23 * * ? *"
We will see if that fixes that one problem tonight.
OK, adjusting the cron trigger as described above seems to have addressed the no Night problem I was seeing.
Here is the log from the past day and the rule as it exists.
2016-10-25 07:21:00.058 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Morning, Previous Time of Day Day
2016-10-25 16:35:00.059 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Twilight, Previous Time of Day Day
2016-10-25 18:05:00.061 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Evening, Previous Time of Day Twilight
2016-10-25 23:00:00.058 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Night, Previous Time of Day Evening
2016-10-26 06:00:00.060 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Morning, Previous Time of Day Night
2016-10-26 07:22:00.101 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Day, Previous Time of Day Morning
rule "Get time period for right now"
when
System started or
Time cron "0 0 6,23 * * ? *" or // Morning start, Night start
Item Sunrise_Event received update ON or // Day start
Item Twilight_Event received update ON or // Twilight start
Item Sunset_Event received update ON // Evening start
then
Thread::sleep(50) // lets make sure we are just a little past the time transition
val morning = new DateTime(now.withTimeAtStartOfDay.plusHours(6).millis)
val sunrise = new DateTime((Sunrise_Time.state as DateTimeType).calendar.timeInMillis)
val twilight = new DateTime((Twilight_Time.state as DateTimeType).calendar.timeInMillis)
val evening = new DateTime((Sunset_Time.state as DateTimeType).calendar.timeInMillis)
val night = new DateTime(now.withTimeAtStartOfDay.plusHours(23).millis)
var currPeriod = "ERROR"
if (now.isAfter(morning) && now.isBefore(sunrise)) currPeriod = "Morning"
else if(now.isAfter(sunrise) && now.isBefore(twilight)) currPeriod = "Day"
else if(now.isAfter(twilight) && now.isBefore(evening)) currPeriod = "Twilight"
else if(now.isAfter(evening) && now.isBefore(night)) currPeriod = "Evening"
else if(now.isAfter(night)) currPeriod = "Night"
if(TimeOfDay.state.toString != currPeriod) {
logInfo(logNameWeather, "Updating Time of Day {}, Previous Time of Day {}", currPeriod, TimeOfDay.state.toString)
PreviousTimeOfDay.sendCommand(TimeOfDay.state.toString)
TimeOfDay.sendCommand(currPeriod)
}
end
Compare your version of the rule to the above. Pay particular attention to the if(TimeOfDay.state.toString != currPeriod) section. Based on your logs I suspect the problem lies there.
I added a Complicated Example showing how the design pattern applies to other non-time based sequences, in this case controlling the zones of a sprinkler system. In this case the only time based event is the one that kicks off the watering. The rest of the events occur when the Zones turn off. A separate rule get triggered by the Irrigation Item’s state changes.
… well, it depends… maybe complicated in OH
No, as always, I appreciate your work, and more so sharing of ideas and patterns… as well as replying to noobs like me…
Good Time of Day Design Pattern example rick… added some extra comments here and there as I learned the rule. Let me know if you want those.
With the latest OH2, I found I needed to encapsulate the information as such:
val long sunset_start = new DateTime((Astro_Sun_Set_Time.state as DateTimeType).calendar.timeInMillis).millis
val long sunrise_start = new DateTime((Astro_Sun_Rise_Time.state as DateTimeType).calendar.timeInMillis).millis
for your sunrise and evening values. Did you have to do this to your rules as well?
Actually I’m my current version, which I thought I had posted here, I don’t use the DateTime objects at all any more and just use the milliseconds. There is no reason to create objects as now.isBefore etc all can handle epoc as well.
Upon looking back I do see that I posted it but only in the Astro 2.0 section.
I tried to follow what you did in the OH2 section, but the only solution that I found that properly set the variables with the milliseconds was that convoluted mess. When I did
(Astro_Sun_Set_Time.state as DateTimeType).calendar.timeInMillis
without the extra stuff around it, it complained about calendar not having a timeInMillis field that it could find. Does it look okay to you?
Also, love the trick of doing the offset by changing the geolocation!
Your rule calls now more than once. Although every call will only differ some milliseconds, from a design pattern perspective it would be stricter to declare a val at the top of the rule that calls now once and use that value across the rule instead of now.
It would but I would consider that a micro optimization which is something I typically do not worry about unless and until I actually experience performance, logic, or timing problems. For a rule that executes five times over the course of the day and even with all the calls to now ends up only taking a hundred milliseconds or so to run I wouldn’t worry.
One could argue that it might make the rule more clear and easier to understand in which case I would consider the change.
However, in this case I do not see how defining a new variable to replace the calls to now would actually clarify anything.
Because I can’t sendCommand(null). It will generate an error. I could use curr = NULL which would set the state of vTimeOfDay to undefined but that would mean that whereever I check vTimeOfDay for some reason I’d need to check if it is NULL in addition to checking if it is “Evening”, for example. By using the String “UNKNOWN” I can avoid those extra checks and since the “UNKNOWN” state doesn’t drive any behavior it will not negatively impact any of my rules.
Honestly, “UNKNOWN” should never actually be sent to to the vTimeOfDay unless there is an error in the rule. I use it mainly to catch errors without breaking my other rules.
rule MyRule
when
Item TimeOfDay received command "AFTERNOON"
then
// do stuff
end
but it doesn’t seem to trigger. I know it is possible to use received command ON for switches. Is it possible at all to use a string after the received command? Or is it only possible to use
rule MyRule
when
Item TimeOfDay received command
then
switch(receivedCommand) {
case "AFTERNOON" : // do some stuff
case "BED" : // do other stuff
}
end