I’m having several KNX switches in my home that intentionally send to an empty group so that I can program the scene settings behind these switches in OpenHAB without having to deal with KNX config. I now have some switches which should control the same scene but have different KNX addresses. The current mirror logic I implement to avoid infinite loops between the switches looks like this:
var java.util.concurrent.locks.ReentrantLock guestroomLock = new java.util.concurrent.locks.ReentrantLock()
rule "Mirror Light_GF_Sleeping_Wall -> _Ceiling ON"
when
Item Light_GF_Sleeping_Wall changed from OFF to ON
then
guestroomLock.lock()
try {
if (Light_GF_Sleeping_Ceiling.state == OFF) {
sendCommand(Light_GF_Sleeping_Ceiling, ON)
}
} finally {
guestroomLock.unlock()
}
end
rule "Mirror Light_GF_Sleeping_Wall -> _Ceiling OFF"
when
Item Light_GF_Sleeping_Wall changed from ON to OFF
then
guestroomLock.lock()
try {
if (Light_GF_Sleeping_Ceiling.state == ON) {
sendCommand(Light_GF_Sleeping_Ceiling, OFF)
}
} finally {
guestroomLock.unlock()
}
end
/* ... */
While this works pretty well, it is still cumbersome work to implement the rules and quite a bit of code duplication. Does anyone have a better solution at hand?
First of all you should need the lock
Second you can condense these two rules into one
Finally, use the .sendCommand method instead of the sendCommand( action
rule "Mirror Light_GF_Sleeping_Wall -> _Ceiling"
when
Item Light_GF_Sleeping_Wall changed
then
if (Light_GF_Sleeping_Ceiling.state == UNDEF || Light_GF_Sleeping_Ceiling.state == NULL) return; //Do nothing
if (Light_GF_Sleeping_Wall.state == ON) {
if (Light_GF_Sleeping_Ceiling.state == OFF) {
Light_GF_Sleeping_Ceiling.sendCommand(ON)
}
} else {
if (Light_GF_Sleeping_Ceiling.state == ON) {
Light_GF_Sleeping_Ceiling.sendCommand(OFF)
}
}
end
Now if your have the same naming structure you can merge ALL your rules into one:
import org.eclipse.smarthome.model.script.ScriptServiceUtil
rule "Any_Item_Sleeping_Wall -> _Ceiling"
when
Item Light_GF_Sleeping_Wall changed or
Item Something_Else_Sleeping_Wall changed
then
if (triggeringItem.state == UNDEF || triggeringItem.state == NULL) return; //Do nothing
val ceilingItemName= triggeringItem.name.split("_").get(0) + "_" + triggeringItem.name.split("_").get(1) + "_" + triggeringItem.name.split("_").get(2) + "_" + "Ceiling"
val ceilingItem = ScriptServiceUtil.getItemRegistry.getItem(ceilingItemName)
if (triggeringItem.state == ON) {
if (ceilingItem.state == OFF) ceilingItem.sendCommand(ON)
} else {
if (ceilingItem.state == ON) ceilingItem.sendCommand(OFF) }
}
end
The lock is needed because the reverse mirror-rule also exists (Mirror Light_GF_Sleeping_Ceiling -> _Wall). Both are switches, so without the lock they would trigger infinite loop change. Or am I wrong?
I’m currently experimenting with a more condensed version that includes all forward/backward ON/OFF combinations in 1 rules (especially since I now have 3 switches in a group). This is what I came up with so far:
rule "Mirror Switches"
when
Member of gSleepingScene received command
then
sleepingSceneLock.lock()
try {
gSleepingScene.members
.filter(item|item != triggeringItem && item.state != triggeringItem.state)
.forEach[item |
item.sendCommand(receivedCommand)
]
} finally {
sleepingSceneLock.unlock()
}
end
All 3 switches are in the group gSleepingScene, so this should work in theory.
Yup, works fine and is way less code. The rule is (of course) triggered 3 times (for each change) in a sequence (due to the lock) but only the first iteration performs a change, because after that all 3 switches are already in the same state.
Yes, you are right. Seems I had an earlier version without this check which caused the race condition.
So, here is the (currently) slimmest version of the rule (for a different scene):
rule "Mirror Switches"
when
Member of gOfficeScene received command
then
gOfficeScene.members
.filter(item|item != triggeringItem && item.state != triggeringItem.state)
.forEach[item |
item.sendCommand(receivedCommand)
]
end
One note for interested people finding this solution: The filter consists of 2 parts which is rather important. The state of the triggeringItem will be changed after the rule was executed due to the received command trigger. Therefore both checks are required in the filter.
yeah, I already saw the mentioning of “profiles” in a changelog, but could not grasp the concept entirely, yet. Same applies to “multi channel linking”.
Let me try to summarize what I understood on basis of one of my examples: I have 2 KNX buttons with empty groups + 1 hue which should be controlled by these buttons. A config like:
Item Scene { channel="…knx1…", channel="…knx2…" channel="…hue…" [profile="follow"] }
would make all 3 channels be in sync, no matter which one is acted on?