openHAB Basics Tutorial - (Part 5/n) - Introduction to Rules DSL

Rules


Next, we’re going to start talking about the biggest topic to tackle in the subject of home automation - the actual automation part. This is what we’ve been working up to in all the posts up to now, and in this post we’ll really just go through an introduction, to get you started automating your home with openHAB.

As you have seen by now, there are multiple ways to do pretty much everything in openHAB. That includes the automation part. There’s the current standard of writing Rules files

there is JSR223 scripting, for those who are more familiar with programming,

and there’s even an exciting new feature, called the Next Generation Rules Engine

which is experimental right now, but when finished, it will allow you to develop automation routines with mostly a point and click interface. This will make automating your smart home much easier!

But for now, we’ll start with the standard openHAB Rules files, because that is the most commonly used method of automating in openHAB, and you’re more likely to get excellent help from the openHAB forums if you use the Rules DSL programming syntax


As I said, openHAB Rules are still the standard method of automating your openHAB system, they are very powerful, and will be part of openHAB in one way or another even when the Next Gen Rules Editor is finished, so learning how to use them is a good idea!

With rules, you can take any event or action that occurs in your openHAB system, and write a set of steps to perform when that action occurs. So, for example, I can write this simple rule, which is triggered when the sun sets, turns on my outdoor lights, and writes a message to the log. This is a very simple rule, but it shows you how to take the Things, Channels and Items that we’ve previously learned how to create, and connect them together to perform a task. It was also the first rule I wrote in my early days with openHAB, and I haven’t thought about my outdoor lights since then - and isn’t that the whole point of home automation? This “just happens” without any intervention from anyone…

So let’s break this rule Syntax down a bit. Check out the purple text going down the column. It reads “Rule When Then End”…It basically spells out how the rule works in human-readable form if you read it top to bottom, that is

RULE Sunset Outdoor Events will fire WHEN the astro sun set channel triggers start and THEN it will send an “ON” command to the OutdoorLights group, log some info and END

The rule block tells openHAB we want to create a new rule named “Sunset Outdoor Events”. Every rule has to have a unique name, so openHAB can distinguish between different rules, but you should also have a descriptive name for each rule so you know what it’s doing without having to look at the code inside it. Trust me, with more rules and ones that are more complex than this, it helps just having one line to look at to figure out where you are in the code! Of course, you can also divide your rules into multiple .rules files, so you can organize them by function.

It’s up to you how to organize them, but I’ve done it by function, such as my lighting rules, the rules that control my scenes, rules for an effects sequencer I wrote for my holiday lights, or general home rules that don’t fit in any other category…


Ok, so let’s talk about the “when” clause…This statement determines WHEN the rule will execute, and it’s also called the “Rule Trigger”. There are different types of triggers, depending on what you want to use to fire off your rule.

Let’s start with the Channel trigger. This is a fairly new type of trigger, specific to openHAB version 2 bindings like Astro or the recently upgraded MQTT binding. You can use this trigger to fire off a rule when an event occurs on one of your things. For example, this Astro binding’s Sun Thing has a LOT of different channels that will set off a channel trigger on events like sunrise, sunset, civil dawn, nautical dusk, etc….)

This is super useful for all kinds of scheduling and sun position based events. There is also a moon thing and associated triggers for moon phase, moon position, etc, if you want to show those for any rules…

I’ll mention here that the new MQTT binding (starting with openHAB version 2.4) also allows you to create trigger channels, so you can set up rules which fire when a particular MQTT topic gets a new message, for example handling the conversion of a temperature value when a new data point is published to the sensor’s temperature topic.

The next, very useful, type of rule trigger is Time. Time events are used for scheduling things like turning off lights at midnight, reminding you to take out the trash every Sunday night, or reading a sensor value once a minute. You use a cron expression to define the rule schedule. You can use a site like this one to come up with the correct cron expression (unless you’re a freak of nature and can do this in your head!)

http://www.cronmaker.com

System triggers are generally used for start up routines. So, for example, one of my “System Started” rules fires every time openHAB restarts, and sets some default values for my internet speed test items so they don’t show up in a NULL state on the GUI.

rule "Speedtest init"
when
    System started
then
    createTimer(now.plusSeconds(195)) [|
        if (SpeedtestRerun.state == NULL) SpeedtestRerun.postUpdate(OFF)
        if (SpeedtestRunning.state == NULL) SpeedtestRunning.postUpdate("-")
        if (SpeedtestSummary.state == NULL || SpeedtestSummary.state == "")
            SpeedtestSummary.postUpdate("⁉ (unknown)")
    ]
