Design Pattern: Working with Groups in Rules

Thanks, i will test it.
Why is the timer better than the sleep method?
Greetings,
Markus

The Timer won’t block a thread. The timer is scheduled, the rule ends.
The next call of the rule will find an already scheduled timer and will end without doing anything.
A Second later, the Scheduler will execute the code in a new thread, and it will be executed only once.

If using a Thread::sleep(1000), the rule will be started 5 times and every rule start will block a thread of the thread pool (default is 5 threads, so a sixth rule won’t start at all). Moreover, the Thread::sleep(1000) would not suffice, you would have to lock the rule or take care, that the code is executed only once.
When it’s guaranteed that all 5 items will be updated in a very short time slot, the timer is for sure very elegant to solve this.

2 Likes

Hi Rich.
It works perfect and with the help of Harry and Udo, i understand the problem and the function now.
I have seen the same problem in a post of you:
https://community.openhab.org/t/solved-error-rule-rulename-null/37359
It’s the same problem here by updating items every 0,002 seconds.
After using the timer the error rule:null is gone.
Thank you very much.
Greetings,
Markus

Hi @rlkoshak I have some LIFX bulbs that when turned on at the wall switch, they return their status to openhab and the corresponding switch gets set to ON. When somone turns off the wall switch, the status isnt reported back meaning the UI switch state is still ON

I am managing this with the following:

rule "lifx online"
when
  Thing "lifx:colorlight:D073DXXX0EF4" changed 
then
 var thingStatusInfo = getThingStatusInfo("lifx:colorlight:D073DXXX0EF4")
  if ((thingStatusInfo !== null) && (thingStatusInfo.getStatus().toString() == "OFFLINE")) {
    Switch_Lifx_Toilet.sendCommand(OFF)
  } 
end 

I have a dozen LIFX bulbs - can you see any way to do the above with a group concept?

1 Like

Unfortunately Things do not support Groups or triggeringItem. I know of no way to make this generic in the Rules DSL.

I think it might be possible to make this more generic in JSR223 or the Next Gen Rules Engine, but I’m still coming up to speed on those and can’t say for sure how yet.

With JSR223, you could iterate through the ItemChannelLinkRegistry, but I decided it was more efficient (and easier) to have some sort of mapping from ThingUID to Item name. Here are two examples using JSR223-Jython and openhab2-Jython. The first uses a dictionary for the mapping, the second uses another Item.

@rule("Alert: Kodi system status update")
@when("Thing kodi:kodi:familyroom changed")
@when("Thing kodi:kodi:bedroomupstairs changed")
@when("Thing kodi:kodi:bedroomdownstairs changed")
def kodiSystemStatusUpdate(event):
    kodiMap = {"kodi:kodi:familyroom"        : "DS_FamilyRoom_Kodi_System",
               "kodi:kodi:bedroomupstairs"   : "US_MasterBedroom_Kodi_System",
               "kodi:kodi:bedroomdownstairs" : "DS_MasterBedroom_Kodi_System"}
    kodiName = kodiMap[str(event.thingUID)]
    log.debug("JSR223: Kodi system update: [{}]: [{}]".format(kodiName, event.statusInfo))
    if str(event.statusInfo) == "ONLINE":
        events.sendCommand(kodiName,"ON")
        log.info("JSR223: Kodi system update: [{}]: ON".format(kodiName))
    elif str(event.statusInfo) == "OFFLINE (COMMUNICATION_ERROR): No connection established":
        events.sendCommand(kodiName,"OFF")
        log.info("JSR223: Kodi system update: [{}]: OFF".format(kodiName))

@rule("Alert: Speaker system status update")
@when("Thing upnpcontrol:upnprenderer:5f9ec1b3-ed59-1900-4530-0007f521099e changed")
@when("Thing upnpcontrol:upnprenderer:5f9ec1b3-ed59-1900-4530-0007f521366d changed")
@when("Thing upnpcontrol:upnprenderer:5f9ec1b3-ed59-1900-4530-0007f522dcaf changed")
@when("Thing upnpcontrol:upnprenderer:5f9ec1b3-ed59-1900-4530-0007f5207868 changed")
@when("Thing upnpcontrol:upnprenderer:5f9ec1b3-ed59-1900-4530-0007f520f682 changed")
@when("Thing upnpcontrol:upnprenderer:5f9ec1b3-ed59-1900-4530-0007f5210909 changed")
@when("Thing upnpcontrol:upnprenderer:5f9ec1b3-ed59-1900-4530-0007f52220b3 changed")
@when("Thing upnpcontrol:upnprenderer:5f9ec1b3-ed59-1900-4530-0007f52109de changed")
def speakerSystemStatusUpdate(event):
    speakerName = filter(lambda item: str(item.state).replace("uuid:", "") == str(event.thingUID).split(":")[2], ir.getItem("gSpeakerUUID").members)[0].name.replace("UUID", "System")
    log.debug("JSR223: Speaker system update [{}]: [{}]".format(speakerName, event.statusInfo))
    if str(event.statusInfo) == "ONLINE":
        events.sendCommand(speakerName,"ON")
        log.info("JSR223: Speaker system update [{}]: ON".format(speakerName))
    elif str(event.statusInfo) == "OFFLINE (COMMUNICATION_ERROR): No connection established":
        events.sendCommand(speakerName,"OFF")
        log.info("JSR223: Speaker system update [{}]: OFF".format(speakerName))

What about a function?

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!

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)
        ]))
    }

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

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