Create a schedule Rule within an other rule?

Hello everyone,

(OpenHab 3)

I have a rule which is “listening” on Changes of an item.

Now I want to let this rule create another new rule (by running a script), which should looks like this:

triggers:
  - id: "1"
    configuration:
      time: 06:00
    type: timer.TimeOfDayTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      itemName: ShellyBuro_Licht
      command: ON
    type: core.ItemCommandAction


I really have searched for many different things, but I only find solutions how to add this new scheduled rule by hand manually, but I want to create a new scheduled rule automatically if the base rule is running…

Thx for any answers and ideas.

Cheers

Alex

You can enable/disable other rules. If this rule is going to be the same every time, just create it as you want above. Then in the actions from your rule monitoring the item state, enable or disable this rule as appropriate.

It’s not clear why you would need to do this, there is almost certainly a better option. Trying to do something like this by creating rules seems to me like a good way to get an extreme proliferation of rules. justinwilczek’s advice is good if, as he says the rule is always the same. As the rule example you show is a Time trigger rule this sounds like it might be handled even better just by using a timer. Or just let this dependent rule run everyday at the required time and make the first step of the rule check the status of your particular item. There is not a huge computational hit to just a letting a simple rule run.

However, to answer the question: to create another rule you would have to make a direct call to the api. This would not be particularly simple as you would have to ensure that your brand new rule has a unique ruleUID each time and that your payload is fully properly formatted.

You might have slightly better luck by just creating a base rule that you can then query from the api to get the rule object, modify only the parts that need to be changed and then use the api to just send the updated rule object.

Again, however, I would strongly advice only going down this road if you find you have no other choice.

That’s what Timers are for. You haven’t found anything because this is not the best way to handle this in OH. While it’s theoretically possible to create rules from rules, you will have to a pretty deep dive into openHAB internals to figure out how to do it. And then you’ll probably be wanting to write your rules in text rules files in Python or JavaScript. Ultimately you will discover it’s way more trouble than it’s worth and, given that you are not trying to figure out how to do this with Timers indicates that you are still quite a bit away from having the knowledge of how OH works to successfully pull it off.

But with either the Timer approach or creating new rules approach, you are not going to be able to manage this without writing the rules with actual code. This cannot be accomplished with the simple codeless UI rules, and creating or disabling/enabling other rules is not possible in Rules DSL.

But I have already said this to you before. I guess you are hoping to find someone who will give you a different answer?

Given my previous examples have not been suitable (or ignored), here is how to write a Script Action that will manage a Timer to send a command to an Item based on a DateTime Item, written in JavaScript.

var ZonedDateTime = Java.type("java.time.ZonedDateTime");
var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
this.timer = (this.timer === undefined) ? null : this.timer;

// Cancel the existing timer
if(this.timer !== null) {
  this.timer.cancel();
}

// TODO: There needs to be some checking added here to test for NULL and UNDEF first
var now = ZonedDateTime.now();
var timerTime = items["MyDateTimeItem"].getZonedDateTime();

// Move the date today if it's before today
if(timerTime.isBefore(now().withHour(0).withMinute(0)) {
  timerTime = timerTime.withYear(now.getYear()).withMonth(now.getMonth()).withDayOfMonth(now.getDayOfMonth());
}

var runme = function() {
  events.sendCommand("ShellyBuro_Licht", "ON");
  this.timer = null;
}

// Create a Timer if timerTime isn't in the past
this.timer = ScriptExecution.createTimer(timerTime, 
               function() { events.sendCommand("ShellyBuro_Licht", "ON");  } );
    

I just typed in the above but it should be pretty close.

To create and manage a rule from a rule would take around 50 lines of code in addition to the above.

Trigger the rule using changes to MyDateTimeItem and a little bit after midnight.

First of all >> thx for all your answers!!!

@rlkoshak I have followed your suggestions on the other post and I don’t want leave the OH Land, because now (after reading more and more the docs and the concept of OH) it should be the best way to use the OH onboard functions like Rules/Items/things/Triggers instead of an completely outsourced own WebUI which only fires some REST API requests, which was my initial plan, so I have not ignored your hints :stuck_out_tongue:

For now I have already:

  1. created a UI with a timePicker, which send the value to an item
  2. created an item with a string as value (no device binding)
  3. created a rule which will be triggered when the item value changes

but after step 3) I was lost, cause I asked myself “How can I now create a timer or scheduled rule which change the state of my items?”

so the answer in OH seems to be TIMERS.

but where can I see and manage all my existing timers, after I have created them?

@rlkoshak Thx for your great help again :slight_smile:

bye

Rickey

Unfortunately there is no central place to see them all. Timers are something that only exist inside rules. Therefore they are managed inside the rules where they were created. There really isn’t anywhere to see all your scheduled Timers. However, with the approach you are using, you don’t really need to. You are setting a DateTime Item which in turn triggers a rule which creates the Timer. Thus you can look at the DateTime Item and see when that Timer is going to run (assuming nothing went wrong).

