[Deprecated] Design Pattern: Time Of Day

now.withTime(6, 30, 0, 0)

2 Likes

It’s always helpful to log out what morning_start is being set to and compare that to the other times to see if they make sense.

NOTE: the plus day minus hours is to deal with daylight savings. Scott’s approach is a better one for getting a specific time. I just haven’t updated the DP with it yet.

1 Like

Thanks, I’ve got all the times right for each val but the syntax above just didn’t seem to run so I assume OH just can’t process it so as it nulls then it will wait until the next time Val.

I like the simplicity of the withtime above, I’ll be changing to that when I get home :+1:

Please update your description under

Simple Example

In this example we will be tracking the time of day. There will be five states: MORNING, DAY, EVENING, NIGHT and BED.

It should say six states: MORNING, DAY, AFTERNOON, EVENING, NIGHT, and BED

alright well you don’t have to yell, corrections are welcome

It wasn’t meant as yelling. The code had the states in upper case so I’m just repeating it the way it was.

1 Like

@rlkoshak noticed that you are using sunrise as daystart, isn’t it more likely daylight than day then?
because during winter season DAY definitely starts even when it’s still dark outside eg. sunrise not yet started.

just curious what DAY actually means in this DP

I’m Thinking if time of the day should be tighted to time of the day rather than astro events. hm?

It’s an example. The user tailors it to their needs; different times, different scheduled periods.

Astro binding offers more than one interpretation of sunrise, choose the one you like and add a fixed offset if you wish.

As rossko57 states, it’s all customizable for a given situation. And there is some subtle yet desirable behavior that the Rule handles here.

I only want the MORNING state to occur when the sun rises after 06:00. When the sun rises before 06:00, the MORNING state is skipped. That is a deliberate choice. It’s also documented in the OP:

If you want MORNING to occur no matter what, then indeed, you want to tie it to sunrise with some sort of offset. But one of the things I wanted to show in the DP was as many different concepts as possible. That’s why we have some Astro events, some Astro events with offsets, static times, and at least one time period that get’s skipped because the sun rises too early.

But, as rossko57 points out, it doesn’t matter what DAY represents in this DP. What do you want DAY to represent? Do you even want a DAY state? Maybe you want to call it something else. Maybe you want to divide it up into more time periods. This DP, as is the case with all DPs, presents an example. It’s intended to be customized to your specific needs. It’s also general, meaning this approach can be used to solve other similar types of problems, not just time of day.

In my particular case, MORNING controls a couple of lamps that I need to come on if the sun rises after 06:00 but do not if the sun rises before.

Any time period means “That time of day between when it is defined to start up to the point that the next time of day starts.” For DAY above, that means that DAY is between 06:00 and Sunset minus 90 minutes.

Make sense, I was just curious what “DAY” is used for in your environment. I get MORNING usecase . Anyway thanks to your DP’s I’ve made couple of nice complex rules for my setup so thank you for your great effort.

Hi all, I have an issue with this desgin pattern. I tried the first one with the map service installed. I have the following items:

Default.tems

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" }

Lighting.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

// Examples for use of vTimeOfDay
rule "Evening sesor GangBoven"
when
  Item SW_SensorGangBoven changed to ON
then
  if(vTimeOfDay.state != "EVENING") {
        Milight_LampGangBoven_G1_Level.sendCommand(60)
    }
end

rule "Night sesor GangBoven"
when
  Item SW_SensorGangBoven changed to ON
then
  if(vTimeOfDay.state != "NIGHT") {
        Milight_LampGangBoven_G1_Level.sendCommand(5)
    }
end

rule "Bed sesor GangBoven"
when
  Item SW_SensorGangBoven changed to ON
then
  if(vTimeOfDay.state != "BED") {
        Milight_LampGangBoven_G1_Level.sendCommand(5)
    }
end

rule "Morning sesor GangBoven"
when
  Item SW_SensorGangBoven changed to ON
then
  if(vTimeOfDay.state != "MORNING") {
        Milight_LampGangBoven_G1_Level.sendCommand(20)
    }
end

rule "Day sesor GangBoven"
when
  Item SW_SensorGangBoven changed to ON
