Design Pattern: Working with Groups in Rules

One more small mistake with whitch iэму spent half an hour (((
Better to correct, so other can use the pattern.

And in Rules also

This construction

val lastChange=gDoorsSensors.members.findFirst[ t | t.name == "DoorEntranceLastChange" ] as DateTimeItem
logInfo("Door", "val opened at:  " + lastChange + "check: " +  DoorEntranceLastChange.state)

seems not ot work on OH 2.5M5 or i made smth wrong?

i get

2019-12-05 18:22:16.089 [INFO ] [.eclipse.smarthome.model.script.Door] - val opened at:  nullcheck: 2019-12-05T18:22:14.957+0100

Why null, item has DateTime and not null at this time

DateTime  DoorEntranceLastChange "Door Entrance Last Change [%1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS]"

Thank you for pointing out the errors. As rossko57 points out, the example never was intended to be copied from the posting and used. It shows how to use the operations discussed but you would be expected to apply those operations to your own Items and Groups.

In some cases, there are better ways to achieve the same thing, sometimes without Rules at all, e.g. there is a Profile to update the LastUpdate Items.

Please don’t double post. I’ve answered on the other thread.

I thought that other members can save time using Pattern out of box)

Of course! Sorry. Sitting with it foк 4 hours, a little bit tired(((

See Design Pattern: What is a Design Pattern and How Do I Use Them.

A DP is a general and reusable solution to a common problem. But the solution is not complete. Instead it is a template that one can apply to a lot of different situations with minor modifications and customizations.

This particular DP is also a little different and probably should never have been a DP in the first place. Frankly, what it is is some reference documentation missing from the Xtend docs for how to manipulate collections. It’s not so much a DP as it is a reference guide. As such, the example is just that, and example. It was never intended to be something someone copies and uses line for line. Some of the other DPs do in fact have examples that could be copied and used line for line though, assuming that the example is useful to you. But even then it is unlikely to be useful “out-of-the-box”. The examples are there to illustrate how to solve a common problem. Rarely will you find any example on the forum, DP or otherwise, that you can just use without modification.

The example above though is mainly for illustrative purposes, to show some of those operations in context. That’s probably why the errors you found haven’t been found by others in the three + years this DP has been posted.

I thought I’d share my code for simulating a time of day type of expire based on groups. Essentially, any member of this group will stay on for an hour if it is turned on between 6am and 11pm; they will only stay on for 30 minutes from 11pm to 6am, though (family is more likely to have fallen asleep…)
I pieced this together mostly from other blocks of code in this thread so credit goes to those guys for blazing the trail. If you have a sizable number of items this beats having lots of rules that all basically do the same thing. It would be trivial to extend this to another group that would have different times as well.

First – groups and items::

Group grp_TODExpire "Expire by Time of Day"

Dimmer Wallplug_Den_BigLamp "Den Lamp" <dimmablelight> (grp_TODExpire)  { channel="zwave..." }
Dimmer WallSwitch_Den_CeilLight "Ceiling Light" <dimmablelight> (grp_TODExpire) { channel="zwave..." }

Now the rule:
off_tod.rule

import java.util.Map

val Map<String, Timer> todTimers = newHashMap

rule "TOD Expire rule"
when
   Member of grp_TODExpire changed
then
   logInfo("RULE","Item "+triggeringItem.name+" turned "+triggeringItem.state+".");
   todTimers.get(triggeringItem.name)?.cancel;
   if (triggeringItem.state==ON || triggeringItem.state>0) {
      if ((new LocalTime().getLocalMillis()) >= (new LocalTime(23, 0, 0, 0).getLocalMillis()) || (new LocalTime().getLocalMillis()) <= (new LocalTime(6, 0, 0, 0).getLocalMillis())) {
          logInfo("RULE","Item "+triggeringItem.name+" turned on.  Will turn it off in 30 minutes");
          todTimers.put(triggeringItem.name, createTimer(now.plusMinutes(30)) [|
             sendCommand(triggeringItem, OFF)
             todTimers.remove(triggeringItem.name)
        ] )
      } else {
          logInfo("RULE","Item "+triggeringItem.name+" turned on.  Will turn it off in 60 minutes");
          todTimers.put(triggeringItem.name, createTimer(now.plusMinutes(60)) [|
             sendCommand(triggeringItem, OFF)
             todTimers.remove(triggeringItem.name)
        ] )
      }
   }
end
2 Likes

I use a rule in this way

when
OG_Wohnzimmer_Motion_Occupancy changed from OFF to ON
then
if (OG_Wohnzimmer_Tuer_Power.state.toString == “OFF” && OG_Wohnzimmer_Sofa_Power.state.toString == “OFF”) {

and would like change the if clause to check the state of the group of the items. How do I do this?

Just insert your group items where you want check it, like this:

items:
items1 (gGroup)
items2 (gGroup)
items3 (gGroup)

rule:

if (gGroup.toString == "OFF") {

}

There are no issue using group items like “normally” items.

that’s pretty simple :joy:
thanks!!!

You’d need to give your Group a type, and an aggregation function, for it to take up a state related to its members
e.g. Group:Switch:OR(ON,OFF)

Remember it is the state you are probably interested in
if (gGroup.state == OFF) {

1 Like

Hi,
I’m trying to get a complete local copy from a group item in a rule so I can perform operations on this group without affecting the global group in my item file.

I tried several ways to get copies of my group item but none of was working.
So I tried filtering the group item and assigning it to a new variable like it could be done with findFirst but that didn’t work either and unfortunately I couldn’t find any working example.

(the following code is not working but they were part of my journey on the way to despairing :grinning:)

val GroupItem sceneItems = Scene_Items.members

val sceneItems = Scene_Items.members.filter[ grp | grp.name.contains(itemNamePrefix+sceneRoomString) ]

So as I spent many hours with kind of trial and error without any success I’d like to ask you if you know any solution for my problem. :slightly_smiling_face:

What kind of operations? There isn’t much you can “do” to a Group. You can send it commands, which don’t affect it, but get passed to members.

This should work fine, as long as all of the members of Scene_Items are groups, although sceneItems is not a GroupItem but Iterable. You can’t create an Item in the rules DSL… you’d need scripted automation for that. The Jython helper libraries make this easier.

val sceneItems = Scene_Items.members.filter[GroupItem group | group.name.contains(itemNamePrefix + sceneRoomString)]

What errors are you getting? It is much better to describe what you would like to do, rather than ask for help on how to implement a particular solution. There may be easier ways to do what you are trying to accomplish. Take a look at #8.

For example, this may provide everything you’re looking to implement…

Thanks for your answer!

I think that is what I unintentionally tried to do.
For now I created a new group item in an items file and this group item gets cleared every time I call the rule so I can then add new members from my ‘Scene_Items’ group. This solves my problem for now but I think in the long run I have to (and will) make use of the Jython helper libraries you mentioned.

1 Like

Hey @rlkoshak and others…

i try to simplify my rules for the OpenSprinkler Telegram rules i wrote to get the label names from the items file… and also to get the buttons in the amount of stations that are not OFF

OLD part:

val OSPI_ReplyId = "OSPI_Reply"
var StationDuration = 600 // 10min
val Station01 = "Rasen oben"
val Station02 = "Rasen Hang"
val StationOFF = "Alle aus"

rule "Telegram Bot receive rasen"
when
    Item TelegramBotLastMessageDate received update
then
    val telegramAction = getActions("telegram","telegram:telegramBot:bot1")

    if (TelegramBotLastMessageText.state.toString.toLowerCase == "/rasen") {
       telegramAction.sendTelegramQuery(Long::parseLong(TelegramBotChatId.state.toString), "*Beregnungs-Optionen:*\nWelcher Bereich soll bewässert werden?", OSPI_ReplyId, Station01, Station02, StationOFF)
    }
end

New Part:

val OSPI_ReplyId = "OSPI_Reply"
var StationDuration = 600 // 10min
val StationOFF = "Alle aus"

rule "Telegram Bot receive rasen"
when
    Item TelegramBotLastMessageDate received update
then
    val telegramAction = getActions("telegram","telegram:telegramBot:bot1")
    val reply_text = "*Beregnungs-Optionen:*\nWelcher Bereich soll bewässert werden?"

    val reply_buttons = gOSPI_Station.members.filter[ i | i.state == OFF ].map[ label ]
    logInfo("OSPI_Reply", reply_buttons.toString)

    if (TelegramBotLastMessageText.state.toString.toLowerCase == "/rasen") {
       telegramAction.sendTelegramQuery(Long::parseLong(TelegramBotChatId.state.toString), reply_text, OSPI_ReplyId, reply_buttons)
    }
end

Now here comes the part where i could need some help…

the reply_buttons i generated in the correct amount of stations… and for each name it is created the button in the telegram chat…
2020-08-19 17_14_02-Telegram

Now i want to add the val StationOFF to that String so that this button is also generated…
any tips on how to achieve that?

One approach could be to create a dummy item and also add it to the group gOSPI_Station, but i want to avoid that.

Thanks
/Holger

I’m not entirely certain what you are asking for here. I don’t use Telegram so don’t have a context. Are you asking to have two strings for each station, one to turn it on and one to turn it off?

If so you probably can’t use the map and instead need to use a for loop to build up the List manually.

val reply_buttons = newArrayList
gOSPI_Station.members.filter[ i | i.state == OFF ].forEach[ s |
    reply_buttons.add(s.label)
    reply_buttons.add(s.label + " OFF")
]
1 Like

Rich, thanks for your help.

No not 2 strings:::

my line
val reply_buttons = gOSPI_Station.members.filter[ i | i.state == OFF ].map[ label ]
is resluting in this
2020-08-19 18:07:31.988 [INFO ] [se.smarthome.model.script.OSPI_Reply] - [Station 1, Station 2]

Station 1, Station 2 are the labels of current gOSPI_Station members (which will be more in the future)

and beside these Station names… I also want to append the String with “All OFF”

how can i add this to the val reply_buttons

...map[ label + " All OFF" ]

nope… i also tried this… this is resulting in
[Station 1All OFF, Station 2All OFF]

But i want

[Station 1, Station 2, All OFF]

Underneath


can’t you just do:

reply_buttons = reply_buttons + ", All OFF"

Nope - see #145