Design Pattern: Simple State Machine (e.g. Time Of Day)

I second that, @rlkoshak . I have the same problem after the start of daylight saving time.


What can be the problem?

See above for a discussion on this. I think the function in timerUtils.js that moves the date time to today has a bug. I assumed that

dt.withYear(now.getYear()).withMonth(now.getMonthValue()).withDayOfMonth(now.getDayOfMonth());

would take into account DST changes. But apparently it does not. My first thought is changing that to:

dt.withYear(now.getYear())
  .withMonth(now.getMonthValue())
  .withDayOfMonth(now.getDayOfMonth())
  .withHour(now.getHour());

That should force it to take into account DST. If not Iā€™ll have to fall back to the jump to the next day and subtract the number of hours like the Rules DSL version does. I was hoping to avoid that though.

1 Like

Thanks @rlkoshak! Iā€™m sorry I didnā€™t read the discusion above, I didnā€™t noticed that it was the same subject.

I have the same problem, just scrolled up before posting. :wink:
This needs to be fixed in the script itself, right?

I am running a rule to redefine the times every day and was thinking about just adding an offset for the case that something like isdaylightsavingtime is true, but that would only be a workaround.

The fix is as I posted in post 668 in timeUtils.js. Add the withHour part. I need to test it out before I post it to the repo.

It should only be a problem that one time when DST changes. Then if you change your manually set DateTimes it will be correct from that point forward until DST changes again. Adding the withHour part posted above should fix it on the days where DST changes.

There is no need for a work around. Just fix the DateTime Items the same way you initialized them in the first place and deploy the fix above. Now that DST has changed you will have to manually adjust your DateTimes manually. The fix will just prevent the problem from happening when DST changes again.

2 Likes

thanks, will try that. No errors so far.
It was not an one time problem for me, as there was no changing of DST since last night. It worked before the DST and is now 1 hour delayed.

Right. On the day that DST occurred it set the DateTime forward only 24 hours. That put the new DateTime one hour off from where it should be. For example, if the time was 07:00 the day before DST, the day after DST it will be 06:00 or 08:00 (depending on whether itā€™s spring or fall). From that point forward the that has changed.

The next day at midnight it takes the current DateTime which is already off by an hour (e.g. 06:00) and moves it forward 24 hours (still 06:00). For a day where DST didnā€™t occur, that is correct behavior. The error only occurred on the day DST changed. But because of the error, the time stored by the DateTime Item changed to an incorrect time.

Once you reset your DateTime Items to the correct time that adding of 24 hours will continue to work. The fix I posted above should make it so that when DST changes next time this fall, the new time calculated wonā€™t end up off by one hour.

I canā€™t figure out whatā€™s wrong, but something is wrong.
Iā€™ve change the code to the code above and add a logger to show me the value of ā€˜dtā€™ and ā€˜nowā€™ in the 'timeUtils.js`

context.toToday = function(when) {
    var now = ZonedDateTime.now();
	var dt = toDateTime(when);
	log.info("DT "+ dt+" NOW "+now)  
	//return dt.withYear(now.getYear()).withMonth(now.getMonthValue()).withDayOfMonth(now.getDayOfMonth());
	return dt.withYear(now.getYear()).withMonth(now.getMonthValue()).withDayOfMonth(now.getDayOfMonth()).withHour(now.getHour());
  }

When I change an Item which hold a time, the log show the following:

==> /var/log/openhab/openhab.log <==
2021-04-02 09:53:18.490 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Today is a holiday day.
2021-04-02 09:53:18.593 [INFO ] [penhab.model.script.Rules.time_utils] - DT 1970-01-01T07:30+01:00 NOW 2021-04-02T09:53:18.513442+02:00[Europe/Amsterdam]
2021-04-02 09:53:18.621 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Moved Holiday_Heating to today.

==> /var/log/openhab/events.log <==
2021-04-02 09:53:18.623 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'Holiday_Heating' changed from 1970-01-01T07:30:00.000+0100 to 2021-04-02T09:30:00.000+0100

As you can see, the var dt holds the wrong timezone, with the DST itā€™s suppose to be GMT+2, as the var now has.
but the more strange thing is, why would it change my item 2 hours further? (from 7:30 what I put in, to 9:30)

