Design Pattern: Proxy Item

Please see Design Pattern: What is a Design Pattern and How Do I Use Them for information on what a DP is and how to use them.

Problem Statement

Often one faces one or more of the following situations:

  • the need to perform additional logic before sending a command to a device
  • the need to send a command to multiple devices based on a single event
  • the need to do something different based on the state of one or more other Items
  • the channel to update the state of an Item and the channel to send commands to the device cannot be combined into one Item definition without problems (e.g. an infinite loop)

In all of the above situations, a Proxy Item can be used.

Concept

image
A Proxy Item is simply an Item that stands in for one or more Items. Proxy Items are a special type of Design Pattern: Unbound Item (aka Virtual Item).

The Proxy Item is what gets placed on your sitemap and it is the Proxy Item which gets referenced by other Rules that may depend on the device’s state for any reason. Any command to control the device goes through the Proxy Item.

In addition to the Proxy Item, there is at least one Item bound to a binding or linked to a Channel.

One or more Rules are written which trigger on commands and/or updates to the Proxy Item. These Rules perform any additional logic and forwards the commands on to the bound or lined Items. Similarly, there may be one or more Rules which trigger on commands and/or updates to the bound or linked Items which interpret incoming States and sets the state of the Proxy Item accordingly.

Simple Example

There is a device which has two separate communications paths, one for sending commands to and one for sending updates and, for some technical reason the two cannot be combined on the same Item. Perhaps the incoming channel always causes the Item to receive a command instead of just an update or there is additional logic that needs to be performed before just blindly accepting the incoming state update.

Items

Switch ProxySwitch "nice label" <icon> // NO Binding, add label and icon because this gets put on your sitemap
Switch BoundSwitch { blah blah blah } // send commands to device
Switch BoundSwithUpdates { blah blah blah } // receive state updates from device

Rules

Python

from core.rules import rule
from core.triggers import when
from core.utils import sendCommandCheckFirst, postUpdateCheckFirst

@rule("ProxySwitch received command")
@when("Item ProxySwitch received command")
def proxy(event):
    sendCommandCheckFirst("BoundSwitch", str(event.itemCommand))

@rule("BoundSwitchUpdates received update")
@when("Item BoundSwitchUpdates received update")
def bound(event):
    postUpdateCheckFirst("ProxySwitch", str(event.itemState))

Rules DSL

rule "ProxySwitch received command"
when
    Item ProxySwitch received command
then
    if(BoundSwitch.state != receivedCommand) BoundSwitch.sendCommand(receivedCommand)
end

rule "BoundSwitchUpdates received update"
when
    Item BoundSwitchUpdates received update
then
    if(ProxySwitch.state != BoundSwitchUpdate.state) ProxySwitch.postUpdate(BoundSwitchUpdate.state)
end

Advantages and Limitations

This design pattern gives one a lot of control over how and when commands to and from a device occur. It also allows for the combining of multiple separate Items into one logical Item. But it enables these capabilities by adding complexity to the configuration.

Related Design Patterns

Design Pattern How It’s Used
Design Pattern: Unbound Item (aka Virtual Item) A Proxy Item is a special case for Virtual Items
Design Pattern: Manual Trigger Detection Uses a Proxy Items in Approach 3
Design Pattern: Gate Keeper Uses a Proxy Item to centralize the commands to the constrained device
Design Pattern: Separation of Behaviors Uses a Proxy Item to kick off the cross cutting Rule
Design Pattern: Sensor Aggregation Uses a Proxy Item to track the anti-flapped presence state

Edit: Added JSR223 Python example and cleaned up the Rules DSL version.

16 Likes

I like dimmer light to be smoothly turn to set value
so base on rich’s code, I made a simple rules

Switch ProxySwitch “nice label” // NO Binding, add label and icon because this gets put on your sitemap
Switch BoundSwitch { blah blah blah } // send commands to device

rule "smooth adjust dimmer"
when
	Item ProxySwitch received update
then
	var Number percent = BoundSwitch.state
	var target = ProxySwitch.state
	var Number speed = 100 //dimmer adjust 1 by 0.1 second, change to 500 for 0.5s
	
	if (percent < target){
			//logInfo("Test", "Increase to: Current:{}%,target:{}%", percent, target)
			while(percent <= target) {
			BoundSwitch.sendCommand(percent.toString)
			percent = percent + 1
			Thread::sleep(speed)
			}}
	else if (percent > target) {
			while(percent >= target) {
			//logInfo("Test", "turnoff:percent:{},target:{}", percent, target)
			BoundSwitch.sendCommand(percent.toString)
			percent = percent - 1
			Thread::sleep(speed)
			}
			}
