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

After landing on 3.0.0 I noticed log-rotation settings have changed.
openhab.log is pruned on startup, but by chance I had a .2 version from last night:

omr@shs2:~$ grep -i 'transitioning' /var/log/openhab/openhab.log.2
2020-12-28 18:04:00.079 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from DAY to EVENING
2020-12-28 23:00:00.001 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from EVENING to NIGHT

Had a power outage 00:44 -

omr@shs2:~$ grep -i 'transitioning' /var/log/openhab/openhab.log
2020-12-29 06:00:00.100 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from BED to MORNING
2020-12-29 06:00:00.100 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from BED to MORNING
2020-12-29 09:27:00.001 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from MORNING to DAY
2020-12-29 09:27:00.002 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from MORNING to DAY

(why the duplicate entries?)

Let’s see what happens this evening at 23:00 …

Update:
Looks good, but still some duplicate events:

2020-12-29 09:27:00.001 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from MORNING to DAY
2020-12-29 09:27:00.002 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from MORNING to DAY
2020-12-29 18:05:00.005 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from DAY to EVENING
2020-12-29 18:05:00.007 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from DAY to EVENING
2020-12-29 23:00:00.001 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from EVENING to NIGHT
2020-12-29 23:00:00.004 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from EVENING to NIGHT
2020-12-29 23:59:59.001 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from NIGHT to BED
2020-12-30 06:00:00.003 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from BED to MORNING
2020-12-30 09:27:00.001 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from MORNING to DAY

Somehow the rule is running twice and failing to cancel the existing timers before creating the new ones. On line 180 of the Script Action you should see

timers.cancelAll();

If not you have an older version of the script with exactly that bug in it. Updating your YAML from the latest in GitHub should fix that problem.

Note that there is one case where even that won’t work. If you’ve Timers that are already created when you edit the rule, the old timers become orphaned and will execute at their scheduled times. I’ve not figured out a way to avoid that from happening with MainUI rules yet.

I did have several attempts copy/pasting from GitHub 3 days ago until it ‘took’ without any errors.

I copy/pasted via Notepad++ to convert from CRLF to LF and that might have thrown the line numbers off a bit. I have:

Maybe try copying and pasting the raw code directly into Openhab. The raw version can be found here:
https://raw.githubusercontent.com/rkoshak/openhab-rules-tools/main/ephem_tod/javascript/ephemTimeOfDay.yml

OK now after a restart.

omr@shs2:~$ grep -i 'transitioning' /var/log/openhab/openhab.log
2020-12-30 23:00:00.038 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from EVENING to NIGHT
2020-12-30 23:59:59.004 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from NIGHT to BED
2020-12-31 06:00:00.001 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from BED to MORNING
2020-12-31 09:27:00.001 [INFO ] [openhab.model.script.Rules.TimeOfDay] - Transitioning Time of Day from MORNING to DAY
omr@shs2:~$

Just a heads up for everyone: I’m on the 3.0.0 testing, i.e. the release candidate, and the ephemeris seems to be quite buggy. It loses its settings again and again, causing the TimeOfDay script to fail. Today I just added a new Zwave device, and that was enough to throw it off. No Openhab restart or anything.

So when the script is not transitioning, look for that. It becomes quite obvious when you watch the logs while manually running the script and you get a bunch of warnings from ephemeris.

After having trouble with OH3 and this DP i would like to share my working (DSL) rule…

rule "TimeOfDay - Calculate State" 
when
	System started or 
	Channel 'astro:sun:home:civilDawn#event'  triggered START or
	Channel 'astro:sun:home:set#event'  triggered START or
	Time cron "0 1 0 * * ? *" or
	Time cron "0 0 1 * * ? *" or
	Time cron "0 0 8 * * ? *" or
	Time cron "0 0 10 * * ? *" or
	Time cron "0 0 12 * * ? *" or
	Time cron "0 0 15 * * ? *" or
	Time cron "0 0 18 * * ? *"
