Mirroring switches

Hi,

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?

Thanks for any input!
Cheers
Toby

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
1 Like

Hi Vincent!

Thanks for your inoput.

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.

Regards
Toby

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.

I don’t think you need it because you are checking the state is different before triggering the switch
Try it

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

I like that version quite much. :slight_smile:

Regards
Toby

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.

Hi Tony,

Depending on your item configuration you maybe should have a look into the follow profile.

Cheers

Hi Christoph,

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?

Thanks in advance for the clarification.

Regards
Toby