end
2 Likes

and then how to bind the http binding with it? OR how to send the percent value via http binding ?

Please do not spread a single conversation over multiple threads.

I am trying to use this Design Pattern to control a bedroom light based on a motion sensor. I have the correct rule setup to switch on the light based on motion and switch it off after a minute. I would also like to have a manual override, so that if somebody flicks the switch, the motion sensor should be ignored.

My override rule is not very clean which i need help with. As soon as the motion is detected, the proxy is switched ON which in turn switches on the light. However, this also turns ON the manual override (I have this setup incase somebody directly flicked the switch). Thus I have to switch OFF this manual override in the rule. Is there any cleaner approach than this?

2017-08-27 14:02:15.082 [INFO ] [lipse.smarthome.model.script.BedRoom] - Current status of Manual override is:OFF
2017-08-27 14:02:15.082 [INFO ] [lipse.smarthome.model.script.BedRoom] - BedRoom Motion Sensor changed to ON 
2017-08-27 14:02:15.089 [INFO ] [lipse.smarthome.model.script.BedRoom] - BedRoom Proxy changed to ON 
2017-08-27 14:02:15.092 [INFO ] [lipse.smarthome.model.script.BedRoom] - BedRoom Switch received a manual override ON 
2017-08-27 14:02:15.094 [INFO ] [lipse.smarthome.model.script.BedRoom] - BedRoom Manual override is set to:OFF
2017-08-27 14:02:21.708 [INFO ] [lipse.smarthome.model.script.BedRoom] - Current status of Manual override is:OFF
2017-08-27 14:03:21.979 [INFO ] [lipse.smarthome.model.script.BedRoom] - BedRoom Motion Sensor changed to OFF 
2017-08-27 14:03:21.986 [INFO ] [lipse.smarthome.model.script.BedRoom] - BedRoom Proxy changed to OFF 

Items file:


Switch sw_BedRoomLight_proxy "BedRoom Light" <light>
Switch timer_BedRoomLight { expire="1m,command=OFF" }
Switch sw_BedRoomLight_manual
Dimmer dim_BedRoomLight "Bed Room [%.1f]" (gLights)   {channel="zigbee:device:blah_switch_level"}
Switch sw_BedRoomLight {channel="zigbee:device:blah_switch_onoff"}

My rules are as follows. The ugly line is sw_BedRoomLight_manual.postUpdate(OFF) in rule “sw_BedRoomLight_proxy changed”:

rule "BedRoom Enter"
when
        Item mo_BedRoom changed
then

logInfo("BedRoom","Current status of Manual override is:"+ sw_BedRoomLight_manual.state.toString())
if (sw_BedRoomLight_manual.state ==OFF) //Ignore motion sensor if the switch has been manually trigger
{
        if (mo_BedRoom.state == ON){
                logInfo("BedRoom","BedRoom Motion Sensor changed to ON ")
                        timer_BedRoomLight.postUpdate(OFF) //cancel the timer if necessary
                        sw_BedRoomLight_proxy.postUpdate(ON)
        }
        else if (mo_BedRoom.state == OFF){
                if (timer_BedRoomLight.state == OFF) {
                        timer_BedRoomLight.sendCommand(ON) //start the timer    
                }
        }
}
end

rule "timer_BedRoomLight expired"
when
    Item timer_BedRoomLight received command OFF
then
    logInfo("BedRoom","BedRoom Motion Sensor changed to OFF ")
    sw_BedRoomLight_proxy.postUpdate(OFF)
end


rule "sw_BedRoomLight_proxy changed"
when
        Item sw_BedRoomLight_proxy changed
