Run rules for semantic groups in OH3

Hey everyone,

Is it possible to configure rules for all items based on semantic properties? For example, I want to create a rule that opens all blinds in my home.

In OH2 I had to create a group and manually insert all blind controls there. In OH3 we have semantic tags, so I was wondering if I can send a command to all items that share the same tag?

Thanks,
Florian

You can get all items from such a semantic tag. This is a part of a daily JS rule which checks for battery levels:

var allBatteries = itemRegistry.getItemsByTag("Battery");
for (var i in allBatteries) {
       var state = allBatteries[i].getState();
...

Br,
Stefan

1 Like

If you use JavaScript, Python, or Groovy you can get at all of the Items with a given tag as Stefan demonstrates with JavaScript.

But if you want to set up a Rule to be triggered by any Items with a given tag, you’ll have to dynamically create a new rule and set the triggering Items from the list of Items returned by the call to getItemsByTag. I have Python examples of this at GitHub - rkoshak/openhab-rules-tools: Library functions, classes, and examples to reuse in the development of new Rules..

This is theoretically possible to do from UI rules but I’ve not gotten it right yet. It’s not at the top of my list of things to do.

I am wondering if there is something similar for "Rule DSL’?

Sorry I am not yet familiar with the syntax. If you could point me to some OH specific documentation it would be much appreciated as well.

This might help me solving my issue:

I have never tried it with Rule DSL so I can just assume that it is something like this:

import org.openhab.core.model.script.ScriptServiceUtil
val allBatteries = ScriptServiceUtil.getItemRegistry.getItemsByTag("Battery");
allBatteries.members.forEach[ i | 
	val state = i.getState();
]

But as I wrote this is just an assumption on I have not tried it.

Br,
Stefan

Is this import supposed to work in a script as well or just within a rule script?
I couldn’t get the import to work.

import org.openhab.core.model.script.ScriptServiceUtil;

// **********************************************************************

rule "Battery Test"
when
    Time cron "0 0/1 * * * ?"
then
    logInfo("Battery", "xxx")
    val allBatteries = ScriptServiceUtil.getItemRegistry.getItemsByTag("Battery");
    allBatteries.forEach[ i |
        logInfo("Battery", "{} {}", i.getName(), i.getState());
    ]
end

Works for me as a text based DSL rule. In the UI the rule is represented in the following way (interesting: without the import):

triggers:
  - id: "0"
    configuration:
      cronExpression: 0 0/1 * * * ?
    type: timer.GenericCronTrigger
conditions: []
actions:
  - inputs: {}
    id: script
    configuration:
      type: application/vnd.openhab.dsl.rule
      script: >
        // context: zwave_system-1

        logInfo("Battery", "xxx")
        	val allBatteries = ScriptServiceUtil.getItemRegistry.getItemsByTag("Battery");
        	allBatteries.forEach[ i | 
        		logInfo("Battery", "{} {}", i.getName(), i.getState());
        	]
    type: script.ScriptAction

Br,
Stefan

1 Like

Unfortunately, it doesn’t work for me. Neither as script nor as rule.
It fails with the import and then a lot of subsequent errors.
1. The method or field import is undefined; line 1, column 0, length 6
I am running OH 3.0.0-2.

Interesting, but unfortunately my skills ends here :wink: I’m on the stable 3.0.0 version.

Br,
Stefan

Create a file in /etc/openhab/rules.

Copy the full contents of Stefan’s first example (the one with “rule Battery Test”).

I just ran it, it works.

It does not appear to work, or I can’t figure out how to make it work, through the UI with Rules DSL. There is no way I can see to access the ImageRegistry as the import is not allowed.

You have to decide if you want to use the JS option or the DSL engine. In JS you can do the following:

var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);
var allBlinds = itemRegistry.getItemsByTag("Blinds");

for (var i in allBlinds) {
  var blinds = allBlinds[i];
  logger.info('Closing' + blinds.name);
  events.sendCommand(blinds.name, '85');     
}

I’m new to OH3, and I would like something similar, but a subgroup. Can I turn on all lights on the ground floor? In the solution above, it mentioned to pull out by a tag. I could tag by location, and type, but this seems just as wordy as assigning items to groups, which I could also do. I’d like to define once and use everywhere.
In my case, I have a building with three floors, the main floor is broken into three main areas, and each area may be further subdivided. It would be very nice to get a group of all lights in the dance hall, regardless of if they are over the dance floor, or in the stage, or in the bar.

Your easiest approach will be to define the Groups yourself, outside of the model. Then you can just send commands to the Group and that command will be forwarded to all of its members. No need do anything more than that.

I was afraid of that. So, basically, I need to flatten out my ontology and duplicate that information. I was really hoping we could use relations to build a group on the fly. Going with groups, or tags, I would need an extra piece of information for each level.

In my locations groups below, it would be nice to be able to pull out Gf_H, and all decedents, and whatever shows up as a light in that group, to turn it on.

a subset of my lights:

Group Gf_F_FourierLight    "Fourier Lights"    <light> (Gf_F_Fourier)    ["Lightbulb"]
Dimmer LightFourierInner "Fourier inner cans"                   <light> (Gf_F_FourierLight)    ["Setpoint", "Light"] {channel="zwave:device:e8358de4:node11:switch_dimmer", autoupdate="true"}
Dimmer LightFourierOuter "Fourier outer cans"                   <light> (Gf_F_FourierLight)    ["Setpoint", "Light"] {channel="zwave:device:e8358de4:node10:switch_dimmer", autoupdate="true"}

my locations:

