Time Based State Machine [4.0.0.0;4.9.9.9]

logo

Home automation often follows a set cycle or states. In the morning the house needs to wake up and come alive as the inhabitants get ready for the day. Next the house should go to sleep a bit while everyone begins the work day. Later in the afternoon the house may wake up again to prepare for everyone’s return and so on.

There are a number of ways to define such states but a common approach is to use times of day. This rule template establishes a rule that uses DateTime Items to drive a state machine. A different set of DateTime Items can be defined for different types of days (e.g. one set for workdays, another for weekends, and a third for holidays) with Ephemeris used to choose between the sets of DateTime Items for a given day.

This is a complete rewrite of the rule for OH 3.x using JS Scripting and the openhab_rules_tools library. The behavior is slightly different in that only the time portion of the DateTime Items is used to schedule the timers so there is no longer a need for the To Today rule template to drive static times. Also, there is a ton more validation of the config with meaningful error messages for how to correct it.

How it Works

Initial Setup

  • The user creates a String Item to hold the current time of day state. For this documentation we’ll call it “TimeOfDay” but you can choose any name.

  • The user creates a Group Item to hold all the DateTime Items that drive the state machine. For this documentation we’ll call it “TimesOfDay” but you can choose any name.

  • The user creates one or more sets of DateTime Items. Each Item is populated with the start time for the time of day state that Item represents. For example, a DateTime Item that represents MORNING might be populated from the Android App’s Alarm Clock feature or Astro’s sunrise time or hard coded to 07:00.

  • Each of those Items must have metadata defined. See below for details. This Item metadata is used to identify which type of day the Item belongs to and what time of day state it represents the start of.

  • Each of those Item are also made direct members of the “TimesOfDay” Group.

  • All the members of “TimesOfDay” need to be configured with Persistence for restoreOnStartup.

Metadata

Each DateTime Item requires Item metadata. The namespace can be defined by you. For this documentation we will use “tsm”.

// Item metadata defined in .items files
tsm="<STATE>"[type="<daytype>", set="<dayset>", file="<uri>"]

// Item metadata defined in the UI
value: <STATE>
config:
  type: <daytype>
  set: <dayset>
  file: <uri>

<STATE>
Replace this with the string you want sent to “TimeOfDay” at the date time held by this Item. The string will be sent as a command but only if it is different from the current state of “TimeOfDay”.

type
The rule supports six types of days as defined by Ephemeris.

Type Purpose
custom When custom holidays are defined in an XML file (see the Ephemeris docs, link above) use custom for the type and the file parameter is required.
holiday When configured with country and region in MainUI (Settings → Ephemeris), Ephemeris will become preconfigured with the national holidays observed by that region.
dayset Ephemeris allows for the definition of a custom dayset, such as defining “trash_day” to occur every Monday. When used as the type, the set parameter is required.
weekend For the days defined as weekend in the Ephemeris configuration.
weekday For the days not defined as weekend in the Ephemeris configuration.
default If Ephemeris doesn’t match any of the above day types or if there is no set of DateTime Items defined for any of the above types this set of DateTime Items is used.

The day types are processed in the order presented above. For example, if Ephemeris says today is a holiday, that set of DateTimes will be used even if today is also a weekend and there is a set of DateTimes for that also.

set
Replace <dayset> with the custom named dayset defined in the Ephemeris config to use. This parameter is required when the type is dayset and should be absent in all other cases.

file
Replace <uri> with the full path to the custom Ephemeris XML file with the custom holidays defined. This parameter is required when the type is custom and should be absent in all other cases.

Examples:
The following are for illustrative purposes, they must be customized for your configuration.