then
  if(vTimeOfDay.state != "DAY") {
        Milight_LampGangBoven_G1_Level.sendCommand(0)
    }
end

rule "Afternoon sesor GangBoven"
when
  Item SW_SensorGangBoven changed to ON
then
  if(vTimeOfDay.state != "AFTERNOON") {
        Milight_LampGangBoven_G1_Level.sendCommand(60)
    }
end

Now I am not sure if the rules that determines the dimming are the issue here or the time of the day is not calculated.
When I activate the motion detection sensor this is what the log looks like:

2019-10-05 21:31:34.570 [vent.ItemStateChangedEvent] - SW_SensorGangBoven changed from NULL to ON

2019-10-05 21:31:34.645 [ome.event.ItemCommandEvent] - Item 'Milight_LampGangBoven_G1_Level' received command 60

2019-10-05 21:31:34.679 [ome.event.ItemCommandEvent] - Item 'Milight_LampGangBoven_G1_Level' received command 5

2019-10-05 21:31:34.682 [ome.event.ItemCommandEvent] - Item 'Milight_LampGangBoven_G1_Level' received command 60

2019-10-05 21:31:34.703 [ome.event.ItemCommandEvent] - Item 'Milight_LampGangBoven_G1_Level' received command 5

2019-10-05 21:31:34.713 [nt.ItemStatePredictedEvent] - Milight_LampGangBoven_G1_Level predicted to become 60

2019-10-05 21:31:34.731 [ome.event.ItemCommandEvent] - Item 'Milight_LampGangBoven_G1_Level' received command 0

2019-10-05 21:31:34.741 [vent.ItemStateChangedEvent] - Milight_LampGangBoven_G1_Level changed from NULL to 60

2019-10-05 21:31:34.743 [ome.event.ItemCommandEvent] - Item 'Milight_LampGangBoven_G1_Level' received command 20

2019-10-05 21:31:34.745 [nt.ItemStatePredictedEvent] - Milight_LampGangBoven_G1_Level predicted to become 5

2019-10-05 21:31:34.750 [nt.ItemStatePredictedEvent] - Milight_LampGangBoven_G1_Level predicted to become 60

2019-10-05 21:31:34.754 [nt.ItemStatePredictedEvent] - Milight_LampGangBoven_G1_Level predicted to become 5

2019-10-05 21:31:34.762 [nt.ItemStatePredictedEvent] - Milight_LampGangBoven_G1_Level predicted to become 0

2019-10-05 21:31:34.799 [vent.ItemStateChangedEvent] - Milight_LampGangBoven_G1_Level changed from 60 to 5

2019-10-05 21:31:34.806 [nt.ItemStatePredictedEvent] - Milight_LampGangBoven_G1_Level predicted to become 20

2019-10-05 21:31:34.811 [vent.ItemStateChangedEvent] - Milight_LampGangBoven_G1_Level changed from 5 to 60

2019-10-05 21:31:34.813 [vent.ItemStateChangedEvent] - Milight_LampGangBoven_G1_Level changed from 60 to 5

2019-10-05 21:31:34.816 [vent.ItemStateChangedEvent] - Milight_LampGangBoven_G1_Level changed from 5 to 0

2019-10-05 21:31:34.818 [vent.ItemStateChangedEvent] - Milight_LampGangBoven_G1_Level changed from 0 to 20

2019-10-05 21:31:35.337 [vent.ItemStateChangedEvent] - Milight_LampGangBoven_G1_Level changed from 20 to 60

2019-10-05 21:31:36.285 [vent.ItemStateChangedEvent] - Milight_LampGangBoven_G1_Level changed from 60 to 20

2019-10-05 21:32:05.035 [vent.ItemStateChangedEvent] - SW_SensorGangBoven changed from ON to OFF

2019-10-05 21:32:05.069 [ome.event.ItemCommandEvent] - Item 'Milight_LampGangBoven_G1_Level' received command 0

After a few seconds the sensor will switch off and the MiLight gets the command to switch off.
Now I have this issue that the MiLight will turn on but will get dimmed with different values and so it changes a few time before it stays on for a few seconds.
I want to achieve different dimming levels based on the time of the day. Any1 can point me to the right direction? Thanks allready for all the examples etc, I learn a lot from it.

