Design Pattern: Time Of Day

jodatime
designpattern
time
Tags: #<Tag:0x00007f0148751d68> #<Tag:0x00007f0148751b38> #<Tag:0x00007f0148751980>

(druciak) #182

You’ll have issues when sun rises after 6:00.


(CM6.5 H102) #183

Ah, so thats why I never seen “MORNING” with original rule. Thanks for the explanation, it makes good sense for your purpose.

If I have no need for lights to automatically turn on that early but still wanted to use the morning/day/evening/night time periods, would removing “if (day_start < morning_start) { new_val = MORNING }” continue work?
Btw, val String day_start = 08:00
Thanks


(Rich Koshak) #184

I suggest that you write down each of your Times of Day states. Then put the start time for each time. If it is driven by an event from Astro for example, put the earliest the event can occur and latest the event can occur.

Now look for overlaps. If your DAY event occurs before the MORNING time then you will never get a MORNING state. Everything is driven by the start times.

If you always want to have a MORNING, then you must always have the start event for MORNING happen before DAY.

In this case when day_start < morning_start you will never have a DAY event.


(CM6.5 H102) #185

Here’s what I have so far:

rule "Calculate time of day state"
when
    Time cron "0 * * * * ? *"
then
    val String curr_time = now.toString("HH:mm")
    val String night_start = "00:00"
    val String day_start = "08:30"
    val String morning_start = (Sun_Dawn_Start.state as DateTimeType).format("%1$tH:%1$tM")
    val String evening_start = (Sun_Dusk_End.state as DateTimeType).format("%1$tH:%1$tM")
    val String midnight_time = "24:00"

    var new_val = "UNKNOWN"

//    if (day_start < morning_start) {
//        morning_start = day_start
//    }
    switch true {
        case night_start <= curr_time && curr_time < morning_start:   new_val = "NIGHT"
        case morning_start <= curr_time && curr_time < day_start:     new_val = "MORNING"
        case day_start <= curr_time && curr_time < evening_start:     new_val = "DAY"
        case evening_start <= curr_time && curr_time < midnight_time: new_val = "EVENING"
    }
//  logInfo("Time_Of_Day", "curr_time=" + curr_time + ", night_start=" + night_start + ", morning_start=" + morning_start
//      + ", day_start=" + day_start + ", evening_start=" + evening_start + ", midnight_time=" + midnight_time
//      + ", new_val=" + new_val)


    if (Time_Of_Day.state.toString != new_val) {
        logInfo("Time_Of_Day", "Current time of day is now " + new_val)
        Time_Of_Day.sendCommand(new_val)
    }

end

I want my day to start at 08:30 and I’m good with the morning being started via the Astro event (05:42). Note, I have the day_start < morning_start commented out. If I’m understanding your suggestion this should work correct?
Thanks


(Rich Koshak) #186

