Design Pattern: Time Of Day

jodatime
designpattern
time
Tags: #<Tag:0x00007f0156d033c0> #<Tag:0x00007f0156d03280> #<Tag:0x00007f0156d03140>

(Rich Koshak) #1

Please see Design Pattern: What is a Design Pattern and How Do I Use Them for an explanation of what a DP is and how to use them.

Problem Statement

Often in home automation one has certain rules they want to run at certain times or have the home automation exhibit different behaviors for different times of day. The naieve approach would be to do the time comparisons inline in each Rule that cares about the time of day. This DP provides an alternative approach.

This DP is particularly suitable for tracking any sequence of events that are denoted by a starting event, not just time. In fact, this DP is really just a simple state machine and can be applied to any similar problem. Other examples include tracking important dates, seasons of the year, controlling an irrigation system (the end of one zone triggers the next zone), etc.

Concept

image

The overall concept is to create a String Item that represents the current state in th sequence. Rules will check to determine what that state is to determine their behavior or trigger when the String Item changes. One or more additional Rules are reponsible for calculating and populating the String Item based on events.

NOTE: The naive approach to implementing this Design pattern is to use Switches, one for each possible state. However, in practice using Switches makes the code more complicated than it needs to be, causes the proliferation of unnecessary Items, and ultimately results in a less scalable implementation when it comes time to add new times of day.

Simple Example

In this example we will be tracking the time of day. There will be five states: MORNING, DAY, EVENING, NIGHT and BED. Some of the boundaries are statically defined while others are based on sunrise and sunset.

  • MORNING: 06:00 to sunrise; Note during some times of year sunrise is before 06:00
  • DAY: Sunrise to 90 minutes before sunset
  • EVENING: 90 minutes before sunset to 23:00
  • NIGHT: 23:00 to 00:100:
  • BED: 00:00 to 06:00

Things

The Astro binding is used to generate the events for sunrise and sunset. The Astro binding supports using an offset but it effects all events for that Thing. For example, if you use an offset of -90 minutes, you cannot get an event at sunset -90 and at sunset from the same Thing so you may need to define more than one Astro Thing depending on where you want to put the boundaries.

There are also two ways to define an offset for the Astro binding. You can define the offset on the Thing or you can add 15 degrees to the latitude to add 60 minutes.

Items

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

See https://www.openhab.org/docs/configuration/items.html#item-definition-and-syntax for creating the tod icons applied to vTimeOfDay.

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 "Day time started"
when
  Item vTimeOfDay changed to "DAY" // does not work prior to OH 2.3 Release
then
  // do stuff when DAY starts
end

rule "Some rule"
when
    // some trigger
then
  if(vTimeOfDay.state != "BED") return;

  // do stuff to do when it isn't BED time
end 

Adding a new time of day is as simple as adding a new trigger to the rule to fire when that time of day starts, calculate the DateTime for today when that time period starts, and then adding a new case to the switch statement to determine whether the current time is between when this new time period starts and the next one starts. The isBefore test for the previous time period would have to be updated as well.

Theory of Operation

There is a DateTime Item for the start of each time of day. In the above those DateTimes are statically populated that are not linked to an Astro channel. This can be expanded using one of the Alarm Clock examples or CalDav binding to populate these DateTime Items if desired.

There is also vTimeOfDay which represents the current state.

There is a Rule that gets triggered at the start of each new time of day and it gets triggered at system start. The Rule calulates the current time and determines what the current state of vTimeOfDay should be based on the current Time.

Advantages and Limitations

The major advantage of this approach is it centralizes all of your time of day calculations into one place. This allows:

  • avoidance of duplicated code
  • avoidance of typos and coding errors scattered through the code
  • ease of debugging
  • simpler Rules logic for Rules that care about Time of Day as now they only need to test vTimeOfDay for the current state