then
    if(sw_BedRoomLight_proxy.state == ON) {
        logInfo("BedRoom","BedRoom Proxy changed to ON ")
        sw_BedRoomLight.sendCommand(ON) // Turn light switch on
        if (now.getHourOfDay() >7 && now.getHourOfDay() <22){  //07:00:00 to 21:59:59 
                dim_BedRoomLight.sendCommand(100)
        }
        else{
                dim_BedRoomLight.sendCommand(1) //dim lights at night
        }
        sw_BedRoomLight_manual.postUpdate(OFF)
        logInfo("BedRoom","BedRoom Manual override is set to:"+sw_BedRoomLight_manual.state.toString() )
    }
    else {
        logInfo("BedRoom","BedRoom Proxy changed to OFF ")
        sw_BedRoomLight.sendCommand(OFF) // Turn light switch off
        dim_BedRoomLight.sendCommand(0)
}
end

rule "sw_BedRoomLight received update" //Manual override
when
    Item sw_BedRoomLight received update
then
if(sw_BedRoomLight.state == ON ) {
        logInfo("BedRoom","BedRoom Switch received a manual override ON ")
        sw_BedRoomLight_manual.postUpdate(ON)
        sw_BedRoomLight_proxy.postUpdate(ON)
}
    else if (sw_BedRoomLight.state == OFF || dim_BedRoomLight.state == 0) {
        sw_BedRoomLight_manual.postUpdate(OFF)
        sw_BedRoomLight_proxy.postUpdate(OFF)
    }
end

I have something similar that I wrote up here in the Manual Trigger Detection Design Pattern

the first approach uses a flag that gets changed when ever a Rule is changing a light’s state (i.e. change the flag to “RULE”). The flag remains in a default state of “MANUAL” at all other times. when the override rule triggers it checks for the flag and if it is MANUAL the rule knows the light was changed because of a manual action.

The Timestamp example in the above is what I currently run with. Instead of keeping track of a flag, I use persistence and lastUpdate to distinguish between a change that occurred because of a rule or a manual action.

Have not tried this BUT one angle of attack would be to take advantage of the fact that in a manual switch operation the PROXY was not changed first. So something like:

rule "determine who went first"
    when
        Item sw_SWITCH_REAL changed 
    then
        if (sw_SWITCH_PROXY.state != sw_SWITCH_REAL.state) {
// the real switch changed to a state which is not the current PROXY state, hence a manual operation
              //DO WHAT YOU WANT HERE
        }
end

You probably would want to add a small delay in any PROXY_SYNCH rule to make sure the states are preserved long enough for this to evaluate…

1 Like

Thanks for the idea. I am using the Deadmans switch approach now. There is one issue though. The manual override is working well for switch the Light ON. It continues to stay ON as desired when the switch is manually flicked.

There is one more complication though. When i go to bed, i turn off the bedroom light manually. So I want to a manual override for switch OFF condition as well. This use case is not captured at the moment.

One solution would be that i use timeofday to disable the motion sensor at night. This is not a very elegant solution though. Another solution could be that i have counter which starts at 0 when i am in bed. It then counts the number of Motion ONs detected (say i am rolling in bed), and turn the light ON only when it reaches 5.

What do you think?

I (kind of) think you are over-complicating things a bit. I use the Astro binding to set virtual item sw_Logical_Night AND a readout of a lux sensor to set virtual item sw_Dark_Day (ie when the lux level is below some threshold.)

These are logical switches as per items defined as (the EXPIRE binding is pretty nice) (N.B. I named my Timer items to include (1) what they are for (KL==Kitchen Light), (2) the timer length (5m, …1s) and (3) the effect of the rule which executes when the timer expires.)

Switch  sw_Logical_Night
Switch  sw_Dark_Day
Switch KLTimer_5m_OFF { expire="5m,command=OFF" }
Switch KLTimer_10s_OFF { expire="10s,command=OFF" }

The timer rules are thus:

rule "KLTimer_5m_OFF received command OFF"
    when
        Item KLTimer_5m_OFF changed to OFF
    then
	logDebug("KITCHEN","Timer 5m received command OFF")
        KLTimer_10s_OFF.sendCommand(ON)
		
end

rule "KLTimer_10s_OFF received command OFF"
    when
        Item KLTimer_10s_OFF changed to OFF
    then
		if (mo_MOTION_Kitchen.state == ON){
			KLTimer_10s_OFF.sendCommand(ON)
		} else {
        sw_LIGHT_Kitchen_PROXY.sendCommand(OFF)
		}
end

These get set in a rules file daynight.rules.

