At home we have two bedside lights and two remotes.
Instead of buying 2 additional remotes (and then not remembering which controls which light), I implemented a “hack” so that any of the 2 bedside remotes (each controlling only one bedside light) can either control the associated light or both bedside lights, as follows:
- One click → only operate the controlled bedside light
- 3 clicks within 5 seconds → operate both bedside lights
The 3-click logic will turn both bedside lights OFF if the controlled light was ON before the 3 clicks. So it’s intuitive logic: 1 click for the intended end state + 2 “do it for both lights” clicks.
The rule is implemented by using timers.
Here’s the items:
Group:Switch:OR(ON, OFF) gLight "All Lights [%d ON]" <light>
Group:Dimmer gDimmer "All Lights [%d %%]" <light>
Group:Dimmer gWhiteSpectrum "All Lights [%d %%]" <light>
Switch FF_MasterBedroom_MumsBedsideLight_Light_Toggle "Mum's bedside light Light" <light> (gLight, gTradfriLight)] {
channel="tradfri:0220:gwabcdefabcdef:65544:brightness"
}
Dimmer FF_MasterBedroom_MumsBedsideLight_Light_Dimmer "Mum's bedside light [%d %%]" <light> (FF_MasterBedroom, gDimmer) {
channel="tradfri:0220:gwabcdefabcdef:65544:brightness"
}
Dimmer FF_MasterBedroom_MumsBedsideLight_Light_ColorTemperature "Mum's bedside light color temperature [%d %%]" <light> (gWhiteSpectrum) {
channel="tradfri:0220:gwabcdefabcdef:65544:color_temperature"
}
Switch FF_MasterBedroom_DadsBedsideLight_Light_Toggle "Dad's bedside light Light" <light> (gLight, gTradfriLight) {
channel="tradfri:0220:gwabcdefabcdef:65564:brightness"
}
Dimmer FF_MasterBedroom_DadsBedsideLight_Light_Dimmer "Dad's bedside light [%d %%]" <light> (gDimmer) {
channel="tradfri:0220:gwabcdefabcdef:65564:brightness"
}
Dimmer FF_MasterBedroom_DadsBedsideLight_Light_ColorTemperature "Dad's bedside light color temperature [%d %%]" <light> (gWhiteSpectrum) {
channel="tradfri:0220:gwabcdefabcdef:65564:color_temperature"
}
And here’s the rules code:
import java.util.HashMap
// Keep track of the per item timers:
val HashMap<String, Timer> timers = newHashMap
// Keep track of the number of clicks while the timer runs:
val HashMap<String, Integer> timer_counts = newHashMap
// Will contain the state at 1st toggle (will be the end state in successful 3-click scenario):
val HashMap<String, String> timer_states = newHashMap
// System startup delay timer (e.g. after reloading a changed rules file):
var Timer timer_Tradfri_startup = null
var boolean STARTED_UP = false
rule "System startup - Tradfri timer"
when
System started
then
val String logTitle = "System startup - Tradfri remotes"
val int STARTUP_DELAY_SECONDS = 30
logInfo(logTitle, "Initializing timers after {} seconds", STARTUP_DELAY_SECONDS)
STARTED_UP = false;
if (timer_Tradfri_startup === null) {
// Wait 30 seconds before resetting all timers
timer_Tradfri_startup = createTimer(now.plusSeconds(STARTUP_DELAY_SECONDS), [ |
STARTED_UP = true;
gTradfriLight.allMembers.forEach[ s |
if (timers.get(s.name) !== null) {
timers.put(s.name, null)
}
]
timer_Tradfri_startup = null;
logInfo(logTitle, "Timers initialized")
])
} else {
timer_Tradfri_startup.reschedule(now.plusSeconds(STARTUP_DELAY_SECONDS))
}
end
rule "A bedside light changed"
when
Item FF_MasterBedroom_MumsBedsideLight_Light_Toggle changed
or Item FF_MasterBedroom_DadsBedsideLight_Light_Toggle changed
then
val String logTitle = "A bedside light changed"
val int MONITORING_TIME_SECONDS = 5
val String name = triggeringItem.name
logDebug(logTitle, "Bedside light {} has state {} - AT START OF RULE", name, triggeringItem.state)
if (STARTED_UP !== true) {
logWarn(logTitle, "Not yet started up (ignoring)")
} else {
if (timers.get(name) === null) {
// No timer running - start a timer:
logDebug(logTitle, "Bedside light {} has state {} (will be the end state) - monitoring started for {} seconds", name, triggeringItem.state, MONITORING_TIME_SECONDS)
timers.put(name, createTimer(now.plusSeconds(MONITORING_TIME_SECONDS), [ |
logDebug(logTitle, "Bedside light {} - monitoring ended after {} seconds of inactivity", name, MONITORING_TIME_SECONDS)
// Reset the counter and kill the timer when the timer expires:
timer_counts.put(name, 0)
timers.put(name, null)
]))
// Set the toggle count to 1 after creating the timer:
timer_counts.put(name, 1)
// Store the desired end state (current state of triggeringItem)
timer_states.put(name, triggeringItem.state.toString)
} else {
// Increment the click timer
val int cnt = timer_counts.get(name) + 1
val String stateInfo = timer_states.get(name)
if (cnt >= 3) {
logInfo(logTitle, "Bedside light {} (end state will be {}) toggle count: {} ≥ 3 -- Switching {} both bedside lights",
name, stateInfo, cnt, stateInfo)
FF_MasterBedroom_MumsBedsideLight_Light_Toggle.sendCommand(stateInfo)
FF_MasterBedroom_DadsBedsideLight_Light_Toggle.sendCommand(stateInfo)
timers.get(name).cancel
timers.put(name, null)
} else {
logDebug(logTitle, "Bedside light {} (end state will be {}) toggle count: {} - rescheduling the counter (adding {} seconds)",
name, stateInfo, cnt, MONITORING_TIME_SECONDS)
// Restart the timer with a fresh lease:
timers.get(name).reschedule(now.plusSeconds(MONITORING_TIME_SECONDS))
// Store the incremented click counter:
timer_counts.put(name, cnt)
}
}
}
end
Don’t over hastily triple-click as you must allow the state changes to be picked up by OpenHAB.
Have fun!