end

Thing triggers are again an openHAB 2 addition. I don’t really use them in my setup yet, but one possible place they could be used is, for example, with the Network binding, where you could check whether your phone Thing is online, to allow presence detection (and set up your lights, scenes, etc…). By the way, check out this great article from David at Smarthomeblog.net if you want to set up something like this yourself! (I’ll link to it in the video description below)

And finally, the most used, in my experience, rule trigger is the Item event trigger. This trigger fires whenever an Item you defined receives a command or an update, depending on what you define. The difference between update and command is subtle, but for example, if you have a temperature sensor that sends a new value every minute to an MQTT topic, the item tied to that topic will receive an update every time the value changes. If you toggle a switch on your sitemap or Habpanel, that action will send a command to your switch item.

You can also use sendCommand and postUpdate functions in your rules to interact with other Items, change their state, and even trigger other rules….A good example of when you’d want to post an Update and not send a command is this “Speedtest Init” rule.

rule "Speedtest init"
when
    System started
then
    createTimer(now.plusSeconds(195)) [|
        if (SpeedtestRerun.state == NULL) SpeedtestRerun.postUpdate(OFF)
        if (SpeedtestRunning.state == NULL) SpeedtestRunning.postUpdate("-")
        if (SpeedtestSummary.state == NULL || SpeedtestSummary.state == "")
            SpeedtestSummary.postUpdate("⁉ (unknown)")
    ]
end

When the system starts up, the Switch that’s used to manually start a new Internet speed test may be in a NULL state (neither ON or OFF). The “Speedtest Init” rule is used to set that state to OFF if it’s NULL, but we don’t want to send a command to the switch, so we use the postUpdate function to simply update the status of the switch on the sitemap. You can also see that one of the triggers for the “Speedtest” rule is when the SpeedTest_Rerun switch item receives an “ON” command. That means we’ve pressed the button on the UI and we want to run the test. This rule will NOT fire if we simply used a postUpdate function to set the state of the switch on the UI to “ON”.

As you could also see from the previous example, we can filter both on an update or command, but also on different states of those updates or commands. For example, this “Speedtest” rule used the switch item’s ON command to fire. But, you can also trigger a rule if the state of the item simply changes…

rule "Compute humidex"
when
    Item localCurrentTemperature changed or
	Item localCurrentHumidity changed
then
	var Number Tf = localCurrentTemperature.state as Number
	var Number T = (new Double(5) / new Double(9)) * Tf - 32 //Convert F to C
	var Number H = localCurrentHumidity.state as Number	
	var Number x = 7.5 * T/(237.7 + T)
	var Number e = 6.112 * Math::pow(10, x.doubleValue) * H/100
	var Number humidex = T + (new Double(5) / new Double(9)) * (e - 10)
	Weather_Humidex.postUpdate(humidex)
end

like this environmental rule that fires any time the local temperature or humidity value changes.

It’s important to note that you can mix and match rule triggers within a when clause. So, for example, I can have a rule that runs my internet speed test every hour OR whenever I press a button on my user interface that changes the Switch Item state.

rule "Speedtest"
when
    //Time cron "0 0 5,13 * * ?" or
    Time cron "0 0 * * * ?" or
    Item SpeedtestRerun received command ON
then
    logInfo("speedtest","--> speedtest executed...")
    SpeedtestRunning.postUpdate("Measurement in progress...")

    // update timestamp for last execution
    SpeedtestResultDate.postUpdate(new DateTimeType())

    // execute the script, you may have to change the path depending on your system
    var String speedtestCliOutput = executeCommandLine("/usr/local/bin/speedtest-cli@@--simple", 120*1000)

    // for debugging:
    //var String speedtestCliOutput = "Ping: 43.32 ms\nDownload: 21.64 Mbit/s\nUpload: 4.27 Mbit/s"
    //logInfo(filename, "--> speedtest output:\n" + speedtestCliOutput + "\n\n")

    SpeedtestRunning.postUpdate("Data analysis...")

    // starts off with a fairly simple error check, should be enough to catch all problems I can think of
    if (speedtestCliOutput.startsWith("Ping") && speedtestCliOutput.endsWith("Mbit/s")) {
        var String[] results = speedtestCliOutput.split("\\r?\\n")
        var float ping = new java.lang.Float(results.get(0).split(" ").get(1))
        var float down = new java.lang.Float(results.get(1).split(" ").get(1))
        var float up   = new java.lang.Float(results.get(2).split(" ").get(1))
        SpeedtestResultPing.postUpdate(ping)
        SpeedtestResultDown.postUpdate(down)
        SpeedtestResultUp.postUpdate(up)
        SpeedtestSummary.postUpdate(String::format("ᐁ  %.1f Mbit/s  ᐃ %.1f Mbit/s (%.0f ms)", down, up, ping))
        SpeedtestRunning.postUpdate("-")
        logInfo("speedtest","--> speedtest finished.")
    } else {
        SpeedtestResultPing.postUpdate(0)
        SpeedtestResultDown.postUpdate(0)
        SpeedtestResultUp.postUpdate(0)
        SpeedtestSummary.postUpdate("(unknown)")
        SpeedtestRunning.postUpdate("Error during execution")
        logError("speedtest","--> speedtest failed. Output:\n" + speedtestCliOutput + "\n\n")
    }
    SpeedtestRerun.postUpdate(OFF)