rule "NIGHT or DARK DAY :-Determine whether it is LOGICAL_NIGHT DARK DAY"
	when
		System started or
		Time cron "0 0/5 * ? * *"   //Every five minutes
	then
		var Number mnDur = MorningNightDuration.state
		var Number adDur = AstroDawnDuration.state
		var Number dlDur = DaylightDuration.state
		var Number calcDawn = (mnDur + adDur +30.0)
		var Number calcDusk = (mnDur + adDur + dlDur -30.0)
		var Number now_minute=now().getMillisOfDay()/(60000)

        if (sw_Dark_Day.state == NULL){
            sw_Dark_Day.sendCommand(OFF)
        }
		if ((now_minute > calcDawn) &&  (now_minute < calcDusk))  {
			if (sw_Logical_Night.state != OFF) {
				sw_Logical_Night.sendCommand(OFF)
            }
            if (zz_lux_dsb05_demo.state < 200) {
                sw_Dark_Day.sendCommand(ON)
            } else {   
                sw_Dark_Day.sendCommand(OFF)
            }
		} else {
			if (sw_Logical_Night.state != ON) {
				sw_Logical_Night.sendCommand(ON)
                                sw_Dark_Day.sendcommand(OFF)
			}
		}
end		

My motion sensor rules for the Kitchen are thus:

rule "MO_1: Events when motion in Kitchen trips"
	when
		Item mo_MOTION_Kitchen changed to ON
	then
		try {
			if (sw_LIGHT_Kitchen.state == OFF) {
				//if light was off, turn cancel any extant timers and turn it on due due to motion
				if (sw_Logical_Night.state == ON) {
					sw_LIGHT_Kitchen_PROXY.sendCommand(ON)
				} else {
					if (sw_Dark_Day.state == ON) {
						sw_LIGHT_Kitchen_PROXY.sendCommand(ON)
					} else {
				  	//NO-OP or log message
					}
				}	
						
			} else {
				logInfo("KITCHEN","MO_1 : LIGHT Kitchen is already ON")
			}
		}finally {		
			
//			NO-OP
		}	
end



rule "MO_2 : events when mo_MOTION_Kitchen changed from OPEN to CLOSED"
	when
		Item mo_MOTION_Kitchen changed from ON to OFF or
		Item mo_MOTION_Kitchen changed from NULL to OFF
	then
		if (sw_LIGHT_Kitchen.state == ON) {
			logInfo("KITCHEN","MO_2 : Kitchen light is ON")
			if 	(KLTimer_5m_OFF.state == ON){
					KLTimer_5m_OFF.postUpdate(OFF)
					KLTimer_10s_OFF.sendCommand(ON)
			} else {
					logInfo("KITCHEN","MO_2 : Kitchen light is OFF")
					if (KLTimer_10s_OFF.state == ON){
						KLTimer_10s_OFF.sendCommand(ON)	
					} else {	
					logWarn("KITCHEN","MO_2 : Kitchen Light OFF timer does NOT exist....SHOULD NOT GET THIS MESSAGE....")
					}
			}	
              
		} else { 
				//LIGHT IS OFF when motion sensor resets -- likely due to manual operation or initialization
			if (KLTimer_5m_OFF.state==ON)  {
 				KLTimer_5m_OFF.postUpdate(OFF)
			}  
				
			if (KLTimer_10s_OFF.state == ON){
				KLTimer_10s_OFF.postUpdate(OFF)
			}
					
				//DO NOT CREATE A TIMER IF THE LIGHT IS OFF
		}
end

Feel free to adapt for your circumstances. (I removed a lot of logging messages to make the logic clearer.)

Without seeing your code I can’t say for sure, but it sounds like you are either triggering the rule only when the light turns ON or you are only setting the override flag when the light turns ON.