then
	logInfo("TimeOfDay", "Calculating time of day...")
	// get values for calculation from astrodata
	val dawnstart = (Dawnstart_Time.state as DateTimeType).getZonedDateTime()
	val sunsetstart = (Sunsetstart_Time.state as DateTimeType).getZonedDateTime()

	// set hardcoded values for calculation
	val morning = now.withHour(8).withMinute(0).withSecond(0)
	val forenoon = now.withHour(10).withMinute(0).withSecond(0)
	val noone = now.withHour(12).withMinute(0).withSecond(0)
	val afternoon = now.withHour(15).withMinute(0).withSecond(0)
	val evening = now.withHour(18).withMinute(0).withSecond(0)
	val dayend = now.withHour(23).withMinute(59).withSecond(59)
	val daystart = now.withHour(0).withMinute(0).withSecond(0)
	val bedtime = now.withHour(1).withMinute(0).withSecond(0)

	// send values to Items
	Morning_Time.postUpdate(morning.toLocalDateTime().toString())
	Forenoon_Time.postUpdate(forenoon.toLocalDateTime().toString())
	Noon_Time.postUpdate(noone.toLocalDateTime().toString())
	Afternoon_Time.postUpdate(afternoon.toLocalDateTime().toString())
	Evening_Time.postUpdate(evening.toLocalDateTime().toString())
	Dayend_Time.postUpdate(dayend.toLocalDateTime().toString())
	Daystart_Time.postUpdate(daystart.toLocalDateTime().toString())
	Bedtime_Time.postUpdate(bedtime.toLocalDateTime().toString())

	// Calculate the current time of day
	var curr = "UNKNOWN"
	var last = TimeOfDay.previousState(true).state.toString

	switch true {
		case 	now.isAfter(bedtime) 		&& now.isBefore(dawnstart)									: curr =    "BEDTIME"
		case 	now.isAfter(dawnstart) 		&& now.isBefore(morning) 		&& now.isBefore(forenoon)   : curr =    "DAWN"
		case 	now.isAfter(dawnstart) 		&& now.isAfter(morning) 		&& now.isBefore(forenoon)   : curr =    "DAWN"
		case 	now.isAfter(morning) 		&& now.isAfter(dawnstart)   	&& now.isBefore(forenoon) 	: curr =    "MORNING" 
		case 	now.isAfter(morning) 		&& now.isBefore(dawnstart)  	&& now.isBefore(forenoon) 	: curr =    "MORNING"      
		case 	now.isAfter(forenoon) 		&& now.isBefore(noone)       								: curr =    "FORENOON"
		case 	now.isAfter(noone) 			&& now.isBefore(afternoon)      							: curr =    "NOON"
		case 	now.isAfter(afternoon) 		&& now.isBefore(evening)    								: curr =    "AFTERNOON"
		case 	now.isAfter(evening)		&& now.isBefore(sunsetstart) 	&& now.isBefore(dayend)		: curr =    "EVENING"
		case 	now.isAfter(evening)		&& now.isAfter(sunsetstart) 	&& now.isBefore(dayend)		: curr =    "EVENING"
		case 	now.isAfter(sunsetstart)	&& now.isAfter(evening)			&& now.isBefore(dayend)		: curr =    "SUNSET"
		case 	now.isAfter(sunsetstart)	&& now.isBefore(evening) 		&& now.isBefore(dayend)		: curr =    "SUNSET"
		case 	now.isAfter(daystart) 		&& now.isBefore(bedtime)									: curr =	"NIGHT"
		case 	now.isAfter(dayend) 		&& now.isBefore(daystart)									: curr =	"EVENING"	
	}

	// Publish the current state
	if ( TimeOfDay.state.toString != curr ) {
		TimeOfDay.sendCommand(curr)
		PreviousTimeOfDay.sendCommand(last)
		logInfo("TimeOfDay", "It's "+curr.toString+" now")
		logInfo("PreviousTimeOfDay", "Previous TimeOfDay: "+last.toString)
	}
end

maybe it differs a bit to the original DP from Rich but I think one can gather some information from this approach…

finally I changed only 3 major things to make it work:
(make sure you change all occurencies)
first I replaced

val morning = now.withTimeAtStartOfDay.plusDays(1).minusHours(16)

with

val morning = now.withHour(8).withMinute(0).withSecond(0)

and also:

Morning_Time.postUpdate(morning.toString)

with

Morning_Time.postUpdate(morning.toLocalDateTime().toString())

and finally

val dawnstart = new DateTime(Dawnstart_Time.state.toString)

with

val dawnstart = (Dawnstart_Time.state as DateTimeType).getZonedDateTime()

hope that helps :wink:

cheers
Dan

2 Likes

Try it in the release and if it still happens file an issue.

I’ve never had Ephemeris fail for new so all I can say is the problem is not universal.

Hi Rich,

Transitioning over from OH2.5 and python implementation to OH3 and JS and have a question - do/should the Default_ items continue to be active during the weekend?

I’ve implemented a series of states as Defaults, and then only those I want to change at the weekend -i.e. morning and night times, but have noticed that the intermediate states don’t happen - having just re-read the readme, I note it says that none listed states will be skipped - which now I think makes it clear that I need items for each trigger time I want, even at the weekend?

Just looking to clarify my understanding.

Cheers
James