end

Finally, let me show you one more possible modification of an Item event trigger - this one is even newer, as of openHAB version 2.3, and it is the “Member of” clause. It’s used to create rules that need to run when ANY item within a group item satisfies the item trigger.

rule "LED Strip Group 1 (Outdoor) JSON Color Single Strip"
when Member of gLEDStrip_Outdoor_Color received update
then 
 	val source = triggeringItem.name.split("_Color").get(0)
	logInfo("LED", "Source " + source)
	val item = gLEDStrip_Outdoor.members.findFirst[ l | l.name == source ] as StringItem
	logInfo("LED", "Item " + item.name)
	item.sendCommand("{\"color\": {" + 
		"\"r\": " + (triggeringItem.state as HSBType).red + "," +
		"\"g\": " + (triggeringItem.state as HSBType).green + "," +
		"\"b\": " + (triggeringItem.state as HSBType).blue + "}}") end

So, check this out - I have my color rule that fires every time a member of the LED strips group color items receives an update. I have 15 or so LED strips throughout the house, so anytime time I set the color for one of those strips, this rule will fire. This one rule handles the color change for 15 different strips. If not for the Member Of clause, I’d either have to write this rule out 15 times, or use a huge OR statement to handle every item that could trigger this rule.


Ok, so let’s look at the actual meat of the rule, the “THEN” clause. This is what actually gets done when the rule triggers. You can have real simple or real complex blocks of logic here, depending on what you need the rule to do.

You can check the state of items here, use if / else if / else or switch statements to take different paths through your code, and you can send commands or post updates to other items, to automate your home.

Shown here is another pretty simple but useful rule, that will set the heating set point of my thermostat to the proper temperature depending on whether I’m home or away on vacation. I have a VacationMode switch in my user interface, that I can manually turn on when we leave the home. When I do that, this rule will automatically set the thermostat heating setpoint to 52 degrees Fahrenheit, keeping my heater off, but also keeping my house warm enough to prevent freezing water pipes in the winter. When we return and set the VacationMode switch to OFF, the rule will take a different path through the code, and set my heater to a higher temperature.

Notice I also use a lot of logInfo statements throughout my code. I use logger statements not only to keep track of the things that are happening in my home automation system, but also to troubleshoot my rules as I’m writing them. You can put log statements everywhere in your rule’s code, and see exactly what path the system is taking through your rule (and help identify any problems your rule code might have).


We talked about the Member Of rule trigger in the when clause, and how useful it is to perform a rule on multiple items. But, there are also very useful helper variables in the “then” clause, as well.

triggeringItem is a varilable that holds a reference to the item that triggered your rule (if using an item event rule trigger). You can use this variable in your code to pull things like the “name” or “state” of the triggering item.

rule "LED Strip Group 1 (Outdoor) JSON Color Single Strip"
when Member of gLEDStrip_Outdoor_Color received update
then 
 	val source = triggeringItem.name.split("_Color").get(0)
	logInfo("LED", "Source " + source)
	val item = gLEDStrip_Outdoor.members.findFirst[ l | l.name == source ] as StringItem
	logInfo("LED", "Item " + item.name)
	item.sendCommand("{\"color\": {" + 
		"\"r\": " + (triggeringItem.state as HSBType).red + "," +
		"\"g\": " + (triggeringItem.state as HSBType).green + "," +
		"\"b\": " + (triggeringItem.state as HSBType).blue + "}}") end

I use this in my LED strip rules to determine which specific item of the LEDstrip group fired the rule (so I can set the color on the specific LED strip that I want to change, instead of the whole group)