The major limitation of this approach is that it does not support overlapping time periods well. It could be expanded to handle that through the addition of additional Time Of Day Items or setting vTimeOfDay to a list of Strings (e.g. “Evening, Twilight”) but that adds complications to the TimeOfDay rule and any Rule that depends upon TimeOfDay.

Complex Example

In this example, the design pattern is applied to a simple three zone irrigation system. The first trigger is time based and the subsequent triggers and states are defined by the end of watering the zones.

The below also excludes error checking and likely has typos and is intended for illustration only. In particular, if OH restarts or reloads the rules file for some reason, the Irrigation will jump to the next Zone no matter how long the previous zone has been watering and it will not make sure the previous zone turned off. Handling these edge cases is beyond the scope of this writing.

Items

String Irrigation
Group gZones
Switch Zone1 (gZones)
Switch Zone2 (gZones) 
Switch Zone3 (gZones)

Rules:

rule "Irrigation control"
when
    System started or
    Time cron "0 0 6 * * ? *" or
    Member of gZones changed to OFF
then
    val currState = Irrigation.state.toString
    val nextState = "Done"

    if(currState == NULL || currState == "Done") nextState = "Zone1"
    else if(currState == "Zone1")                nextState = "Zone2"
    else if(currState == "Zone2")                nextState = "Zone3"
    // If it is "Zone3" the next state is already set to "Done"

    Irrigation.sendCommand(nextState)
end

rule "Zone management"
when
    Item Irrigation received command