Let’s say TimeOfDayb is AFTERNOON andSW_SensorGangBoven changes to ON. All of you rules will run. And the if states for all but the hat role will run because

  • vTimeOfDay.state != “EVENING” is true so lamp is commanded to 60
  • vTimeOfDay.state != “NIGHT” is true so lamp is commanded to 5
  • vTimeOfDay.state != “BED” is true so lamp is commanded to 5
  • vTimeOfDay.state != “MORNING” is true so lamp is commanded to 20
  • vTimeOfDay.state != “DAY” is true so lamp is commanded to 0
  • vTimeOfDay.state != “AFTERNOON” is false so the role does nothing (vTimeOfDay.state == “AFTERNOON” is true)

So either I don’t username what you are trying to accomplish or you don’t know the difference between equals (==) and not equals (!=).

Hey Rich, I really didn’t know about the difference in != and ==. I copied the code from the pattern example and tried to achieve my own goals by tweaking it here and there.
I can’t see the solution. I thought this code’s purpose is to define which part of the day the system is in. And from there further on we can add rules to these parts. Correct?

Design Patterns show you how to solve common coding problems in OH but they still require you to know and understand the basics of OH ok and how to code Rules.
The Time of Day Rule appears to be working given the logs you provided. But your Rules are not right. For example, your “Evening sesor GangBoven” code literally means " when SW_SensorGangBoven changes to ON, command Milight_LampGangBoven_G1_Level to 60 every time of day except EVENING."

if(vTimeOfDay.state != "EVENING") means if vTimeOfDay’s State **is not*z EVENING. You probably want "=="which means if vTimeOfDay is EVENING.

This is super basic stuff you must understand to be successful with OH rules. You might benefit from going though one of the many beginning programming courses there are online.

Thanks for the explanation. I went through the basics and I see the difference now. I also found out that I was not using the astro.sun.home items but mine were called astro.sun.local. So I changed these entries. But before I proceed with investigating my rules I want to make sure that time of the day is working. Now when I look on my sitemap, Time Of The Day states “-”:

It appears to me that it is still not calculating the parts of the day right. I also tried to reboot my OH system because the rule will be triggered at system startup.

Are you sure, your rule is running ? I didn’t compare it with Rich’s original, but mine runs


and shows the correct value (transformed)

String    vTimeOfDay       "Current Time of Day [MAP(astroDE.map):%s]" <time>  (gRichTime)
//String    vTimeOfDay       "Current Time of Day [%s]"             <time>          (gRichTime)  // Testversion without transformation

But what I’m missing is the declaration of

val logName = "Time Of Day"

at the top of your rule.

1 Like

I took it out because I get an error when I have the val logName on top of the rule. My rule is as follows for now:

val logName = "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:local:rise#event'    triggered START or
  Channel 'astro:sun:local:set#event'     triggered START or
  Channel 'astro:sun:local:set#start'  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 The 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

Then I see this error in the log:

2019-10-07 19:13:50.457 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model ‘lighting.rules’ has errors, therefore ignoring it: [29,1]: missing EOF at ‘val’

And interesting enough I see this error when the evening started:

2019-10-07 19:02:00.044 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘Calculate time of day state’: The name ‘logName’ cannot be resolved to an item or type; line 42, column 11, length 7

Line 42 is:

logInfo(logName, “Calculating Time Of The Day”)

And I am curious how your astroDE.map file looks like. I’m now using en.map and it looks like this:
CLOSED=closed
OPEN=open
NULL=unknown
-=unknown

My UI looks like this now:

I think your “logInfo” will not work correctly. So if your “placeholder” logName does not work, use it in this way:

logInfo("myRuleName in double quotes", "Calculating Time Of the Day")

I also saw that you are not using ‘astro:sun:minus90:set#event’ -Thing which has normally has to be declared in this way:

Thing astro:sun:minus90   "Offset -90"     [geolocation="12.34578,1.23456,123", interval=300]{
  Channels:
    Type rangeEvent : set#event [
      offset=-90
    ]
    Type start : set#start [
      offset=-90
    ]
    Type end : set#end [
      offset=-90
    ]
  }

