Edit: Updates for OH 4
Please see Design Pattern: What is a Design Pattern and How Do I Use Them for information on what a DP is and how to use them.
Problem Statement
Often one faces one or more of the following situations:
- perform additional logic before sending a command to a device (e.g. make sure the device can safely receive the command)
- send a command to multiple devices based on a single event and Groups, the follow profile, or linking multiple Channels to one Item is not feasible
- the need to do something different based on the state of one or more other Items from the one that received the command
- the Channel to update the state of an Item and the channel to send commands to the device cannot be combined into one Item definition without problems (e.g. an infinite loop)
All of the above can be achieved through a Proxy Item.
Concept
A Proxy Item is simply an Item that stands in for one or more Items. Proxy Items are a special type of Design Pattern: Unbound Item (aka Virtual Item).
The Proxy Item stands in place of the “real” Items linked to the actual devices or action. All attempts to interact with the device must go through the Proxy Item. The Proxy Item is what gets placed on your sitemap and it is the Proxy Item which gets referenced by other Rules that may depend on the device’s state for any reason. Any command to control the device goes through the Proxy Item.
One or more Rules are written which trigger on commands and/or updates to the Proxy Item. These Rules perform any additional logic and forwards the commands on to the bound or linked Items or performs other actions (e.g. run a Scene Rule, block the command if the system isn’t in a state that can handle the command, alerting, etc.). Similarly, there may be one or more Rules which trigger on commands and/or updates to the bound or linked Items which interpret incoming States and sets the state of the Proxy Item accordingly, avoiding loops.
Simple Example
There is a device which has two separate communications paths, one for sending commands to and one for sending updates and, for some technical reason the two cannot be combined on the same Item. Perhaps the incoming channel always causes the Item to receive a command instead of just an update or there is additional logic that needs to be performed before just blindly accepting the incoming state update.
Items
Switch ProxySwitch "nice label" <icon> // NO Binding, add label and icon because this gets put on your sitemap
Switch BoundSwitch { blah blah blah } // send commands to device
Switch BoundSwitchUpdates { blah blah blah } // receive state updates from device
Rules
The general approach is to create one rule that triggers on commands to the Proxy that will do what ever preprocessing is required and then command the Switch. The second rule triggers on updates to the bound updates Item and forwards those updates to the Proxy.
These two rules can be combined into one if desired by testing the event type and triggering Item.
Blockly
Trigger the rule on commands to the ProxySwitch
and updates to BoundSwitchUpdates
.
JS Scripting
if(event.itemName == "ProxySwitch"
&& items.BoundSwitch.state != event.receivedCommand.toString()){
items.BoundSwitch.sendCommand(event.receivedCommand.toString());
}
else if(event.itemName == "BoundSwitchUpdates"
&& items.ProxySwitch.state != event.itemState.toString()){
items.ProxyItem.postUpdate(event.itemState.toString());
}
Python
from core.rules import rule
from core.triggers import when
from core.utils import sendCommandCheckFirst, postUpdateCheckFirst
@rule("ProxySwitch received command")
@when("Item ProxySwitch received command")
def proxy(event):
sendCommandCheckFirst("BoundSwitch", str(event.itemCommand))
@rule("BoundSwitchUpdates received update")
@when("Item BoundSwitchUpdates received update")
def bound(event):
postUpdateCheckFirst("ProxySwitch", str(event.itemState))
Rules DSL
rule "ProxySwitch received command"
when
Item ProxySwitch received command
then
if(BoundSwitch.state != receivedCommand) BoundSwitch.sendCommand(receivedCommand)
end
rule "BoundSwitchUpdates received update"
when
Item BoundSwitchUpdates received update
then
if(ProxySwitch.state != newState) ProxySwitch.postUpdate(newState)
end
More Complex Example
In this example an alert is sent when a command is sent to a garage door controller when the garage door controller is offline. This is a managed rule using JS Scripting inline scripts for the condition and action.
configuration: {}
triggers:
- id: "1"
configuration:
itemName: Large_Garagedoor_Opener
type: core.ItemCommandTrigger
- id: "2"
configuration:
itemName: Small_Garagedoor_Opener
type: core.ItemCommandTrigger
conditions:
- inputs: {}
id: "3"
configuration:
type: application/javascript;version=ECMAScript-2021
script: items.getItem('GarageControllerComputer').state != 'ON' ||
items.getItem('GarageControllerService').state != 'ON';
type: script.ScriptCondition
actions:
- inputs: {}
id: "4"
configuration:
type: application/javascript;version=ECMAScript-2021
script: >-
var {alerting} = require('rlk_personal');
alerting.sendAlert('Attempting to trigger a garage door but the controller is
not online!');
items[event.itemName+'_Linked"].sendCommand(event.receivedCommand.toString());
type: script.ScriptAction
Advantages and Limitations
This design pattern gives one a lot of control over how and when commands to and from a device occur. It also allows for the combining of multiple separate Items into one logical Item. But it enables these capabilities by adding complexity to the configuration.
Related Design Patterns
Design Pattern | How It’s Used |
---|---|
Design Pattern: Unbound Item (aka Virtual Item) | A Proxy Item is a special case for Virtual Items |
Design Pattern: Manual Trigger Detection | Uses a Proxy Items in Approach 3 |
Design Pattern: Gate Keeper | Uses a Proxy Item to centralize the commands to the constrained device |
Design Pattern: Separation of Behaviors | Uses a Proxy Item to kick off the cross cutting Rule |
Design Pattern: Sensor Aggregation | Uses a Proxy Item to track the anti-flapped presence state |