[Deprecated] Design Pattern: Time Of Day

@vzorglub Thank you, I had tried searching only on this post and didn’t find anything here but didn’t think to check the forum since I assumed the map was specific to the Time Of Day design pattern. I appreciate the quick response though! Thank You

As mentioned in the What is a DP link in the OP, a DP is not necessarily a complete and wholly self contained example. Above all the weather.map does is just take the all uppercase states (e.g DAY) and makes them lower case (e.g. day) for the sitemap

Thanks Rick! Working on getting this implemented now. Makes sense.

Thanks for your helpful and informative design patterns,

Here I want to ask why you use .millis in comparison switch:

switch now {
  	case now.isAfter(morning_start.millis)

It’s left over from a time when morning_start et. al. were not Joda DateTime Types. It is not needed and I missed removing them the last time I edited the DP.

I updated the OP. Thanks for pointing out this oversight!

Here is a version using JSR223-Jython (w/ openhab2-jython modules)…

from org.slf4j import Logger, LoggerFactory
from openhab.triggers import time_triggered
from org.joda.time import DateTime
from org.joda.time import Interval
log = LoggerFactory.getLogger("org.eclipse.smarthome.model.script.Rules")

@time_triggered("0 0 7 * * ?")
@time_triggered("0 0 9 * * ?")
@time_triggered("0 55 17 * * ?")
@time_triggered("0 0 21 * * ?")
@time_triggered("0 0 23 * * ?")
def updateMode():
    #log.debug("JSR223: Update Mode")
    modeIntervals = {
        "Morning" : Interval(DateTime.now().withTime(7,0,0,0),DateTime.now().withTime(9,0,0,0)),
        "Day" : Interval(DateTime.now().withTime(9,0,0,0),DateTime.now().withTime(17,55,0,0)),
        "Evening" : Interval(DateTime.now().withTime(17,55,0,0),DateTime.now().withTime(21,0,0,0)),
        "Night" : Interval(DateTime.now().withTime(21,0,0,0),DateTime.now().withTime(23,0,0,0))
    newMode = "Late Night"
    for mode in list(modeIntervals.keys()):
        if modeIntervals.get(mode).contains(DateTime.now()):
            newMode = mode
        log.debug("JSR223: Update Mode: interval=[{}], name=[{}]".format(modeIntervals.get(mode),mode))
    if items["Mode"] != StringType(newMode):
        log.debug("JSR223: Update Mode: Mode changed to [{}]".format(newMode))
        log.debug("JSR223: Update Mode: Job ran but Mode did not need to be changed [{}]".format(newMode))

And I add this to the end of the script file so that this runs on startup and when the file is saved, instead of adding a Startup trigger for it…

1 Like

Thanks for posting this. A couple of questions to make sure I’m not missing it.

  • I see cron triggers but not Astro event triggers. Are they there and I’m just not good enough with it to recognize them? Is DateTimeType easy to convert to DateTime in JSR223 (e.g. new DateTime(Sunset.state.toString))

  • Mode is the name of the Item? It’s the equivalent of vTimeOfDay in the OP?

  • Does this run at System start/when the file is loaded?

I don’t have Astro triggers… our time of day intervals are all time driven. Channel triggers can be used too, but there is no decorator setup for them. DateTimeType conversion is the same…

if items["Calendar_Upcoming_Gymnastics"] != UnDefType.UNDEF and Interval(Duration(TimeUnit.DAYS.toMillis(1)),DateTime(items["Calendar_Upcoming_Gymnastics"].zonedDateTime.toInstant().toEpochMilli())).contains(DateTime.now()):

Yes… something I carried over from ST.

Funny you should ask that. You can make a rule that triggers on startup, but I didn’t see a need… I just added the updateMode() function to the end of the script. I’ll add that to the example. This was the function runs when the file is saved and when OH starts up.

To cover the case where OH goes down, a time transition occurs, and OH comes back up. It would have missed the time transition so at best Mode would remain the previous state until the next transition, essentially skipping one whole time period.

Thanks for posting!

I didn’t communicate this well… I didn’t see a need to add it as a trigger, because the function will run when the file is reloaded, by putting it at the end of the file. This also makes it a little easier to spot what is going to run when I save the file.

You are very welcome, and thank you for all you do here!

OK, makes sense. A Systtem started trigger is not needed then.

ive got an error

2018-07-22 18:11:11.949 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Calculate time of day state': Could not cast NULL to org.eclipse.smarthome.core.library.types.DateTimeType; line 30, column 39, length 35

my rule is as follows

rule "Calculate time of day state" 
  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
  Item test7 changed from OFF to ON or
  Time cron "0 0 23 * * ? *"

  logInfo("Log", "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)

  val night_start = now.withTimeAtStartOfDay.plusDays(1).minusHours(1)

  val bed_start = now.withTimeAtStartOfDay

  // Convert the Astro Items to Joda DateTime
  val day_start = new DateTime((vSunrise_Time.state as DateTimeType).getZonedDateTime.toInstant.toEpochMilli) 
  val evening_start = new DateTime((vSunset_Time.state as DateTimeType).getZonedDateTime.toInstant.toEpochMilli)
  val afternoon_start = new DateTime((vEvening_Time.state as DateTimeType).getZonedDateTime.toInstant.toEpochMilli)

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

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

any ideas as I’m a little lost?

On of these are not initialized correctly. My gues is you didnt define a thing. vSunrise_Time,vSunset_Time, vEvening_Time
So please define the thing correctly.

this is my items file, I presume that you meant items and not things? the only thing file that I have is for the astro binding and that has been setup and working for a while now.

String vTimeOfDay "Current Time of Day [MAP(weather.map):%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" }

From the log, the error was at line 30. Assuming this is the first rule in the file with nothing above it…

val afternoon_start = new DateTime((vEvening_Time.state as DateTimeType).getZonedDateTime.toInstant.toEpochMilli)

Does vEvening_Time have a value? Check it in a UI or Karaf (smarthome:status vEvening_Time).

No I meant thing!

vEvening_Time currently has no value, all of the others do have values

Please create a file in your things folder and put in (e.g. astro.things):

astro:sun:home  [ geolocation="XX.XXXX,XX", interval=60 ]
astro:moon:home [ geolocation="XX.XX,XX.XX", interval=60 ]
astro:sun:minus90 [ geolocation="XX.XX,XX.XX", interval=60 ] // +15 degrees == -60 minutes

And leave your Items as described at the top. (Especially the channels) (XX.XX are your location coordinates)
That will solve your problem.

thanks, I had the top two lines but not the third. vEvening_Time and vSunset_Time are both showing 17:17 is that correct? the sunset time is right but im a little surprised by the evening time

There’s the cause of your error.