Lights in my home automation setup may be controlled multiple ways: by a physical switch hardwired to the light, by a button on a UI screen, or by a rule in response to some other events. To keep it simple, I combine the design patterns for Proxy Items,for Groups and for Associated Items. I define rules for the desired behavior at the level of a group, and then assign the lights to that group.
With this setup, the proxy item will always correctly reflect the status of the light, independent of what caused that status (command from a rule, gesture on a physical control, gesture on a UI element).
Naming conventions
I always follow this naming convention:
- the status item is named Basename_Stat and belongs to group gSwS
- the control item is named Basename_Cmnd and belongs to group gSwC
- the proxy switch is named Basename_Proxy and belongs to group gSwP
- optionally, there is an Expire item named Basename_Expire, and if the status item is in group gSwA, then the light will automatically turn off after the set time
So a typical items definition for a light might look like this (this is a custom-made light with an ESP8266 running Tasmota firmware):
Switch PotteryCabinet_Proxy <light> (gSwP,gIniOff) Switch PotteryCabinet_Cmnd (gSwC) {mqtt=">[mosquitto:cmnd/esp8266-B/power:command:*:default]"} Switch PotteryCabinet_Stat (gSwS) {mqtt="<[mosquitto:stat/esp8266-B/POWER:state:default]"} Switch PotteryCabinet_Expire (gSwX) {expire="10m,command=OFF" }
Rules
Now let’s define how commands sent to the proxy switch affect the physical light, and how the proxy item is affected when the status of the physical light changes, maybe because someone flipped the physical light switch or because of a UI action.
When the proxy item changes, then set the physical light accordingly:
rule "GENERIC: proxy switch received command" when Member of gSwP received command then val theProxy = triggeringItem val theCmndName = theProxy.name.replace("_Proxy","_Cmnd") val theCmnd = gSwC.members.findFirst[ t | t.name == theCmndName] if (theCmnd !== null) { theCmnd.sendCommand(receivedCommand) } end
When the physical light status changes, then update the proxy item accordingly: If someone turned it OFF, then also cancel the auto-off timer.
rule "GENERIC: real switch received update" when Member of gSwS received update then val theStat = triggeringItem val theProxyName = theStat.name.replace("_Stat","_Proxy") val theExpireName = theStat.name.replace("_Stat","_Expire") val theProxy = gSwP.members.findFirst[ t | t.name == theProxyName] val theExpire = gSwX.members.findFirst[ t | t.name == theExpireName] if (theProxy !== null) { if (theProxy.state != theStat.state) theProxy.postUpdate(theStat.state) } if (theExpire !== null) { if (theExpire.state==ON && theStat.state==OFF) theExpire.postUpdate(OFF) } end
When the auto-off timer expires, then turn off the light, via its proxy item.
rule "GENERIC: switch expire" when Member of gSwX received command then val theExpire = triggeringItem val theProxyName = theExpire.name.replace("_Expire","_Proxy") val theProxy = gSwP.members.findFirst[ t | t.name == theProxyName] if (theProxy !== null) { if (theProxy.state != receivedCommand) theProxy.sendCommand(receivedCommand) } end
If someone turned ON the light, and it is configured for auto-off, i.e. if Basename_Stat is in group gSwA, then start the expiration timer:
rule "GENERIC: start auto-expire" when Member of gSwA changed to ON then val theStat = triggeringItem val theExpireName = theStat.name.replace("_Stat","_Expire") val theExpire = gSwX.members.findFirst[ t | t.name == theExpireName] if (theExpire !== null) { theExpire.sendCommand(ON) } end