[Deprecated] Design Pattern: Time Of Day

@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!

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.

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!

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.

Hi Rich,

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

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

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.

@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?

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.

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?

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?

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


Your Astro Thing IDs match?

Note there are two Things, one names astro:sun:home and astro:sun:minus90. By default I think OH creates a Thing names astro:sun:local so you need to create these two Things.

What version of OH are you running? If you are not seeing at least “Calculating time of day…” in the log then the Rule isn’t triggering which means the System started trigger is not working.

Hi Rich

Im using 2.4.0-SNAPSHOT
Build #1341

Ill check the thing ID

See attached…looks like they match

It might work when one of the times in the tirggers occurs, but the fact that it isn’t triggering on System started is a problem and not one I’ll be able to help with. I think I saw a posting that yesterday’s build broke System started triggers but I might be mistaken.

Do you have any other System started rules? Do you have any Thread::sleeps in any of them? These used to cause problems.

But ultimately vTimeOfDay isn’t getting set because the Rule is never running.

HI Rich

I dont use systemstarted in any rule. Nor threadsleeps in those… ill add a time cron for 10am and see if it updates. Its 9.49am here so we will see.

Hi Rich…

See log after adding a cron timer for 10am


10:00:47.262 [ERROR] [untime.internal.engine.RuleEngineImpl] - Error during the execution of startup rule 'Calculate time of day state': Invalid format: "NULL"

OK, issue fixed.

Rich, you need to update your OP because its missing the THING definition, your post assumes manual creation of things but provides no examples of how to create them.

Here is the THING definition you require to make this work:

astro:sun:home [ geolocation="-33.8798767,151.2081442", interval=60 ]
astro:moon:home [ geolocation="-33.8798767,151.2081442", interval=60 ]
astro:sun:minus90 [ geolocation="-33.8798767,151.2081442", interval=60 ]

You also should update it to say you MUST create a things file, because auto discovery using the Binding does not work.


10:40:49.430 [INFO ] [del.core.internal.ModelRepositoryImpl] - Refreshing model 'timeofday.things'
10:40:57.032 [INFO ] [se.smarthome.model.script.Time Of Day] - Calculating time of day...
10:40:57.042 [INFO ] [se.smarthome.model.script.Time of Day] - Calculated time of day is DAY
10:40:57.045 [INFO ] [smarthome.event.ItemCommandEvent     ] - Item 'vTimeOfDay' received command DAY
10:40:57.047 [INFO ] [smarthome.event.ItemStateChangedEvent] - vTimeOfDay changed from NULL to DAY

As it says in Design Pattern: What is a Design Pattern and How Do I Use Them, which the very first line of thisv and all my DPs asks you to read first,

a DP is not a comprehensive example. They are a template that one can follow to solve a given class of problems one may encounter while developing Rules.

The example code that is included with the DPs may not be fully explained. The purpose of the example code is to illustrate the DP only. But since the DPs are only templates and not complete in and of themselves, the example code will necessarily include additional code.

Fully documenting the Astro binding, how to create Astro Things, how to link Channels to Items, etc is documented elsewhere and out of scope for a DP. I can’t fully document every single teeny tiny detail or else I’d be reproducing half the official docs on every post and reach past would be dozens of pages long.

Consequently, and as it also says in that link

Because a DP is not a complete solution, the reader needs to possess a few skills in order to apply them to their situation:

  • the basics of how to define Items and use them in Rules code
  • Rule syntax
  • the ability to follow a block of Rules code and understand at a basic level what the code does
  • the ability to recognize and deal with code in a block that is not fully explained or defined in the DP text (e.g. reference to Items not defined in the current DP)
  • the ability to generalize an example to multiple situations

I suppose in the case I can add how to create and use Things for the relevant bindings.

You don’t have to create a .things file. You can create Astro Things manually through PaperUI.