When I change it back to the original code, it keeps the right time (only the wrong timezone).

==> /var/log/openhab/openhab.log <==
2021-04-02 10:02:30.013 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Today is a holiday day.
2021-04-02 10:02:30.118 [INFO ] [penhab.model.script.Rules.time_utils] - DT 1970-01-01T07:30+01:00 NOW 2021-04-02T10:02:30.029764+02:00[Europe/Amsterdam]
2021-04-02 10:02:30.143 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Moved Holiday_Heating to today.

==> /var/log/openhab/events.log <==
2021-04-02 10:02:30.147 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'Holiday_Heating' changed from 1970-01-01T07:30:00.000+0100 to 2021-04-02T07:30:00.000+0100

I think I found the solution. Iā€™ve to make some further test, but if you change the orginal code to:

return dt.withYear(now.getYear()).withMonth(now.getMonthValue()).withDayOfMonth(now.getDayOfMonth()).withZoneSameLocal(now.getOffset());

Itā€™ll get the right timezone.

@rlkoshak, shall I create an issue this on GitHub?

2 Likes

Yes please! Thanks for figuring this out!

Thanks, just found that toToday didnā€™t work correct any more, it just leaves the minutes and overrides everything else with now.

1 Like

Iā€™ve created a PR. https://github.com/rkoshak/openhab-rules-tools/pull/60

1 Like

Merged. Thanks!

OK, I am almost there. Working in the MainUI is a challenge for me, but I am getting where I need to be. I hope you can answer my last questions.

The defined astro channels are base on ā€œastro:sun:set120:set#startā€ and channel=ā€œastro:sun:local:set#startā€. Where and how is channel set120 defined ? I aussume itā€™s not the same channel as local. Do I need to define this channel somewhere ?

In the example you define times for the holiday.

// Weekend day, notice that not all the states are listed, the unlisted states are skipped
DateTime Weekend_Day (TimesOfDay) { channel="astro:sun:set120:set#start", etod="DAY"[type="weekend"] }
DateTime Weekend_Evening (TimesOfDay) { channel="astro:sun:local:set#start", etod="EVENING"[type="weekend"] }
DateTime Default_Bed (TimesOfDay) { etod="BED"[type="weekend"] }

// Default holiday
DateTime Weekend_Day (TimesOfDay) { channel="astro:sun:local:set#start", etod="DAY"[type="holiday"] }
DateTime Weekend_Evening (TimesOfDay) { channel="astro:sun:local:set#start", etod="EVENING"[type="holiday"] }
DateTime Default_Bed (TimesOfDay) { etod="BED"[type="holiday"] }

I assume that you have to define other items than I defined for the weekend i.e. Holiday_Evening instead of Weekend_Evening. Am I right ?

Thanks for the all the help. A lot of rules inmu OH2 configuration where based on this design, so I want to get it working in OH3 Javascript

Itā€™s a Thing you define in your Astro binding configuration.

You may define several Astro Things, each has a type (sun, moon) and you set a location. Each creates a full set of predefined channels (sunrise, azimuth etc.).

You might choose to put a sun Thing at a location 30 degrees of longitude to the west of you, and call it mePlus120, because the astro calculations will be two hours behind your real location.

Clear, I can do that ! But shouldntā€™ it be :rise#start for the xxxx_ā€œDAYā€ items then ? Why is the :set#start being used ? I am confused.

Probably. It is an example, not a copy/paste solution.

1 Like

Thanks ! I thought it was a good example to start with, which I could tune to my needs afterwards. Now I changed everything to rise it works !

Donā€™t worry, you were not the only one having this problem! :wink:

1 Like

Hi!

Today i tried out to add this pattern to my oh3 installation.
I added the item and rules from Rules DSL.

When i trigger the rule thats calculates the tod, i get this error in the log:

21:11:56.785 [ERROR] [.internal.handler.ScriptActionHandler] - Script execution of rule with UID 'time_of_day-1' failed: 'withTimeAtStartOfDay' is not a member of 'java.time.ZonedDateTime'; line 20, column 23, length 24 in time_of_day

Here is the complete rule:

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
  Item vTimeofDay_Calc changed to ON or
  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) 

  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