Design Pattern: Working with Groups in Rules

You’d need to use JSR223, but you can add this info (timer timeouts, notification text, etc.) into Item metadata and use it in your rule. I’ll get an example post out one of these days…

I use separate Items with similar names (see Design Pattern: Associated Items) and the Expire binding. Each door would have it’s own Timer Item with a custom Expire for that door. The message gets a little tricky but I would probably skip the “for X minutes” in the alert message and just say “too long” or something generic as not worth the effort to implement.

In Rules DSL you can dynamically add tags. It would be awkward but you can associated the message with the Item as a tag since metadata isn’t available in Rules DSL apparently.

1 Like

I think you’re right, ill just remove the ‘open for X minnutes’ - its kind of irrelevant really.

Hi Rich,

Your DPs are awesome and I use them quite a lot.
I also use your “Working with Groups” for Windows, Vehicle doors etc.

For my main Things to monitor (12) I would do it the same way to reduce code.
My single Thing switches look like this:

Switch ZwaveThing "Zwave" (G_Things)

whereas the Group is:

Group:Switch:AND(ON,OFF) G_Things “Things”

My rules for each single Thing look like this:

rule "OFFLINE check ZWave"
when 
Member of G_Things changed 
then
	var status = ThingAction.getThingStatusInfo("zwave:serial_zstick:49ddd2f3").getStatus()
	if(status.toString() == 'ONLINE') {
		if(ZwaveThing.state != ON) {
			ZwaveThing.postUpdate(ON)
		}
	}		
	else { 
		if(ZwaveThing.state != OFF) {
			ZwaveThing.postUpdate(OFF)
		}
	}
end

So I end up with 12 rules of this type and I am sure that there is a smarter way.

My problem is how to handle the different things (like zwave:serial_zstick:49ddd2f3) within this group.
With Switches it’s easy, but I possibly need to handle these things as a group of Strings?!

Any hint into the right direction would help.

Scratching my head a bit further to write efficient code…

big picture:
To control the temperature in my house, I’m building some code.

detail question
I’m building some code to determine in which time-block I am of the day.

    vTCD_HOUR_01_Number.postUpdate(6) // 6 = 06
    vTCD_MINUTE_01_Number.postUpdate(0) // 0 = 00
    vTCD_TEMP_01_Number.postUpdate(20)
//means: at 06 hours, 00 minutes, set temp to 20   
    vTCD_HOUR_02_Number.postUpdate(07)
    vTCD_MINUTE_02_Number.postUpdate(00)
    vTCD_TEMP_02_Number.postUpdate(21)
   ...
    vTCD_HOUR_06_Number.postUpdate(22)
    vTCD_MINUTE_06_Number.postUpdate(0)
    vTCD_TEMP_06_Number.postUpdate(15)
   

This means:
I have in this example 6 markers in my day.
The first one (01) starts at 06:00 and sets the temp to 20.
The second (02) starts at 07:00 and sets the temp to 21.

The last one (06) starts at 22:00 and sets the temp to 15.

My current code consists of (pseudo code):

switch (true)
   case (now >= hour_01:minute_01 && now < hour_02:minute_02): 
     logInfo("temp", "we are in block 1")
....

but in reality, this code is already 200 lines long and what frustrates the most, is that I’m repeating a lot of code and just changing the indexes from 1 to 2 and so on.

A problem I see is that I can not use something like (pseudo code):

val block_index=1
switch (true)
   case (now >= hour_$block_index:minute_$block_index && now < hour_($block_index+1):minute_($block_index+1)): 
     logInfo("temp", "we are in block 1")
....

Then I considered using group_members as described in this design patterns.
But again, I see no way to use them in the case part of the switch.

I could rewrite the code completely to something like (pseudo-code)

for i in block_indexes
   if (now >= hour_$block_index:minute_$block_index && now < hour_($block_index+1):minute_($block_index+1)): 
   then     
      logInfo("temp", "we are in block 1")
   else
      i++

Maybe I could find my way out using the above rewrite, but - and maybe this is my question - I feel like group_members usage might even be more reusable code after all, but I don’t see it how to do it.

I could use “findfirst”, but how to check on both conditions.
I could use “filter”, but again… I don’ see it…

So my question might boil down to:
anyone a hint how to (pseudo-code)