and which corresponding (linked) to this item:

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

Please check this all.

As I don’t know what exactly is your line 29 (and of course 28 and 30) I can’t say what’s wrong exactly. Maybe you have declared a variable twice or not declared.

The second error comes up, as you have not declared the variable val logName and want to use in the logInfo .

EDIT:
astroDE.map (look at the bottom of the Map)

// Zodiac - Sternzeichen
ARIES=♈ Widder
TAURUS=♉ Stier
GEMINI=♊ Zwilling
CANCER=♋ Krebs
LEO=♌ Löwe
VIRGO=♍ Jungfrau
LIBRA=♎ Waage
SCORPIO=♏ Skorpion
SAGITTARIUS=♐ Schütze
CAPRICORN=♑ Steinbock
AQUARIUS=♒ Wassermann
PISCES=♓ Fische

//seasons
Season=Jahreszeit
SPRING=Frühling
SUMMER=Sommer
AUTUMN=Herbst
WINTER=Winter

// day - night phases
SUN_RISE=Sonnenaufgang
ASTRO_DAWN=astronomische Morgendämmerung
NAUTIC_DAWN=nautische Morgendämmerung
CIVIL_DAWN=zivile Morgendämmerung
CIVIL_DUSK=zivile Abenddämmerung
NAUTIC_DUSK=nautische Abenddämmerung
ASTRO_DUSK=astronomische Abenddämmerung
SUN_SET=Sonnenuntergang
DAYLIGHT=Tag
NOON=Abend
NIGHT=Nacht
Night=Nacht

// moon phases
NEW=Neumond
WAXING_CRESCENT=zunehmender Halbmond
FIRST_QUARTER=erstes Viertel
WAXING_GIBBOUS=zunehmender Mond
FULL=Vollmond
WANING_GIBBOUS=abnehmender Mond
THIRD_QUARTER=letztes Viertel
WANING_CRESCENT=abnehmender Halbmond

//error-codes
NULL=unbekannt
-=-nicht verfügbar
UNDEF=nicht definiert

// Rich's Declarations for Time of the Day
MORNING=Morgen
DAY=Tag
AFTERNOON=Nachmittag
EVENING=Abend
NIGHT=Nacht
BED=Schlafenszeit

1 Like
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

This works fine for most of the year :slight_smile:

But during the summer sunrise is before 6, and sunset can be 02.00 so the table should also cover those cases:

State Start End
MORNING 06:00(mon-fri), 08.00(sat-sun Sunrise or 08.00(mon-friday), 10.00(sat-sun)
DAY Sunrise or 08.00(mon-fri),10.00 (sat-sun) and 16.00(mon-fri) Sunset - 90 minutes or 20.00
WORK(mon-friday) 09.00 or voice command 16.00 or presence detection
EVENING Sunset - 90 minutes or 20.00 Sunset or 23.00
NIGHT 23:00(mon-fri),24:00 (sat-sun) or voice command 06:00

Would be nice to include this for the one leaving above ±50deg latitude. I will work on an example that include this and share it here

Here is an example, which I’m using sometimes for testing purposes:

Thing astro:sun:stowing1   "Offset 20"     [geolocation="48.887211,9.8709123,502", interval=300]{
  Channels:
    Type rangeEvent : noon#event [
      offset=230
    ]
    Type start : noon#start [
      offset=230
    ]
    Type rangeEvent : set#event [
      offset=20,
      earliest="20:10"
    ]
    Type rangeEvent : night#event [
      offset=20
    ]
    Type start : rise#start [
      offset=20,
     earliest="09:00"
    ]
    Type end : rise#end [
      offset=20
    ]
    Type start : set#start [
      offset=20,
      earliest="21:10"
    ]
    Type end : set#end [
      offset=10,
      latest="21:50"
    ]
      Type rangeEvent : civilDusk#event [
      offset=-180
    ]
    Type start : civilDusk#start [
      offset=-180
    ]
}

As you can see there are some more parameters in the channel descriptions (earliest, latest). You can add this too, for calculating time (#start, #end) or triggering a rule (#event).