Implementing a Script/Rule

The ability to create rules through the UI effectively is new to OH 3. So you won’t find a whole lot of examples using it. But when ever you see someone post a rule in YAML you are looking at a UI created Rule.

Example:

triggers:
  - id: "1"
    configuration:
      groupName: MinIndoorHumidity
    type: core.GroupStateChangeTrigger
conditions:
  - inputs: {}
    id: "3"
    label: The new state is lower than the previous state and lower than the humi_alert metadata
    configuration:
      type: application/javascript
      script: >-
        var alert_threshold = items[event.itemName + "_Setpoint"] - 11;


        oldState.class !== UnDefType.class 

        && alert_threshold !== undefined 

        && alert_threshold.class !== UnDefType.class 

        && event.itemState < alert_threshold 

        && oldState > event.itemState;
    type: script.ScriptCondition
actions:
  - inputs: {}
    id: "4"
    configuration:
      type: application/javascript
      script: >-
        var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.HumiAlert");

        var ZonedDateTime = Java.type("java.time.ZonedDateTime");


        this.OPENHAB_CONF = (this.OPENHAB_CONF === undefined) ? java.lang.System.getenv("OPENHAB_CONF") : this.OPENHAB_CONF;

        load(OPENHAB_CONF + "/automation/lib/javascript/community/rateLimit.js");

        load(OPENHAB_CONF + "/automation/lib/javascript/personal/alerting.js");

        load(OPENHAB_CONF + "/automation/lib/javascript/personal/metadata.js");

        load(OPENHAB_CONF + "/automation/lib/javascript/community/timerMgr.js");


        this.rateLimits = (this.rateLimits === undefined) ? {} : this.rateLimits;

        //this.rateLimit = (this.rateLimit === undefined) ? new RateLimit() : this.rateLimit;

        this.timers = (this.timers === undefined) ? new TimerMgr() : this.timers;


        var sendAlertGenerator = function(name, state, timers, logger) {
          return function() {
            var msg = "Remember to fill the humidifier in " + name + ", current humidity is " + state + ".";
            var now = ZonedDateTime.now()
            var nowHours = now.getHour();

            var when = null;
            if(nowHours > 22) {
              when = now.plusDays(1);
            }
            
            if(when !== null || nowHours < 8) {
              logger.info("It's night, scheduling the alert for tomorrow morning");
              when = when.withHour(8).withMinute(0).withSecond(0);
              timers.check(name, 
                           when, 
                           function() { sendAlert(msg); }, 
                           true, 
                           function() { logger.warn("There is already a timer set for " + name + "!"); });
            }
            else {
              logger.debug(name + " is not rate limited, sending the alert");
              sendAlert(msg);    
            }
          }
        }


        var name = getName(event.itemName);

        logger.debug(name + " is below the threshold, rate limiting the alert");

        if(this.rateLimits[name] === undefined) {
          this.rateLimits[name] = new RateLimit();
        }

        this.rateLimits[name].run(sendAlertGenerator(name, event.itemState, this.timers, logger), "1d");
    type: script.ScriptAction

The trigger and rule metadata (description, name, ID, etc.) is defined through the UI. Conditions are something that only exist for UI rules and allow you to set comparisons to determine wither or not to run the rule (in the above the rule is triggered when MinIndorrHumidity changes but only runs the rule if it changes to a value 11 under a setpoint and it is not changing from NULL or UNDEF. The rule defines one Action, a JavaScript script that loads some libraries and sends me an alert, but only once a day.

If you see an example that has a structure along the lines of

rule "This is my rule"
when
    Item MyItem changed
then
  // a bunch of code goes here
end

You are looking at a text based Rules DSL rule. This lives in a .rules file. To convert a rule like that to a UI rule you would define the rule and when part using the UI. If the stuff after the then includes an if with a return; you’d put that into the Condition. The rest would go into a Script Action with Rules DSL chosen as the language

Rules DSL barely supports this in .rules files and not at all in UI. In .rules files you’d use Reusable Functions: A simple lambda example with copious notes but beware. Lambdas are not thread safe nor can they see any variable except those passed to it.

A better approach in Rules DSL is to write your rule in a generic way so that the one rule handles all your dimmers. See Design Pattern: DRY, How Not to Repeat Yourself in Rules DSL for a few approaches. Design Pattern: Associated Items is the most commonly used approach.

If you really want to create library type functions in Rules DSL without using lambdas, Design Pattern: Separation of Behaviors is your best bet.

But as a programmer if you want to write your rules like you are used to using functions and data structures and such, you’ll want to use one of the other rules languages. They all have their advantages and disadvantages but they at least let you define reusable library functions. See OH 3 Examples: Writing and using JavaScript Libraries in MainUI created Rules for an example.

Everything created through the UI gets stored in the JSONDB which you can find at /var/lib/openhab/jsondb. Rules are in the automation.json file.