Implementing a Script/Rule

NuBee here… At least to OpenHAB, 40 years as a programmer. This will probably be a really silly question, but one that needs to be answered.

I am looking for a rule or script to dim lights over a period of time. I have found a few examples on this site, they look great! What I can not figure out is the steps/process for implementing one of these solutions. Ideally would like a script/rule I can tweak to my preference than be able to call it for different lights.

Can someone point me to some instructions on how to implement the rules here.

Thanks in advance!

You should be able to find all you need to get started in the docs:

That’ll walk you through UI based rule definitions and tell you about the file structure if you would prefer file based rules.

Little confusion here. I have created a few rules in the UI but they are not in the /etc/openhab/rules folder but there is nothing there.

When I take one of these rules from the forum and I creating a file in the rules folder, or am I trying to put the rule in place with the UI?

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.

I think this may help you get started. This configuration is entirely in files. My dimmer is a z-wave, adjust your items as needed. The folders and file types are significant, the file names can be whatever you like.

In /config/items/myitems.items

Switch      LightsOutInProgress  "Lights out in Progress" // This is a helper item used to store if the light is currently in the process of dimming
Dimmer MasterLight             "Master Light"                  (gDimmers)         {channel="zwave:device:39d51d11b2:node9:switch_dimmer"} 
Number MasterScene             "Master Scene number"                              {channel="zwave:device:39d51d11b2:node9:scene_number"}

In /config/rules.myrules.rules

rule MasterLightSceneChange
when Item MasterScene received update  
then  
	 //  Cancels the dimming when the paddle is pressed again after dimming has begun.
	 //  Sets the light to 90% if the top paddle was pressed, else sets the light off
	 if (LightsOutInProgress.state == ON) {
	     sendCommand(LightsOutInProgress, OFF)
         logInfo("MasterLightSceneChange","Master light sunrise cancelled due to scene {}", MasterScene.state.toString) 
	 	 if (MasterScene.state < 2.0) {
	        sendCommand(MasterLight, 90) 
		 } else {
		 	sendCommand(MasterLight, OFF) 
		 }
		 return
	 }
	 //  Double tap of the top paddle sets the light to 90%.
	 //  Double tap of the bottom paddle starts the dimming.
     switch(MasterScene.state) 	{
       case 1.3 : { sendCommand(MasterLight, 90) }
	   case 2.3 : { 
		    sendCommand(LightsOutInProgress,ON)
			if (MasterLight.state > 70) {
	           sendCommand(MasterLight, 70) 
			}
            createTimer(now.plusSeconds(4)) [ | callScript("LightsOut.script") ]
            logInfo("MasterLightSceneChange","Master scene changed to {}", MasterScene.state.toString) 
			}
	}
}

In conf/scripts/LightsOut.script

//logInfo("LightsOutScript","---------- Started")
var Integer currentBrightness = Integer::parseInt(MasterLight.state.toString)
var Integer newBrightness = currentBrightness - 7
if (LightsOutInProgress.state == OFF) {
    logInfo("LightsOutScript","LightsOutInProgress is OFF")
    return 
    }
if (newBrightness <= 1) {
   MasterLight.sendCommand(0)
   LightsOutInProgress.sendCommand(OFF)
   logInfo("LightsOutScript","LightsOut complete")
   return
}

//logInfo("LightsOutScript","Setting MasterLight to {}", newBrightness.toString)
MasterLight.sendCommand(newBrightness.toString)
createTimer(now.plusSeconds(5)) [ | callScript("LightsOut.script") ]
//logInfo("LightsOutScript","Timer Created, newBrightness is {}", newBrightness.toString)

Thanks guys!!!

Got a few things here to try out. The biggest hurdle is crossed! Was trying to figure out how these rules fit into the UI, which of course they don’t.

Will take a look at the sample code and see what I can make it do. Will post results here later.

OK this is making a little more sense now. Looking at the code, and the OverView UI in OH3, is it possible to create a rule that would start by the pressing of a button on the UI but end up changing the value of a different item. ie: Press a button on the UI and have a Smart Bulb dim or brighten? How do I assign rules to the UI buttons and/or find out the button’s name?

Or am I going about this all wrong?

The UI deals with Items. Poke a UI widget, it sends a command to an Item.

Rules may be triggered by stuff happening to Items. Receiving a command, for example.
Rules may update or send commands to any Item they like. Items linked to devices willpass commands on to the device, via a binding.

AH OK, that makes sense. Guess I will have to rethink what I want to do and how to do it.

There’s no reason you can’t create an Item solely for the purpose of being exposed on the UI and triggering rules to do whatever. Items are not tied to devices or other Items, unless you make it so.