3-way switch rule for a switch and a dimmer

  • Platform information: Windows
    • Hardware: Intel/16G/2TB
    • OS: Windows 10
    • Java Runtime Environment: JRE 8
    • openHAB version: 2.5
  • Issue of the topic:

Greetings!

I’m trying to setup a rule to get a dimmer and switch arranged in a 3-way setup and am running into a problem with what I think is a feedback loop. I’m stuck so I was hoping someone with more experience at this can help get me straightened out.

Here is where I am:

Light is connected to a z-wave switch. A z-wave dimmer in another room controls/triggers the switch with this first rule and it works correctly (both physical dimmer and switch mirrors the other). Please note that dimmer.state is numerical % or binary on/off.

// *****************************Laundry Room 2 Garage Rules ************** */

rule "Garage_Switch_2_Laundry"
when
    Item zwave_device_XXXXXX_node51_switch_dimmer changed // Garage 1 Dimmer
then
	if(zwave_device_XXXXXX_node61_switch_binary.state != zwave_device_XXXXXX_node51_switch_dimmer.state)
		(if (zwave_device_XXXXXX_node51_switch_dimmer.state >=3)
		sendCommand(zwave_device_XXXXXX_node61_switch_binary, ON) // Laundry Garage
		
		else
		sendCommand(zwave_device_XXXXXX_node61_switch_binary, OFF) // Laundry Garage
)		
end

However, I run into a problem when I try to mirror the switch state to the dimmer using a similar rule. I get a feedback loop of the light going on and off repeatedly.

From the log what I think is happening is the dimmer sends an update to openhab and it lags causing the problem.

2021-12-02 15:45:52.816 [ome.event.ItemCommandEvent] - Item 'zwave_device_XXXXXX_node51_switch_dimmer' received command ON
2021-12-02 15:45:52.817 [nt.ItemStatePredictedEvent] - zwave_device_XXXXXX_node51_switch_dimmer predicted to become ON
2021-12-02 15:45:52.821 [vent.ItemStateChangedEvent] - zwave_device_XXXXXX_node51_switch_dimmer changed from 0 to 100
2021-12-02 15:45:53.083 [ome.event.ItemCommandEvent] - Item 'zwave_device_XXXXXX_node61_switch_binary' received command ON
2021-12-02 15:45:53.083 [nt.ItemStatePredictedEvent] - zwave_device_XXXXXX_node61_switch_binary predicted to become ON
2021-12-02 15:45:53.087 [vent.ItemStateChangedEvent] - zwave_device_XXXXXX_node61_switch_binary changed from OFF to ON

2021-12-02 15:45:55.709 [ome.event.ItemCommandEvent] - Item 'zwave_device_XXXXXX_node51_switch_dimmer' received command OFF
2021-12-02 15:45:55.710 [nt.ItemStatePredictedEvent] - zwave_device_XXXXXX_node51_switch_dimmer predicted to become OFF
2021-12-02 15:45:55.713 [vent.ItemStateChangedEvent] - zwave_device_XXXXXX_node51_switch_dimmer changed from 100 to 0
2021-12-02 15:45:55.718 [ome.event.ItemCommandEvent] - Item 'zwave_device_XXXXXX_node61_switch_binary' received command OFF
2021-12-02 15:45:55.719 [nt.ItemStatePredictedEvent] - zwave_device_XXXXXX_node61_switch_binary predicted to become OFF
2021-12-02 15:45:55.722 [vent.ItemStateChangedEvent] - zwave_device_XXXXXX_node61_switch_binary changed from ON to OFF

													Item 'zwave_device_XXXXXX_node61_switch_binary' received command ON
2021-12-02 15:47:09.980 [nt.ItemStatePredictedEvent] - zwave_device_XXXXXX_node61_switch_binary predicted to become ON
2021-12-02 15:47:09.983 [vent.ItemStateChangedEvent] - zwave_device_XXXXXX_node61_switch_binary changed from OFF to ON
2021-12-02 15:47:09.993 [vent.ItemStateChangedEvent] - zwave_device_XXXXXX_node51_switch_dimmer changed from 0 to 100

