[Deprecated] Design Pattern: Time Of Day

I now have installed the DP of Rich and tested in several versions. As @rlkoshak said, the problem is not the map.
I found out, when I made changes(i.e the Label) in the Channel-linked items the values are lost. When waiting for the interval-time (i.e. interval=300) of the defined Astro-Thing they will be updated again. But only the “normal-ones”. The Scheduling will not work for those Things and channel-linked Items with Offset.

If the Rule starts (with Debug-Points) and if a value is not set, the Logger shows

2019-04-16 10:32:05.744 [INFO ] [e.smarthome.model.script.Time Of Day] - Calculating time of day...
2019-04-16 10:32:05.769 [INFO ] [e.smarthome.model.script.Time Of Day] - Calculating time of day...1
2019-04-16 10:32:05.788 [INFO ] [e.smarthome.model.script.Time Of Day] - Calculating time of day...2
2019-04-16 10:32:05.806 [INFO ] [e.smarthome.model.script.Time Of Day] - Calculating time of day...3
2019-04-16 10:32:05.814 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Calculate time of day state': Invalid format: "NULL"

when it comes to NULL-Value.

After a Restart of OH2 or Bundle-Restart all values are set. But one can see in the Logger what happens.

2019-04-16 11:01:38.536 [INFO ] [thome.binding.astro.internal.job.Job] - Scheduled Astro event-jobs for thing astro:sun:stowing1
2019-04-16 11:01:38.640 [INFO ] [thome.binding.astro.internal.job.Job] - Scheduled Astro event-jobs for thing astro:moon:local
2019-04-16 11:01:38.654 [INFO ] [ding.astro.handler.AstroThingHandler] - Scheduled Positional job astro:moon:local every 300 seconds
2019-04-16 11:01:38.799 [INFO ] [thome.binding.astro.internal.job.Job] - Scheduled Astro event-jobs for thing astro:moon:local
2019-04-16 11:01:38.820 [INFO ] [ding.astro.handler.AstroThingHandler] - Scheduled Positional job astro:moon:local every 300 seconds
2019-04-16 11:01:38.866 [INFO ] [thome.binding.astro.internal.job.Job] - Scheduled Astro event-jobs for thing astro:sun:local
2019-04-16 11:01:38.891 [INFO ] [ding.astro.handler.AstroThingHandler] - Scheduled Positional job astro:sun:local every 300 seconds
2019-04-16 11:01:38.979 [INFO ] [thome.binding.astro.internal.job.Job] - Scheduled Astro event-jobs for thing astro:sun:local
2019-04-16 11:01:39.037 [INFO ] [ding.astro.handler.AstroThingHandler] - Scheduled Positional job astro:sun:local every 300 seconds
2019-04-16 11:01:39.059 [INFO ] [thome.binding.astro.internal.job.Job] - Scheduled Astro event-jobs for thing astro:sun:minus90
2019-04-16 11:01:39.246 [INFO ] [thome.binding.astro.internal.job.Job] - Scheduled Astro event-jobs for thing astro:sun:stowing
2019-04-16 11:01:39.500 [INFO ] [thome.binding.astro.internal.job.Job] - Scheduled Astro event-jobs for thing astro:sun:stowing2

The Re-Scheduling will only work for for the “Normal-Tings”

I’m using the following Set-Up:

RPi 3B, OH2.5M1

rich_time.items:

Group gRichTime "Rich's Daytime"
//String    vTimeOfDay       "Current Time of Day [MAP(weather.map):%s]" <time>  (gRichTime)
String    vTimeOfDay       "Current Time of Day [%s]"             <time>          (gRichTime)  // Testversion without transformation
DateTime  vMorning_Time    "Morning [%1$tH:%1$tM]"                <sunrise>       (gRichTime)
DateTime  vSunrise_Time    "Day Test1 [%1$tH:%1$tM]"              <sun>           (gRichTime)     {channel="astro:sun:local:rise#start"}
DateTime  vSunset_Time     "Evening Test1 [%1$tH:%1$tM]"          <sunset>        (gRichTime)     {channel="astro:sun:local:set#start"}
DateTime  vNight_Time      "Night [%1$tH:%1$tM]"                  <moon>          (gRichTime)    
DateTime  vBed_Time        "Bed [%1$tH:%1$tM]"                    <bedroom_blue>  (gRichTime)    
DateTime  vEvening_Time    "Late Afternoon Test1 [ %1$tH:%1$tM]"  <sunset>        (gRichTime)     {channel="astro:sun:minus90:set#start"}

rich_time.rules:

val logName = "Time Of Day"

