Design Pattern: Working with Groups in Rules

groups
designpattern
Tags: #<Tag:0x00007fe05db8e0d0> #<Tag:0x00007fe05db8dd10>

(Andrew Pawelski) #81

What about a function?


(Kris K) #82

Hi @rlkoshak

I’ve been using your DP for the Doors but my TTS is obviously just reading aloud what i’ve written rather than the state, like your broadcast alerts do.

What would be the best method for TTS to tell if its open or closed verbally?

My current rule snippet:


  // Alert if necessary
  if(alert){
    sendBroadcastNotification(msg.toString)
     Echo_Living_Room_TTS.sendCommand('The Garage Door is Open')
  }

Would this work? I want Alexa to basically verbally if the door is open or closed using TTS


  // Alert if necessary
  if(alert){
    sendBroadcastNotification(msg.toString)
     Echo_Living_Room_TTS.sendCommand(msg.ToString)
  }

Or, this?

  // Alert if necessary & Door is Open
  if(alert && state == OPEN) {
    sendBroadcastNotification(msg.toString)
     Echo_Living_Room_TTS.sendCommand('The Garage Door is Open')
  }
  // Alert if necessary & Door is Closed
  if(alert && state == CLOSED) {
    sendBroadcastNotification(msg.toString)
     Echo_Living_Room_TTS.sendCommand('The Garage Door is Closed')
  }

Thanks!


(Rich Koshak) #83

I don’t actually use TTS. I just send a myopenhab.org message.

I find TTS announcements to be too intrusive. It’s easy to ignore or delay looking at an OH alert when it beeps on my phone or watch but all conversation will stop when speaker starts talking at you.

From what I know I don’t see anything wrong with any of those. I’d work to make it more generic.

    // If alerted == OFF and triggeringItem == OFF then sensor went offline and we have not yet alerted
    // If alerted == ON and triggeringItem == ON then the sensor came back online after we alerted that it was offline
    if(alerted.state == triggeringItem.state) {
        val currState = triggeringItem.state
        // wait one minute before alerting to make sure it isn't flapping
        timers.put(triggeringItem.name, createTimer(now.plusMinutes(1), [ |
            // If the current state of the Item matches the saved state after 5 minutes send the alert
            if(triggeringItem.state == currState) {
                aInfo.sendCommand(name + " is now " + transform("MAP", "admin.map", triggeringItem.state.toString) + "!")
                alerted.postUpdate(if(currState == ON) OFF else ON)
            }
            timers.put(triggeringItem.name, null)
        ]))
    }

(Kris K) #84

Thanks ill look into your suggestion. How have people previously delt with notifications that have different expire timers? One of my doors I want an alert after 1 minute, but the other is an hour.

The notification is generic in that it will say the gate (with 1m expire) has been open for one hour, despite the timer being 1 minute

It would be great if we could somehow have notifications/alerts that are specific to the group members


(Scott Rushworth) #85

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…


Released: Openhab2 Amazon Echo Control Binding (controlling alexa from openhab2)
(Rich Koshak) #86

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.


(Kris K) #87

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


(E. Gerland) #88

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.


(Raf Daems) #89

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() 
   

(Raf Daems) #90

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


(Hallo Ween) #91

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

(Scott Rushworth) #92

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

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

(Hallo Ween) #93

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
}

(Scott Rushworth) #94

I updated my post.


(Hallo Ween) #95

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.


(Lionello Marrelli) #96

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


(Scott Rushworth) #97

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


(Lionello Marrelli) #98

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 ]

(Scott Rushworth) #99

: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]

(Lionello Marrelli) #100

Thank you. VSC does not complain anymore.