Design Pattern: Rule Disable

Tags: #<Tag:0x00007fc1fda68698> #<Tag:0x00007fc1fda685d0>

Please see Design Pattern: What is a Design Pattern and How Do I Use Them for an explanation of what a DP is and how to use them.

Problem Statement

Often one has a set of Rules that need to run under a given set of conditions and not run when those conditions are not present. For example, one may have certain Rules that only run when everyone is away, only during a holiday season, or when the family is on vacation.

This DP covers various techniques that one can use to enable and disable Rules based on Item states.

Concept

There are two parts to achieve this. First is to determine the conditions under which the Rule should run. The second is to enable/disable the Rule based on those conditions. The best approach for each depends on how you are defining your Rules.

but only if…

When building Rules using the UI instead of hand coding, you can utilize the “but only if…” section of the Rule definition.

image

This section lets you establish one or more tests that must be True. If all the conditions are false, the Rule will not run. There are a number of simple tests such as “It is a weekend” and “an item has a given state” and such. If you need something more complicated you can use “a script evaluates to true” which lets you write a complicated “function” that will run and return true or false. You can choose any of the Scripted Automation languages you have currently installed.

For a Python example,

(items["MyItem1"] == ON and items["MyItem2"] == OFF)

In the above case, the Rule will only run if MyItem1 is ON and MyItem2 is OFF.

The code can actually be quite complex, such as performing a long and involved calculation to come up with a value to compare against.

This feature is only available for UI/REST API created Rules.

Advantages and Limitations

This approach is similar to the approach used for Rules DSL Rules (see below) except it promotes the if return; to be a separate part of the Rule instead of being just another part of the Rule. This provides a nice logical separation between the concerns. However, it does require the Rule to know about things that it shouldn’t have to know about. For example, if you want to turn off a humidifier controlling Rule at Christmas time because you are reusing the smart plug to control lights, why should this Rule need to care about Christmas? This forces you to diffuse the handling of Christmas time throughout your Rules. It would be better to centralize that into one location.

Disable/Enable Rules

Both UI Developed Rules and Scripted Automation run on the Next Generation Rules Engine (NGRE). Consequently they are fully compatible with each other meaning that this approach can also work with UI Developed Rules.

The NGRE supports the ability to enable and disable Rules. This can be done through the REST API (e.g. from PaperUI) or from other Rules.

PaperUI

When you have the NGRE installed, you will find a “Rules” entry on the left. When you click on that entry you will see a list of all the Rules you have defined using NGRE (both UI defined and Scripted Automation defined). Browse down to the Rule you want to enable/disable and click the clock icon.

A disabled Rule will be grayed out and indicate an uninitialized status.

Rules that are defined using Scripted Automation will become re-enabled on a restart of OH or a reload of the source files. Rules defined through the REST API (e.g. PaperUI) will remain disabled on an OH restart.

The command can also be issued through the REST API Docs as well. You must first search to find the UUID of the Rule you want to control and then you can enable/disable through the /rule/{ruleUID}/enable end point.

Advantages and Disadvantages

This gives you full control over what Rules are active or not. It can be particularly handy when building Rules that you want to not have running until they are done (e.g. build for a while and then disable when you have to step away so it doesn’t interfere with the rest of your Rules).

The disadvantages are that it’s a fully manual process and for Scripted Automation, the setting only lasts until the next time the script files are reloaded.

Disable/Enable Rules from Rules

PaperUI

There are two ways to do this as well. If using PaperUI, there is an Action under the “then…” clause to “enable/disable rules”.

You can select one or more Rules to enable/disable when this Rule runs. Simple as that. So, for example, you can define a Rule that triggers when Christmas changes to ON that enables the Christmas lights Rule and disables the Rules that use the smart plugs that have just been re-purposed for the Christmas lights. If you are controlling Scripted Automation Rules in this way, you will also want to trigger the Rule at System started (see above regarding how disabled Scripted Automation Rules will become re-enabled when those Rules are reloaded).

