OH3 Presence simulation

I am working towards mifgrating my 2.5 environment to 3.0 and experimenting with understanding the rules and script section and wondering if someone could help - below is the old rule I used for presence simulation based on a group of items when the “away” switch was selected.

This rule fired every 1 minute of every day.

Is there a better way to do this in OH3? is there a better way than running a cron rule to check for a switch state to fire the rule?

If not would the below rule “just work” in OH3?

var int presence_days = 7
var int presence_delay = 1000  
var String persistence = "influxdb"

rule "Presence Simulation"
when
    Time cron "0 0/1 * 1/1 * ? *"
then
    if (Away_switch.state == ON) {
	gVacation_Lights.members.forEach(light,i |
		if(light.historicState(now.minusDays(presence_days), persistence).state != light.state) {
			createTimer(now.plusMillis(i*presence_delay)) [|
				logInfo("RULES",light.name + " got " + light.historicState(now.minusDays(presence_days), persistence).state)
				light.sendCommand(light.historicState(now.minusDays(presence_days), persistence).state.toString)
			]
		}
	)
	}

end

The rule will just work in OH 3 almost as written (you’ll have to convert the plusMillis to plusNanos since ZonedDateTime doesn’t provide plusMillis.

But you have some other options if you move to the UI or move to one of the other rules languages.

  • Put the test of Away_switch into a condition (“but only if…” section on the UI). The rule will only run when the switch is ON even when triggered by the cron expression.

  • Create a rule that triggers when Away_switch changes to ON. As the action for that rule enable this “Presence Simulation” rule. Create another one that triggers when it changes to OFF to disable the rule. This will work best for you if the “Presence Simulation” rule is defined in the UI because when the .rules file reloads it will become reenabled. If using one of the non-rules DSL rules, you can enable/disable rules in the text files too but you’ll have to reenable/disable it when the .js/.py/groovy file where the presence simulation rule lives is reloaded.

  • Instead of using a cron triggered rule, use a looping timer. Create the timer when Away_switch changes to ON (and on reboot when Away_switch was restoredOnStartup to ON). The Timer reschedules itself as long as Away_switch remains ON to do the presence simulation stuff. Then when it changes to OFF cancel the Timer.

Thanks @rlkoshak, point 1 seems to make the most sense and within my limited capabilites to me - I am hoping to keep everything UI based as I slowly work through moving 2.5 to 3, so far so good.

Unfortunately the other two points are a bit beyond my comprehension at this stage.

Can you please explain why 2 and 3 would be better than his cronjob solution? I did basically the same as described in the original question and just found your optimization suggestions. I adopted your first suggestion as it’s an obvious improvement, because the script doesn’t need to be executed at all if the switch is turned off.
But in my understanding the other two suggestions make this over-complicated, just for saving a cronjob. Is the performance of running a cronjob that checks for a virtual switch actually noticable at all?

It depends on what the rule is doing.

2 is not complicated at all. You don’t even need to write any code.

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: TisTheSeason
      state: ON
    type: core.ItemStateChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      enable: true
      ruleUIDs:
        - christmas_lights
    type: core.RuleEnablementAction

This is one of the two rules that enables/disables my christmas light rules based on the state of the TisTheSeason Switch Item.

The nice thing about this is you have a visual indicator in MainUI which rules are runnable or not.

A disabled rule is also not even loaded into memory. There is no chance that it can interact with other rules or be a source for errors and such. It’s generally a good thing to not run things that are not needed, especially when debugging a problem.

3 is a little more complicated but not significantly so when using some of the other rules languages where looping timer is available as a library. But the main advantage is it reacts immediately. If you have, for example, an every five minutes cron trigger, it can be up to five minutes before the rule reacts. With an event driven rule/implementation the rule will react immediately after the switch changes.

Sometimes that matters, sometimes it doesn’t. I mentioned it for those where it does matter.

Finally, if you use lots of cron triggered rules, unless you are very careful with your expressions, you will be pounding your OH every so many minutes as you have lots of rules all triggering at the same time. This could have a performance impact compared to the rules triggering in a more spread out way.

It’s probably worth noting that the Presence Simulation rule template on the marketplace implements a cron trigger with the but only if condition approach. And since it’s on the marketplace, there really is no longer any need for anyone to implement this themselves anymore. Persistence Presence Simulation [3.2.0;3.4.9) just install the rule template and instantiate and configure the rule.

Thanks for clarifying the second suggestion. I will consider doing this for the “away” switch.
I understand the benefits but would like to point out the downsides:

  1. Two additional rules in the list (more to see, more to understand). Imho that’s against the KISS principles.
  2. If the rule is re-enabled after a reboot (if I understood you correctly), then this needs to be handled as well, because the last status should be preserved.

Regarding the point 3:
I see a continuously running script loop taking up more resources than a cronjob that runs every couple of minutes. Also if the script breaks for some reason, the rule won’t be restarted, so it will just stop working.

When applied properly, KISS almost always results in more but shorter and simpler functions/classes/etc. More rules that are shorter and simpler is very much within the goals of KISS. Making fewer much more complicated rules would violate KISS.

Put another way, if having fewer rules is the goal of KISS, then one massive rule with all sorts of filters and switch statements and branches and such would be the simplest setup of them all. Clearly that’s not the case.

As for more to understand, if the name and description of such a simple rule doesn’t provide everything you need to know to understand what the rule does, you need to write better descriptions for your rules.

This happens only in one case. If you write your rules in text files instead of in the UI and you do not provide your own UUID for the rules that are defined in the text files then every time that text file is loaded, all those rules defined in those files will have a new randomly generated UUID. The simple rules to disable or enable another rule depend on that UUID remaining the same.

But if you define the rules in the UI, or you specify the UUID in your text defined rules so they don’t change every time the file is loaded, you don’t need to do anything at all. A disabled rule will remain disabled after a reboot as will enabled rules. The last status is preserved.

It is exactly identical to a cron job that runs every couple of minutes. A Timer isn’t continuously running. A Timer is a way to schedule a block of code to execute at some time in the future. In openHAB Timers and cron triggered rules are even handled by the exact same scheduler inside openHAB.

The biggest difference is you have more control over how and when a Timer runs than you do with cron triggered rules. And if you care about performance (which doesn’t really matter here) the Timer will consume far fewer resources because it will only be running when there is something to do unlike a cron triggered rule which has to run all the time just in case there is something to do.

By that logic, if the rule breaks for some reason it will continue to break every time it’s triggered by the cron trigger. So what’s the difference? Something’s broken in either case. And yes indeed the rule will still retrigger and when the rule does retrigger based on events it will see that the timer isn’t running and create another one.

These are all options. None is necessarily better than the others. It all depends on what your requirements and end goals are. In my rule template I chose a cron trigger with a condition because based on the requirements that was the most simplest choice for implementation. But if I had a case where a simple rule condition wouldn’t be sufficient, I’d use another rule to enable/disable. If I had a case where I really needed something to start happening immediately after events occur and states change I’d use a looping timer.

I would argue, that there is a difference between having everything in one rule or just the stuff that belongs together. Having one rule with the sole purpose to control another rule seems like they better belong together. Just by looking at the actual rule, you don’t see when it’s used. You need the trigger rule to understand this. But I absolutely understand your point. Maybe this depends on personal taste. I might try your suggestion in this specific case and see how it feels over time.

Thanks for clarifying, apparently I misunderstood a previous statement. My bad.

Maybe I’m misunderstanding something again, but to put it in your own words:
“long running Rules are a bad idea”
Quote from here: Design Pattern: Looping Timers
or:
“The tl;dr is avoid long running Rules wherever possible.”
Quote from here: (OH 1.x and OH 2.x Rules DSL only] Why have my Rules stopped running? Why Thread::sleep is a bad idea

How is this case any different?

No, that’s not unnecessarily true. There are little gnomes in your computer that sometimes once in a full moon like to flip a bit and you’ll have a problem that you cannot reproduce. I’m sure you know what I mean. To avoid having that you can either run the script freshly every time through a cronjob or implement a separately running watcher rule/script/cron that makes sure the actual rule is still functional. Imho the second option makes it unnecessarily complicated.

Correct, but it’s worth discussing them to learn and improve. I appreciate your time and arguments. Thanks.

First, in both of those statements in both of those posts it’s referring to OH 2, not OH 3. There was a major change in OH 3 that greatly reduces the impact of long running rules. In OH 2, there were a mere 5 threads shared by all your rules with an additional 3 (IIRC) threads for cron triggered rules and Timers. If you had long running rules you’d consume one of those threads and block other rules ability to run. In OH 3, each rule gets their own dedicated thread. So now a long running rule only impacts that one rule, not all your rules.

Secondly, Looping Timers is a way to solve the problem of long running rules. That whole DP post is to show how to use Looping Timers to avoid long running rules.

However, let’s pretend that long running rules are still a really big deal.

If I create a timer to go off in now.plusMinutes(5) (i.e. five minutes from now), the rule is only running long enough to create the timer. The rule will stop executing and return in milliseconds.

Then in five minutes the timer will be triggered and the timer will only be running long enough to execute the code that was passed to it, again usually just a few milliseconds.

All the rest of the time the rule is not running. No thread is being consumed. No resources are being blocked. A looping timer is not a busy wait loop. It’s a way to asynchronously schedule a block of code to run at a later time.

There is no long running rule here. There is a few milliseconds worth of execution every five minutes as the looping timer triggers, exactly the same as a cron triggered rule would consume. The only difference, is we have more control over when and how the looping timer executes (e.g. we can change how often it runs on the fly based on state or events, trigger it to start running immediately upon an event, etc.).

But you are not running a fresh script. It’s fresh only the first time the rule is triggered and run. The script is reused on every run after that. If you want a fresh rule, you need to disable and reenable it on every run or reload the file it’s defined in or something.

In fact, if you did want to have a fresh script on every run of the rule, you’re better able to create that situation by using a looping timer. The rule will trigger based on some event. When it triggers it can cancel and create a fresh new looping timer.