// .items files examples
DateTime Default_Bed (TimesOfDay) { tsm="BED"[type="default"] }
DateTime Weekend_Evening (TimesOfDay { channel="astro:sun:local:set#start", tsm="EVENING"[type="weekend"] }
DateTime Trash_Morning (TimesOfDay) { tsm="MORNING"[type="dayset", set="trash"] }
DateTime Birthday_DAY (TimesOfDay) { tsm="DAY"[type="custom", file="/etc/openhab/service/birthdays.xml"] }

// UI Metadata
value: BED
config:
  type: default

value: EVENING
config:
  type: weekend

value: MORNING
config:
  type: dayset
  set: trash

value: DAY
config:
  type: custom
  file: /etc/openhab/service/birthdays.xml

Item Sets

At a minimum there should be a set of DateTime Items defined for “default” type days. This will be the sets of states and times used when no other sets are found for the current day type. You might stop with just the default sets of Items. However, if that is your plan, see Creating Capabilities with Rule Templates: Time of Day for a simpler approach. A warning will appear in the logs if there is no “default” set but the rule will still try to run without them.

The states for each day type must be unique. If a state is repeated an error will be generated by the rule.

There must be a completely separate set of start times for each day type for which you want to have a state machine. The rule does not support reusing the same DateTime Item for more than one day type though.

Populating the DateTime Items

Actually populating and managing the DateTime states of the Items is outside the scope of this rule. Options include:

  • Astro binding based on solar or lunar events.
  • iCal and similar bindings that can define start times.
  • Android app’s Alarm Clock option that publishes the next scheduled Alarm to an Item.
  • Populated by hand via the REST API, the karaf console, or a widget (see DateTime Standalone Widget and DateTime List Item which can be installed from the marketplace)
  • A more advanced timeline picker widget such as Timeline which can be installed from the marketplace.
  • Populated from a Rule.

What the Rule Does

Whenever one of the members of TimesOfDay changes its state, openHAB restarts, and a few minutes after midnight, the rule triggers.

First the rule waits for ten seconds before doing anything. The Astro binding calculates the new times for the celestial events a minute after midnight, updating all the linked Items. This can result in the rule triggering a bunch of times. This delay causes the rule to wait for the updates to stop before calculating the new state machine timers.

After the initial delay, the rule first validates the Item configs. After changing the Item configs for this rule, run it manually to validate and apply them immediately.

Next it determines what type of day it is and selects those DateTime Items that are for that day type (if no Items exists the “default” set is chosen, if no Items are found an error is logged).

Then it sorts the date times based on their time, skips those that have already passed, and creates a timer to command the “TimeOfDay” Item to the state in the metadata at that time.

Language: JS Scripting

Dependencies:

  • A “TimeOfDay” String Item
  • A “TimesOfDay” Group
  • Sets of DateTime Items who are members of the “TimesOfDay” Group and have valid Item metadata and states
  • openhab-js 4.5+
  • openhab_rules_tools 2.0.3+

Changelog

Version 1.0

  • decided this is stable and used enough to give it a full version number, finally 1.0!
  • fixed error caused by breaking change in openhab-js 4.9.0, the fix is backwards compatible so it should work with any version from 4.5 on.

Version 0.5

  • improved error checking for cases where one or more member of the DT Group doesn’t have metadata at all.

Version 0.4

  • do not pass raw ZonedDateTime Objects to create timers

Version 0.3

  • adjustments for changed made to OHRT

Version 0.2

  • now throws an exception if the openhab-js or openhab_rules_tools are not new enough

Version 0.1

  • initial release

Sponsorship

If you want to send a tip my way or sponsor my work you can through Sponsor @rkoshak on GitHub or PayPal. It won’t change what I contribute to OH but it might keep me in coffee or let me buy hardware to test out new things.

Resources

https://raw.githubusercontent.com/rkoshak/openhab-rules-tools/main/rule-templates/ephemToD/time_state_machine.yaml

8 Likes

Hi @rlkoshak,

Thanks very much for your work and creating this template.
I tried to use it and I’ve got an error:

2023-03-24 12:38:28.095 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID ‘f4ae1ad990’ failed: org.graalvm.polyglot.PolyglotException: Error: “2023-03-24T12:38:38.065Z[SYSTEM]” is an unsupported type for conversion to time.ZonedDateTime

I suppose that error is related to “2023-03-24T12:38:38.065Z**[SYSTEM]**” which is returned by time.toZDT(). I’ve found that system default time zone ID system is returned by js-joda and I have no idea how to resolve this.

Do you have any idea?

This should be handled properly by the helper library. Which version of OH 4 are you running right now?

openhab-js should be able to handle both the ISO8601 format as well as the Java pseudo-ISO8601 format that puts the timezone in [US/DENVER] format instead of -7 format.

I’m not seeing this error in my instance. Put the rule into debug level logging either by adding an entry to log4j2.xml, through the karaf console, or by uncommenting the line in the script action near the top that sets the logging level for the rule and setting the level to DEBUG. See if you can identify the line and Item that is generating the error.

Actually it’s the opposite. time.toZDT() cannot convert “2023-03-24T12:38:38.065Z**[SYSTEM]**” to a joda-js ZonedDateTime. This should be handled by the helper library.

Did you ever run the rule manually and watch the log for errors? When run manually the rule validates the rule’s configuration and Item configuration. That might give us a clue also if an error is being reported there.

UI shows now openHAB 4.0.0 Build #3380 but I tried snapshot with the same result.
Uncommenting logging line with level changed to ‘DEBUG’ gives no diffrence, the same error with unsupported conversion.

Yes, I run this rule manualy and the log don’t shows other meaningful errors.
About validations, I’ve comment the last part with timers.chceck and invoked validateAllConfig() what gives below results:

==> /var/log/openhab/openhab.log <==
2023-03-24 14:28:38.267 [DEBUG] [omation.rules_tools.TimeStateMachine] - Starting state machine in ten seconds…
2023-03-24 14:28:38.268 [DEBUG] [omation.rules_tools.TimeStateMachine] - Validating Item types, Item metadata, and Group membership
2023-03-24 14:28:38.285 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_02_Morning is valid
2023-03-24 14:28:38.287 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_15_Wknd_Evening is valid
2023-03-24 14:28:38.289 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_12_Wknd_Morning is valid
2023-03-24 14:28:38.292 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_04_Day is valid
2023-03-24 14:28:38.294 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_11_Wknd_Early_Morning is valid
2023-03-24 14:28:38.296 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_16_Wknd_Night is valid
2023-03-24 14:28:38.298 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_05_Afternoon is valid
2023-03-24 14:28:38.300 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_03_School_Morning is valid
2023-03-24 14:28:38.302 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_18_Wknd_MiddleNight is valid
2023-03-24 14:28:38.304 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_08_Bed is valid
2023-03-24 14:28:38.305 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_13_Wknd_Late_Morning is valid
2023-03-24 14:28:38.307 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_09_MiddleNight is valid
2023-03-24 14:28:38.309 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_06_Evening is valid
2023-03-24 14:28:38.311 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_01_Work_Morning is valid
2023-03-24 14:28:38.312 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_17_Wknd_Bed is valid
2023-03-24 14:28:38.314 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_14_Wknd_Day is valid
2023-03-24 14:28:38.315 [DEBUG] [omation.rules_tools.TimeStateMachine] - ToD_07_Night is valid
2023-03-24 14:28:38.318 [INFO ] [omation.rules_tools.TimeStateMachine] - All etod Items are configured correctly

==> /var/log/openhab/events.log <==
2023-03-24 14:28:38.382 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item ‘PowerMeterA’ changed from 263.28 W to 264.78 W
2023-03-24 14:28:38.383 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item ‘PowerMeterB’ changed from 545.8 W to 63.59 W
2023-03-24 14:28:38.384 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item ‘PowerMeterC’ changed from 587.73 W to 553.12 W

==> /var/log/openhab/openhab.log <==
2023-03-24 14:28:38.481 [DEBUG] [omation.rules_tools.TimeStateMachine] - All etod Items are configured correctly

So, it looks like configuration have no errors.
I don’t know how to give you full error log in readable form, but for me only few lines from begin are meaningfull:

==> /var/log/openhab/openhab.log <==
2023-03-24 14:35:24.645 [DEBUG] [omation.rules_tools.TimeStateMachine] - Starting state machine in ten seconds…
2023-03-24 14:35:24.659 [ERROR] [b.automation.script.javascript.stack] - Failed to execute script:
org.graalvm.polyglot.PolyglotException: Error: “2023-03-24T14:35:34.648Z[SYSTEM]” is an unsupported type for conversion to time.ZonedDateTime
at .toZDT(/etc/openhab/automation/js/node_modules/openhab/time.js:281) ~[?:?]
at .check(/etc/openhab/automation/js/node_modules/openhab_rules_tools/timerMgr.js:46) ~[?:?]
at .:program(:278) ~[?:?]
at org.graalvm.polyglot.Context.eval(Context.java:399) ~[?:?]
at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:458) ~[?:?]
at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:426) ~[?:?]
at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:262) ~[java.scripting:?]
at org.openhab.automation.jsscripting.internal.scriptengine.DelegatingScriptEngineWithInvocableAndAutocloseable.eval(DelegatingScriptEngineWithInvocableAndAutocloseable.java:53) ~[?:?]

