[Deprecated] Design Pattern: Time Of Day

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

As stated in the first link in the DP that describes the purpose of a DP, it is not and never was intended to be a complete copy and paste solution. The code above and the table that goes with it is intended to be just an example. As an example, it should avoid unnecessary complexities. Those complexities that are specific to your part of the world are very much “an exercise left to the student” to implement.

I’m not inclined to add these complexities to the example as they don’t really add anything in terms of explanation of the DP. They are just more of the same sorts of things already demonstrated.

But, even in my part of the world sunrise will happen before 06:00. And during those times of year, the MORNING state simply never occurs.

NOTE: There is a new capability in OH 2.5 M2 or later (I think) called Ephemeris which will let you define and use type of day type information (e.g. weekend, workday, etc.) in your Rules. See Ephemeris binding?.

2 Likes

Thanks for the tips, the logInfo line fixed the error. I also created the thing called astro:sun:minus90 (sorry for not seeing this myself :slight_smile: )

I rebooted the OpenHab but still I am not seeing the correct current time of the day :frowning:.

The log now looks like this:

2019-10-12 12:59:29.306 [vent.ItemStateChangedEvent] - vMorning_Time changed from NULL to 2019-10-12T06:00:00.000+0200

2019-10-12 12:59:29.328 [vent.ItemStateChangedEvent] - vNight_Time changed from NULL to 2019-10-12T23:00:00.000+0200

2019-10-12 12:59:29.331 [vent.ItemStateChangedEvent] - vBed_Time changed from NULL to 2019-10-12T00:00:00.000+0200

==> /var/log/openhab2/openhab.log <==

2019-10-12 12:59:33.679 [INFO ] [b.handler.EspMilightHubBridgeHandler] - Sucessfully subscribed to milight/states/#

==> /var/log/openhab2/events.log <==

2019-10-12 12:59:34.538 [hingStatusInfoChangedEvent] - ‘zwave:device:a5af8125:node6’ changed from OFFLINE (BRIDGE_OFFLINE): Controller is offline to ONLINE

2019-10-12 12:59:34.545 [hingStatusInfoChangedEvent] - ‘zwave:serial_zstick:a5af8125’ changed from OFFLINE (BRIDGE_OFFLINE): Controller is offline to ONLINE

2019-10-12 12:59:34.552 [me.event.ThingUpdatedEvent] - Thing ‘zwave:serial_zstick:a5af8125’ has been updated.

2019-10-12 12:59:34.555 [hingStatusInfoChangedEvent] - ‘zwave:device:a5af8125:node5’ changed from OFFLINE (BRIDGE_OFFLINE): Controller is offline to ONLINE

2019-10-12 12:59:46.972 [hingStatusInfoChangedEvent] - ‘zwave:device:a5af8125:node5’ changed from ONLINE to ONLINE: Node initialising: REQUEST_NIF

2019-10-12 12:59:47.424 [vent.ItemStateChangedEvent] - SW_Buitenlamp changed from NULL to 0

2019-10-12 12:59:47.692 [hingStatusInfoChangedEvent] - ‘zwave:device:a5af8125:node5’ changed from ONLINE: Node initialising: REQUEST_NIF to ONLINE

2019-10-12 12:59:59.360 [hingStatusInfoChangedEvent] - ‘ntp:ntp:local’ changed from ONLINE to REMOVING

2019-10-12 12:59:59.372 [hingStatusInfoChangedEvent] - ‘ntp:ntp:local’ changed from REMOVING to REMOVED

2019-10-12 12:59:59.421 [hingStatusInfoChangedEvent] - ‘ntp:ntp:local’ changed from REMOVED to UNINITIALIZED

2019-10-12 12:59:59.437 [hingStatusInfoChangedEvent] - ‘ntp:ntp:local’ changed from UNINITIALIZED to UNINITIALIZED (HANDLER_MISSING_ERROR)

==> /var/log/openhab2/openhab.log <==

This is my things file:

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

Thing astro:sun:local “Local Sun” [geolocation=“12.34578,1.23456,123”, interval=300]{
Channels:
Type rangeEvent : rise#event [
offset=-0
]
Type start : rise#start [
offset=-0
]
Type rangeEvent : set#event [
offset=-0
]
Type end : rise#end [
offset=-0
]
}