then
    sendCommand(receivedCommand.toString, "ON")
    createTimer(now.plusMinutes(5), [ | sendCommand(receivedCommand.toString, "OFF") ] 
end

Theory of Operation

Each Zone is represented by an Item and there is a controlling String Item to represent the state of the Irrigation.

The Rule that calculates the irrigation state gets triggered based on Time, at System started, and when any member of gZones turns OFF. Based on the current state stored in Irrigation the next state is calculated and commanded.

There is a companion Rule that is driven by the Irrigation state that sends the ON command to the zone represented by the state of Irrigation and sets a Timer to turn it OFF in five minutes.

Related Design Patterns

Design Pattern How It’s Used
Design Pattern: Unbound Item (aka Virtual Item) vTimeOfDay, Irrigation, and other Items are examples of a Virtual Item
Design Pattern: Separation of Behaviors This DP is a specific implementation of Separation of Behaviors
A State Machine Primer with HABlladin, the openHAB Genie This DP is a simplified implementation of a state machine

Edit: A near complete rewrite to match formatting of other DP postings and relfect changes in OH 2.3. Eliminated examples for prior versions of OH.
Edit: Use toString instead of going through the long set of calls to get a millis to convert DateTimeType to DateTime
Edit. Remove call to millis in the switch statement.


Time related heating depending on presence
Event Scheduler
Whole house lighting
Sun (raised / set) indicator
Design Pattern: Cancel Activity
Day long script
[OH2] [Astro Binding] [Feature Request] Between Sunset and Rise
Design Pattern: Working with Groups in Rules
Light window lights 30 minutes before sunset
[SOLVED] DateTimeType is deprecated
Rule to check status and change it if necessary (SOLVED)
Rule logic failing
Good Morning, Afternoon, and Evening
Thermostat bathroom - on/off based on sensor value
Help with a loop (Runs too fast / Ignores timers)
Astro set#event used in two .rules files only executes one
Dates in rules - have I got this right
Create timer for sonoff pow tasmota
DateTime, time, and Astro binding questions
Design Pattern: Manual Trigger Detection
[SOLVED] Simple Rule - but i am rusty
NodeRED Flow for "Time of Day" Design Pattern
Rule for Roller Shutter via Astro Binding an Paper UI/rule engine
How to calculate DateTime
Rule on specific months
Design Pattern: Associated Items
Cron expression
Automation/Orchestration Design Patterns
File Include
Design Pattern: Looping Timers
Thread:Sleep apparently shutting down OH2 execution randomly
Rule only acting between two hours
Compare an epoch with now's epoch
Node-RED as Alternative Rule Engine
Fire rule when item changed between hours of midnight and 6
OpenHAB or HAAS
[SOLVED] Openhab 2 Contact Sensor Time Rule
[SOLVED] Help wanted to get Astro rules working
GDPR Compliance and NEW WEBSITE!
How I have automated my lights
Cron expression in if statement
What are your top 3 automations
How to compare DateTimeType of the AstroBinding with "now"
Rule for Rollershutters - with proxy items and reed-contacts
Lambda in rule fails with 'Error during the execution of rule '{RuleName}': null'
Time between x and y to do something
Help to create a rule "turn on a lamp when motion is detected but only after sunset until sunrise"
My openHab Setup
Average of 1 item during 'sun'
Weird crap! with my HVAC and Openhab
DateTime calculations in rules for Daylight switch
DateTime calculations in rules for Daylight switch
Stop timer and reset to null. Structure of rule okay?
[Solved] iCloud Presence Turn on outside light
Rules if then flag [solved]
Timeclock
Motion detection timer rule
Has someone an Idea of making a rule that checks the time in an presence management heating rule
GCAL in combination with Sunrise/-set?
[HELP] Have I missed something in a Rule?
Weather Underground widget with forecast
Compare DateTime to Static DateTime
Two trigger with a condition each in one single rule
[HELP] Have I missed something in a Rule?
Help simplifying rule
How to compre time?
How to compre time?
Changing cron by profile: design pattern around?
Assumed timing issues with rule
Create a datetime item
Can I get a Phone location within my house?
Astro binding to move switches at sunset/sunrise
Compare Hours/Minutes (NTP Binding)
Rule Push Message to iOS App when Window longer Open
How to make a rule active for only a certain time interval within the day?
Sunset: Light On
Error during the execution of rule: org.eclipse.smarthome.core.library.types.DateTimeType
PaperUI Rules vs HABmin
plusMinutes in rule - how to?
Integrate landscape lighting
Get Time Period on System Start Failing due to getCalendar()
Time of Day Events
Setting light themes in rules
Easy Away Switch / Ruleengine
Configuring Insteon Items to use Astro
Openhab.log goes crazy
Need some guidance on series of time based rules
Making Decisions Based on Time of Day
JSR223 Jython Openhab Imports Erroring?
Announce the time and weather via tts
Switching lights on at dark and of at specific times
Scheduler
Switch value changing to Null. Why!?
Send command with .map with MQTT
TimeSetup in openHAB
Astro binding on snapshot #1031 delayed 30s compared to #1025
Central Heating system control - rules the only option?
Design Pattern: Proxy Item
Astro binding to move switches at sunset/sunrise
OpenHAB or not? (KNX, HUE...)
Rule: Sunrise & motion sensor in order to open rollershutter
How to create a complex light status check rule?
Design Pattern: Manual Trigger Detection
Create daily lighting
Notifications in group design pattern
Compare string variables with equals
Check, whether sun is set in if statement in a rule
Set item by timebound rules
DateTime issue again
[SHARE] MY RULE : The Goal is turn on my garage Lights ON when the garage door is open and it nite time for 10MIN
Light controlled by Zwave + Hue + Kodi
OH2: Need help on rules that require multiple conditions to be true
Temperature check for pool pump
I need help with making a rule using astro
Cron rules
Turn Lights On Only if After Sunset and If Presence is Detected
Dimming lights based on time of day
Turn on light an hour after sunset
Example of a Lambda Function for timing the turning on and off of Lights (or switches)
Combine cron with item update rule
Looking for design pattern to handle rules based on presence state
Rule that executes only after a given time
Automation/Orchestration Design Patterns
The method getLocalMillis() is not visible, error
OnSunrise not before 6 but latest at 8
Compairing times
Lights ON at sunset / Arriving home @ Night lights on
[OH2] Rule help to play a random sound file
OH2 AstroBinding in Rule
Help, I'm ready to give up
Set Variable with OH2 Astro Binding
Rules issue in OH2. Is it my rules or OH2?
Set last tripped/alarm date, or last action date to an item - best practice?
A State Machine Primer with HABlladin, the openHAB Genie
How can I get the initial state of an MQTT switch?
'Tis The Season (Almost)
Rule: if month=1,2,3 then
Some newb questions I could not find answer for
Direction and focus
When Time cron not firing
Contact vs Switch items?
My Christmas Rule
Send Mail some Time before Calendar entry
Check day of the week / between two hours
My diy HVAC zoning setup
[SOLVED] RULE QUESTION: After sunset do this
Lambda calling other lambda? (JSR223/JYTHON)
Presence rule with reed switch state & time
[SOLVED] Switch light on when motion detected but only after a certain time
[SOLVED] Switch light on when motion detected but only after a certain time
Help with turning lights on when garage door opens
Iconset: Animated Climacons
How to use system time in rules?
[SOLVED] Morning Routine: Rule with 2 conditions is not working consistently
[SOLVED] Create a 'flexible' workweek
Event/Switch toggles
Switch Relays Based on TIme
Is it possible to......... relating to time
(Max G) #2

I tried this exact script (was using your old one, which worked like a treat), and it only switches from Day to Twilight and back.

2016-10-21 16:30:00.195 [INFO ] [openhab.model.script.ToD.rule1] - Setting TimeOfDay to "Twilight"
2016-10-21 16:34:44.832 [INFO ] [hab.model.script.Weather1.rule] - The Weather_Cloudy condition has changed to: false (34)
2016-10-22 05:09:00.146 [INFO ] [openhab.model.script.ToD.rule1] - Setting TimeOfDay to "Day"
2016-10-22 05:09:00.163 [INFO ] [openhab.model.script.ToD.rule1] - Setting TimeOfDay to "Day"
2016-10-22 06:34:44.866 [INFO ] [hab.model.script.Weather1.rule] - The Weather_Cloudy condition has changed to: false (32)
2016-10-22 07:34:44.815 [INFO ] [hab.model.script.Weather1.rule] - The Weather_Cloudy condition has changed to: true (28)
2016-10-22 12:34:45.110 [INFO ] [hab.model.script.Weather1.rule] - The Weather_Cloudy condition has changed to: true (26)
2016-10-22 16:31:00.189 [INFO ] [openhab.model.script.ToD.rule1] - Setting TimeOfDay to "Twilight"
2016-10-22 16:31:00.213 [INFO ] [openhab.model.script.ToD.rule1] - Setting TimeOfDay to "Twilight"
2016-10-22 18:34:45.073 [INFO ] [hab.model.script.Weather1.rule] - The Weather_Cloudy condition has changed to: true (11)

Also, the log entry appears twice, and I have no idea why.

    if(TimeOfDay.state.toString != currPeriod) {
      logInfo("ToD.rule1", "Setting TimeOfDay to \"" + currPeriod + "\"")
      TimeOfDay.sendCommand(currPeriod)
    }

Any hints appreciated.


(Rich Koshak) #3

Hmmmm. I think I can explain the two events.

When Astro triggers an event it toggles the switch ON and then immediately OFF. Try changing the Astro event triggers to received update ON or (I’ll update the code above accordingly.

I also see a typo in the code above. There should be no “Twilight” period.

This is what happens when I try to distill my more complicated code into simpler examples. I’ll also correct it above.

Unfortunately this doesn’t explain what you are seeing. My working rule which has an extra time period and an extra Item I use to easily keep track of the previous time of day. I’ve pasted it below. It has been working for me reliably.

rule "Get time period for right now"
when
        System started or
        Time cron "0 0 6 * * ? *" or             // Morning start
        Item Sunrise_Event received update ON or // Day start
        Item Twilight_Event received update ON or // Twilight start
        Item Sunset_Event received update ON or  //  Evening start
        Time cron "0 0 23 * * ? *"               // Night start
then
    Thread::sleep(50) // lets make sure we are just a little past the time transition

    val morning = now.withTimeAtStartOfDay.plusHours(6).millis
    val sunrise = new DateTime((Sunrise_Time.state as DateTimeType).calendar.timeInMillis)
    val twilight = new DateTime((Twilight_Time.state as DateTimeType).calendar.timeInMillis)
    val evening = new DateTime((Sunset_Time.state as DateTimeType).calendar.timeInMillis)
    val night = now.withTimeAtStartOfDay.plusHours(23).millis

    var currPeriod = "Night"
    if(now.isAfter(morning) && now.isBefore(sunrise)) currPeriod = "Morning"
    else if(now.isAfter(sunrise) && now.isBefore(twilight)) currPeriod = "Day"
    else if(now.isAfter(twilight) && now.isBefore(evening)) currPeriod = "Twilight"
    else if(now.isAfter(evening) && now.isBefore(night)) currPeriod = "Evening"

    if(TimeOfDay.state.toString != currPeriod) {
        logInfo(logNameWeather, "Updating Time of Day {}, Previous Time of Day {}", TimeOfDay.state.toString, currPeriod)
        PreviousTimeOfDay.sendCommand(TimeOfDay.state.toString)
        TimeOfDay.sendCommand(currPeriod)
    }

end

( ) #4

Hey @rlkoshak, I really like this solution. I just feel it does however not fall into the category of Design Patterns. Would you agree? I would call it a snippet, similar to the two solutions already posted here. In comparison your solution is quite neat! :wink:


Jython scripting examples using lucid, an openHAB 2.x jsr223 Jython helper library
(Rich Koshak) #5

I put it as a Design Pattern because it can be augmented beyond just Time of Day tracking. It could also be used for Day tracking, it can be used to develop a rule cascade (e.g. something that can be used for controlling one’s irrigation zones), and I’m currently working through using this approach to develop a generic state machine (one that isn’t as much work or more than just custom coding for one’s particular problem).

Perhaps if I illustrate a different non-time based example or added more text to show its generic nature.


(Max G) #6

Hmm… checked my log, and this is what it does:

2016-10-23 05:08:00.211 [INFO ] [openhab.model.script.ToD.rule1] - Setting TimeOfDay to "Day"
2016-10-23 05:08:00.221 [INFO ] [openhab.model.script.ToD.rule1] - Setting TimeOfDay to "Day"
2016-10-23 16:31:01.625 [INFO ] [openhab.model.script.ToD.rule1] - Updating Time of Day Day, Previous Time of Day Twilight
2016-10-23 18:01:00.999 [INFO ] [openhab.model.script.ToD.rule1] - Updating Time of Day Day, Previous Time of Day Evening
2016-10-23 22:00:00.218 [INFO ] [openhab.model.script.ToD.rule1] - Updating Time of Day Day, Previous Time of Day Evening
2016-10-24 16:32:01.522 [INFO ] [openhab.model.script.ToD.rule1] - Updating Time of Day to Day, Previous Time of Day was Twilight
2016-10-24 18:02:01.077 [INFO ] [openhab.model.script.ToD.rule1] - Updating Time of Day to Day, Previous Time of Day was Evening

It is always Day… took your code verbatim, and only changed the wording of the log info…

Edit – added:

2016-10-25 20:57:48.626 [INFO ] [openhab.model.script.ToD.rule1] - TimeOfDay morning...: 1477339200000
2016-10-25 20:57:48.921 [INFO ] [openhab.model.script.ToD.rule1] - TimeOfDay sunrise...: 2016-10-25T05:06:00.000+10:00
2016-10-25 20:57:49.130 [INFO ] [openhab.model.script.ToD.rule1] - TimeOfDay twilight..: 2016-10-25T16:33:00.000+10:00
2016-10-25 20:57:49.377 [INFO ] [openhab.model.script.ToD.rule1] - TimeOfDay evening...: 2016-10-25T18:03:00.000+10:00
2016-10-25 20:57:49.582 [INFO ] [openhab.model.script.ToD.rule1] - TimeOfDay night.....: 1477400400000
2016-10-25 20:57:52.078 [INFO ] [openhab.model.script.ToD.rule1] - Updating TimeOfDay: Day, Previous TimeOfDay: Evening

What I have noticed is that sunrise is before morning… morning and night come up as millis, while the others are date/time stamps. Can OH compare these without casting them first?


(Rich Koshak) #7

I have noticed one error in my setup now that I’ve been home to notice (it was a busy week this past week) in that it is not transitioning to Night for me. I do need to look into that but that does not address what you are seeing. Here is my Time Of Day logs for the past few days:

2016-10-21 06:00:00.053 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Evening, Previous Time of Day Morning
2016-10-21 07:17:00.060 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Morning, Previous Time of Day Day
2016-10-21 16:40:00.079 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Day, Previous Time of Day Twilight
2016-10-21 18:10:00.058 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Twilight, Previous Time of Day Evening
2016-10-22 06:52:52.439 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Evening, Previous Time of Day Morning
2016-10-22 07:18:00.062 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Morning, Previous Time of Day Day
2016-10-22 16:39:00.063 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Day, Previous Time of Day Twilight
2016-10-22 18:09:00.058 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Twilight, Previous Time of Day Evening
2016-10-23 06:00:00.093 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Evening, Previous Time of Day Morning
2016-10-23 07:19:00.116 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Morning, Previous Time of Day Day
2016-10-23 16:38:00.060 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Day, Previous Time of Day Twilight
2016-10-23 18:08:00.060 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Twilight, Previous Time of Day Evening
2016-10-24 06:00:00.059 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Evening, Previous Time of Day Morning
2016-10-24 07:20:00.086 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Morning, Previous Time of Day Day
2016-10-24 16:36:00.064 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Day, Previous Time of Day Twilight
2016-10-24 18:06:00.057 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Twilight, Previous Time of Day Evening
2016-10-25 06:00:00.056 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Evening, Previous Time of Day Morning
2016-10-25 07:21:00.058 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Morning, Previous Time of Day Day

That can happen if your sunrise is before 06:00 which it is indeed for me some parts of the year for me, but not at the moment. There might be a bug there, though I as pretty sure I had accounted for that in this rule. Since the actual time the sun comes up moves around (not to mention the daylight savings nonsense) the rule should account for this. It should just skip over Morning entirely if Sunrise is before Morning starts.

That is indeed an inconsistency. They should all be the same. However it works as is because the Joda DateTime class has two versions on .isAfter and .isBefore, one that takes a long (i.e. milliseconds) and one that takes a DateTime (really a parent of DateTime but let’s not bring OO programming and inheritance into this right now). It really isn’t OH that is doing the comparison, it’s the Joda DateTime class.

Updates to make them consistent.

    val morning = new DateTime(now.withTimeAtStartOfDay.plusHours(6).millis)
    val sunrise = new DateTime((Sunrise_Time.state as DateTimeType).calendar.timeInMillis)
    val twilight = new DateTime((Twilight_Time.state as DateTimeType).calendar.timeInMillis)
    val evening = new DateTime((Sunset_Time.state as DateTimeType).calendar.timeInMillis)
    val night = new DateTime(now.withTimeAtStartOfDay.plusHours(23).millis)

I need to think some more on why when I run I’m failing to transition to night and for you it is stuck as Day. What is weird is the “Previous Time of Day” does seem to be changing some.

Are you certain the rule is copied in verbatim? It almost looks like PreviousTimeOfDay is being updated twice and TimeOfDay is not or something like that.

    if(TimeOfDay.state.toString != currPeriod) {
    	logInfo(logNameWeather, "Updating Time of Day {}, Previous Time of Day {}", TimeOfDay.state.toString, currPeriod)
    	PreviousTimeOfDay.sendCommand(TimeOfDay.state.toString)
    	TimeOfDay.sendCommand(currPeriod)
    }

EDIT: Wait, I am seeing weirdness in the timestamps in my logs, though the behavior of my other rules seems to be correct. Investigating…

EDIT 2: My bad, I had the arguments passed to the log statement backward. doh! It should be:L

logInfo(logNameWeather, "Updating Time of Day {}, Previous Time of Day {}", currPeriod, TimeOfDay.state.toString)

So in your logs, “Previous Time of Day” is actually the current and visa versa.

EDIT 3: I do see that indeed you should never see a morning since your sunrise is well before 06:00. And based on the error I see in my log statement I see that it is your PreviousTimeOfDay that is stuck at Day and your TimeOfDay is flipping between Twilight and Evening. That is still very wrong but another data point.

EDIT 4: The lack of the transition to Night may have something to do with the problem described here:

I’ve changed my trigger to use only one cron trigger:

Time cron "0 0 6,23 * * ? *"

We will see if that fixes that one problem tonight.


(Rich Koshak) #8

OK, adjusting the cron trigger as described above seems to have addressed the no Night problem I was seeing.

Here is the log from the past day and the rule as it exists.

2016-10-25 07:21:00.058 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Morning, Previous Time of Day Day
2016-10-25 16:35:00.059 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Twilight, Previous Time of Day Day
2016-10-25 18:05:00.061 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Evening, Previous Time of Day Twilight
2016-10-25 23:00:00.058 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Night, Previous Time of Day Evening
2016-10-26 06:00:00.060 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Morning, Previous Time of Day Night
2016-10-26 07:22:00.101 [INFO ] [lipse.smarthome.model.script.weather] - Updating Time of Day Day, Previous Time of Day Morning
rule "Get time period for right now" 
when
	System started or
	Time cron "0 0 6,23 * * ? *" or           // Morning start, Night start
	Item Sunrise_Event received update ON or  // Day start
    Item Twilight_Event received update ON or // Twilight start
	Item Sunset_Event received update ON      // Evening start
then
    Thread::sleep(50) // lets make sure we are just a little past the time transition

    val morning = new DateTime(now.withTimeAtStartOfDay.plusHours(6).millis)
	val sunrise = new DateTime((Sunrise_Time.state as DateTimeType).calendar.timeInMillis)
	val twilight = new DateTime((Twilight_Time.state as DateTimeType).calendar.timeInMillis)
	val evening = new DateTime((Sunset_Time.state as DateTimeType).calendar.timeInMillis)
	val night = new DateTime(now.withTimeAtStartOfDay.plusHours(23).millis)

    var currPeriod = "ERROR"
    if     (now.isAfter(morning)  && now.isBefore(sunrise))  currPeriod = "Morning"
    else if(now.isAfter(sunrise)  && now.isBefore(twilight)) currPeriod = "Day"
    else if(now.isAfter(twilight) && now.isBefore(evening))  currPeriod = "Twilight"
    else if(now.isAfter(evening)  && now.isBefore(night))    currPeriod = "Evening"
    else if(now.isAfter(night))                              currPeriod = "Night"

    if(TimeOfDay.state.toString != currPeriod) {
    	logInfo(logNameWeather, "Updating Time of Day {}, Previous Time of Day {}", currPeriod, TimeOfDay.state.toString)
    	PreviousTimeOfDay.sendCommand(TimeOfDay.state.toString)
    	TimeOfDay.sendCommand(currPeriod)
    }
        
end

Compare your version of the rule to the above. Pay particular attention to the if(TimeOfDay.state.toString != currPeriod) section. Based on your logs I suspect the problem lies there.


(Rich Koshak) #9

I added a Complicated Example showing how the design pattern applies to other non-time based sequences, in this case controlling the zones of a sprinkler system. In this case the only time based event is the one that kicks off the watering. The rest of the events occur when the Zones turn off. A separate rule get triggered by the Irrigation Item’s state changes.


(Max G) #10

… well, it depends… maybe complicated in OH :slight_smile:
No, as always, I appreciate your work, and more so sharing of ideas and patterns… as well as replying to noobs like me…


(Jack De Winter) #11

Good Time of Day Design Pattern example rick… added some extra comments here and there as I learned the rule. Let me know if you want those.

With the latest OH2, I found I needed to encapsulate the information as such:

val long sunset_start = new DateTime((Astro_Sun_Set_Time.state as DateTimeType).calendar.timeInMillis).millis
val long sunrise_start = new DateTime((Astro_Sun_Rise_Time.state as DateTimeType).calendar.timeInMillis).millis

for your sunrise and evening values. Did you have to do this to your rules as well?


(Rich Koshak) #12

Actually I’m my current version, which I thought I had posted here, I don’t use the DateTime objects at all any more and just use the milliseconds. There is no reason to create objects as now.isBefore etc all can handle epoc as well.

Upon looking back I do see that I posted it but only in the Astro 2.0 section.


(Jack De Winter) #13

I tried to follow what you did in the OH2 section, but the only solution that I found that properly set the variables with the milliseconds was that convoluted mess. When I did

(Astro_Sun_Set_Time.state as DateTimeType).calendar.timeInMillis

without the extra stuff around it, it complained about calendar not having a timeInMillis field that it could find. Does it look okay to you?

Also, love the trick of doing the offset by changing the geolocation!


(Rich Koshak) #14

Was Designer complaining or the oh logs?

If Designer, which version?

For the moment, please use Eclipse SmartHome Designer 0.8.

If in the log in not sure what the issue could be. The code above is copied verbatim from my running config.


(Robert) #15

Your rule calls now more than once. Although every call will only differ some milliseconds, from a design pattern perspective it would be stricter to declare a val at the top of the rule that calls now once and use that value across the rule instead of now.


(Rich Koshak) #16

It would but I would consider that a micro optimization which is something I typically do not worry about unless and until I actually experience performance, logic, or timing problems. For a rule that executes five times over the course of the day and even with all the calls to now ends up only taking a hundred milliseconds or so to run I wouldn’t worry.

One could argue that it might make the rule more clear and easier to understand in which case I would consider the change.

However, in this case I do not see how defining a new variable to replace the calls to now would actually clarify anything.


(Robert) #17

What is the specific purpose of assigning a value and not use

var curr = NULL

instead?


[OH2] [Astro Binding] [Feature Request] Between Sunset and Rise
(Rich Koshak) #18

Because I can’t sendCommand(null). It will generate an error. I could use curr = NULL which would set the state of vTimeOfDay to undefined but that would mean that whereever I check vTimeOfDay for some reason I’d need to check if it is NULL in addition to checking if it is “Evening”, for example. By using the String “UNKNOWN” I can avoid those extra checks and since the “UNKNOWN” state doesn’t drive any behavior it will not negatively impact any of my rules.

Honestly, “UNKNOWN” should never actually be sent to to the vTimeOfDay unless there is an error in the rule. I use it mainly to catch errors without breaking my other rules.


(Robert) #19

I am testing a rule with the clause

rule MyRule
when
    Item TimeOfDay received command "AFTERNOON"
then
    // do stuff
end

but it doesn’t seem to trigger. I know it is possible to use received command ON for switches. Is it possible at all to use a string after the received command? Or is it only possible to use

rule MyRule
when
    Item TimeOfDay received command
then
    switch(receivedCommand) {
        case "AFTERNOON" : // do some stuff
        case "BED" : // do other stuff
    }
end

(MikeH) #20

I use the second method you mentioned. Something like this:

rule MyRule
when
    Item TimeOfDay received command
then
    if(TimeOfDay.state.toString == "AFTERNOON") {
                                                   //Do some stuff                            
    }
end