Note, please post logs using code fences:

```
code goes here
```

Or there’s something I don’t check for yet. I do make sure the state of the Item is a DateTimeType so that’s not it. The metadata is fine and the versions of the openhab-js and openhab_rules_tools libraries are recent enough. So at least I can eliminate all those as potential problems.

The error is coming from timerMgr; line 226 in the rule where the timer is created for each Item in today’s set of DateTime Items.

The relevant lines are:

      var mapped = startTimes.map(i => { return { 'state': i.getMetadata()[NAMESPACE].value,
                                                  'time' : time.toZDT(i.state).toToday() } });
      mapped.sort((a,b) => {
               if(a.time.isBefore(b.time)) return -1;
                else if(a.time.isAfter(b.time)) return 1;
                else return 0;
              })
              .filter(tod => tod.time.isAfter(time.toZDT()))
              .forEach(tod => {
                // TODO: see if we can move to rules instead of timers
                console.debug('Creating timer for ' + tod.state + ' at ' + tod.time);
                timers.check(tod.state, tod.time, stateTransitionGenerator(tod.state));
              });

Here is where it gets weird. At this point, tod.time is already a joda-js ZonedDateTime so the error makes no sense.

Before I start really digging into the libraries more I have one more thing to confirm. When did you install the template? Look at line 215-227 in your rule and see if they match the code above.

Maybe you have a slightly older version. If the lines do not match:

  1. Navigate to Settings → Automation → Time Based State Machine
  2. Remove the tempalte
  3. Refresh the browser page
  4. Add the rule template again
  5. Instantiate a new rule using the latest version of the template

That will eliminate the possibility that there is a bug in the code that was fixed in version 0.2 and explain why I’m not seeing the problem.

I’ve created the rule manually few days ago using code from https://raw.githubusercontent.com/rkoshak/openhab-rules-tools/main/rule-templates/ephemToD/time_state_machine.yaml
Yesterday I copied the code lastly.
There is one little difference from what you put above, in second line I had:

'time' : time.toZDT(i).toToday() } });

but I checked, change to

'time' : time.toZDT(i.state).toToday() } });

do nothing. Rest of the code match.
I’ve did what you ask me, added template from settings-automation, added rule from template (tag which I can see is marketplace:144208), run it manually - gives the same error.

My OHB installation is on Ubuntu and I use it above 3 years with updates from OHB 2.0 I suppose and with OS updates, so maybe it’s time to refresh it from scratch. :slight_smile:

Why copy when you can just install it? That’s the whole point of rule templates on the marketplace.

That is definitely wrong or, at a minimum points to a difference between what I’m running and the template, though even this should be a meaningless difference. time.toZDT() should handle both the Item, and the String. But one thing you can try which would be informative is to change that line to