Default gets chosen when no other type of day comes up. If it is coming up when it shouldn’t if because Ephemeris isn’t working, the metadata is wrong for the weekend set, or something like that.

you have to define the full set of Items for the weekend. It’s the only way to handle the case where you want to skip a time of day on the weekend.

@Dan
Thank you very much. It’s much cleaner than what I’ve come up with.

I noticed three things:

  • if one does not need to display the hardcoded times in a sitemap or such, one does not actually need to push the values to items. The variables are set in " // set hardcoded values for calculation" and then used in “// Calculate the current time of day”. So technically the whole “// send values to Items” block is not necessary in that case.
  • The double “&&'s” in the case block are there because in your setup some times may switch places, right? Like at sometimes during the year “dawnstart” maybe either before or after “morning”…just wondering…
  • Good idea to publish the “last” variable to the logs, but I think that can only work if the TimeOfDay variable is persisted in a database other than MapDB. I, for one, use MapDB to populate the items on startup and influxdb for a lot of other stuff, but not for the TimeOfDay item. That’s probably why I get an error on " org.openhab.core.persistence.HistoricItem.getState()".

@rlkoshak
I’ve been on the release since it came out a few days before Christmas, still happens.

Rather strangely, I added a new zwave device yesterday, didn’t even restart OH, and ephemeris was still screwed up after that. I already filed an issue on GitHub a few days ago, but no replies yet.

you’re welcome :slight_smile:

Never thought about that, but I think you’re right - but I use them in my sitemap and to trigger rules in my case.

yes you’re right :wink: this was a little painful but the only way for me to switch morning & dawn / evening & sunset events…

I persist everything with influxdb… so no problem for me
edit:
forgot to mention that I created a new db for OH3 in influxdb to avoid compatiblity problems while updating from 2.5…

Not necessary, my influxdb works fine with OH3 now. No reason why it shouldn’t, really.

About the time items to trigger rules: you actually don’t need that, either. That’s exactly what the TimeOfDay is for:

When
Item TimeOfDay changes
then
if (TimeOfDay.state = "DAY") do something
else if (TimeOfDay.state = "MORNING") do something else
etc...

:see_no_evil: :see_no_evil: :see_no_evil: oh sorry you’re right… I use TimeOfDay that way already… but also show them in sitemap. so I need them anyway

my old db was older than 5 years and I changed a lot of Item names and changed their persistence quite often :wink: it wasn’t inconsistent but with many unused fields full of old crap… So it was the easiest way to get rid of that old stuff…

Thanks again @rlkoshak, makes sense now and confirmed with the neccessary additional items.

Cheers
James

I need help with the new OpenHAB3 JavaScript time of Day rule. So far I’ve done the Time of Day rule with the java script. It setup the 3 instances to run the script. I did not know how to set a schedule tag. I also setup the TimeOfDay String and the TimesOfDay Group. Do I have to copy all the DateTime items into the items folder like I would in OpenHAB2. Any help would be appreciated. Thanks!!!

You need one DateTime item for each transition that you want, not all of them.
Those have to be member of the group and have the ephemeris metatags set.

To initialize them for the first time I’d recommend using “openhab:send” from the console. Just the time is sufficient.

Not sure what you mean by “3 instances/schedule tag”, though.

In addition to Michael’s excellent advice, did you also install the time_utils and time_mgr libraries? Those are required as well.

Setting it up should be something like the following:

  1. copy the timeUtils.js and timerMgr.js files from my repo and place them in $OH_CONF/automation/lib/javascript/community.
  2. copy the YAML from the repo and paste it into a new rule in MainUI
  3. create the TimesOfDay Group and TimeOfDay String Item
  4. Create a set of DateTime Items for a given type of day. You need a set for default at a minimum but can add a set for weekend and holiday as well. These Items need to have metadata set (see the readme for the rule or the original post for details) to identify the Item as a Time of Day Item and which type of day it’s for and other config info as necessary.
  5. Populate these Items with a value. There are lots of ways to do this including from a binding like Astro, from the console as Michael indicates. You can create an input card, update it from a rule, etc. I particularly like opening the -Scratchpad- from the developer sidebar (alt-shift-d in main UI) and can write some quick commands to execute in there.
  6. Add the Items to the TimesOfDay Group.

Thanks for both your replies.

  1. I used the openhabian install of OpenHAB3. I can’t find the $OH_CONF/automation/lib/javascript/community directory to put the .js files

  2. I did put the YAML in a new rule. It created the following:
    When:
    When a member of TimesOfDay changed
    When the system has reached start level 20
    One minute after midnight
    Then:
    execute a given script

I did create the TimesOfDay and TimeOfDay string items