General best practices for maintainable rule and item arrangements

Has anybody come up with a good method of defining rules and scenes for their openhab setups that aren’t too difficult to maintain? I’ve had openhab up and running for about 6 months with 4 lights in two rooms in my apartment. The rules seemed a bit complicated to maintain for what I wanted to do. For example, when I say “bedtime”, the living room lights where I am turn up to 50% so I can see my way to the bathroom to get ready for bed. After a bit of time, they gradually dim all the way to OFF. At the same time the living room lights turned on, the bedroom lights turn on also. My phone begins to auto listen. Once I am finally in bed, I say “tucked in” and the lights all turn off. That rule was a bit complicated, but worked pretty consistently once I got it set up. However, even basic scenes where I turn the scene on and various devices change to predefined settings have seemed like a fair amount of work to set up and could be error prone. I just moved from an apartment to a bigger house and will be adding more devices gradually. I want to come up with a manageable method for configuring scenes and rules.

Here’s an example of a scene I have set up:

Items:

Scene_Living_Room - a number used to track and set scene state. 0 is off, 1 is dim, 2 is medium, 3 is bright, 4 is custom

Bulbs 1 - 4

Bulb1LivingRoomBrightSetting
Bulb1LivingRoomMedSetting
Bulb1LivingRoomDimSetting - These are numbers between 0 and 100 that I use to configure the bulbs when the scene is enabled. Each bulb has its own set of numbers. They aren’t necessary, but the other option is to hard code values in the rule. I’d prefer to set up a rule once and not have to edit the configuration files again to make small tweaks. This allows me to change bulb values from the site maps. I have all of the setting values in their own pages. I have a switch named showSettingsValues and the setting items visibility is set for when showSettingsValues is ON.

Rules: (these are pseudo code from memory)

Rule “living room scene”
When
Scene_Living_Room received command
Then
If received Command == 3
{
Sendcommand(bulb1, Bulb1LivingRoomBrightSetting)
Sendcommand(bulb2, Bulb2LivingRoomBrightSetting)
… The rest of the bright bulb settings…
}
If received Command == 2
{
… Same thing as 3 but with med settings
} If received Command == 1
{
… Same thing as 3 but with low settings…
}
If received Command == 0
{
… Same thing as 3 but off…
}
End

I also have a rule for when any of bulb1 through 4 is updated where it compares all 4 bulb values with the settings for the scenes above. If all of the bulbs match a scene setting then I update Scene_Living_Room to that scene’s value. Otherwise I set it to 4 for custom.

Is this setup along the lines of what other people are doing? Have I made it too complicated? Does anybody know of good ways to streamline the rules? It seems like if I add another bulb it would be quite a few places that I have to update, and I’m trying to make it as simple as possible.

Looking forward to hearing about other people’s experience!

2 Likes

These sorts of rules are inherently complicated.

My lighting rules are even simpler than yours yet I have a few hundred lines of rules. I have a group of lights that I want to come on a little before sundown, turn off at 11 pm, or come on when the weather says it is cloudy and turn off when the weather is sunny (I don’t have a light sensor yet). However, if someone manually turns on or off the light it should stay that way no matter what the weather says. This took about 170 lines of rules code.

Here are a few things I have done to make my rules more maintainable:

  • Put items into groups and write your rules to operate on the group instead of on individual items. Run a foreach on the group in the rules. This way you can add or remove an item from a group to affect its behavior without needing to touch the rules
  • I make heavy use of lambdas in cases where the group trick won’t work (e.g. I have two garage door openers). The rules call the lambdas with the items that triggered it. This way I don’t have to maintain duplicate code everywhere.
  • Come up with a naming convention for items and groups. For example, all of my groups start with a ‘g’ (e.g. “gBasement”). I also have a convention so I can determine by the name what the item type is and object type. For example, T_D_Garage1 is a Trigger for a Door which is Garage1 whereas the sensor for that door is named N_D_Garage1 for a seNsor for a Door which is Garage1. Admittedly my conventions are a little baroque and I even had to create a cheat sheet for myself for use when I’ve been away from rules development for too long. But I can instantly tell or guess what an Item is based solely on its name.
  • If it can be done in the Items file, do it there. This includes things like transforms.
  • Put rules and items for different types of automation in separate files. For example, I have a separate set of files for lighting, weather, presence, and the garage door openers with a default set of files for everything else. This way your files don’t get too long and complicated to understand (e.g. openahb.cfg) and it forces you to think about modularization of your system in ways you might not have otherwsie.