'time' : time.toZDT(i.rawState).toToday()

But that’s not where the error is coming from. All three are successfully converting the Item’s state to a joda-js ZonedDateTime on that line. The error is coming later in the call to time.toZDT inside the timerMgr.check() which should be just a passthrough because it’s already a ZonedDateTime.

I’m still trying to reproduce the error.

Are you certain you’ve not installed the openhab-js library? Maybe my checks on versions is failing and you have an older version.

Edit: I removed my rule, updated the template and recreated my rule, still no error on my side. This is very odd.

One other possible idea. Near the top the script action there is a line

helpers.validateLibraries('4.1.0', '2.0.1');

Change the 2.0.1 to 2.0.2. Maybe the small bug I fixed between those two versions is relevant here.

Nope, the same error.

After update to OHB 4.0 I updated java to Zulu17 and installed JavaScript Scripting from settings-automation. If you can point me what should I check I will do this. I’m pretty sure that problem is on my side.

'time' : time.toZDT(i.rawState).toToday()

haven’t change anything also

That was just to confirm that you have the latest openhab_rules_tools.

I can’t figure it out. Unless my validateLibraries utility is broken, you are running with the latest of both openhab-js and openhab_rules_tools so that’s not going to be a difference. And unfortunately, that’s where the error is coming from.

Hi @rlkoshak , Unfortunately it’s not a problem with the specific configuration for @Robert_Koziol , I’m seeing the same error. I have tried all the steps you’ve mentioned earlier (updated openhab-rules-tools and reinstalled the template), but no success.

I do see the same small difference in the rule on line 216, even after reinstalling the template:

'time' : time.toZDT(i).toToday() } });

instead of

'time' : time.toZDT(i.state).toToday() } });

I was wondering if Daylight Savings Time could be a factor, that was last weekend over here.

I’ll try some more tweaks, I’ll update the thread if I make any progress. Until then I’ll keep your old rule as the last Nashorn rule in my system, that has been working perfectly for me.

Sorry to be the bearer of bad news (and weird problems), your work here is much appreciated! Your examples and community answers have helped me a lot.

Shouldn’t be unless you had a date time Item at exactly when the time change occurs (e.g. in the US that’s 2 am). This version of the template only looks at the time so as long as that time exists on that day it will use the proper time for that day. The 3.x version template depends on the To Today rule template to advance the dates to today but not thins one.

Ultimately, unless and until I can reproduce the error I’m not sure what I can recommend. I’m using the version of openhab-js that comes with the add-on. I’m running the OH 4 snapshots 3380 (I think). And I’m running with the latest published version of openhab_rules_tools.

Hello @rlkoshak, @Huib0513,

Good news, I managed to get a fully working TSM in js-scripting without any errors!
What I did:

  1. uninstall JavaScript Scripting add-on in Automation UI
  2. manually delete folder /etc/openhab/automation/js
  3. restart OHB and install JavaScript Scripting again (after installation ls /etc/openhab/automation/js shows empty folder)
  4. In Settings → Other services → JavaScript Scripting set Do Not Use Included Library
  5. manually install from /etc/openhab/automation/js:
  • sudo -u openhab npm i openhab_rules_tools
  • sudo -u openhab npm i openhab
  • sudo -u openhab npm i git+https://github.com/openhab/openhab-js.git

After restart OHB, TSM rule run with no errors.
Additionally, I tried to set back the original settings Use Included Library and it brings back the error.

If you need some more info please let me know.

Thanks

3 Likes

Are you on the milestone release or snapshot releaseof OH 4?

If you are on openHABian you can/should install the libraries using openhabian-config.

And you don’t need to install openhab-js both ways. You can either install the latest release (your first command) which is what I recommend, or get the latest unreleased changed from Github (your second command).

That implies that the version of the library that ships with the version of OH you are running is the root of the error. But running with the latest from GitHub does not have the error.