Why are you running this every second? Far far better to set up your cron to trigger the Rule as needed (when these events occur and at System start.

As long as Sun_Dawn_Start is no later than 08:30 then these Rules will work. If Sun_Dawn_Start ever is later than 08:30 then MORNING will be skipped.


(druciak) #187

This is every minute, not second :wink:
As I have already explained I had issues with astro triggers, so I recaclulate every minute. I guess @H102 just copied it from my original rule…


(CM6.5 H102) #188

Thanks for pointing out the every one second. I set to run with Astro triggers and per cron time needed but no luck with the Astro. Guess I’ll adjust the cron time to “0 * * ? * * *” and see how things workout.
Thanks


(Rich Koshak) #189

druciak is right, I misread the cron. It is running once per minute.


(Kristof Rado) #190

@rlkoshak I’m a little bit confused now with this rule, can you please help me?
Somehow, today it seemed that this rule stopped working. I have restarted OH, now it is working again (or it seems), but now it says that the current time of day is afternoon… However I can’t see an afternoon tag in your example (and I never had before), but when I looked into the rule, there is a case for afternoon… How does this works? Why I get AFTERNOON now if I except to be EVENING?

Thanks!


(Rich Koshak) #191

For each time period you define a state. In the Rule above we have

State Start End
MORNING 06:00 Sunrise
DAY Sunrise Sunset - 90 minutes
AFTERNOON Sunset - 90 minutes Sunset
EVENING Sunset 11:00
NIGHT 11:00 06:00

The Rule first gets the start time for each of these time periods:

  val morning_start = now.withTimeAtStartOfDay.plusDays(1).minusHours(18)
  vMorning_Time.postUpdate(morning_start.toString) 

  val night_start = now.withTimeAtStartOfDay.plusDays(1).minusHours(1)
  vNight_Time.postUpdate(night_start.toString)

  val bed_start = now.withTimeAtStartOfDay
  vBed_Time.postUpdate(bed_start.toString)

  // Convert the Astro Items to Joda DateTime
  val day_start = new DateTime(vSunrise_Time.state.toString) 
  val evening_start = new DateTime(vSunset_Time.state.toString)
  val afternoon_start = new DateTime(vEvening_Time.state.toString)

Notice that last line, that is where we get the start of AFTERNOON.

Then we determine between which start and end time now is.

  var curr = "UNKNOWN"
  switch now {
  	case now.isAfter(morning_start)   && now.isBefore(day_start):       curr = "MORNING"
  	case now.isAfter(day_start)       && now.isBefore(afternoon_start): curr = "DAY"
  	case now.isAfter(afternoon_start) && now.isBefore(evening_start):   curr = "AFTERNOON"
  	case now.isAfter(evening_start)   && now.isBefore(night_start):     curr = "EVENING"
  	case now.isAfter(night_start):                                      curr = "NIGHT"
  	case now.isAfter(bed_start)       && now.isBefore(morning_start):   curr = "BED"
  }

Finally we send the new state to vTimeOfDay.

Create a table like the above for all of YOUR time periods. For each of your time periods create a DateTime for the start of each as I did above. Then test to see which time period now is to determine the time of day.


(Smhgit) #192

Hi @rlkoshak ,

I am trying to add time of day to my environment but somehow I’ve got lost with this long thread and I am not sure what is the latest and greatest version of the rule.

Will appreciate if you can post here the most updated version (OH 2.3 & up).

Thx!


(Leif) #193

Thank you very much for that DP, it simplifies my rules a lot. There is just one question left:

When adding:

val boolean isWeekend = if(now.dayOfWeek == 6 || now.dayOfWeek == 7) true else false

to the rule, VSC-Editor says: “Ambiguous feature call. The methods dayOfWeek() in DateTime and getDayOfWeek() in AbstractDateTime both match.”

Can I just ignore it? The rule is working fine and it doesn´t generate any errors or warnings within the logs.


(Craig) #194

Hi Rich,

How come you changed from the ‘switch case’ structure to ‘if else’?


(CM6.5 H102) #195

I can’t answer for Rich but since your asking a question, this topic may be of interest.https://community.openhab.org/t/one-button-20-40-60-80-100-off-steps-amazon-dash-button/52882/6?u=h102


(Rich Koshak) #196

The original post is edited frequently to reflect any updates. The original post is the latest and greatest.

Probably not. You will also get a runtime error.

Try just calling getDayOfWeek which should resolve the ambiguity.

I’m still using a switch statement in the most recent top posting. The very original version of the DP used if statements so some of the older posts may still use if statements, but it has used switch for quite some time.

I don’t actually use the trinary operator in the top posting, but indeed, if there are only two options I will use the trinary operation either inline or to set a variable instead of if/else or switch statements.


(Smhgit) #197

@rlkoshak

In the rule, the items are not protected for null value:

  val day_start = new DateTime(vSunrise_Time.state.toString) 
  val evening_start = new DateTime(vSunset_Time.state.toString)
  val afternoon_start = new DateTime(vEvening_Time.state.toString)

In my system, after restart, the sunset item not yet set and I get error (of course later when it is set, it is working). Do u also get error after system starts?


(Rich Koshak) #198

If the Rule starts running before the Astro binding has loaded and calculated the new Times then this will happen. I’ve never experienced this happening in my Rules but I know it does happen.

One way you could deal with it is to:

  • remove the System started trigger
  • add a trigger on changes to those three Items
  • check up front to see if any of the three Items are NULL and exit the Rule if they are.

(Kris K) #199

Hi Rich ive added your rule and the astro binding and im getting the sunrise/sunset times but I’m not getting values for the calculated items, like Time of Day.

I restarted the system, but I get a null value.

Any thoughts?


(Rich Koshak) #200

Did you create all the Items?

Did you copy the Rule as shown or did you make any modifications?

Do you ever see anything in the logs?


(Kris K) #201

Yes I did.

See attached, there are no logs

items:


/* Astro Binding Items*/

String vTimeOfDay "Current Time of Day [%s]" <tod>

DateTime vMorning_Time "Morning [%1$tH:%1$tM]" <sunrise>

DateTime vSunrise_Time "Day [%1$tH:%1$tM]" <sun> { channel="astro:sun:home:rise#start" }

DateTime vSunset_Time "Evening [%1$tH:%1$tM]" <sunset> { channel="astro:sun:home:set#start" }

DateTime vNight_Time "Night [%1$tH:%1$tM]" <moon>

DateTime vBed_Time "Bed [%1$tH:%1$tM]" <bedroom_blue>

DateTime vEvening_Time "Afternoon [ %1$tH:%1$tM]" <sunset> { channel="astro:sun:minus90:set#start" }

Rules:

rule "Calculate time of day state"
when
  System started or // run at system start in case the time changed when OH was offline
  Channel 'astro:sun:home:rise#event'    triggered START or
  Channel 'astro:sun:home:set#event'     triggered START or
  Channel 'astro:sun:minus90:set#event'  triggered START or
  Time cron "0 1 0 * * ? *" or // one minute after midnight so give Astro time to calculate the new day's times
  Time cron "0 0 6 * * ? *" or
  Time cron "0 0 23 * * ? *"
then

  logInfo(logName, "Calculating time of day...")

  // Calculate the times for the static tods and populate the associated Items
  // Update when changing static times
  // Jump to tomorrow and subtract to avoid problems at the change over to/from DST
  val morning_start = now.withTimeAtStartOfDay.plusDays(1).minusHours(18)
  vMorning_Time.postUpdate(morning_start.toString)

  val night_start = now.withTimeAtStartOfDay.plusDays(1).minusHours(1)
  vNight_Time.postUpdate(night_start.toString)

  val bed_start = now.withTimeAtStartOfDay
  vBed_Time.postUpdate(bed_start.toString)

  // Convert the Astro Items to Joda DateTime
  val day_start = new DateTime(vSunrise_Time.state.toString)
  val evening_start = new DateTime(vSunset_Time.state.toString)
  val afternoon_start = new DateTime(vEvening_Time.state.toString)

  // Calculate the current time of day
  var curr = "UNKNOWN"
  switch now {
        case now.isAfter(morning_start)   && now.isBefore(day_start):       curr = "MORNING"
        case now.isAfter(day_start)       && now.isBefore(afternoon_start): curr = "DAY"
        case now.isAfter(afternoon_start) && now.isBefore(evening_start):   curr = "AFTERNOON"
        case now.isAfter(evening_start)   && now.isBefore(night_start):     curr = "EVENING"
        case now.isAfter(night_start):                                      curr = "NIGHT"
        case now.isAfter(bed_start)       && now.isBefore(morning_start):   curr = "BED"
  }

  // Publish the current state
  logInfo(logName, "Calculated time of day is " + curr)
  vTimeOfDay.sendCommand(curr)
end