That being said, I would probably set up your rule to use a lambda for the body of the if statements. I’d pass the group of lights to be operated on as well as the setting desired. Then in the body of your rule would look like:

val Functions$Function2 updateLights = [Group gr, Number setting |
    gr?.members.foreach[light | sendCommand(light, setting) ]
]

if receivedCommand == 3 {
    updateLights.apply(gLivingRoomLights, LivingRoomBrightSetting)
} else if receivedCommand == 2{
    updateLights.apply(gLivingRoomLights, LivingRoomMedSetting)
} ...

Note: Above is just typed in and probably contains errors.

I’d also probably try to spend some time figuring out how to eliminate the if else construct somehow, maybe having a list of the settings and using the receivedCommand as an index into the list so it collapses to something like:

updateLights.apply(gLivingRoomLights, settings[receivedCommand])

Rich

5 Likes

Thanks for the response! You’ve given me some good stuff to think about.

Particularly, I noticed that there is one version of sendCommand that lets
you pass a string for the item argument. That means that it would be
possible to compute the name of an item that you want to send a command to.
If there was a similar option that allows you to get an item from a string
name (is there?), then it would be possible to create setting items such
that they follow a pattern like sceneName_commandIndex_itemName and then
automatically look up the values for the setting items within a lambda
expression. And using groups like you suggest, that would minimize the
amount of copy&paste code that needs to be done by using a convention
instead.

You mention storing values in an array. How can I do that?

I do not believe there is a way to go from a String name of an Item to a reference to that item. However, you can do something like that with a little bit of work up front using a hashMap. On system startup create and populate a hashMap using the names of your items as the key and the Items themselves as the value. Then in your rules or lambdas you can construct the name and pull the object out of the hashMap. One thing to note with lambdas, they don’t have access to your global vars and vals so you will have to pass them.

For example:

import java.util.Map
import java.util.HashMap

val Map <String, NumberItem> brightSettings = newHashMap

// lambda goes here

rule "On Startup"
when
    System started or
    Time cron "0 0 0 * * ? *"  // periodically reload to get updates to the items
then
    // all of your settings for bright, med, low, and off are in this group
    gLivingRoomSettings?.members.foreach[setting | brightSettings.put(setting.name, setting)]
end

Putting the values into an array is basically the same only you would import and use ArrayList instead of the HashMap.

import java.util.List
import java.util.ArrayList

val ArrayList<Integer> settings = newArrayList

rule "On System Start"
when
    System started or
    Time cron "0 0 0 * * ? *"  // periodically reload to get updates to the items
then
    // Assumes all bulbs have the same brightness settings for the given command number
    settings.add(offSetting)
    settings.add(lowSetting)
    settings.add(medSetting)
    settings.add(highSetting)

    // ArrayList is 0 indexed so getting a value would be settings.get(2) for the medSetting value
end

Rule are a full fledged programming environment and you have access to standard Java stuff if you find something you can do in Xtend so there is almost no limit as to what you can do.

Rich

2 Likes

Hi Rich,

I have also started with lambha in rules but then I was pretty much stuck in many situations in which I had to avoid simultaneous sending (Sonos binding, Homematic, …). Unfortunately within lambhas Threed::sleep does not work and the workaround by using many timers is not great.
Same applies also for the functions which are also based on lambha.

Therefore, I stopped using lambhas altogether.
Same applies for group commands.

Splitting rules in many files is in principal a good idea and I am still using this (7 rules files in my case).
The disadvantage is, that at least in openhab 1.x, there is a nasty unresolved bug, that creates errors when loading those rules and one need workarounds to reinitiating the loading.
So my advise here, if complexity permits, stay with one rule as long as you can.

regards Martin

Thanks to both of you for the suggestion of using lambdas as well as the
warning of situations when lambdas don’t work well. I’ll have to consider
both opinions as I work on my rules in the future.

It seems to me like it might make sense for there to be a binding that
implements a “scene”, or in my mind a group of items that is generally
changed at the same time. A scene could have multiple levels, basically
just like I described in my first post. I’m not sure what the setup or
interface would look like for that, but it seems like a lot of home
automation technologies make setting up scenes really easy and it’s
difficult to do in openhab without being a programmer. Thankfully I am a
programmer, but I still don’t necessarily want to write a program for every
single grouping of items I want to make. Anybody know if something like
this exists, or if there has been any discussion about it?

Hey all,
interesting read. I’m also trying to modular’ize my rules and always stumble upon the same issues. The following sentence reads quite interesting:

Rhich, would you mind sharing a sample code for that? How to I remove an item from a group? Can I also add an item to a group if needed?
Thanks for your help.
Sebastian

Have you tried using Reentrant locks? I have no idea whether they work in lambdas either, I’m mainly curious.

I’ve not encountered this bug. Is the bug that if there is an error loading one rule file each subsequent file fails to load? Or is there something else going on? Do you have the bug reference number?

Rich

Sebastian,

Here is an example I use for turning on all the lights in my gTimerLights group.

First to add an item to a group just put the group name in “()” for the item definition. For example.

Group:Switch:OR(ON, OFF) gTimerLights "Timer Lights [(%d)]" <light>

Switch S_L_Front  "Front Room Lamp"  <light> (gTimerLights) {zwave...}
Switch S_L_Family "Family Room Lamp" <light> (gTimerLights) {zwave...}
Switch S_L_All    "All Lights"       <light>

NOTE: The group gTimerLights is define to act like a switch on the sitemap

Then a rule to toggle all the lights in gTimerLights would look like:

rule "Toggle all timer lights"
when
    Item S_L_All received command
then
    if(S_L_All.state == ON)
        gTimerLights?.members.forEach(item | sendCommand(item, ON))
    else
        gTimerLights?.members.forEach(item | sendCommand(item, OFF))
end

To add or remove an item from gTimerLights just add or remove gTimerLights from the “()” in the item definition. I think it may be possible to programmatically add or remove items to/from a group but any changes made this way will not persist beyond an openHAB restart and might actually get blown away on the next items file reload.

Note: Because I defined gTimerLights as a switch, the rule above is actually redundant. I can just put gTimerLights on the sitemap instead of S_L_All and get the same behavior. I present them both ways here for illustrative purposes.

Rich

1 Like

Hey Rhich,
thanks for your detailed explanation and I perfectly understand your idea. My use case is a little bit different and would need to programmatically remove an item from a group. Or I just need to go a different approach? Let me describe my use case:
Think of if you want to switch off all light when you go to bed except one “night light” for the kids that should remain turned on. Of course you can create a new group which only consists “all light minus night light”, but that does not scale if you have 10 or more scenarios like that. Having 10 different groups with proper naming to know what they reflect/mean is very difficult.

In fact I’m looking for something like the below (more UML than real rule code):

gGF_lights?.members.foreach[light | 
   if(light.name == "night_light") {
       continue()
   } else {
     sendCommand(light, off)]
   }

And to throw a second obstacle in the round, is “how to know which item of a group triggered the rule”?

Switch light_1   "Light 1"   <light>   (gLights)   {zwave...}
Switch light_2   "Light 2"   <light>   (gLights)   {zwave...}
Switch light_3   "Light 3"   <light>   (gLights)   {zwave...}

rule "Which item triggered the rule"
when
   Group gLights received update
then
   ...some for loop which spits the item name that received the command..
   ... Do something with that item only...
end

And here’s my use case for that:
I do have plenty of roller shutters, where I’d like to create a switch that every single roller shutter has 4 options: Open, half, three_third, closed. I do need a rule for every single defined switch to react on that switch and send the proper command to the roller shutter. In fact I’m ending up with 14 rules that are identical where just the name of the device to control changes. There must be a better way than having 14 rules!

Any help is appreciated.
Thanks much

For your first use case:

The complexity and scalability is the same whether you implement your exceptions in groups or implement them in your rules. An item can be a member of multiple groups so you would need to come up with your ten scenarios and create groups accordingly. Sure, you will have ten groups that look like a Venn diagramer’s nightmare but if you implement it in rules you will have ten if else statements and possible nested if elses and your rule’s code will become long, difficult to read, and difficult to update. Perhaps there is a middle road.

How do you plan on activating each of these ten scenarios? Perhaps that might guide you in which is the better choice.

Also, items have a .name attribute so you should be able to do exactly what your code above outlines using the item names.

My personal preference would be to move the complexity to the Groups and let the rules be pretty simple. However, that all depends on how I would intend on using those different scenarios. What I like about groups is you never have to worry about breaking anything to add, remove, or change your items around. At worse you will break that one item’s behavior. That is a lot of different switches on your sitemap.

A personal example might prove illustrative for my thought processes.

I have two lamps which turn on when the weather says it will be cloudy and turn off when the weather says it is sunny, but only from sunup to 90 minutes before sundown. However, if the lamps are manually turned on or off during the day, they stay that way regardless of the weather. Also, if no one is home during the day, they stay off regardless of the weather. These same lamps turn on regardless of the weather from 90 minutes before sunset to 11 pm. Then I have a porch light that I have come on at sunset and turns off at 11 pm.