When OH triggers your rule and calls the Script Action, any variables that were set previously still exit. So the trick is not to blindly recreate and initialize the variables you want to keep around from one run of the rule to the next. Thats what this line is doing:

this.timer = (this.timer === undefined) ? null : this.timer;

If this.timer is undefined it means this is the first time the rule has run so let’s initialize the variable to null. If it is undefined, keep the value it was set to the last time the rule ran. This way you can keep the handle on your Timer and cancel it or reschedule it or whatever you need to.

Just for other users >> here is a bracket missing

if(timerTime.isBefore(now().withHour(0).withMinute(0))) {

Thx at @rlkoshak :slight_smile:

Here I have debugged line by line and after this section I am getting this error here

 TypeError: now is not a function in <eval> at line number 20

Solution: if(timerTime.isBefore(now**()**.withHour(0).withMinute(0)) {

remove the 2 brackets after now**()** because otherwise you will call a function now(), which not exists. Now is just a variable which we have defined in the above section…

like this:

if(timerTime.isBefore(now.withHour(0).withMinute(0)) {

thx @rlkoshak

i have tried it with your code, I have also checked some typos and other things like the correct items names etc. but I have 2 issues:

  1. When I will save a new dateTime in my DateTime item (via habPanel) then the item / device at the end of my rule will go on, how can I avoid this?

  2. i have saved this dateTime string here into my dateTime item

2021-03-03T20:03:00.000+0100

but at my planed time, the device was not switched on :frowning: I have also checked in linux directly the system time, so this could not be the reason I think. The system time was:

Wed  3 Mar 20:06:19 GMT 2021

here is my rule based on your code replaced with my item names and added with some corrections like missing brackets etc.

triggers:
  - id: "1"
    configuration:
      itemName: Kuechenrollozeit_hoch_stunde
      state: ""
    type: core.ItemStateChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >-
        var ZonedDateTime = Java.type("java.time.ZonedDateTime");

        var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");

        this.timer = (this.timer === undefined) ? null : this.timer;

        // Cancel the existing timer
        
        if(this.timer !== null) {
          this.timer.cancel();
        }


        // TODO: There needs to be some checking added here to test for NULL and UNDEF first
        
        var now = ZonedDateTime.now();

        var timerTime = items["Kuechenrollozeit_hoch_stunde"].getZonedDateTime();


        // Move the date today if it's before today

        if(timerTime.isBefore(now.withHour(0).withMinute(0))) {
          timerTime = timerTime.withYear(now.getYear()).withMonth(now.getMonth()).withDayOfMonth(now.getDayOfMonth());
        }

        var runme = function() {
          events.sendCommand("ShellyBuro_Betrieb", "ON");
          this.timer = null;
        }

        // Create a Timer if timerTime isn't in the past

        this.timer = ScriptExecution.createTimer(timerTime, 
                       function() { events.sendCommand("ShellyBuro_Betrieb", "ON");  }

        );
    type: script.ScriptAction


I want to write single variable values of the code in the log, but how can I do this?

I have tried

console.log(VARIABLE)

and also

logDebug(VARIABLE)

but nothing of them gave me an output back, I have also check on the system itself the log files with

tail -f /var/log/openhab/events.log

and

tail -f /var/log/openhab/openhab.log

but nothing happened

any idea?

Thx for your great support and all your suggestions and hints.

Cheers

Alex

1 and 2 are related. Your timer is going off immediately, not at the scheduled time.

I notice another error in the code I typed above. I created a runme function but then I don’t use it. The intent was to pass runme to the call to createTimer instead of creating a new anonymous function.

Without calling runme the timer never gets reset to null and therefore a new timer will not be created.

this.timer = ScriptExecution.createTimer(timerTime, runme);

Add some logging throughout the rule to see what your Items are, what timerTime ends up being, confirmation that the timer is created and when runme runs.

Examples of logging:

var Log = Java.type("org.openhab.core.model.script.actions.Log");
Log.logError("Experiments", "This is an OH error log");
Log.logWarn("Experiments", "This is an OH warn log");
Log.logInfo("Experiments", "This is an OH info log");
Log.logDebug("Experiments", "This is an OH debug log");
1 Like

so I will try to replace

with this here right?

thx

Yes

1 Like

@rlkoshak works really really great, thank you so much for your help.

Can I create later an own post hier to explain other users how to handle this in OH3 in a simple way like a small tutorial like “How to manage devices time-controlled based on Panel Widgets in OH3”

Again >> thx for your great help

Tutorials are always welcome. Post them to the Tutorials and Solutions category though.

1 Like

2 last questions, to make sure that I really understand what is happening “behind the scenes”…

  1. A timer is creating a cronjob within the linux env right?
  2. A timer based on the above code, is not “still active” after the first time the device was controlled successfully right? so its not possible that every day at 8am my shutter or light goes on, right?

Edit: I have changed the system time to tomorrow and the rule script was not triggered. I think because of the fact, that I only have now a ONE TIME timer created with the above script…

… but my goal was that the shutter goes up every morning at the same time…

I have searched here in the forum and I have found this here:

so can I use something like:

this.timer.reschedule(timerTime.plusSeconds(86400));

??

No, absolutely not. It’s all handled within the Java program.

You’ll have to add some code. The easiest would probably be to add a trigger to the rule so it runs at midnight every night to recreate the timer after moving the time of your DateTime Item forward to the current day.

Even with the above doing that wouldn’t work because openHAB is event driven. If you jump the clock forward to tomorrow you will miss the event that occurs at midnight and the timer wouldn’t be recreated.

You could probably do something like that too but it wouldn’t handle daylight savings very well.

Also, if you make sure you persist your target datetime Item so that it survives a reboot, you can run the setup rule with a system started trigger too, so that it recreates the actual timer.

You’d probably add a check that it wasn’t already past time, though.

2 Likes

understand

ok, I hope I will find some concrete Syntax and Code Explanations about this stuff in the doc :slight_smile:

thx four your time / help

var ZonedDateTime = Java.type("java.time.ZonedDateTime");
var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
var Log = Java.type("org.openhab.core.model.script.actions.Log");

// Set the timer var to "null" for 'no timer exists'
this.timer = (this.timer === undefined) ? null : this.timer;

// Cancel the existing timer
if(this.timer !== null) {
  this.timer.cancel();
}

// TODO: There needs to be some checking added here to test for NULL and UNDEF first
var now = ZonedDateTime.now();
var timerTime = items["licht_buero_an_zeit"].getZonedDateTime();

Log.logInfo("Zeit des TimerItems", timerTime);

// Move the date today if it's before today
if(timerTime.isBefore(now.withHour(0).withMinute(0))) {
  timerTime = timerTime.withYear(now.getYear()).withMonth(now.getMonth()).withDayOfMonth(now.getDayOfMonth());
}

var runme = function() {
  events.sendCommand("ShellyBuro_Betrieb", "ON");
  this.timer = null;
}

// Create a Timer if timerTime isn't in the past
this.timer = ScriptExecution.createTimer(timerTime, runme);

this.timer.reschedule(timerTime.plusSeconds(86400));

@rlkoshak The last line of code is not correct right? Am I close?
My goal ist that every day the timer will be reCreated… :confused:

First of all, there is a far easier way to do this. You are writing this rule through the UI. So in the “when” part of the rule click to add a new trigger to run the rule at around midnight. While you are at it, add yet another trigger to run the rule when openHAB starts up.

Done. That’s all you have to do. With those changes to the rule, and no changes to the actual code the timer will be recreated:

  • when the time Item changes
  • every day at midnight
  • when openHAB restarts

Again, there is nothing you have to do in the Script Action. All you have to do is add a couple more events that cause the Script Action to run.

Now for your looping timer attempt which I feel the need to explain why it won’t work because it shows a fundamental misunderstanding of how Timers work.

No, that last line of code won’t work. Try to walk through the code in your head and you will see why.

  1. Initialize the this.timer if necessary
  2. Move the DateTime Item’s date/time to today
  3. Create the timer to execute the code in runme at timerTime
  4. Reschedule this.timer to trigger one day from timerTime (note plusDays(1) or plusHours(24) is way easier for a person to understand).

Do you see the problem? By adding that last line of code you ensure that the timer will not execute runme today. Assuming that the rule doesn’t get run again it will execute runme tomorrow. Then it will never execute runme again.

To get the behavior you actually want, you need to put the call to reschedule inside of runme.

Another option is to advance your timer Item to tomorrow inside of runme which will cause the rule to run again create a new Timer for tomorrow. But you have to be very careful with using plusDays(1) (or any other way to add 24 hours to the time) because in places that use Daylight Savings Time there is one day of year that is only 23 hours long and another day of the year that is 25 hours long. Your timer will fail on those days if you just naively move the timer forward 24 hours.

This is why a better approach is to trigger the rule around midnight to reschedule the Timer. That way you can schedule it for the exact time desired and it will just work. You don’t have to care about DST change overs.

And of course, as rossko57 pointed out, you’ll need to reschedule the Timer when OH restarts so add a system runlevel reached trigger so it runs when OH starts up to create the Timer.

Finally, every time you change the rule, you’ll need to manually run it by pressing the play button to reschedule the Timer then too because when you change the rule your Timer gets orphaned (you’ll see an error in the logs when that orphaned timer finally does execute and the code in runme won’t execute).