mygroup.members.find_first_hour_and_minute_combo_index( combo_index | where
   hour(combo_index)*60 + minute(combo_index) >= now.getMinuteOfDay() 
   && 
   hour(combo_index+1)*60 + minute(combo_index+1) < now.getMinuteOfDay() 
   

replying to myself:
found the answer (more or less) here:

Hi, i have a small problem with the design-pattern rules…

I know the way with triggeringitem and adding something to the item-name to create a new item with maybe last time of switching the item.

But now i want to place a proxy item between my xiaomi-temp-sensors and the temp-items, which i use for my graphs and inside rules… I have some sensors, which give me sometimes wired values. Sometimes they send me a temp of -100°C - so my graphs will look very bad, when there is such a big jump inside the temperature. So i will place a proxy item between this and only send real values to my temp-item.

My current rule fo only one sensor looks like this:

rule "Update Temp 1 Proxy Item"
when
	Item Xiaomi_Temp_1_Original received update
then
	if (Xiaomi_Temp_1_Original.state > -50) {
			postUpdate(Xiaomi_Temp_1, Xiaomi_Temp_1_Original.state)
	}
end

I use the items Xiaomi_Temp_x inside my rules and the item Xiaomi_Temp_x_Original is directly from the temp-sensor. So how can i remove the “_Original” from my proxy-item to send the value to the real item?
I think, someting insida a variable with split-command?

Here is my idea of the new rule, i hope someone could complete this:

rule "Licht State Changed"
when
    Member of gTemp_Original received update
then
    if(triggeringItem.state > -100) {
        val ???
        postUpdate(triggeringItem_without_Original, triggeringItem.state)
    }
end

Your post is very confusing, but I think this is what you are after…

postUpdate(triggeringItem.name.replace("_Original",""), triggeringItem.state.toString)
1 Like

Thanks, but i get this error for your line:

{
	"resource": "/w:/rules/temperatur_anzeigen.rules",
	"owner": "_generated_diagnostic_collection_name_#0",
	"code": "org.eclipse.xtext.xbase.validation.IssueCodes.incompatible_types",
	"severity": 8,
	"message": "Type mismatch: cannot convert from State to String",
	"startLineNumber": 134,
	"startColumn": 60,
	"endLineNumber": 134,
	"endColumn": 80
}

I updated my post.

1 Like

Thanks, i saw the same at one of my old rules… Now the error is gone, thanks for your help. Will test it now, i only get new values every 50 minutes.

1 Like

In my Rule I have to perform the same piece of code on all Items of a Group. In my previous version of the rule (OpenHAB 1.8.3) I was using a long lambda expression. In OpenHAB 2.4 I followed the DP Working with Groups and encoding names in Items.
(By the way let me thank the OP for its fantastic work!)
VSC complains that my code is incorrect (because I am using a var and not a val into the lambda), but it works. Let me show a simplified version of my rule.

Items

Number Fz_Lavatrice      "Lavatrice "        <washingmachine> (gPriority)
Number Fz_Lavastoviglie  "Lavastoviglie "    <dishwasher>     (gPriority)

Number Pw_Fz_Lavatrice      "Lavatrice [%.1f W] "       <washingmachine> (gPowerNow, gTotalPowerNow) 
Number Pw_Fz_Lavastoviglie  "Lavastoviglie  [%.1f W] "  <dishwasher>     (gPowerNow, gTotalPowerNow) 

Rule

when
    Time cron "0/20 * * * * ?" // Trigger control every 20 seconds
then
       for (var Priority = 1 ; Priority < 8 ; Priority++) { // cycle on Items in Priority order

// extract from the Groups the Generic Items to be processed in the following
                val appliance     = gPriority.members.findFirst[ i | i.state == Priority ]
                val Pw_appliance  = gPowerNow.members.findFirst[i | i.name == "Pw_"+appliance.name]

// the rule continues here working on the generic Items

}

end

Priority is a local variable, and VSC complains “Cannot refere to the non-final variable Priority inside a lambda expression”
appliance is a val , but it is modified correctly through the for cycle.

The code performs as intended but I’m wondering if I’m exploiting a “bug” that may be corrected in future versions of OpenHAB. Is it true?
Thank you,
Lionello

It’s not clear where you are using the lambda, but have you tried passing the variable as an argument of the lambda?

The Lambda expression is the part enclosed in square brackets

[ i | i.state == Priority ]

which appears in

val appliance     = gPriority.members.findFirst[ i | i.state == Priority ]

:blush: I was looking for something outside the rule. For this, try setting a temp val to hold the priority in the loop.

       for (var Priority = 1 ; Priority < 8 ; Priority++) { // cycle on Items in Priority order

// extract from the Groups the Generic Items to be processed in the following
                val tempPriority = Priority
                val appliance     = gPriority.members.findFirst[ i | i.state == tempPriority ]
                val Pw_appliance  = gPowerNow.members.findFirst[i | i.name == "Pw_"+appliance.name]

Thank you. VSC does not complain anymore.

1 Like

I just uploaded some code to github that uses some of the techniques in this pattern.

I have temperature sensors and smart vents in a bunch of different rooms in my house, and I use this code to manage the smart vent states. I have it set up so that I can use the thermostat set point for heat/cool target or override the target temperature by room. It is also possible to turn individual room control on and off manually or via other external rules.

Just thought I’d share in case it’s useful to anybody else.

2 Likes

Is there any solution to this problem with DateTime groups, @rlkoshak … 2 years later? :wink: According to the openHAB docs MIN/MAX should work on decimal types only.

Could you elaborate, @davorf?

The updates to the docs are the solution. This used to work in OH 1.8 but did what ever reason, they decided not to support DateTime any more. There were lots of bugs and changes necessary to the aggregation functions at that time which probably broke DateTime, and the fact it worked before that may have been a fluke.

I need to update the tutorial.

1 Like

Currently I am doing this:

group.members.forEach[ i |
            i.sendCommand(OFF)
        ]

        sceneGroup.members.forEach[ i |
            i.sendCommand(ON)
        ]

However its a bit unnecessary to switch lights off then on again, so I would like to do something like this:

group.members.forEach[ i |
        if i.memberOf( sceneGroup) {
            i.sendCommand(ON)
        else{
          i.sendCommand(OFF)
        }
        ]

but in the examples I can not figure how to figure out if an item is member of a group