Group Gf                    "Ground Floor"        <groundfloor>           ["GroundFloor"]
Group Gf_F                  "Front Area"          <groundfloor> (Gf)      ["GroundFloor"]
Group Gf_F_Fourier          "Fourier"             <groundfloor> (Gf_F)    ["Entry"]
Group Gf_F_Vestibule        "Vestibule"           <groundfloor> (Gf_F)    ["Room"]
Group Gf_F_MenBR            "Front Mens BR"       <groundfloor> (Gf_F)    ["Bathroom"]
Group Gf_F_WomenBR          "Front Womens BR"     <groundfloor> (Gf_F)    ["Bathroom"]
Group Gf_H                  "Hall"                <groundfloor> (Gf)      ["GroundFloor"]
Group Gf_H_Stage            "Stage"               <groundfloor> (Gf_H)    ["Room"]
Group Gf_H_DanceFloor       "DanceFloor"          <groundfloor> (Gf_H)    ["Room"]
Group Gf_H_NWing            "North Wing"          <groundfloor> (Gf_H)    ["Room"]
Group Gf_H_SWing            "South Wing"          <groundfloor> (Gf_H)    ["Room"]
Group Gf_H_MenBR            "Hall Mens BR"        <groundfloor> (Gf_H)    ["Bathroom"]
Group Gf_H_WomenBR          "Hall Womens BR"      <groundfloor> (Gf_H)    ["Bathroom"]
Group Gf_H_Bar              "Hall Bar"            <groundfloor> (Gf_H)    ["Room"]
Group Gf_B                  "Back Area"           <groundfloor> (Gf)      ["GroundFloor"]
Group Gf_B_Concourse        "Concourse"           <groundfloor> (Gf_B)    ["Room"]
Group Gf_B_A                "Alley"               <groundfloor> (Gf_B)    ["Room"]
Group Gf_B_A_Consoles       "Consoles"            <groundfloor> (Gf_B_A)  ["Room"]
Group Gf_B_A_Lanes          "Lanes"               <groundfloor> (Gf_B_A)  ["Room"]
Group Gf_B_A_Mechanical     "Mechanical"          <groundfloor> (Gf_B_A)  ["Room"]
Group Gf_B_Kitchen          "Kitchen"             <groundfloor> (Gf_B)    ["Kitchen"]
Group Gf_B_Locker           "Locker"              <groundfloor> (Gf_B)    ["Room"]
Group Gf_B_MenBR            "Concourse Mens BR"   <groundfloor> (Gf_B)    ["Bathroom"]
Group Gf_B_WomenBR          "Concourse Womens BR" <groundfloor> (Gf_B)    ["Bathroom"]
Group Gf_B_ElevatorRoom     "ElevatorRoom"        <groundfloor> (Gf_B)    ["Room"]
Group Gf_B_MainStairs       "MainStairs"          <groundfloor> (Gf_B)    ["Corridor"]
Group Gf_B_StairsToAlley    "StairsToAlley"       <groundfloor> (Gf_B)    ["Corridor"]
Group Gf_B_StairsToBasement "StairsToBasement"    <groundfloor> (Gf_B)    ["Corridor"]
Group Gf_B_StairsToChoir    "StairsToChoir"       <groundfloor> (Gf_B)    ["Corridor"]
Group Gf_B_Janitor          "Concourse Janitor"   <groundfloor> (Gf_B)    ["Room"]
Group Sf                    "Second Floor"        <firstfloor>            ["SecondFloor"]
Group Sf_Patio              "Patio"               <firstfloor>  (Sf)      ["Patio"]
Group Sf_Choir              "Choir"               <firstfloor>  (Sf)      ["Room"]
Group Sf_Kitchen            "Choir Kitchen"       <firstfloor>  (Sf)      ["Kitchen"]
Group Sf_Janitor            "Choir Janitor"       <firstfloor>  (Sf)      ["Room"]
Group Sf_Vorstand           "Vorstand"            <firstfloor>  (Sf)      ["Room"]
Group Sf_MenBR              "Choir Mens BR"       <firstfloor>  (Sf)      ["Bathroom"]
Group Sf_womenBR            "Choir Womens BR"     <firstfloor>  (Sf)      ["Bathroom"]
Group Bf                    "Basement"            <cellar>                ["Basement"]
Group Bf_Storage            "Basement Storage"    <cellar>      (Bf)      ["Cellar"]
Group Bf_office             "Basement Office"     <cellar>      (Bf)      ["Office"]
Group Bf_unfinished         "Basement unfinished" <cellar>      (Bf)      ["Cellar"]

You can absolutely do that as demonstrated above. Call getAllMembers on the Group Item for the location and filter them based on the tags. It’ll just be easier to create the Group and then you have a one liner in your Rules and something you can put in your Pages or even in your Model to control them all as a Group.

To do it in a rule though (JavaScript):

var Collectors = Java.type("java.util.stream.Collectors");
var MyLocationGroup = ir.getItem("MyLocationGroup");
MyLocationGroup
        .getAllMembers()
        .stream()
        .filter(function(i) { return i instanceof Switch && i.getTags().contains("Light"); })
        .forEach(function(i){ events.sendCommand(i.name, "OFF"); });

Note I just typed that in, there are likely typos.

Notice though that I have to filer based on the Item type too because a Lux sensor would also have the Light tag. You’ll have to be careful about that sort of thing (another reason this becomes tricky).

You don’t have to do anything to your ontology really. The extra Groups would either be outside of your ontology or they could even become a Point inside your ontology. If you want to be able to control all the lights on a floor or in a room I would think you’d want to be able to do so from your UI in addition to being able to do this from your Rules alone.

2 Likes

Thank you very much. This is exactly what I was looking for.