This gave me enough info to go on and I’m finally able to reproduce the error too. It’s definitely an openhab-js library version problem. Something after openhab-js 4.1.0 fixed the problem and 4.1.0 is what ships with the add-on right now. So installing from npm and setting the flag to not use the included library are required for now until the latest version of openhab-js get’s included in the add-on.

I’ll update the required version numbers in the check a the top of the rule accordingly so the solution to the problem is more apparent.

But I have confirmed that the latest release on npm works so you don’t need to use that git installation line.

Excellent work @Robert_Koziol !

Everything is running perfectly now, using the latest release of the library.

I’m running the milestone release, “openHAB 4.0.0.M1 Milestone Build”.
Installed on Debian 11 from the jfrog-repository, no openHABian.

1 Like

I’m on snapshot installed from jfrog on Ubuntu.

1 Like

i also keep having issues with the current version of OH and Time Based State Machine.

i’ve already set it up the way it is described in this post including all the workarounds, but i always kept getting the error message that it basically couldn’t use the provided time format (for example 2023-08-05T10:00:00.000+0200). but that doesn’t seem to be the issue, since changing the datetime widget to only provide time essentially outputs the same format just with the beginning being 1970-01-01TXX:XX:XX.XXX+0100 and i keep getting the same error message.
It seems like the issue is the system time, wherever it get’s it from…
the weird thing is if i get it running for the day it works, but on the next day it stops working. then i just have to run the rule 2 or 3 times and then it works. I can provide console output tomorrow when i have to run the rule again manually.

any ideas?
(i set the javascript scripting add-on to not use the internal library, fyi)

@edit: hm, oddly enough, today i had to run the rule again manually but it didn’t give me any error messages. i now set the time-trigger to run the rule at 00:15, maybe that fixes it.

So what version have you installed using npm? There was a bug in a previous version that made it unable to parse ISO8601 Strings.

I can’t explain that.

Depends on where in the rule it’s running into problems.

it seems like now its working, i ran the rule manually today observing the logs, no errors. maybe it was something with the wrong version being installed by npm or it just not using the right version despite setting it.

but somehow it seems that my trigger is not working. the rule should change the time of day at 07:00 in the morning but that doesn’t happen. only when i run the rule manually it actually changes the time of day. is there anything else which could cause the trigger to not work?

One of the first lines in the new rule template now runs a check to see if the openhab-js and openhab_rules_tools libraries are recent enough. But I know that specific error was related to a bug in openhab-js that got fixed so it’s kind of a mystery.

I might need to up the current minimum to 4.2.0 or 4.3.0.

I’m glad that part is working at least.

Keep in mind how the rule works. When it’s run it goes through the time of day items metadata and figures out which ones are the appropriate Items for today given the ephemeris day type (weekday, weekend, holiday) or default if there isn’t a set of Items that match the day type. Then all it does is schedule a Timer for time of day that hasn’t already passed to update the time of day Item with the value in the metadata.

So if it’s not updating the time of day Item on it’s own, it’s not scheduling the timer which means either it’s finding the wrong dayset or something else is going on. I couldn’t begin to guess without debug logs.

You can change the logging level for the rule in all the usual ways (karaf console, log4j2.xml file) or uncomment out the line at the top of the script action:

//osgi.getService('org.apache.karaf.log.core.LogService').setLevel(console.loggerName, 'DEBUG');

Don’t forget to run the rule at least once when you are done to return the logging level to INFO and then comment out the line again, if that’s the route you take. The logger name is org.openhab.automation.rules_tools.TimeStateMachine.

Hi there,

so, i’ve changed the log level to see if there’s anything wrong. The only thing i found was that everything seems to be working as expected, only that my new time items are not using the correct time zone. I did change this when the rule wasn’t working at all, so i did change it back to have the correct format and now the timers seem to be working. i’ll report back tomorrow if the new timers are running correctly from now on.

but thanks a lot for your help!
(See attached screenshot for reference, the DAY and NIGHT Timers were set to +01:00, so that might’ve caused the issue)