This my time of the day rules file:
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: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 The Day")
  logInfo("Calculate time of day state", "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

The item on my sitemap says: Current time of the day: UNKNOWN

The two lines at the end of your rule should produce a line each in your logs.
Can’t see that in the log snippets shown.
Maybe your xx.rules file didn’t load (there will be a log)

As Rossko said, there seems to be something wrong with your rule. When looking in your log, one can see, that the times for your items are set:

Morning = 06:00:00 Night = 23:00:00 Bed = 00:00:00

So that seems to be ok.

But what is weird, is the state of your current time of the day. It’s initially set to “UNKNOWN” and the “state machine” should set it to its correct value, when triggering.

So I’m wondering how can it be, that there was a change of your items (vBed_Time, etc) at 12:59:29. I can’t find a Trigger in the rule at that time.

Can you really see the rule triggering as Rossko asked ?

There are two logs in the rule:

logInfo("Calculate time of day state", "Calculating Time Of the Day")
.......
logInfo(logName, "Calculated time of day is" + curr)

and as I said some posts before: Either you have to define the variable “logName” or you have to change the second logInfo like in the first one shown.

EDIT:

I’ve made a short test and changed something in my rule and it triggers after that (I think it’s because of the “System started” Trigger).And you should see this in the log:

2019-10-12 14:32:56.010 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'rich_time.rules'
2019-10-12 14:33:03.446 [INFO ] [e.smarthome.model.script.Time Of Day] - Calculating time of day...
2019-10-12 14:33:03.472 [INFO ] [e.smarthome.model.script.Time Of Day] - Calculated time of day is DAY

Thanks guys for the replies, the logs let me see that the rule for the calculating part is triggered:

2019-10-12 13:17:34.250 [INFO ] [l.script.Calculate time of day state] - Calculating Time Of the Day

But I wont’t get the output which states the time of the day.

and as I said some posts before: Either you have to define the variable “logName” or you have to change the second logInfo like in the first one shown.

When I change this one into the format like the first one I’ll get an error. I changed the second one into:

logInfo("Calculated time of day is" + curr)
  vTimeOfDay.sendCommand(curr)

I do get this error after saving it:

2019-10-12 15:21:01.705 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model ‘lighting.rules’

2019-10-12 15:21:07.986 [INFO ] [l.script.Calculate time of day state] - Calculating Time Of the Day

2019-10-12 15:21:08.029 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Error during the execution of startup rule ‘Calculate time of day state’: index=1, size=1

Remon: I think there is a syntax error in your log statement. Please examine Peter’s example from above

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

There is
logInfo (“some stuff in double quotes” , “actual log statement”)

So the order is the
logInfo
open parenthesis
stuff in double quotes
comma
atual log entry in double quotes
close parenthesis

here is the documentation for adding log entries to your rules

2 Likes

Thanks Andrew you were right and that was the final step to take! I changed the logInfo to:
logInfo("Calculated time of day is", "Calculated time of day logs" + curr)

And now it gives me the time of the day state. Thanks a lot!

1 Like

“Little Step for mankind, but giant leap for you”. (Hope so) :crazy_face::wink:

@rlkoshak
I have a problem with this rule. So it seemed that AFTERNOON never got triggered. And I assumed that some config was wrong.
Added a dummy switch to trigger the rule manually. If I trigger it in the AFTERNOON state, it calculates it right, but not automatically.

The vEvening_Time has the right time (sunset - 90 mins) if I look at it (so the config is right I think).
The trigger can be also seen in the log, however:

2019-10-30 16:29:00.015 [vent.ChannelTriggeredEvent] - astro:sun:91a227b3:set#event triggered START
2019-10-30 16:29:00.025 [vent.ChannelTriggeredEvent] - astro:sun:local:set#event triggered START

(91a227b3 -> this is the sun with the offset). So the trigger however not seem to get shifted. Why is that? Do you have any idea? And that trigger time is the actual sunset time (so it looks like that the offset doesn’t have an effect on the trigger, but only on item output)

I have this problem long time ago, I have restarted the system numerous time, now I even have a fresh install, and still the same problem…

There is something wrong with your Thing definition if the event isn’t being offset. You’ve not supplied the offset or you’ve supplied it in an incorrect manner. Or there is a bug with Astro and an issue needs to be filed.

But the item which is linked to that Thing, shows the correct offset. So that being said, I assume that the config is right, or not?

There is something different about it in some way. You’ve not posted the latest versions of the Item nor have you posted the Rule triggers. I don’t actually use Astro events any longer (see the Python version above) so I’ve no recent experience using this, but no one else is complaining about this issue so there is either something wrong with your Thing definition or there is a bug that is unique to your configuration.

Yes I saw that, I’m already planning to migrate everything to Python, but that takes some time… And I assumed that I will also have this problem there as well, because that seems to be an Astro issue nor a rule issue for me.

Rules:

val String filename = "DayState.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:local:rise#event'		triggered START or
	Channel 'astro:sun:local:set#event'			triggered START or
	Channel 'astro:sun:91a227b3:set#event'		triggered START or
	Item Recalculate_DayState received command ON or
	Time cron "10 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 "1 0 22 * * ? *"
then

	logInfo(filename, "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(2)
	vNight_Time.postUpdate(night_start.toString)

	val bed_start = now.withTimeAtStartOfDay
	vBed_Time.postUpdate(bed_start.toString)

	val now_5= new DateTime(now.plusSeconds(5))

	// 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_5 {
		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"
		default : curr = "Unknown"
	}
	
	if(Recalculate_DayState.state == ON) {
		Recalculate_DayState.sendCommand(OFF)
	}
	// Publish the current state
	if (vTimeOfDay.state.toString != curr) {
		logInfo(filename, "Calculated and set time of day is " + curr)
		vTimeOfDay.sendCommand(curr)
	}
	else {
		logInfo(filename, "Calculated time of day is " + curr)
	}
end

Item definition

DateTime    vEvening_Time   "Afternoon [ %1$tH:%1$tM]"  <sunset>        { channel="astro:sun:91a227b3:set#start" }

I use PaperUI to set up my Things, so I can’t show you textually the Thing defition. But it only differs from the autodiscovered Thing that I put -90 to the Sunset Offset.

The Python version doesn’t use triggers to the Rule to drive the state machine. Instead it uses only DateTime Items and uses Timers to drive the state machine. If the DateTime Item is correct, the Python Rule is correct.

I’ve just posted a new version of the Rule that lets you use Ephemeris and you can define different times of day for different types of days (weekends, workdays, holidays, etc).

On which Channel did you put the offset?

That question did the trick :slight_smile: I only set the offset for the Start Time, not for the Range Trigger. I assume that now it will be correct :slight_smile: