Migrating DSL Rules to JSR223/Javascript with a case example

Porting the Time of Day rule as a case example

Hi all,

Since i am currently trying to move my rules to a jsr223 based system, i would like to share my experience with you with a simple usecase which hopefully helps some of you with their own setup.

Maybe you are in the same situation like me.

  1. You have a running openHAB setup and more or less rules which have grown and evolved during the time that your setup exists.
  2. You already have read about JSR223 and its benefits/downsides and are interested in a future proof openHAB setup
  3. You are a bit scared/skeptical about the effort it will take to move your rules.

If at least two of those three points fit your personal situation, i can help you with that hopefully.

Table of contents

  1. General informations
  2. Set up jsr223 with javascript support and lewies library
  3. Check if jsr223 works as expected, aka “my first rule which just logs some stuff”
  4. Solve upcoming errors, when using lewies current state of the javascript library (2019-04)
  5. Analyze the Time of Day DSL rules structure
  6. Migrate the Time of Day rule in sub parts

1. General informations

This tutorial is based on some (popular) threads/things in the community.

It is also based on an openHABian setup, so you may have to adapt the OPENHAB_CONF environment variable according to your setup, when you run openHAB in a adocker container for example.

I have choosen this setup, because i think that time of day is very popular and many users will have a running dsl-rules version in their setup.
So many will have a direct usecase, which lowers the entry hurdle.
Also the corresponding Design Pattern threads are well written and documented, so you will have a big source of good documentation in case of problems.

2. Set up jsr223 with javascript support and lewies library

Activate experimental rules support, if not done yet

Get lewies library, place it in the approbiate folder and check for errors

First of all you should log into the openHAB console and start a log tail (or use the log viewer), to get all relevant log messages.

After that you have to download lewies library and copy the contents to th automation\jsr223 folder like shown in the image below.

If you are not seeing any messages/errors you may need to reload the scripts/automation bundle.
I have simply restarted the whole openHAB service and got the following outpout in the console afterwards:

As you can see, everything loaded without throwing errors.
This is going to change in a moment, when we are trying to run our first demo rule.

3. Check if jsr223 works, aka “my first rule which just logs some stuff”

Now we will create our first javascript rule file example.js, with some basic content:

load(Java.type("java.lang.System").getenv("OPENHAB_CONF")+'/automation/jsr223/jslib/JSRule.js');
logInfo("Hello JSR223");

The first line will load lewies library for us.
(Just as a side info: His library will make it easier for us to define rules, that’s why we use it.)
The second line will log an information exactly one, when the file is loaded.

The file needs to be placed in the jsr223 folder, beside the jslib folder.
image

It should look like this in your console:
image

Still no errors.

4. Solve upcoming errors, when using current library state(2019-04)

Let’s replace the log with our first rule:

load(Java.type("java.lang.System").getenv("OPENHAB_CONF")+'/automation/jsr223/jslib/JSRule.js');

JSRule({
    name: "My first JS Rule",
    description: "This rules is a simple example for getting started with JSR223 Javascript",
    triggers: [
        TimerTrigger("0/15 * * * * ?")//Enable/Disable Rule
    ],
    execute: function( module, input){
        logInfo("This is a example rule, which runs every 15 seconds.");
    }
});

We should have a short look at the different parts of the JSRule:

name and description shold be self explaining. You should add some usefull stuff here.

triggers can be compared to the when part of a dsl rule.
We will configure the triggers for our astro rule later in this area.

execute is the part that will run, when the javascript rule got triggered.

When you save example.js now, the log should be a bit different:

You can see that theres something wrong on line 158 of the triggersAndConditions.js file of the library.
If you open the file you will find the following function at that line:

var getTrName = function(trn){
	return trn == undefined || trn == null || trn == "" ? uuid.randomUUID() + "-" + me.replace(/[^\w]/g, "-") : trn;
	//return trn == undefined || trn == null || trn == "" ? uuid.randomUUID() : trn;
}

delete the first return statement and uncomment the second one, which should solve this problem for now.

var getTrName = function(trn){
	return trn == undefined || trn == null || trn == "" ? uuid.randomUUID() : trn;
}

If you save the example.jsfile now, the rule should run and log the message every 15 seconds.

Congratulations.
You have set up jsr223 and lewies javascript library succesful.

Attention
The library is in a refactoring currently, so there may be other errors or even none, when something got fixed.
lewies waiting for some openHAB changes, until he can continue fixing/improving it.
I will try to keep this tutorial up to date to the library status.

Additional information about jsr223 and the next generation rules engine can be found on the website/docs and here.
I have linked one thread about n genereation rules engine scripting above and rich has written some more.

Since the post as already really long, i want to focus on my start topic now.
How do i migrate an existing dsl rule to javascript with the time of day rule as an example.

5. Analyze the Time of Day DSL rules structure

So we will first have a look at the dsl time of day rule and check what tasks have to be done within the rule.

I will not explain the process of implementing “Time of Day” in general.
Please read the design pattern Thread for deeper information about the concept and needed items.

Here we have the rule, like its documented in the Design Pattern Thread:

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: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

Thankfully, we don’t need to investigate too much here.
Everything is well documented and commented, so we have a nice todo-list for our javascript equivalent.

6. Migrate the Time of Day rule in sub parts

Ok, let us focus on the triggers later and get the functional part up.
I will for now add a 15 seconds trigger, for testing the rule, which i would suggest you to do similar.

JSRule({
    name: "Time of day",
    description: "Calculates the time of day, depending on several triggers",
    triggers: [
        TimerTrigger("0/15 * * * * ?")// 15 seconds for testing/debugging
    ],
    execute: function( module, input){
        logInfo("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

        // Calculate the current time of day

        // Publish the current state
    }
});

I have directly added the dsl rule comments, as a to do list.
This way we can’t forget anything.

Check, if the test setup works with saving the example.js file.
It should look like this:

Fine.
Now we will do some basic preparations.

  • We will get the vTimeOfDay item, to read and write it.
    (This has to be done with jsr223 and is different to rules-dsl, where we can simply adress item names.)
  • We will also get the current time, with a javascript Date object.
JSRule({
    name: "Time of day",
    description: "Calculates the time of day, depending on several triggers",
    triggers: [
        TimerTrigger("0/15 * * * * ?")// 15 seconds for testing/debugging
    ],
    execute: function( module, input){
        // Get the current time as number
        var now = new Date().getTime();
        //Get the vTimeOfDay item as json object
        var cTimeOfDay = getItem("vTimeOfDay");

        logInfo("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

        // Calculate the current time of day

        // Publish the current state
    }
});

Did you notice the getItem() method?
It will grab the specified item for you as a json object.

Lets see, how we can calculate the static times with javascript:

Info:
I am not using the “morning”, “night” and “bed” items, since they are static and i don’t want to view them in my personal setup.
If one wants to display them, it will be a bigger effort, because the js rule is working with number based timestamps.

JSRule({
    name: "Time of day",
    description: "Calculates the time of day, depending on several triggers",
    triggers: [
        TimerTrigger("0/15 * * * * ?")// 15 seconds for testing/debugging
    ],
    execute: function( module, input){
        // Get the current time as number
        var now = new Date().getTime();
        //Get the vTimeOfDay item as json object
        var currentTimeOfDay = getItem("vTimeOfDay");

        logInfo("Calculating time of day...")

        // Calculate the times for the static times
        var morning_start = new Date().setHours(6, 0, 0, 0); // .setHours(Hour, Minute, Second, Millisecond)
        var bed_start = new Date().setHours(0, 0, 0, 0);

        // Get the astro calculated times as numbers for comparison
        var day_start = new Date(getItem("vSunrise_Time").state).getTime();
        var afternoon_start = new Date(getItem("vEvening_Time").state).getTime();
        var evening_start = new Date(getItem("vSunset_Time").state).getTime();

        // Calculate the current time of day

        // Publish the current state
    }
});

Time for saving and checking again.
Your log should display no errors and run the rule every 15 seconds:

image

Time for some calculation.
The rule now has many times in variables as millisecond numbers, so we can easily do some math.

JSRule({
    name: "Time of day",
    description: "Calculates the time of day, depending on several triggers",
    triggers: [
        TimerTrigger("0/15 * * * * ?")// 15 seconds for testing/debugging
    ],
    execute: function( module, input){
        // Get the current time as number
        var now = new Date().getTime();
        //Get the vTimeOfDay item as json object
        var currentTimeOfDay = getItem("vTimeOfDay");

        logInfo("Calculating time of day...")

        // Calculate the times for the static times
        var morning_start = new Date().setHours(6, 0, 0, 0); // .setHours(Hour, Minute, Second, Millisecond)
        var bed_start = new Date().setHours(0, 0, 0, 0);

        // Get the astro calculated times as numbers for comparison
        var day_start = new Date(getItem("vSunrise_Time").state).getTime();
        var afternoon_start = new Date(getItem("vEvening_Time").state).getTime();
        var evening_start = new Date(getItem("vSunset_Time").state).getTime();

        // Calculate the current time of day
        var curr = "UNKNOWN"

        if(now > bed_start)       curr = "BED";
        if(now > morning_start)     curr = "MORNING";
        if(now > day_start)         curr = "DAY";
        if(now > afternoon_start)   curr = "AFTERNOON";
        if(now > evening_start)     curr = "NIGHT";

        // Publish the current state
        logInfo("Current time of day is now " + curr);
    }
});

These if conditions simply go through every configured Time of Day state and set it.
When the correct state is set, the left over if conditions will be false and left out.

Example (Time is after day_start):
The curr variable will be set to BED, then to MORNING, then to Day and after that the if conditions for afternoon and night will be false.
curr will stay at DAY and send as new state to the Item.

Since we are only writing one statement after each if-condition, i have left the brackets.
But you can of course do it with brackets, if you like this more.

Example:

if((now - night_start > 0) || (now - bed_start > 0 && now - morning_start < 0)) {
    curr = "BED";
}

Let’s save and check the logs again.
I have added some logging already which should output the current time of day,
according to our calculation.

image

Depending on the time of day, when you are doing this you should get some result for the calculation.
For me it’s DAY while im writing this thread.

Well, looks like we are close to the end.
Just two steps left.

Updating the item and configuring the triggers.
Lets start with the item.

JSRule({
    name: "Time of day",
    description: "Calculates the time of day, depending on several triggers",
    triggers: [
        TimerTrigger("0/15 * * * * ?")// 15 seconds for testing/debugging
    ],
    execute: function( module, input){
        // Get the current time as number
        var now = new Date().getTime();
        //Get the vTimeOfDay item as json object
        var currentTimeOfDay = getItem("vTimeOfDay");

        logInfo("Calculating time of day...")

        // Calculate the times for the static times
        var morning_start = new Date().setHours(6, 0, 0, 0); // .setHours(Hour, Minute, Second, Millisecond)
        var bed_start = new Date().setHours(0, 0, 0, 0);

        // Get the astro calculated times as numbers for comparison
        var day_start = new Date(getItem("vSunrise_Time").state).getTime();
        var afternoon_start = new Date(getItem("vEvening_Time").state).getTime();
        var evening_start = new Date(getItem("vSunset_Time").state).getTime();

        // Calculate the current time of day
        var curr = "UNKNOWN"

        if(now > bed_start)       curr = "BED";
        if(now > morning_start)     curr = "MORNING";
        if(now > day_start)         curr = "DAY";
        if(now > afternoon_start)   curr = "AFTERNOON";
        if(now > evening_start)     curr = "NIGHT";

        // Publish the current state
        if(currentTimeOfDay.state != curr) {
           logInfo("Current time of day is now " + curr);
           sendCommand(currentTimeOfDay, curr);
        }
    }
});

Simple as that.
We check, if the current item state is different from our calculation and update the item if that’s the case.

Should look like this in your logs:
image

Ok, since we don’t want to do this every 15 seconds, there is still one task left.
The triggers.

Info:
System startup trigger seems to have some problems (or at least the library) at the time this tutorial is written, so we have to leave this out for now.
We will do the cron triggers, which should not be that difficult since you have seen one ove the whole tutorial.
Also we will do the channel triggers.

JSRule({
    name: "Time of day",
    description: "Calculates the time of day, depending on several triggers",
    triggers: [
        ChannelEventTrigger("astro:sun:home:rise#event", "START", "astroSunriseTrigger"),
        ChannelEventTrigger("astro:sun:home:set#event", "START", "astroSunsetTrigger"),
        ChannelEventTrigger("astro:sun:minus90:set#event", "START", "astroSunsetDelayTrigger"),
        TimerTrigger("0 1 0 * * ? *"), // one minute after midnight so give Astro time to calculate the new day's times
        TimerTrigger("0 0 6 * * ? *"),
        TimerTrigger("0 0 23 * * ? *")
    ],
    execute: function( module, input){
        // Get the current time as number
        var now = new Date().getTime();
        //Get the vTimeOfDay item as json object
        var currentTimeOfDay = getItem("vTimeOfDay");

        logInfo("Calculating time of day...")

        // Calculate the times for the static times
        var morning_start = new Date().setHours(6, 0, 0, 0); // .setHours(Hour, Minute, Second, Millisecond)
        var bed_start = new Date().setHours(0, 0, 0, 0);

        // Get the astro calculated times as numbers for comparison
        var day_start = new Date(getItem("vSunrise_Time").state).getTime();
        var afternoon_start = new Date(getItem("vEvening_Time").state).getTime();
        var evening_start = new Date(getItem("vSunset_Time").state).getTime();

        // Calculate the current time of day
        var curr = "UNKNOWN"

        if(now > bed_start)       curr = "BED";
        if(now > morning_start)     curr = "MORNING";
        if(now > day_start)         curr = "DAY";
        if(now > afternoon_start)   curr = "AFTERNOON";
        if(now > evening_start)     curr = "NIGHT";

        // Publish the current state
        if(currentTimeOfDay.state != curr) {
           logInfo("Current time of day is now " + curr);
           sendCommand(currentTimeOfDay, curr);
        }
    }
});

Thanks to lewies library this is straight forward too and not to far from what you know through the rules-dsl.

The ChannelEventTrigger needs the channel name and the trigger type, which should already be familiar for you from rules-dsl.
The trigger name (e.g. astroSunriseTrigger) is optional, but a nice place to make your rules more readable.
The TimerTrigger is straight forward too, since it just needs the cron expression.

Finish

That’s it.
It might look like a long tutorial, but the comple jsr setup part has to be done only once.

Also you may have recognized, that many commands and behaviors are similar to the rules-dsl with slightly differences.

I have done most of my migration due to copying the old rule and editing it to a valid javascript function.
Functions like postUpdate and sendCommand can be copied, pasted and adapted very fast.

I have also recognized that i got way faster after migrating some rules.
So hopefully this hels some of you with their start in jsr223.

Again attention
The js library may get some fixes/changes, when the openHAB build system migration is completed.
I will try to keep this tut up to date, but please poke me if i am late.

Also this may not be the best implementation of the time of day rule.
I am still starting with jsr223/javascript currently and so i am learning new thigns too every day. :slightly_smiling_face:

Any feedback is welcome and i will try to improve the tutorial based on that.

22 Likes

Thanks so much! I get it now! Looks like I have a project to move my rules…

1 Like

Thanks, that’s what i wanted to hear. :smiley:

I just corrected some typos.
Would love to get some feedback and hear about migration problems that maybe occured,
so we can improve this continously. :slight_smile:

1 Like

Fantastic tutorial! I will provide a link to it from the Time of Day DP.

I would recommend users consider using a git clone to pull the library and provide a softlink to the jslib folder under the automation folder. This will allow you to update the library more easily with a git pull. It’s not a strong recommendation, just an alternative to consider.

cd $OH_CONF
git clone git@github.com:lewie/openhab2-javascript.git
ln -s openhab2-javascript/jslib/ $OH_CONF/automation/jsr223/jslib

Then to update:

cd $OH_CONF/openhab2-javascript
git pull

Pick a good location to check it out into that works for your configuration.

Also note that as of this writing the JSR223 helper authors are working to merge the three into a single repo so they can be centrally managed and updated. So the location of the repo for the library may change in the not to distant future.

There is also work in progress to make these helper libraries for all three JSR223 libraries easier to install if not something that gets installed automatically for you. With the efforts of @5iver and others JSR223 is becoming easier and easier to use every day.

Maybe not so self explaining. What is __LINE__?

Just a note on the docs page linked to. That was a first attempt at documenting the Next Gen Rules Engine (NGRE). It is worth mentioning here that the NGRE has two interfaces, a GUI based one and JSR223. As such there are some very key areas where they differ. For example, GUI created Rules will be saved in JSONDB but JSR223 is saved in .js files. Also, the rule parts of the code (JSRule, triggers, etc.) get defined through the GUI so the only JS code you would write will be for Action Scripts or Conditional Scripts. But the code in those Scripts will look just like the code presented in the execute:function.

You can read more about the differences and similarities between the GUI based Rules and JSR223 at Experimental Next-Gen Rules Engine Documentation 1 of : Introduction in the “How NGRE Is Related to JSR223” section.

As we move forward with a replacement for PaperUI, the GUI based way of defining Rules will likely become the one used by most non-technical users. But because both are executing on the same rules engine, it shouldn’t be hard to migrate to all text based rules should the user desire.

Some more information about working with Date Time Objects of various types in JS see Experimental Next-Gen Rules Engine Documentation 4 of : Writing Scripts in the DateTimeType section. It’s not complete but it should provide enough information for users to figure out how to get to a DateTimeType you can post to a DateTime Item.

Two important things to note is one can use the Java OffsetDateTime class or even good old Joda DateTime if you need to, though when OH goes to a new version of Java, the Joda DateTime may no longer be supported.

Obviously, I would encourage everyone to use native JavaScript stuff as much as possible in JS Rules, but be aware that you have full access to all the core Java classes as well, just like in Rule DSL.

should these be else if? Once you find a match you don’t need to do the remaining comparisons. By using only if every case is checked.

Do you really need the subtractions in the comparisons? Couldn’t you just say, for example:

if(now > morning_start && now < day_start)

That is checking to see if now is between morning_start and day_start which I think is equivalent to what you are doing with the subtractions.

Now, another idea is instead of following my suggestion of using else if statements, we can use multiple if statements and simplify the conditions.

    if(now > night_start)     curr = "BED"; // The first condition must be the first time period after midnight
    if(now > morning_start)   curr = "MORNING";
    if(now > day_start)       curr = "DAY";
    if(now > afternoon_start) curr = "AFTERNOON";
    if(now > evening_start)   curr = "NIGHT";

With the above, it will check each time in turn. So when it’s after day_start and before afternoon_start, curr will become “BED”, then “MORNING”, then “DAY” and stay there because the conditions for afternoon_start and evening_start will evaluate to false.

Great job. I hope this becomes one of the most popular tutorials on the forum as users start to migrate more and more to JSR223 Rules. Thanks for posting!

1 Like

Hey Rich,

Thanks for the feedback.
Well written as known from you. :slight_smile:

Library related:
Yes git clone, should be a good alternative.
I will prepare a gif for that, but will wait some days for this.
I have thrown in my offer for help for the unified JSR223 repo migration and further development.
So i can maybe help out and see recent updates/changes very fast.

I am also already thinking about refactoring the jsr docs and may start with some parts of this tutorial.
But i will contact you and the other related persons anyway on that topic with an orga thread in the development area next days.

A bad exampe of copy pasting…
Its a magic variable, but i should possibly better change the example.
Magic variables is notihing one should deal with in a beginner guide.

I can only confirm that.
Javascript is a very popular language and you cant fin tons of guides/docs/tutorials for countless problems you may want to solve in your rule.
So finding examples and guidance should be really smooth.

I think that’s more a philosophical question, than a coding one. :smile:
Both ways will work and even if i like brackets (i stated that in another thread), i also like a clean and small code structure.
In my opinion thos else’s will blow up the code and its readability.
Possible that others rate this completely different.

Good catch. Maybe this is a result of many refactoring while creating this rule clone.
I will evaulate the short variant and adatp the tutorial if your suggestion works fine. :slightly_smiling_face:

This is also worth a try and thats what i am going to do. :slight_smile:

Again thanks for this feedback.
We have a public holiday in germany on wednesday and i will do an update then.

:+1:

:smiley:

I look forward to that thread.

It’s important to realize with JavaScript though that Nashorn (the JavaScript we are using) is not standard JavaScript. There are lots of things one can do with JavaScript (e.g. XmlHttpRequest) that is not available in Nashorn. So one needs to be careful with some tutorials and answers on StackOverflow and the like.

It may be the programmer in me, but one reason to use the else if instead of just if’s is because it slightly closes some opportunities for hard to detect bugs. For example, sometimes (more often than you think) the conditional in an if statement actually can modify state. This is pretty much always a bad thing and often even experienced programmers may not realize this is happening. Therefore it is usually a good defensive move to avoid having code execute that doesn’t need to execute.

The elses are also not superfluous. They tell the reader (future you in this case) that only one of these if statements should ever evaluate to true, and only the first one that evaluates to true will be used.

In some cases this can also help avoid an error being masked. I’m not sure that’s the case here but it can in many situations.

These are the sorts of things that help avoid bugs.

Of course after saying all that, my last suggestion ignores all that and utilizes the fact that all the if statements execute. For every rule of thumb there are many exceptions.

I have tested the “minimal version” with those if conditions and it works nice,
so i will use it.
This also lets one static time calculation get obsolete,
since the “Night” time is then set through a value thats bigger than evening time.

1 Like

This is actually an edge case I have to deal with. I live far enough north that during parts of the year day_start is before morning_start (i.e. sunrise is before 6 AM). During that part of the year I simply skip the MORNING time of day and go straight from BED to DAY.

I’m glad to see that even though I forgot about it this case is still handled.

1 Like

Just a quick post to helpfully save people some time.

When I followed the instructions yesterday on my OH2.5 snapshot version I noticed that when OH loaded my rule file, the rule would fail to be parsed with a NoClassDefFoundError (at least I think that was the correct exception name).

If you get this error, my tip would be to ensure when you download the JS libs, you make sure you use the smarthome-To-openhab.core branch.

Details of how to clone the repo can be found above in @rlkoshak’s post. Doing so will enable you to quickly update (git pull) and switch branches (git checkout -b smarthome-To-openhab.core) as necessary…

2 Likes

Hi all,

Just for the interested:

I have ported this example to a UI based openHAB 3 rule currently.
If you are using the same items and things the YAML code below should do what it’s supposed to do.
Otherwise you would have to adjust some thing or item names.

triggers:
  - id: "1"
    configuration:
      thingUID: astro:sun:home
      event: START
      channelUID: astro:sun:local:rise#event
    type: core.ChannelEventTrigger
  - id: "2"
    configuration:
      thingUID: astro:sun:home
      event: START
      channelUID: astro:sun:home:set#event
    type: core.ChannelEventTrigger
  - id: "3"
    configuration:
      thingUID: astro:sun:homeMinus90
      event: START
      channelUID: astro:sun:home:set#event
    type: core.ChannelEventTrigger
  - id: "4"
    label: Every 2 hours
    configuration:
      cronExpression: 11 0 0/2 ? * * *
    type: timer.GenericCronTrigger
conditions: []
actions:
  - inputs: {}
    id: "5"
    configuration:
      type: application/javascript
      script: >
        var logger =
        Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' +
        ctx.ruleUID);


        // Get current time

        var now = new Date() // Just Date for a foramtted Log Entry

        logger.info("Curent Time: " + now.toString())

        now = now.getTime() // Now as a Number for proper calculation


        // Get current time of Day

        var currentTimeOfDay = itemRegistry.getItem("vTimeOfDay")

        logger.info("Current Time of Day: " + currentTimeOfDay.state)


        // Get relevant times for calculation

        var morning_start    = new Date().setHours(6)

        var day_start        = new Date(itemRegistry.getItem("vSunrise_Time").state).getTime()

        var afternoon_start  = new Date(itemRegistry.getItem("vEvening_Time").state).getTime()

        var evening_start    = new Date(itemRegistry.getItem("vSunset_Time").state).getTime()

        var bed_start        = new Date().setHours(0, 0, 10, 0)


        // Log entries for debugging, when needed

        /*

        logger.info("morning_start: " + morning_start.toString())

        logger.info("day_start: " + day_start.toString())

        logger.info("afternoon_start: " + afternoon_start.toString())

        logger.info("evening_start: " + evening_start.toString())

        logger.info("bed_start: " + bed_start.toString())

        */


        var calculatedTimeOfDay = "UNKNOWN"


        if(now > bed_start)         calculatedTimeOfDay = "BED"

        if(now > morning_start)     calculatedTimeOfDay = "MORNING"

        if(now > day_start)         calculatedTimeOfDay = "DAY"

        if(now > afternoon_start)   calculatedTimeOfDay = "AFTERNOON"

        if(now > evening_start)     calculatedTimeOfDay = "NIGHT"


        if(currentTimeOfDay.state != calculatedTimeOfDay){
          logger.info("Current time of day is now " + calculatedTimeOfDay);
          events.sendCommand(currentTimeOfDay, calculatedTimeOfDay)
        }
    type: script.ScriptAction

2 Likes

Looks good. But I do want to mention that I’ve a Python and JavaScript MainUI version of the Time of Day Rule posted at GitHub - rkoshak/openhab-rules-tools: Library functions, classes, and examples to reuse in the development of new Rules. which might be of interest.

The advantages of those versions of the rules are:

  • they work with ephemeris so you can have a different set of times of day based on day type (e.g. a different set for weekdays and weekends).

  • No modifications to the code is required to add/remove/change the time of day times or add/remove a time of day.

  • The Jython version will initialize the statically defined DateTime Items for you.

  • The also come with some libraries that may make writing your own rules easier. For example, time_utils will convert all sorts of stuff including an Expire formatted String (e.g. 2h5m) to a ZonedDateTime for you giving you lots of options for scheduling Timers. timer_mgr is a class that handles all of the tedious book keeping required to manage lots of Timers (e.g. one timer per Item). For example, you can schedule a timer with timerMgr.check(event.itemName, "5m", runme, true, runFlapping) which will execute “runme” in five minutes or, if a Timer already exists for event.itemName it will call runFlapping and reschedule the timer.

And it occurs to me that there could be another good approach that doesn’t even use a script that could work. You can create a separate Rule per time of day. Each rule triggers either at a fixed time or based on an Astro Channel event. Then all you need to do is sendCommand to the vTimeOfDay Item with the String for that time period. Alternatively you can also set the corresponding DateTime Items to now in a separate Action. If it were not for the Astro Items, you could even tag the Rules with “Schedule” and it would show the times of day on the Schedule Page in MainUI.

1 Like