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