Reality check
When implementing subscription for all events, ran into the following rules/limits/features I had to impose.
Simple mode:
- If there are any triggers present in the IF block, the list of unique device/attribute pairs involved in the triggers is used for subscription. Regular conditions are ignored for the purpose of subscribing
- If there are no triggers present in the IF block, the list of unique device/attribute pairs involved in the conditions is used
By unique, I mean that if you use “This door is open” twice in the logical tree, I only subscribe to it once. It may be that ST filters out the second subscription, but I don’t want to rely on them
Else-If mode:
- same as Simple, BUT:
- second IF does NOT create any subscriptions. This is because the second IF is to be evaluated only after the first one is evaluated. To keep things sane, the second IF block should NOT be able to trigger the evaluation of the first, therefore, I am forced to NOT allow triggers in the second IF block - after all, that IF is supposed to be evaluated when the first IF is false.
Latching mode:
- complicated
- each of the two IFs runs its own logic flow, however, a trigger from the first IF should not trigger the evaluation of the second IF block, unless the same device/attribute is used in that block too. Therefore, I had to subscribe to each block separately. But I don’t want to subscribe to the same device/attribute twice (say you use the same pair in the IF block and in the BUT IF block as well). For those that are common to both blocks, I run separate subscriptions that have a different handler that then broadcasts the event to both blocks. I do NOT want to subscribe to the same device/attr twice, no matter what.
Attention needs to be paid to the type of event (trigger vs condition). Since I subscribe to a device/attr for both lists, a case may happen where the IF block uses it as a trigger, whereas the BUT IF uses it as a condition. I need to check the following: before processing any event, for any of the two blocks (IF or BUT IF), perform the following checks:
- does the block use triggers? if no, go to 3
- does the block have a trigger (not condition) matching the device/attr in the event? if not, exit
- evaluate the block, using the event as the source of the evaluation (later used for logging)
These are all internals, but I thought I should share them with you so you can react in case my logic is not sane.
The “subscribe” logic so far:
def subscribeToConditionDevices(condition, triggersOnly, handler, subscriptions, onlySubscriptions, excludeSubscriptions) {
if (subscriptions == null) {
subscriptions = [:]
}
def result = 0
if (condition) {
if (condition.children != null) {
//we're dealing with a group
for (child in condition.children) {
subscribeToConditionDevices(child, triggersOnly, handler, subscriptions, onlySubscriptions, excludeSubscriptions)
}
} else {
if (condition.trg || !triggersOnly) {
//get the details
def devices = settings["condDevices${condition.id}"]
def attribute = cleanUpAttribute(settings["condAttr${condition.id}"])
if (devices) {
for (device in devices) {
def subscription = "${device.id}-${attribute}"
if ((excludeSubscriptions == null) || !(excludeSubscriptions[subscription])) {
//if we're provided with an exclusion list, we don't subscribe to those devices/attributes events
if ((onlySubscriptions == null) || onlySubscriptions[subscription]) {
//if we're provided with a restriction list, we use it
if (!subscriptions[subscription]) {
subscriptions[subscription] = true //[deviceId: device.id, attribute: attribute]
if (handler) {
//we only subscribe to the device if we're provided a handler (not simulating)
log.trace "Subscribing events from $device for attribute $attribute, handler is $handler"
subscribe(device, attribute, deviceHandler)
}
}
}
}
}
} else {
return
}
}
}
}
return subscriptions
}
def subscribeToAll(app) {
//we have to maintain two separate logic threads for the latching mode
//to do so, we first simulate
def triggerCount = getConditionTriggerCount(app.conditions)
def latchingTriggerCount = 0
if (settings.mode == "Latching") {
//we really get the count
latchingTriggerCount = getConditionTriggerCount(app.otherConditions)
//simulate subscribing to both lists
def subscriptions = subscribeToConditionDevices(app.conditions, triggerCount > 0, null, null, null, null)
def latchingSubscriptions = subscribeToConditionDevices(app.otherConditions, latchingTriggerCount > 0, null, null, null, null)
//we now have the two lists that we'd be subscribing to, let's figure out the common elements
def commonSubscriptions = [:]
for (subscription in subscriptions) {
if (latchingSubscriptions.containsKey(subscription.key)) {
//found a common subscription, save it
commonSubscriptions[subscription.key] = true
}
}
//perform subscriptions
subscribeToConditionDevices(app.conditions, false, bothDeviceHandler, null, commonSubscriptions, null)
subscribeToConditionDevices(app.conditions, triggerCount > 0, deviceHandler, null, null, commonSubscriptions)
subscribeToConditionDevices(app.otherConditions, latchingTriggerCount > 0, latchingDeviceHandler, null, null, commonSubscriptions)
} else {
//simple IF case, no worries here
subscribeToConditionDevices(app.conditions, triggerCount > 0, deviceHandler, null, null, null)
}
}