Please see Design Pattern: What is a Design Pattern and How Do I Use Them to understand the scope and purpose of this and other Design Patterns.
Problem Statement
When using a switch to control a dimmable light, one may want to achieve the same behavior than classical switches with an electrical relay behind the scenes:
- a short press on the switch turns the light on or off
- holding down the switch for a longer time will alternating increase or decrease the light
In order to avoid this complex behavior within a rule that controls the light, we want to be able to react on the different states of the switch individually:
- Short press of the switch (push & release within a certain amount of time, e.g. 500ms)
- Holding down the switch (recurrent iterations, e.g. after each 500ms while held down)
Concept
Use Unbound Item (aka Virtual Item) for the events and separate the concern of controlling the switch with individual rules.
Simple Example
In this example we will show how to handle a single switch.
Things
Typically, one has a Rocker Switch, e.g. the enocean FT55 (single or double). Let’s assume we have a single switch with on channel.
Items
// Real Rocker Switch
Switch MySwitch1 "Intelligent Switch 1" <switch> {channel="enocean:rockerSwitch:********:********:rockerswitchA"}
// Virtual items
Switch MySwitch1Up "Switch 1 Up" <switch>
Switch MySwitch1UpShortPress "Switch 1 Up: Short Press" <switch>
Switch MySwitch1UpLongPress "Switch 1 Up: Long Press" <switch>
Switch MySwitch1Down "Switch 1 Down" <switch>
Switch MySwitch1DownShortPress "Switch 1 Down: Short Press" <switch>
Switch MySwitch1DownLongPress "Switch 1 Down: Long Press" <switch>
Rules
import java.util.Map
val Map<String, Timer> Timers = newHashMap
val Map<String, DateTime> Timestamps = newHashMap
val SwitchPressed = [ Map<String, Timer> Timers, Map<String, DateTime> Timestamps, String id |
Timestamps.put(id, now)
sendCommand(id, "ON")
// Start timer if switch is held down
val offset = now.plusMillis(500)
Timers.put(id, createTimer(offset, [|
// Handle long press
sendCommand(id + "LongPress", "ON")
sendCommand(id + "LongPress", "OFF")
// Reschedule timer for this switch
// (max. 10 times, according to the times used in this example)
// You may want to change the times or remove the if statement completely.
if (now.isBefore(Timestamps.get(id)?.plusSeconds(5))) {
val offset = now.plusMillis(500)
Timers.get(id)?.reschedule(offset)
}
]))
]
val SwitchReleased = [ Map<String, Timer> Timers, Map<String, DateTime> Timestamps, String id |
// Deactivate the timer (if running)
Timers.get(id)?.cancel()
Timers.remove(id)
// Get timestamp of switch pressed down
var pressed = Timestamps.get(id)
Timestamps.remove(id)
// Handle short press
val offset = pressed?.plusMillis(500)
if (now.isBefore(offset)) {
sendCommand(id + "ShortPress", "ON")
sendCommand(id + "ShortPress", "OFF")
}
sendCommand(id, "OFF")
]
rule "Switch1Events"
when
Channel "enocean:rockerSwitch:********:********:rockerswitchA" triggered
then
switch (receivedEvent.event) {
case "DIR1_PRESSED" : { SwitchPressed.apply(Timers, Timestamps, "MySwitch1Up") }
case "DIR1_RELEASED" : { SwitchReleased.apply(Timers, Timestamps, "MySwitch1Up") }
case "DIR2_PRESSED" : { SwitchPressed.apply(Timers, Timestamps, "MySwitch1Down") }
case "DIR2_RELEASED" : { SwitchReleased.apply(Timers, Timestamps, "MySwitch1Down") }
}
end
Applications
On/Off & Dimmer Switch
This example of applications show, how we can use a single switch to control a color light with the following rules:
- A short press on the switch will turn the light on or off
- Holding down the switch will alternating increase or decrease the brightness of the light
- When the light is switched off, the next dimmer action will be set to “increase”
- When the light is switched on, the next dimmer action will be set to “decrease”
This is the most natural behavior according to classical dimmer switches with a relay.
Note: If you are using persistence, then you can replace the global command variable and simply compare the last two brightness values of the light.
// May be replaced by previousState if persistence is available!
var String MyColorLightCommand = "INCREASE"
rule "MyColorLight_OnOff"
when
Item MySwitch1UpShortPress received command ON
then
val color = MyColorLight.state as HSBType
if (color.brightness == 0) {
MyColorLightCommand = "INCREASE"
MyColorLight.sendCommand(ON)
} else {
MyColorLightCommand = "DECREASE"
MyColorLight.sendCommand(OFF)
}
end
rule "MyColorLight_Dimmer"
when
Item MySwitch1UpLongPress received command ON
then
MyColorLight.sendCommand(MySwitch1Command)
end
rule "MyColorLight_CommandToggle"
when
Item MySwitch1Up received command OFF
then
if (MyColorLightCommand == "INCREASE") {
MyColorLightCommand = "DECREASE"
} else {
MyColorLightCommand = "INCREASE"
}
end
Theory of Operation
Every time the switch is used the special rules for handling of the switch will determine the state of the switch (whether or not it is pressed and release immediately or held down). Those rules with set the state of the virtual items accordingly. This clearly separates the concern of the switch from the actual control of the lights or whatever you want to control with the switch and allows to differentiate between short and long presses of the switch.
Advantages and Limitations
The major advantage of this approach is it centralizes the control of the switch and separate it from the actual command that will be send on activation. This allows:
- avoidance of duplicated code
- avoidance of typos and coding errors scattered through the code
- ease of debugging
- simpler Rules logic for Rules that differentiate between long and short presses of the switch
The major limitation of this approach is that it does introduces a little time delay for the actual execution of the control logic of the light.
Related Design Patterns
Design Pattern | How It’s Used |
---|---|
Design Pattern: Unbound Item (aka Virtual Item) | vTimeOfDay, Irrigation, and other Items are examples of a Virtual Item |
Design Pattern: Separation of Behaviors 7 | This DP is a specific implementation of Separation of Behaviors |