rule "Calculate time of day state" 
when
  Item Dummy4 changed to ON or
  System started or // run at system start in case the time changed when OH was offline
  Channel 'astro:sun:local:rise#event'    triggered START or
  Channel 'astro:sun:local: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) 
  logInfo(logName, "Calculating time of day...1")

  val night_start = now.withTimeAtStartOfDay.plusDays(1).minusHours(1)
  vNight_Time.postUpdate(night_start.toString)
  logInfo(logName, "Calculating time of day...2")

  val bed_start = now.withTimeAtStartOfDay
  vBed_Time.postUpdate(bed_start.toString)
  logInfo(logName, "Calculating time of day...3")

  // Convert the Astro Items to Joda DateTime
  val day_start = new DateTime(vSunrise_Time.state.toString) 
  logInfo(logName, "Calculating time of day...4")
  val evening_start = new DateTime(vSunset_Time.state.toString)
  logInfo(logName, "Calculating time of day...5")
  val afternoon_start = new DateTime(vEvening_Time.state.toString)
  logInfo(logName, "Calculating time of day...6")

  // 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

astro.things:

//    Astro - Binding Geo-Position  geolocation="xx.xxxxxx,y.yyyyyy,zzz"

Thing astro:sun:local     "Sonnen Daten"    [geolocation="xx.xxxxxx,y.yyyyyy,zzz", interval=300]
Thing astro:moon:local    "Mond Daten"      [geolocation="xx.xxxxxx,y.yyyyyy,zzz", interval=300]
                                        

Thing astro:sun:minus90   "Offset -90"     [geolocation="xx.xxxxxx,y.yyyyyy,zzz", interval=300]{
  Channels:
    Type rangeEvent : set#event [
      offset=-90
    ]
    Type start : set#start [
      offset=-90
    ]
    Type end : set#end [
      offset=-90
    ]
  }

Thing astro:sun:stowing   "Offset -180"     [geolocation="xx.xxxxxx,y.yyyyyy,zzz", interval=300]{
  Channels:
    Type rangeEvent : set#event [
      offset=-180,
      earliest="19:40"
    ]
  }
Thing astro:sun:stowing1   "Offset 200"     [geolocation="xx.xxxxxx,y.yyyyyy,zzz", interval=300]{
  Channels:
    Type rangeEvent : noon#event [
      offset=200,
      earliest="13:00"
    ]
    Type rangeEvent : set#event [
      offset=90,
      earliest="20:10"
    ]
    Type start : rise#start [
      offset=90,
     earliest="09:00"
    ]
    Type end : rise#end [
      offset=90
    ]
    Type start : set#start [
//      offset=90,
      earliest="23:50"
    ]
    Type end : set#end [
      offset=90
//      latest="21:50"
    ]
  }
  Thing astro:sun:stowing2   "Offset -90"     [geolocation="xx.xxxxxx,y.yyyyyy,zzz", interval=300]{
  Channels:
    Type rangeEvent : set#event [
      offset=-90
    ]
    Type start : set#start [
      offset=-90
    ]
    Type end : set#end [
      offset=-90
    ]
  }


So one can see, even if the interval is set in the Thing (minus90) it will not work. For me this looks like a bug in the binding :wink:

Cheers,
Peter

Btw: Is there an icon for <tod>. Didn’t find one.

Thanks or your help. By adding in the additional “logInfo” entries, I was able to sort things out and get a bit further, until I was getting the “NULL” value error. Based on that additional “logInfo” information, I was able to determine that it was failing to set a value for the ‘vEvening_Time’ value, which is the one with the ‘minus90’ value in the channel setting. I believe this is what fibu-freak was referring to just above in his response to me.

I am currently using PaperUI to manage my Things and the text files for everything else, but I don’t think that make a difference.

I see I can set an offset in PaperUI for the Astro Sunset, but than changes the actual sunset time, rather than allowing me to still have the proper sunset time, but apply an offset for another item (such as to the vEvening_Time item)

Hi Allen,
if I understand you right, you’re using only one Thing (“local” or “home”). Do you ? If so, you have to create another one (“minus90” or whatever name you want to have) with the offset-time. You can do this with Paper UI too, I think. Or just creating a .things File for the minus90-Thing as shown in my example above. I think this should work too.
Try it. :wink: When you have done it, you should see a log-message like the below one:

The result should look like here:

But be aware, if you make changes in your .items-file, your channel linked items values will be NULL. So for the “normal” ones (thats the items which are channel-linked to “local” or “home”) will be updated after the interval-time, but not the item linked with the “minus90”-Channel. This one is only updated after a Start of OH2 or after the midnight-rescheduling. Another way in this case could be to restart the Binding on the Console.Use

bundle:list | grep -i astro