2021-12-02 15:47:11.596 [ome.event.ItemCommandEvent] - Item 'zwave_device_XXXXXX_node61_switch_binary' received command OFF
2021-12-02 15:47:11.597 [nt.ItemStatePredictedEvent] - zwave_device_XXXXXX_node61_switch_binary predicted to become OFF
2021-12-02 15:47:11.601 [vent.ItemStateChangedEvent] - zwave_device_XXXXXX_node61_switch_binary changed from ON to OFF
2021-12-02 15:47:11.608 [vent.ItemStateChangedEvent] - zwave_device_XXXXXX_node51_switch_dimmer changed from 100 to 0

I tried using using postUpdate updates the state, but the physical dimmer itself does not update/mirror the switch and requires turning it on, then off again before it will trigger the switch in the first rule to turn the light on.

The “solution” I’m using is to sleep (wait) for openhab to update the state before checking its state like this:

rule "Laundry_2_Garage_Switch_ON"
when
	Item zwave_device_XXXXXX_node61_switch_binary changed // Laundry Garage
then
	Thread::sleep(500) // sleep half a second
	if(zwave_device_XXXXXX_node61_switch_binary.state != zwave_device_XXXXXX_node51_switch_dimmer.state)
		(if (zwave_device_XXXXXX_node61_switch_binary.state === ON)
		zwave_device_XXXXXX_node51_switch_dimmer.sendCommand(ON) // Garage 1 Dimmer
		
		else
		zwave_device_XXXXXX_node51_switch_dimmer.sendCommand(OFF) // Garage 1 Dimmer
		)
end

It kind of works most of the time, but I’m hoping there is perhaps a more elegant and reliable solution to this.

Thank you!
Ben

This is a hard problem that gets even harder when you have to synchronize more than two or the Items are a different type.

Because this code needs to be be absolutely clear, let’s address a couple of coding issues. You can address these or not as you see fit.

  1. What’s the deal with the extra pair of parens? Don’t add stuff that doesn’t need to be there unless it adds clarity. The parens in this context don’t do anything.
	if(zwave_device_XXXXXX_node61_switch_binary.state != zwave_device_XXXXXX_node51_switch_dimmer.state)
		if (zwave_device_XXXXXX_node51_switch_dimmer.state >=3)
		    sendCommand(zwave_device_XXXXXX_node61_switch_binary, ON) // Laundry Garage
		
		else
  		    sendCommand(zwave_device_XXXXXX_node61_switch_binary, OFF) // Laundry Garage

If you want to add clarity use curly brackets.

	if(zwave_device_XXXXXX_node61_switch_binary.state != zwave_device_XXXXXX_node51_switch_dimmer.state) {
		if (zwave_device_XXXXXX_node51_switch_dimmer.state >=3) {
		    sendCommand(zwave_device_XXXXXX_node61_switch_binary, ON) // Laundry Garage
		}
		else {
  		    sendCommand(zwave_device_XXXXXX_node61_switch_binary, OFF) // Laundry Garage
        }
    }
  1. Where these Items created automatically in OH 2.x? These are, frankly, terrible names for Items. One of the whole points of Items is that it doesn’t matter what type of technology it’s connected to. Laundry_Light and Garage_Dimmer would be much better names for these. Not only are they shorter you can look at them and immediately know what the Item controls. I’m not super big on renaming Items any more (since you basically have to delete the old and create a new one with the new name) but this is one case where I think it’s worth the effort.

OK, with that out of the way let’s address the rule itself.

  1. The rule only triggers when the switch changes. What about the dimmer? I would expect the rule to trigger for either Item.

  2. if(zwave_device_XXXXXX_node61_switch_binary.state != zwave_device_XXXXXX_node51_switch_dimmer.state) I don’t think that will work. If the second Item is in fact a Dimmer, it will never be the case that a Dimmer’s state (without conversion) will equal the state of a Switch Item. This is another place where using better Item names can help because it’s really hard to tell what’s what here. But basically, since the states of the two Items will always be different, the rule will always send a command to node51, even when they are the same equivalent state.

    Thankfully we can get the state of a dimmer as an OnOffType.