There are lots of different ways to implement this and I cut a middle road between leveraging groups and rules. I have one complicated rule that checks the weather code, whether it is day, or whether anyone is home and turns on or off the lamps in the weather controlled group appropriately. The code to turn on and off the lamps is in a lambda largely because depending on the reason the lamp is being turned on or off I want to block it if the lamp has been manually overridden (i.e. if overridden weather has no effect but the 11 pm timer always shuts them off).

Then I have some Time cron triggered rules to handle the 90 minutes before sundown, sundown, and 11 pm events. Again, they call the on/off lambda.

Finally, because individual lights can be controlled and manually overridden, I have a rule for each lamp that sets a flag and calls the same on/off lambda that everything else calls to toggle the lamp.

I probably could have done more using groups but in my case I wanted one switch in the sitemap to turn on ALL the lights, and another Frame where I could turn on or off each light individually. So I have one group that all a lights belong to and another that all only the weather lights belong to. On the sitemap I have two a switch and a group which opens a new frame listing all the lights where I can control them individually. I will probably go in and rework this a little more to make better use of groups but with only three lights it hasn’t been a priority yet.

For your second use case:

The answer is “not directly”. Once the trigger flows from the Item to the Group openHAB loses the context of the original Item that was triggered. However, you can back into it by seeing which Item in the group has the most recent update using the lastUpdate method on the Item object. This approach isn’t perfect though if you have cases where Items in the same group can be triggered at the same time. I also don’t know how the persistence works (persistence is where lastUpdate comes from). If an update is saved to persistence before the rule is called there is no problem. But if the update is saved at the same time or after the rule is called lastUpdate will not always give the most recent lastUpdate time.

This is where I use lambdas. I indeed would have 14 different rules, one for each roller shutter, which would each call the same lambda, passing in the Item that was triggered for the action and any other Items necessary to execute the code. The important part is to not to have 14 copies of the same functional code. Having 14 rules which all call the same functional code doesn’t bother me because if I want to change the behavior I only have to change code in one place (verses 14). Roller shutters is the actual use case for the lambda example in the wiki.

This is exactly how I set up my entry sensors. I have three house door sensors and two garage door sensors and the code to handle reporting their status is all the same so I have a lambda that implements the code and five rules which just call the lambda.

Hope this helps

Rich

4 Likes

Your second use case got me to thinking and I’ve done a little experimentation and this appears to be working to get which is the most recent item that changed in a group.

val mostRecent = gLights?.getAllMembers.sortBy[lastUpdate].last as SwitchItem

It sorts all the members of the gLigths group (note, I have nested groups so I use getAllMembers to get those Items which are part of subgroups) by their lastUpdate and grabs the last one (largest lastUpdate means most recent).

Third edit:

It looks like I have to use this as the trigger:

Item gLights received update

Unfortunately this causes three triggers of the rule for each light I toggle. Running the rule for me isn’t going to hurt anything but I don’t like it.

I tried received command but the rule won’t trigger at all. When I tried changed the rule only triggered when all the lights were in the same state and I changed one.

It is starting to look more and more like you can do what you want the way that you want. :smile:

Rich

2 Likes

OK, I’ve completed moving my lighting rules over to take advantage the mostRecent line of code that I posted previously. It shaved about 50-100 lines out of my rules. And now all I need to do to add, remove, or change the default behavior of a light is to change the groups that the light belongs to. I have some lights that turn on at sunset, some that turn on 90 minutes prior to sunset, and some that turn on if the weather says it is cloudy and a set that gets turned off at 11 pm.

This worked pretty well so I might look into doing the same for my entry sensors described above, but only if I can figure out how to trigger the group rule only once per sensor change. I don’t want to become blasted by alerts every time an alert is generated.

Rich

1 Like

Hey Rich,
thanks for this awesome feedback! I got so much input and ideas to keep me busy for the next few weeks, optimizing my rules. I was totally unaware of the lambda support. Sound quite interesting. I have to implement this!
But also seems like my question got you going as well. Saving 100 lines of rule code is a great success!

Again, thanks for your input/ideas and this awesome new forum.
Still stoked!

OP here. I’ve been playing with some of my rules lately to make them more friendly to look at. The group/lambda option wasn’t a perfect fit for some of the things I want to do because I either wanted different members of the group to have different levels, or I wanted rules that did more complicated things.

One thing that kept causing me headaches was trying to get values from Items that I set up to hold settings, and then pass the values as a command to another Item. One of the problems I would run into was that if I had recently restarted openHAB, some of the settings items wouldn’t be initialized. I found this could be addressed by using historicState.

I have come up with a lambda function that I can pass a variety of things in order to get a value. Using this function, I can simply using a rule’s state as a command for another item, and if I choose to go with a string rather than an Item as the source of the comand, I don’t need to change much in the rule. It’s got really verbose logs so that I could track what was happening, but I will probably eventually change or remove those.

[code]val org.eclipse.xtext.xbase.lib.Functions$Function1<java.lang.Object, int> intVal = [ java.lang.Object item |
var int result = new Integer(0)
if(item instanceof DecimalType) {
logInfo(“intVal”, “Got DecimalType item”)
result = (item as DecimalType).intValue
logInfo(“intVal”, “DecimalType value =[{}]”, result)
}
else if(item instanceof NumberItem) {
var numItem = item as NumberItem
if(numItem.state instanceof org.openhab.core.types.UnDefType){
result = 0
var historicState = numItem.historicState(now)
logInfo(“intVal”, “Got UnDefType=[{}]”, numItem)
logInfo(“intVal”, “State of now =[{}]”, numItem.state)
logInfo(“intVal”, “Historic state of now =[{}]”, historicState)
logInfo(“intVal”, “Setting value of item from historic state of [{}] as [{}]”, historicState.getName(), historicState.getState())
postUpdate(numItem, historicState.getState())
result = new Integer(historicState.getState().toString()).intValue
}
else if(numItem.state instanceof org.openhab.core.library.types.DecimalType){
logInfo(“intVal”, “Got DecimalType state”)
var historicState = numItem.historicState(now)
logInfo(“intVal”, “Historic state of now =[{}]”, historicState)
result = new Integer(historicState.getState().toString()).intValue
logInfo(“intVal”, “DecimalType value =[{}]”, result)
}
else{
logInfo(“intVal”, “Got other state Type ({})”, numItem.state.class.name)
var historicState = numItem.historicState(now)
logInfo(“intVal”, “Historic state of now =[{}]”, historicState)
result = new Integer(historicState.getState().toString()).intValue
logInfo(“intVal”, “Othertype toString() parsed as Integer value =[{}]”, result)
}
}
else if (item instanceof String){
logInfo(“intVal”, “Got String”)
result = new Integer(item.toString())
logInfo(“intVal”, “String value =[{}]”, result)
}
else{
logInfo(“intVal”, “Got unknown type=[{}]”, item.class.name)
}

logInfo("intVal", "Returning result=[{}]", result)	
return result.intValue

][/code]

I can use this function in rules like this:

var settingBulbBrightness = intVal.apply(setting_mbsl_BulbBrightness)
sendCommand(masterBathLamp, settingBulbBrightness)

In this example, setting_mbsl_BulbBrightness is Number Item, and masterBathLamp is a Dimmer.

Using this has cleaned up the body of my rules by not having to do if/else code to analyze a setting source within the rule. Hope it’s helpful to somebody else.

@jonathan1, are you using the restoreOnStartup strategy in your persistence? The reason I ask is that I wouldn’t think restoreOnStartup would fail to initialize the state of your items yet historicState would work. If you are using restoreOnStartup than I don’t think you would need your code that gets the historic state as your items shouldn’t show up as undefined.

It is odd behavior if you are using restoreOnStartup and yet still need to deal with the UnDefType and can successfully do so using historicState. If is that is the case and it is another place in openHAB where my assumptions are false.

EDIT: Corrected a couple of typos.

Rich

@rlkoshak, I just checked, and I’m actually not using the restore on
startup rule. I’m not sure if I was aware of it or not, as I might have
just moved on to other things once I saw that items were being persisted.
Now that I think of it, I think my setting items were available on startup
at some point in the past but may have begin causing the UnDefType issues
recently. Maybe I accidentally removed the restore on startup when I was
debugging something else. I have my whole openhab deployment in a git
repository, so I’ll check it out.

Thanks for the tip!

Hi everybody

I’m trying to implement a rule that would be triggered by a group of contact items (Alarm).
That rule will should identify the item that triggered the rule and postupdate another item with de new datetime.
I’ve tried the val = …and does’nt work.
For exemple : Zone 1 Contact
Zone1_update String

Thanks for your help!
Jose

Is that true? Is there a problem with Thread::sleep in lambdas? I’ve never come across this problem…

No problem using thread::sleep in lambdas, just use it this way:

try { Thread::sleep(Timeout) } catch(Exception e) {}