You should set the override for ANY change in the light’s state (or really any update the to the light that occurs after the motion sensor stops reporting.

If you look at my Time based example, you will see I do something a little similar in that I only care about the Override at a certain Time of Day. And personally, I would probably set yours up this way because I wouldn’t want to require someone to turn off the light manually before bed to make sure the motion sensor doesn’t turn it back on.

But if you are OK with that behavior, I think the issue is in your rule as I already stated.

Here is my code. At the moment i am switching off the motion sensor at night. In the last rule block you can see that the override is for both ON and OFF state. Suppose i switch off the light, then the last rule (sw_BedRoomLight received update) is triggered. Now I want this to stick. However, the next time motion is sensed, the lights turn ON again.

Items

Switch sw_BedRoomLight <light> {channel="blah"}
String DeadMansSwitch { expire="30s,state=MANUAL" } 
Switch sw_BedRoomLight_proxy "BedRoom Light" <light>
Switch timer_BedRoomLight { expire="1m,command=OFF" }
Switch mo_BedRoom "BedRoom Motion Sensor"   {channel="blah"}

rule "System startup"
        when
                System started
        then
                DeadMansSwitch.sendCommand("STARTUP")
end

rule "BedRoom Motion detected"
when     
        Item mo_BedRoom changed
then     
         
if (now.getHourOfDay() >7 && now.getHourOfDay() <22){  //07:00:00 to 21:59:59  only enable sensor during the day
     
        DeadMansSwitch.sendCommand("RULE")
                Thread::sleep(50) // give it time to populate through the event bus, may not be needed, may need to be longer
         
         
                if (mo_BedRoom.state == ON){
                        logInfo("BedRoom","BedRoom Motion Sensor changed to ON ")
         
                                timer_BedRoomLight.postUpdate(OFF) //cancel the timer if necessary
                                sw_BedRoomLight_proxy.postUpdate(ON) 
                }
                else if (mo_BedRoom.state == OFF){
                        if (timer_BedRoomLight.state == OFF) {
                                timer_BedRoomLight.sendCommand(ON) //start the timer    
                        }
                }
        Thread::sleep(500) // give stuff time to complete, may not be needed, may need to be longer
                DeadMansSwitch.sendCommand("MANUAL")
}
end

rule "timer_BedRoomLight expired"
when
    Item timer_BedRoomLight received command OFF
then
    DeadMansSwitch.sendCommand("RULE")
    Thread::sleep(50) // give it time to populate through the event bus, may not be needed, may need to be longer
    logInfo("BedRoom","BedRoom Motion Sensor changed to OFF ")
    sw_BedRoomLight_proxy.postUpdate(OFF)

    Thread::sleep(500) // give stuff time to complete, may not be needed, may need to be longer
    DeadMansSwitch.sendCommand("MANUAL")
end


rule "sw_BedRoomLight_proxy changed"
when
        Item sw_BedRoomLight_proxy changed
then
    if(sw_BedRoomLight_proxy.state == ON) {
        logInfo("BedRoom","BedRoom Proxy changed to ON ")
        sw_BedRoomLight.sendCommand(ON) // Turn light switch on
        if (now.getHourOfDay() >7 && now.getHourOfDay() <22){  //07:00:00 to 21:59:59 
                dim_BedRoomLight.sendCommand(100)
        }
        else{
                dim_BedRoomLight.sendCommand(1) //dim lights at night
        }

    }
    else {
        logInfo("BedRoom","BedRoom Proxy changed to OFF ")
        sw_BedRoomLight.sendCommand(OFF) // Turn light switch off
        dim_BedRoomLight.sendCommand(0)
    }
end

rule "sw_BedRoomLight received update" //Manual override
when
    Item sw_BedRoomLight received update
then
if(DeadMansSwitch.state.toString == "MANUAL") {
        Thread::sleep(100)
        if(sw_BedRoomLight.state == ON ) {
                logInfo("BedRoom","BedRoom Switch received a manual override ON ")
                sw_BedRoomLight_proxy.postUpdate(ON)
        }
        else if (sw_BedRoomLight.state == OFF || dim_BedRoomLight.state == 0) {
                logInfo("BedRoom","BedRoom Switch received a manual override OFF ")
                sw_BedRoomLight_proxy.postUpdate(OFF)
        }
}
end

You should postUpdate ON to sw_BedRoomLihgt_manual in both the case when the light is ON and when it is OFF.

As written, when the light manually turns OFF you disable the override.

Actually, the sw_BedRoomLight_manual item is not being used in the code at all. This is leftover code from before I implemented Deadman switch.

PS:Code above cleaned up

Then you need to create and set an actual Override Switch that gets set to ON when a MANUAL update is received and set to OFF at some point (midnight? first thing in the morning?). Then in your Motion Detected Rule you need to check the state of this Override Switch before turningon or off the light.

Ok, are you suggesting that Deadmans switch isnt sufficient. So i do need to implement the sw_BedRoomLight_manual switch.

I guess in that case the timeoftheday rule i have set solves the same way?

if (now.getHourOfDay() >7 && now.getHourOfDay() <22){  //07:00:00 to 21:59:59  only enable sensor during the day

The DeadMansSwitch is sufficient for detecting when a light was switched manually. That is all the Design Pattern does. What you do with that knowledge is left up to you. In this case, you want to set/check an override flag and avoid turning on/off the light when the override flag is set.

The timers are tricky to follow. Can you explain in words what the two timers are supposed to do?

See

KLTimer_5m_OFF is an Item configured with the Expire binding to receive an OFF command five minutes after it received the last ON command.

KLTimer_10s_OFF is an Item configured with the Expire binding to receive an OFF command five minutes after it received the last ON command.

There is a rule that gets triggered when either of these Timer Item changes to OFF (I would have used received command OFF instead of changed to OFF so that the rules do not trigger when the Timer is canceled but Bob is really deliberate in his Rules so expect there is a reason he wants the rule to fire even when the Timer is cancelled with an update).

When the 5m timer goes to OFF, the 10s timer gets set to ON. When the 10s timer goes OFF, the kitchen motion sensor is checked. If it is ON then the 10s timer is reset to fire in another 10 seconds. If not then the light is turned OFF.

The Rule that uses the Timers gets fires when the motion sensor changes from ON to OFF. If I remember correctly, his motion sensors remain ON for a period of time after detecting motion so this rule gets triggered some minutes after the last motion detected. If the light was ON when the motion sensor went OFF then if the 5m timer is still running then it gets turned OFF. In either case the 10s timer get set to turn off the Light in 10 seconds (assuming there isn’t another motion event).

If the light is OFF then the timers are canceled with a postUpdate(OFF).

So, at a high level, he has a 5-minute timer that gets set somewhere (probably his lights proxy rule). When that timer expires he sets a 10-second timer that when it expires turns off the lights.

3 Likes

Correct explanation of the intent. In reality the 5m_OFF timer is a kind of safety valve. My particular motion sensors re-evaluate motion status after 4 minutes (+/- a few seconds). So if the motion sensor shifts to OFF/CLOSED at 4 minutes (+/- a few sec) , the 5m_OFF timer gets cancelled and the 10s_OFF timer goes into a tight re-evaluation loop. As long as the motion sensor stays OFF/CLOSED, (in most likely case 10s), the PROXY will be switched off after 10s. If by chance, someone reactivates the motion sensor in that time, the 10s timer will get reset and continue doing so as long as the motion sensor is in ON/OPEN state. A reactivation of the sensor also re-establishes the 5m_OFF safety valve timer.

Agree with @rlkoshak on received command — I actually implemented that after my original post.

BTW there is redundancy in the outer ELSE clause of the MO_2 rule–leftover from some debugging. I think I edited the original post to remove it, but just FYI.

I really like the expire binding – it simplifies timers a lot — you have to have more rules but they are simpler in context. And (another big plus) the expire-binding timers are commonly accessible across rule sets (I have 8 or so). Does make it somewhat easier to shoot yourself in the foot, but being able to segment rule sets into different files is worth it.

1 Like

Hello, I have been struggling with correct settings of my new setup and this design pattern (Proxy / Virtual item) is exactly what I needed. It took me a while until I fully understood it to apply to my case and finally have it working. I have improved it with what I have learnt from @rlkoshak in another topic about PIR alarm, by using a group of items and triggeringItem and would like to share it for anyone else who would be searching for such solution.

My setup:
I have a relay (actually 2 x 8 relays modules = 16 relays) which are connected to Arduino MEGA and those relays are connected to my room(s) lights. I have also wall switches, also connected to MEGA.

The MEGA is then communicatig via serial with ESP8266 which is then publishing and subscribing to MQTT topics managed by a mosquitto broker on my RPI3 where also Openhabian is running.

My approach was to keep things running even if Openhab would be offline for any reason so Wall switches are toggling states of relays directly on MEGA. So the MEGA is the only point of failure which could be easily replaced by another one which (will be :slight_smile: ) ready for that.

To be able to add Openhab in, I had to connect MEGA to the wifi. I decided to use ESP8266 which is forwarding commands to relays (from Openhab etc.) and states of relays (to Openhab). I am exchanging these between MEGA and ESP via EasyTransfer library for Arduino which is basically serial communication where I am exchanging only Pin ID and Pin Value (e.g. Pin ID = 22 and Pin Value = 1 means relay connected to pin D22 will switch OFF (this would actually switch the relay ON due to inverted logic on relays but that is not important here).

So whenever my wall switch toggles the relay (living room light) state, I am sending this information from MEGA to ESP and publishing this change of relay’s state to a relevant topic (e.g. house/livingroom/lightState)

The rule then updates my virtual switch item in openhab to the new state.

And vice versa, when my virtual item switch (or a rule or a mobile app) wants to toggle the relay/light, the rule (which could make some additional checks) forwards this command via MQTT topic (e.g. house/livingroom/lightCommand) to ESP which then forwards it to MEGA which finally toggles the relay to desired state and replies back with the new state to confirm command received.

I decided to use two separated MQTT topics to avoid loops which was my issue when I tried to use a simpler way of having only one switch item subscribed and publishing it’s state to one same topic, not even using a virtual item.

So you could easily use a one rule for a one set of relay/switch, but in my case this would be about 12+ sets so 12+ x rules. Very inefficient, hard to maintain etc. So I used the group way that I learnt from Rich (thanks again) and improved that for me.

So my items (simplified and generalized) are:

//Lights
Group:Switch:OR(ON, OFF) gCommands "All relays [(%d)]"
Group:Switch:OR(ON, OFF) gVirtualSwitches "All virtual switches [(%d)]"

Switch LRLights_State		    "Living room lights state"        <switch> 	                {mqtt="<[mosquitto:house/livingroom/lightState:state:default]"}
Switch LRLights_Command		    "Living room lights command"      <switch>    (gCommands)     {mqtt=">[mosquitto:house/livingroom/lightCommand:command:*:default]"}
Switch LRLights_VirtualSwitch    "Living room lights virtual switch"      <light>     (gVirtualSwitches)

And my rules are:

rule "VirtualSwitch received command" //so I want to toggle relay via openhab/mobile app, rule etc.
when
    Item LRLights_VirtualSwitch received command// or
    //Item LRLights2_VirtualSwitch received command 
    //...
    
then
        //logInfo("light.rules switch received command", "STARTED")
     // getting here the command Item object related to the same lights virtual switch which triggered this rule    
     val relCommand = gCommands.members.findFirst[c| c.name == triggeringItem.name.split("_").get(0) + "_Command"]
        //logInfo("light.rules switch received command", "triggeringItem.name=[{}]",triggeringItem.name)
        //logInfo("light.rules switch received command", "virtSwitch.name=[{}]",triggeringItem.name)
    
    switch(triggeringItem.name){
        case "LRLights_VirtualSwitch": {
                //logInfo("light.rules switch received command", "case: LRLights_VirtualSwitch")
            if(receivedCommand == ON) {
                // additional ON logic, e.g. check presence, time of day, motion detectors, set/unset a flag, etc.
                relCommand.sendCommand(ON) // forward the command
                    //logInfo("light.rules switch received command", "send command: ON to:[{}]",relCommand.name)
            }
            else {
                // additional OFF logic, e.g. check presence, time of day, motion detectors, set/unset a flag, etc.
                relCommand.sendCommand(OFF) // forward the command
                    //logInfo("light.rules switch received command", "send command: OFF to:[{}]",relCommand.name)
            }
        }
        //case "LRLights2_VirtualSwitch": {}
        //....   
 }
end

rule "State item received update"// so the relay state has changed, via virtual switch above or via wall switch, so ESP is informing (confirming) openhab about it's new state
when
    Item LRLights_State received update //or
    //Item LRLights2_State received update or
    //...
then
    //getting here the virtual switch item object which is related to the relay/light that changed it's state so I can update the new state to the virtual switch    
    val virtSwitch = gVirtualSwitches.members.findFirst[c| c.name == triggeringItem.name.split("_").get(0) + "_VirtualSwitch"]
        logInfo("light.rules state received update", "triggeringItem.name=[{}]",triggeringItem.name)
    
    switch(triggeringItem.name){
        case "LRLights_State": {
            if(triggeringItem.state == ON) {
                // additional ON logic, e.g. check presence, time of day, motion detectors, set/unset a flag, etc.
                virtSwitch.postUpdate(ON) // forward the command
            }
            else {
                // additional OFF logic, e.g. check presence, time of day, motion detectors, set/unset a flag, etc.
                virtSwitch.postUpdate(OFF) // forward the command
            }
        }
        //case "LRLights2_State": {}
        //...    
}
end
3 Likes