if(zwave_device_XXXXXX_node61_switch_binary.state != zwave_device_XXXXXX_node51_switch_dimmer.getStateAs(OnOffType)) {

Or, using better Item names:

if(Laundry_Light.state != GarageDimmer.getStateAs(OnOffType)){

That’s a probably the main thing that’s wrong and causing the loop. Unless node51 is in fact a Switch Item instead of a Dimmer in which case :person_shrugging: . It’s impossible to really understand this rule without a lot more information.

  1. I like to use Design Pattern: How to Structure a Rule. It sometimes results in longer rules but those rules are always easier to understand.
    // 1. Decide if the rule needs to run
    if(Laundry_Light.state == GarageDimmer.getStateAs(OnOffType)) return;

    // 2. Calculate what to do
    var syncState =  Laundry_Light.state

    // 3. Do it
    Garage_Dimmer.postUpdate(syncState)

Already, that’s much simpler, but remember my question in 1 above, shouldn’t the rule run when the dimmer changes too?

rule "Laundry_2_Garage_Sync"
when
    Item Laundry_Light changed or
    Item Garage_Dimmer changed
then
    // 1. Decide if the rule needs to run
    if(Laundry_Light.state == GarageDimmer.getStateAs(OnOffType)) return;

    // 2. Calculate what to do
    val item2update = if(triggeringItemName == 'Laundry_Light') Garage_Dimmer else Laundry_Light
    val syncState = if(triggeringItemName == 'Laundry_Light') Laundry_Light.state else Garage_Dimmer.getStateAs(OnOffType)

    // 3. Do it
    item2update.postUpdate(syncState)    
end

The rule will trigger on either Item changing.

  • If the states are already the same the rule simply exits.
  • Then we determine which Item to update based on which Item triggered the rule (see Implicit Variables). Also calculate the command to send to the Item that needs to be synchronized.
  • Finally post the update to the switch that didn’t change.

If the ternary operator is something you might forget section 2 could be something like this.

    // 2. Calculate what to do
    var item2update = Laundry_Light
    if(triggeringItemName == 'Laundry_Light') {
        item2command = Garage_Dimmer
    }
    var syncState = newState
    if(syncState instanceof PercentType) {
        syncState = Garage_Dimmer.getStateAs(OnOffType)
    }

In this case we assume that the Item that needs to be updated is the Laundry_Light. But if the Laundry_Light triggered the rule change to Garage_Dimmer. We do the same for the syncState. If the type of the newState is from the Dimmer, we get the state from the Dimmer as an OnOffType.

The flow will be something like the following:

  1. Laundry_Light changes to ON, triggering the rule.
  2. The Dimmer isn’t on so the Dimmer will be updated to ON.
  3. The Dimmer will change to 100 (or what ever) triggering the rule again.
  4. This time Laundry_Light and Garage_Dimmer will be the same so the rule exits without doing anything.

The same happens if the Dimmer changes to ON to the Laundry_Light.

There might be a problem here though. Updating the Item will not in fact change the state of the end device. It only updates OH internally. Later on, when the binding polls the device’s state it will update the Item back to it’s old state which will trigger the rule again and undo the last command. If that’s the case, use sendCommand instead of postUpdate in section 3 of the rule.

1 Like

Hi, Rich!

Thank you for the awesome reply! I especially appreciate the coding convention pointers. I’ll post an update when I get it working.

Thank you again. Have a great day!
Ben

Update:

It all works! However, I did get an error at first:

The name 'triggeringItemName' cannot be resolved to an item or type;

Which was resolved by replacing:

triggeringItemName
with
triggeringItem.name

It seems the former is for version 3.x and latter for version 2.x which I’m currently running. I am in the process of migrating to Openhabian 3, so I’ll get to try both versions. :slight_smile:

Thank you!

1 Like