I run into the same problem and made a not 100% working solution and needs more testing:
Here what I’ve done, sorry for some german comments in the rules:
Manage all with groups and group memberships.
// all active items to monitor/controll
Group:Number:SUM gContactStatus "gContactStatus [(%d)]"
// all items to monitor/controll, to get on overview about all item status that are open
Group:Number:SUM gContactStatusALL "gContactStatusAll [(%d)]"
// all proxy items to control membership of items in the group gContactStatus
Group:Number:SUM gContactOnOFF "gContactOnOFF [(%d)]"
Then i configure the items, some are neded for debug.
coContactxx is the item to monitor
coContactxx_Helper is just for debug reasons and changing state of contacts
coContactxx_Proxy is used to enable disable the corresponsing coContactxx item to be in the group gContactStatus
Contact coContact01 "Contact01 [%s]" (gContactStatus,gContactStatusALL)
Switch coContact01_Helper "Contact01 helper [%s]"
Contact coContact02 "Contact02 [%s]" (gContactStatus,gContactStatusALL)
Switch coContact02_Helper "Contact02 helper [%s]"
Contact coContact03 "Contact03 [%s]" (gContactStatus,gContactStatusALL)
Switch coContact03_Helper "Contact03 helper [%s]"
Contact coContact04 "Contact04 [%s]" (gContactStatus,gContactStatusALL)
Switch coContact04_Helper "Contact04 helper [%s]"
Switch coContact03_Proxy "Contact03 einschalten [%s]" (gContactOnOFF)
Switch coContact04_Proxy "Contact04 einschalten [%s]" (gContactOnOFF)
Switch coContact05 "Contact04 nochmal OPEN[%s]"
Switch coContact06 "System Startup simulieren [%s]"
Switch coContact10 "Loop through items and groups [%s]"
To put items into the group gContactStatus or remove them:
rule "gContactOnOFF changed"
when
Item gContactOnOFF changed
then
Thread::sleep(100)
//logInfo("gContact","gContactOnOFF changed")
//logInfo("gContact","######################")
//ermitteln welcher SwitchProxy gedrückt wurde
val gContactOnOFFChanged = gContactOnOFF.members.filter[l | l.lastUpdate !== null].sortBy[lastUpdate].last
//logInfo("gContact","gContactOnOFFChanged.name: "+ gContactOnOFFChanged.name)
// Ermitteln, wie der dazugehörige Switch heißt
val gContactToChange=gContactStatusALL.members.filter[i | i.name==gContactOnOFFChanged.name.toString.substring(0,gContactOnOFFChanged.name.toString.indexOf('_'))].head as GenericItem
//logInfo("gContact","gContactToChange.name: "+ gContactToChange.name)
// Switch muss hinzugefügt werden
if(gContactOnOFFChanged.state == ON)
{
gContactStatus.addMember(gContactToChange)
//logInfo("gContact","gContactStatus.addMember: "+gContactToChange.name)
gContactToChange.addGroupName(gContactStatus.getName())
//logInfo("gContact","gContactToChange.addGroupName: "+ gContactToChange.name)
// Ein Item ermitteln, welches noch in der Gruppe ist
// wird benötigt um die Anzahl richig zu berechnen
val item = gContactStatus.members.take(1).head
// Sollte der Contact schon OPEN sein, dann wird danach ein change event ausgelöst, dieses abfangen
if(item.state==OPEN)
ContactAddRemoveIndicator = true
item.postUpdate(item.state.toString)
// nachdem eventuell das event changed verarbeitet wurde den Merker wieder aufheben, sonst wird das erste richtige event nicht verarbeitet
Thread::sleep(250)
ContactAddRemoveIndicator = false
}
// Switch muss entfernt werden
else
{
gContactStatus.removeMember(gContactToChange)
//logInfo("gContact","gContactStatus.removeMember: " + gContactToChange.name)
gContactToChange.removeGroupName(gContactStatus.getName())
//logInfo("gContact","gContactToChange.removeGroupName: "+ gContactToChange.name)
// Sollte der Switch schon ON sein, dass wird danach ein change event ausgelöst, dieses abfangen
if(gContactToChange.state==OPEN)
{
ContactAddRemoveIndicator = true
//logInfo("gContact","gContactOnOFF ContactAddRemoveIndicator = true:")
}
// wird benötigt um die Anzahl richig zu berechnen
val item = gContactStatus.members.take(1).head
item.postUpdate(item.state.toString)
// nachdem eventuell das event changed verarbeitet wurde den Merker wieder aufheben, sonst wird das erste richtige event nicht verarbeitet
Thread::sleep(250)
ContactAddRemoveIndicator = false
}
end
On every restart of OH or on every reload of item file the group membership must be restored
rule "system startup rules"
when
System started
then
Thread::sleep(1000)
logInfo("Logger","##################################### system startup rules started")
swSwitch06.postUpdate(ON)
coContact06.postUpdate(ON)
end
and this is used to do it:
var boolean ContactAddRemoveIndicator = false
// Beim Start von OH werden die Switchproxy-Schalter ausgewertet und für alle Switchews, welche nicht aktiv in der Gruppe sein sollen, diese entfernt
rule "gContact Gruppenzugehörigkeit initial setzen"
when
Item coContact06 changed to ON
then
// Gruppenmitgliedschaften wieder richig herstellen, gehen nach einem reload verloren
gContactOnOFF.members.forEach[item |
//logInfo("Logger","gContactOnOFFChanged: "+item.name + ": "+ item.state.toString)
var gContactToChange=gContactStatusALL.members.filter[i | i.name==item.name.toString.substring(0,item.name.toString.indexOf('_'))].head as GenericItem
if(item.state == ON)
{
gContactStatus.addMember(gContactToChange)
//logInfo("gContact","OnSystemStart gContactStatus.addMember: "+gContactToChange.name)
gContactToChange.addGroupName(gContactStatus.getName())
//logInfo("gContact","OnSystemStart gContactToChange.addGroupName: "+ gContactToChange.name)
}
else
{
gContactStatus.removeMember(gContactToChange)
//logInfo("gContact","OnSystemStart gContactStatus.addMember: "+gContactToChange.name)
gContactToChange.removeGroupName(gContactStatus.getName())
//logInfo("gContact","OnSystemStart gContactToChange.addGroupName: "+ gContactToChange.name)
}
]
// Sollte der Contact schon OPEN sein, dann wird danach ein changed event ausgelöst, dieses abfangen
var item = gContactStatus.members.take(1).head
if(item.state==OPEN)
{
ContactAddRemoveIndicator = true
//logInfo("gContact","OnSystemStart ContactAddRemoveIndicator = true:")
}
item.postUpdate(item.state.toString)
// nachdem eventuell das event changed verarbeitet wurde den Merker wieder aufheben, sonst wird das erste richtige event nicht verarbeitet
Thread::sleep(250)
ContactAddRemoveIndicator = false
coContact06.postUpdate(OFF)
end
After all this work in done, a very simple rule can be used to get the trigger:
// ermitteln, ob ein Item der Gruppe geändert wurde
rule "gContactStatus changed"
when
Item gContactStatus changed
then
Thread::sleep(100)
//logInfo("gContact","ContactAddRemoveIndicator: "+ ContactAddRemoveIndicator)
if(ContactAddRemoveIndicator == false)
{
val item = gContactStatus.members.filter[l | l.lastUpdate !== null].sortBy[lastUpdate].last
logInfo("gContact","########## changed: "+item.name+": "+item.state.toString)
}
else
ContactAddRemoveIndicator = false
end
Some debug code, if needed:
rule "coContact10 Loop through groupitems"
when
Item coContact10 changed to ON
then
// may be required for persistence to catch up
Thread::sleep(100)
logInfo("Logger","LoopThroughGroupitems")
logInfo("Logger","##########################################")
//logInfo("voice", "gContactStatus.getMembers" + gContactStatus.getMembers)
//logInfo("Logger","##########################################")
logInfo("voice", "coContact01.getGroupNames" + coContact01.getGroupNames)
logInfo("voice", "coContact02.getGroupNames" + coContact02.getGroupNames)
logInfo("voice", "coContact03.getGroupNames" + coContact03.getGroupNames)
logInfo("voice", "coContact04.getGroupNames" + coContact04.getGroupNames)
logInfo("Logger","##########################################")
logInfo("Logger","gContactStatus-List")
gContactStatus.members.forEach[item |
logInfo("Logger","gContactStatus Itemname: "+item.name + ": "+ item.state.toString)
]
coContact10.postUpdate(OFF)
end
rule "coContact01_Helper"
when
Item coContact01_Helper changed to ON
then
if(coContact01.state == OPEN)
coContact01.postUpdate(CLOSED)
else
coContact01.postUpdate(OPEN)
coContact01_Helper.postUpdate(OFF)
end
rule "coContact02_Helper"
when
Item coContact02_Helper changed to ON
then
if(coContact02.state == OPEN)
coContact02.postUpdate(CLOSED)
else
coContact02.postUpdate(OPEN)
coContact02_Helper.postUpdate(OFF)
end
rule "coContact03_Helper"
when
Item coContact03_Helper changed to ON
then
if(coContact03.state == OPEN)
coContact03.postUpdate(CLOSED)
else
coContact03.postUpdate(OPEN)
coContact03_Helper.postUpdate(OFF)
end
rule "coContact04_Helper"
when
Item coContact04_Helper changed to ON
then
if(coContact04.state == OPEN)
coContact04.postUpdate(CLOSED)
else
coContact04.postUpdate(OPEN)
coContact04_Helper.postUpdate(OFF)
end
And the sitemap:
Frame label="Group notification Contact" {
Switch item=coContact10
Switch item=coContact06
Text item=gContactStatus
Text item=gContactStatusALL
Text item=coContact01
Text item=coContact02
Text item=coContact03
Text item=coContact04
Switch item=coContact01_Helper
Switch item=coContact02_Helper
Switch item=coContact03_Helper
Switch item=coContact04_Helper
Switch item=coContact03_Proxy
Switch item=coContact04_Proxy
Switch item=coContact05
}
existing problems only can be solved if the member of group ==> triggeringItem PR is implemented:
If two or more items change to fast to each other, this connot be recognices by the rule because of to slow persistance.
This line give not on every change the right item
val item = gContactStatus.members.filter[l | l.lastUpdate !== null].sortBy[lastUpdate].last
I hope no one kills me about my code. But I want to have very flexible code and not so much similar code for a lot of same items/things.
Comments are welcome.