Advantages and Disadvantages

The biggest advantage here is that Rules like these are really easy to create. Most of the time you would need not create any hand written code at all. It also centralizes the handling of the figuring out which Rules run under what conditions, avoiding smearing information across a bunch of Rules that really shouldn’t care about. The disadvantage is unless you are writing all your Rules in PaperUI right now, you will likely end up with a mix of Rules with some defined in Scripted Automation and others defined in PaperUI.

Scripted Automation

When defining Rules using Scripted Automation, enabling and disabling a Rule from a Rule is a two step process. The UID for the Rule is defined when the Rules is loaded and it is not guaranteed to be the same every time OH restarts. So you must first search for the UID based on the Rule’s name. Then you can enable/disable the Rule using the discovered UID.

Here is an example that enables a couple of Rules based on the state of a Switch Item.

from core.rules import rule
from core.triggers import when
from core import osgi

@rule("Christmas Mode",
      description="Enable/disable rules based on the state of vChristmas",
      tags=["christmas"])
@when("Item vChristmas changed")
@when("System started")
def xmas_enable(event):
    """
    Because smart plugs are reused for Christmas lights, disable the rules that
    normally use those plugs when Christmas mode is enabled and enable the
    Christmas lights Rule.
    """
    import time
    time.sleep(40) # Wait until all Rules are loaded

    xmas_enable.log.info("Turning {} Christmas mode"
                         .format(items["vChristmas"]))

    lightRuleId = [rule for rule in rules.getAll()
                   if rule.name == "Christmas lights"][0].UID
    humidifierRuleId = [rule for rule in rules.getAll()
                        if rule.name == "MBR Humidifier"][0].UID

    ruleEngine = (osgi.get_service("org.openhab.core.automation.RuleManager") or
               osgi.get_service("org.eclipse.smarthome.automation.RuleManager"))
    ruleEngine.setEnabled(lightRuleId, items["vChristmas"] == ON)
    ruleEngine.setEnabled(humidifierRuleId, items["vChristmas"] != ON)

Advantages and Disadvantages

As with the PaperUI approach, this lets you centralize your handling of which Rules run under what conditions instead of needing to spread those checks out everywhere. But it is a little awkward to have to search for the Rule’s UID first before enabling/disabling it.

Rules DSL

Unfortunately, Rules DSL supports neither “but only if…” nor the ability to enable/disable so the best option is through the use of an if return; statement at the top of the Rule.

rule "Christmas lights"
when
    Item vTimeOfDay changed to EVENING or
    Item vTimeOfDay changed to NIGHT
then
    if(vChristmas.state != ON) return;

    // lights turn on or off
end

rule "MBR Humidifier"
when
    Item vMBR_Humidity changed
then
    if(vChristmas.state == ON) return;

    // humidifier control
end

Advantages and Disadvantages

One of the real disadvantages of the Rules DSL approach is that the MBR humidifier Rule has to know and care about whether or not it’s Christmas. With the Rules Enable/Disable approaches with NGRE, the Rule need not know or care at all about this state, it just needs to do what it does and that lets you put the logic that does have to care about vChristmas’s state in a central location instead of scattered throughout all of your Rules.

Advanced Concepts

The examples above are very simple: if Item is in X state, enable these Rules and Disable those Rules. That is not the limit of what you can achieve with this DP, though those are expected to be the most common ways to use this, for example, enable a set of Rules when you are present and a different set when you are away. But there are other things this concept can be used for like a cascade of Rules with more than one path through the Rules. For example, you start a cascade of Rules and based on what happens in one Rule enable/disable a different set of Rules.

Do you have any clever ideas? Post a reply below!

2 Likes

Nope… items is in the default script scope.

1 Like

I wasn’t sure if the default script scope applied to the “but only if…” clauses. Good to know. :slight_smile:

When you use Scripted Conditions and Actions, you are actually using scripted automation. With the Jython bundle (not required, but nice), we can put out rule templates with Scripted Conditions and Actions that utilized community libraries!