Edit: Updates for OH 4
Please see Design Pattern: What is a Design Pattern and How Do I Use Them for details on what a DP is and how to use it.
Problem Statement
Sometimes you are in a situation where you want to know where a certain command came from but there is no way built into the binding or the device to tell the difference between a manually initiated command at the device or a command initiated by a rule or the sitemap. For example, when one wants to override a Rule’s behavior when a light switch is manually triggered.
Concept
There are multiple ways to implement this design pattern. This DP will show a couple.
The first approach is to use a Proxy Item with separate controlling Items for each way to control the device (sitemap, Rule, device itself). The Rule that synchronizes all the Items will know where the command came from based on the Item that changed.
The second approach creates a deadman’s switch which gets set to one value at all times except when a rule is running. When a rule runs, it temporarily sets the deadman’s switch to something besides the default value and then changes it back to the default value. A Rule gets triggered for ALL changes to the device’s Item. This rule check’s the deadman’s switch and if it is set to the default value we know the Item was manually triggered. Then pair this with a Rule that triggers on any update to the Items one is watching, check the deadman’s switch and if it is set you know that a Rule has triggered the change.
Approach 2: Proxy Items
This approach uses Design Pattern: Proxy Item to determine the source of a change to a device. Each way to control the device will have its own Item with one Proxy Item to represent the consolidated state of the device. Then there is a Rule that gets triggered when any of those Items change and based on what Item received the command or changes we know the source of the command.
Items
- Group: holds all the Items so we can trigger the rule with member of triggers
- Proxy: holds the consolidated state, this Item is used in the UI
- Device: linked to the Channel to control the device and get updates
- Rules: used by rules to command the device, rules should use Proxy to determine the state of the device though
Be sure to disable autoupdate on at least the Proxy and Rules Items. If your device reports changes, disable autoupdate on that Item too. This prevents the Item from changing to a predicted state in response to the commands.
Group:Switch LightControls
Switch PorchLight_Proxy (LightControls) { autoupdate="false" } // represents the synchronized state, use on UIs
Switch PorchLight_Device (LightControls) { binding config, autoupdate="false" } // controls the device, updates from device
Switch PorchLight_Rules (LightControls) // control from Rules
Rules
Trigger the rule using members of LightControls
received command and another trigger using members of LightControls
changed. In the rule we can tell where a command or change came from based on the Item that triggered the rule.
Item | Event | What happened |
---|---|---|
Proxy | Command | Indicates the device was commanded from the UI |
Proxy | Change | Can be ignored, changes should only come from this rule |
Device | Command | Can be ignored, commands to the device should only come from this rule |
Device | Change | Indicates the device has changed state outside of OH (e.g. physically flipped the switch) |
Rules | Command | Indicates a rule commanded the device |
Rules | Update | Should never happen so can be ignored |
The rule can therefore determine the source of the event based on the type of the event and the Item that triggered the rule. The rule can then take what ever action necessary based on the source of the event which will usually involve updating the Proxy with the changes from the Device and commanding the Device with commands sent to the Proxy or Rules.
Blockly
First we use Design Pattern: Associated Items to determine the names of all the Items we need and we determine what type of event triggered the rule. If it was a:
- command to the proxy then command the device
- update from the device then update the proxy
- command from the rules then command the device, the proxy will be updated when the device reports it’s changed and the rule runs again
This shows the bare minimum that needs to be done by the rule to keep everything in sync. But you wouldn’t be reading this if you didn’t want to do something different depending on where the event comes from. We will add to this rule a calculation on whether the rule was manually triggered or triggered by automation.
JS Scripting
This is basically a JS implementation of the second version of the Blockly version above.
var basename = event.itemName.split('_')[0];
var proxy = basename+'_Proxy';
var device = basename+'_Device';
var rules = basename+'_Rules';
var isCommand = event.type == 'ItemCommandEvent';
// Calculate whether it's a manual event or a result of automation
var isManual = (( event.itemName == proxy && isCommand )
|| ( event.itemName == device && !isCommand ));
// Sync the Item states
if( event.itemName == proxy && isCommand ) {
log.info('Item commanded from the UI');
items[device].sendCommand(event.itemCommand.toString());
}
else if( event.itemName == device && !isCommand ) {
log.info('Item updated from the device');
items[proxy].postUpdate(event.itemState.toString());
}
else if( event.itemName = rules && isCommand) {
log.info('Item commanded from a rule');
items[device].sendCommand(event.itemCommand.toString());
}
else {
log.info('Rule triggered with an event we can ignore.');
}
if(isManual) {
// manually triggered
}
else {
// automation triggered
}
Advanages and Disadvantages
Advantages:
- Does not depend on timing of events.
Disadvantages:
- Requires a proliferation of new Items
Approach 2: Deadman’s Switch
In this approach we have a flag, an Item or shared variable accessible by the manual trigger checking rule. Under normal circumstances the flag is set to a default value (e.g. MANUAL
). When a rule that commands the Item runs, it sets the flag so something else (e.g. AUTO
) before sending the command and then back to MANUAL
when it’s done. Rules that care how the Item was commanded will check that flag.
In the implementations below, the shared cache is used to store the dead man’s switch. All three blow could be implemented using a boolean flag. I show using a String to show that one could determine which rule commanded the Item by setting the String to something specific to the rule and testing for that in the manual detection rule.
Blockly
We use the cache to store the deadman’s switch. Sending a command to an Item from a rule will look like the following where we store in the cache “AUTO” to show the command came from a rule:
Then in the manual detection rule that is triggered by commands to the Item we check the cache to determine how the Item was commanded.
JS Scripting (ECMAScript 11)
We will use the cache
to store the deadman’s switch. Sending a command to an Item from a rule will be as follows:
cache.shared.put(itemName, 'AUTO');
items.itemName.sendCommand(command);
The manual detection rule gets triggered by itemName
received command. This rule implements the extra steps to take for manual or auto detection and updates the dead man’s switch back to ‘MANUAL’.
if(cache.shared.get(itemName, () => 'MANUAL')) {
// code for manual commands
}
else {
cache.shared.put(itemName, 'MANUAL'); // set the flag back to MANUAL
// code for AUTO commands
}
Any rule that needs to know if an Item was manually commanded or commanded from another rule can check the cache. The function passed in that first get
initializes the cache if this is the first time the Item has received a command. If the value in the cache is anything but 'MANUAL'
we know that the Item was commanded from a rule. All other commands will be detected as a manual command.
Rules DSL
Same implementation as above using the shared cache. When commanding the Item put “AUTO” into the cache.
sharedCache.put(itemName, "AUTO");
itemName.sendCommand(command);
In the rule that triggers by commands to the Item and care if it was manually triggered:
if(sharedCache.get(itemName, [ | "MANUAL" ]) {
// manual command
}
else {
sharedCache.put(itemName, "MANUAL")
// automated command
}
Advantages and Disadvantages
Advantages:
- Simple to implement
- Doesn’t require extra Items
- Has no timing dependencies
Disadvantages:
- Does not differentiate between manually interacting with the device and commanding from the UI, it can only distinguish between rules commanding the Item and anything else.
Related Design Patterns
Design Pattern | How It’s Used |
---|---|
Design Pattern: Expire Binding Based Timers | Approach 1 uses Expire to reset the dead man’s switch |
[Deprecated] Design Pattern: Time Of Day | The Dead Man’s Switch is an implementation of a simple State Machine, uses the TOD example in Approach 2 |
Design Pattern: Working with Groups in Rules | Approach 2 uses a several Group operations |
Design Pattern: Separation of Behaviors | Approach 2 uses this for calculating whether it is cloudy |
Design Pattern: Associated Items | Used to access the Override switches in Approach 2 and accessing the controlling Items in Approach 3 |
Design Pattern: Proxy Item | The many proxy Items used in all three approaches. |