to find out the ID of your astro-binding

and then use (but with your ID)

bundle:restart 212

to restart the Binding/Bundle

EDIT:
For testing purposes you can set a value to a specific item in the console too. Use this command

smarthome:send vEvening_Time 18:32

Yep, that was it: I was able to add another Astro binding in PaperUI and for the Sunset Start event put an offset of -90. I got the ID of the binding (like I did for my other ones) and put that into my “vEvening_Time” channel id and my “Afternoon” value is now showing 90 minutes before the real sunset start.

Thanks! I feel d-u-m-b, but unfortunately it is not the only time I will probably feel that way :frowning:

Fortunately, I believe I know enough now to be able to set some rules based on the TOD (like turning on my lights, etc.), and build form there.

Thanks again!

Hi,

I’ve made some changes to the original rule. All seems to work as expected until my last case statement for ‘BED’. Instead, I recieve [INFO ] [e.smarthome.model.script.Time Of Day] - Calculated time of day is UNKNOWN

My rule is as follows:

val todLog = "Time Of Day"
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
  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 12 * * ? *" or
  Time cron "0 0 17 * * ? *" or
  Time cron "0 0 22 * * ? *" or
  Time cron "0 0 23 * * ? *"
then
  logInfo(todLog, "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 midday_start = now.withTimeAtStartOfDay.plusDays(1).minusHours(12)
  vMidday_Time.postUpdate(midday_start.toString) 

  val earlyEvening_start = now.withTimeAtStartOfDay.plusDays(1).minusHours(7)
  vEarlyEvening_Time.postUpdate(earlyEvening_start.toString) 

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

  val bed_start = now.withTimeAtStartOfDay.plusDays(1).minusHours(1)
  vBed_Time.postUpdate(bed_start.toString)

  // Convert the Astro Items to Joda DateTime
  val sunrise_start = new DateTime(vSunrise_Time.state.toString) 
  val lateEvening_start = new DateTime(vSunset_Time.state.toString)

  // Calculate the current time of day
  var curr = "UNKNOWN"
  switch now {
    case now.isAfter(sunrise_start)       && now.isBefore(midday_start):        curr = "MORNING"
  	case now.isAfter(midday_start)        && now.isBefore(earlyEvening_start):  curr = "AFTERNOON"
    case now.isAfter(earlyEvening_start)  && now.isBefore(lateEvening_start):   curr = "EARLY EVENING"
  	case now.isAfter(lateEvening_start)   && now.isBefore(night_start):         curr = "LATE EVENING"
  	case now.isAfter(night_start)         && now.isBefore(bed_start):           curr = "NIGHT"
  	case now.isAfter(bed_start)           && now.isBefore(sunrise_start):       curr = "BED"
  }

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

Any help would be most appreciated. As i say, all my other time of days work correctly :frowning:

Many thanks,

Jeevs

Consider when these values get calculated.

bed_start get’s calculated for today. sunrise_start get’s calculated for today. So

bed_start      = 2019-04-18 23:00
sunrise_start  = 2019-04-18 06:20

Your last case can never be true between 2019-04-18 23:00 and 2019-04-19 00:00 (or whenever Astro generates the sunrise start time for the new day).

The switch statement works in the OP because bed_time is 2019-04-19 00:00 so there isn’t that gap between when bedtime starts and Astro recalculates the sunrise time for the new day.

This is why it is particularly important to not just copy this DP but to really understand how this DP works in order to use it successfully.

To do this successfully, you need to have a separate case statement to cover the time between bed_start and midnight. Then you need another one to cover the time between midnight and sunrise.

1 Like
rule "TimeOfDayRule"
when
    System started or
    Time cron "0 0 * ? * * *"
then
    if (now.getHourOfDay() >= 00 && now.getHourOfDay() < 06){
		myTimeOfDayMode.postUpdate("Night")
	}
	if (now.getHourOfDay() >= 06 && now.getHourOfDay() < 09){
		myTimeOfDayMode.postUpdate("Morning")
	}
	if (now.getHourOfDay() >= 09 && now.getHourOfDay() < 16){
		myTimeOfDayMode.postUpdate("Daytime")
	}
	if (now.getHourOfDay() >= 16 && now.getHourOfDay() < 23){
		myTimeOfDayMode.postUpdate("Evening")
	}
	if (now.getHourOfDay() >= 23 && now.getHourOfDay() < 24){
		myTimeOfDayMode.postUpdate("Night")
	}
end
1 Like

Thank you both very much! It’s now clear how this works. So, thank you both for the clear responses.

I’ve made changes to my rule, so fingers crossed it should work as expected.

Many thanks :wink:

Hi all,

So the more I’m getting into OH the more I find I’m using this pattern to run other rules depending on the time of day, so first off thanks for creating it!

I have relatively the same time sections, with
BED being midnight to 6am
And
DAWN being 6am to sunrise

Now this works fine for some of the year, when sunrise is after 6am, and I’ve coded it this way so that my lights slowly go off at dawn, and any light on motion doesn’t go on after sunrise etc.

However, for me and I’m guessing many others, sunrise is before 6am for at least half of the year so DAWN as a time never occurs, it goes straight to DAY, missing out some of my hard coded rules where vTimeOfDay == DAWN.

How do others work around this and is there a way to code in an if sunrise > 6am then do something else sunrise < 6am

Or, should we not really be using hard coded times in this instance?

Thoughts?

Simply use ASTRO to get the locationsspecific times for sunrise, dawn etc. This way the modes are independent of season and location and always correct

Thanks, so that’s 1 for don’t hard code any times in there, use the astro binding…

How about when dawn and sunrise are much earlier in the morning that when people get up, for example, if I turned off my cameras as dawn or sunset right now they would be off at 4:35 or 5:35, we don’t get up until nearer 7 usually.

What @rlkoshak made is a DP to learn all of us how the handling with Time Of Day could be done. It’s not a solution. One can change it as one need.

If you want to handle your specific purposes you can use the “Astro-Thing(s)” as you want, even with Offsets and/or “earliest/latest”-restriction as shown in my example above

As you see you can make as much Astro-Things as you need for your own purposes and use it in your Rules as you want and the DP can help you to realise.

Cheers
Peter

Hi,

Yes, I wasn’t for a second saying the original rule wasn’t right, far from it, I’ve used it well for many things.

I was just after a few ideas on different ways to tweak it to avoid the sunrise / 6am issue I mentioned.

For me this is the desired behavior. But if you don’t want this to occur, set the dawn start to sunrise with an offset in Astro instead of hard coding it to 6am.

Of course, a long as you can have some event that yesterday the rule at the right time. Then you just have

if(dawn_start.isAfter(day_start)) 
    // Do something else

Thanks, I could offset but as sunset and dawn move, it will always be out, just less and less as the year goes on, which is why I hard coded the 6am, but now I’m finding / realising why you wouldn’t do this.

To be honest, I might just have to hard code this rule I’ve got, rather than shoe horn it into the time of day rule I have

With the ‘isafter’ part, that’s exactly what I was after. I know OH uses Xtend but is there a resource anywhere with just a list of the coding options? I’ve trawled the web but am yet to find a site / reference page with all the options on, it would really speed up my knowledge / development if there was something I could refer to???

The answer is yes. The problem is there are four of them. There is the Xtend reference docs (linked to from the Rules page), the Java API, the OH API, and in this case the Joda API.

There are literally hundreds and hundreds of pages of this stuff that we simply cannot reproduce/duplicate in the OH docs.

If you are dealing with a DateTime look at the Joda javadocs. If you are dealing with something OH specific, look at the ESH javadocs (I don’t think these have moved yet). If you are dealing with standard Java stuff look at the Java 8 javadocs.

But what you really should do is use VSCode with the oh extension which will tell you all the valid ways to compete a line as you type. For example, if you type “now.” a dialog will pop up and tell you all the methods like “isAfter” that are available.

Just aside:
Please open an issue if you found something that hasn’t been ported yet.

Thanks, going to download VSCode editor and take a look, that sounds an awful lot better than my current editor!

Hi,

So I had a fiddle with the isBefore idea, here’s the code, basically morning start is hard coded 6am and day start is sunrise. so if 6am is before sunrise do one switch(now) otherwise do the other.

Is there anything to add / modify to make it tidier, (still trying to learn best practise)

  if(morning_start.isBefore(day_start))
    {
    switch now {
  	  case now.isAfter(morning_start)   && now.isBefore(day_start):       curr = "DAWN"
  	  case now.isAfter(day_start)       && now.isBefore(afternoon_start): curr = "DAY"
  	  case now.isAfter(afternoon_start) && now.isBefore(evening_start):   curr = "TWILIGHT"
  	  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"
      }
    }
    else
    {
    switch now {
  	  case now.isAfter(day_start)       && now.isBefore(morning_start):   curr = "DAWN"
  	  case now.isAfter(morning_start)   && now.isBefore(afternoon_start): curr = "DAY"
  	  case now.isAfter(afternoon_start) && now.isBefore(evening_start):   curr = "TWILIGHT"
  	  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(day_start):       curr = "BED"  
      }
    

this is a pretty good start
xtend documentation
https://eclipse.org/xtend/documentation/203_xtend_expressions.html