receivedCommand holds the state of the command that was issued (again, this is for those Item event triggers, where you’re triggering the rule when an Item receives a command, for example a Zwave switch attached to your openHAB system). You can use this variable to identify the action that should be performed depending on the specific command.

rule "MonoPrice Zone Power"
when
        Item MonoPrice_Z11_Power received command or
        Item MonoPrice_Z12_Power received command or
        Item MonoPrice_Z13_Power received command or
        Item MonoPrice_Z14_Power received command or
        Item MonoPrice_Z15_Power received command or
        Item MonoPrice_Z16_Power received command
then
        Thread::sleep(100)
        val power = triggeringItem as SwitchItem 
        switch power {	
        	case MonoPrice_Z11_Power: zone ="1"
        	case MonoPrice_Z12_Power: zone ="2"
        	case MonoPrice_Z13_Power: zone ="3"
        	case MonoPrice_Z14_Power: zone ="4"
        	case MonoPrice_Z15_Power: zone ="5"
        	case MonoPrice_Z16_Power: zone ="6"
        }	        	
        if(receivedCommand==ON){
                AudioMTX.sendCommand("<1" + zone + "PR01\r")
                logInfo("MonoPrice", "Zone "+ zone + " Power ON")
        }
        else if(receivedCommand==OFF){
                AudioMTX.sendCommand("<1" + zone + "PR00\r")
                logInfo("MonoPrice", "Zone " + zone + " Power OFF")
        }
        AudioMTX.sendCommand("?1" + zone + "\r")                                            
        Thread::sleep(300)
        MonoPrice_Status.postUpdate(AudioMTX.state.toString.trim)
end

For example, I use the receivedCommand in my MonoPrice whole house audio amp rules, to determine whether the command that came into my power switch rule was to turn the amp ON or OFF.

previousState holds the state of the Item BEFORE the rule was triggered. With this variable and the current state, for example, you can determine the direction of change of a sensor’s temperature or humidity and use it to figure out whether the shower is on in the bathroom.


Of course, the openHAB documentation already has all of the info I’m providing here and more. If you need to look up any reference I mentioned in this video, I recommend you store this page in your favorite links.

And, of course, the excellent openHAB community forums are another place where you can easily find the help you need while you’re writing your first, second, tenth, or whatever rule (I use the forums all the time to come up with ideas, and to learn how to make my rules better, too).

https://community.openhab.org/search?q=Design%20Patterns

Speaking of ideas and making your rules better, just do a quick search for “Design Pattern” in the OH forum, and take a look at each of the articles that pop up. These design patterns are thorough and extremely useful examples of how to write rules for specific tasks. You’ll find Rich Koshak wrote most of these, because Rich is the absolute Rules Guru on the openHAB forums, and you’re very likely to get a response from him if you post a question. The very first Design Pattern article I recommend reading, is Rich’s “How to Structure a Rule” post. This article will explain to you in very detailed terms, how and why to write your rules in a specific format.

I’ve also decided to share my actual home automation configuration with you on GitHub, so you can take a look at my rules, see some examples of how I use them to automate things around my house, and maybe even use some of my rules in your own setup!

Ok, we’re technically at the end of the openHAB Basics series….but, really, this is just the beginning. As I’ve mentioned before, Rules are a huge topic and we’ve only just scratched the surface. So, I’ll definitely be making more openHAB videos about Rules, but also other topics. If you notice, we kind of skipped over Persistence, for example, and Persistence is a very important topic as well, especially if you want to do things like charts, or restore your lights to the previous state when your openHAB system loses power. I will definitely be tackling that topic in one of my upcoming videos, as well as other more advanced topics. In the meantime, you can take a look at some of the other videos I’ve already created about openHAB. For example, the Scenes and Routines video uses some more complex rules to store and restore Item states, and to create dynamically configurable lighting scenes, so definitely check that one out!


Previous Topic


Next Topic

(TBD)


Full VIdeo

13 Likes

Very good article, @bartus.
Small thing, your diagram links the rules to items only and the first rule example has a channel trigger.
I would also link events to rules. and links events to items, channels and system (System Stated, cron…)
Just a suggestion

Thanks @vzorglub - that’s a very good point, and something I missed when designing the diagram. That said, I could add events and channel triggers to this diagram, but I’m worried about making it convoluted and harder to follow. In its current state, the diagram shows the most common rule trigger (Item event) and that may be good enough. I intend to expand on the Rules topic in my upcoming videos (and companion posts), which is when I will probably create a new, Rule-centric diagram, and all the associated rule triggers can be shown on that diagram.