deconz-rest-plugin: Rules triggering logic
I’ve mentioned this in several other issues, but maybe it deserves an issue on its own: the way the deCONZ REST API plugin triggers rules differs significantly from the way the Hue bridge triggers rules. On the Hue bridge, rules are triggered when the last condition becomes true, on deCONZ while all conditions are true.
This causes many issues using rules created for the Hue bridge in deCONZ. While not unsolvable, re-writing the rules for deCONZ almost triples their footprint (as in number of rules and conditions needed to achieve the same logic).
As an example, on the Hue bridge I use a series of rules to control the lights in a room, based on:
- The virtual master switch of the room, a CLIPGenericFlag sensor say
/sensors/220; - The light sensor in the Hue motion sensor in that room, a ZLLLightLevel/ZHALight sensor, say
/sensors/222; - A CLIPGenericFlag sensor to indicate night mode, say
/sensors/2.
On the Hue bridge, I would use the following conditions, in a rule that recalls the daytime scene for the room (I actually use /sensors/222/state/dark in the second condition, but that’s another story):
"conditions": [
{
"address": "/sensors/220/state/flag",
"operator": "eq",
"value": true
},
{
"address": "/sensors/222/state/lightlevel",
"operator": "lt",
"value": 12000
},
{
"address": "/sensors/102/state/flag",
"operator": "eq",
"value": false
}
]
On the Hue bridge this rule fires when:
- The virtual switch is switched on while light level < 12000 and night mode is off;
- The light level drops below 12000 while the virtual switch is on and night mode is off;
- Night mode is switched off while the virtual switch is on and light level < 12000.
On the deCONZ this rule fires continuously while all three conditions hold, causing a scene recall every second. With a couple of rooms setup like this, the ZigBee network is swamped. To remedy this, I’d need three rules, each with an added dx condition. So instead of one rule with three conditions, I need three rules with a total of twelve conditions.
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Comments: 33 (10 by maintainers)
Commits related to this issue
- Fix rule triggering for changed values and in operator Related GH issue: https://github.com/dresden-elektronik/deconz-rest-plugin/issues/98 — committed to dresden-elektronik/deconz-rest-plugin by manup 7 years ago
- Fix rule triggering for changed values and in operator (2) Retrigger the rule if anything changes within the active **in** range. https://github.com/dresden-elektronik/deconz-rest-plugin/issues/98 — committed to dresden-elektronik/deconz-rest-plugin by manup 7 years ago
- Refactor rules handling based on events (experimental) This may break existing rules and needs more testing. * testet: operator dx, ddx, in, eq, gt, lt * unsupported: operators stable, not stable I... — committed to dresden-elektronik/deconz-rest-plugin by manup 7 years ago
- Combine sensor/state push events, fix duplicates If anything has changed in sensor state, push only one event which contains the complete state. Issues https://github.com/dresden-elektronik/deconz-r... — committed to dresden-elektronik/deconz-rest-plugin by manup 7 years ago
- Merge pull request #98 from Smanar/master Update — committed to dresden-elektronik/deconz-rest-plugin by Smanar 3 years ago
YES, let’s do this!
I’m afraid I cannot. Sorry, I’m afraid you need to “unthink” the current rules triggering logic in deCONZ and take a fresh look. Your first example is, in fact, artificial; the other two are not - I’ve been using both extensively.
The Hue documentation is very vague, so my understanding is based on reverse-engineering and lots of trial and error.
The most confusing part (if you will: paradigm shift) is that each element (object) in the
conditionsarray serves one of two distinct purposes:The rule
actionsare only executed when a trigger happens and all conditions hold. No need to considerlasttriggered. When evaluating a rule, only one element serves as trigger; the other elements serve as conditions.A trigger is always the result of a change, causing the element to become true. What change, depends on the
operator:dx: whenlastupdatedchanged just now;ddx: whenlocaltimereachedlastupdated+value;in: whenlocaltimereached the first time fromvalue;not in: whenlocaltimereached the last time fromvalue;eq: when the attribute changed from another value tovalue;lt: when the attribute changed from a value >=valueto a value <value;gt: when the attribute changed from a value <=valueto a value >value;stable: never;not stable: never.When a trigger occurs, the conditions (i.e. the other elements in the
conditionsarray) are evaluated. Again, the logic depends on theoperator:dx: always evaluates to false;ddx: always evaluates to false;in: evaluates to true ifflocaltimeis within the time range invalue;not in: evaluates to true ifflocaltimeis outside the range invalue;eq: evaluaties to true iff attribute =value;lt: evaluates to true iff attribute <value;gt: evaluates to true iff attribute >value;stable: evaluates to true ifflocaltime>=lastupdated+value;not stable: evaluates to true ifflocaltime<lastudated+value.So
dxandddxonly ever serve as trigger - consequently, you should only specify one of these operators in a rule. Also, when a rule contains one of these operators, you don’t need to consider any triggers from other elements, because thedxorddxelement evaluates to false anyways.stableandnot stableonly serve as conditions - a rule with only elements with these operators is never even evaluated. The other operators might serve as trigger or as condition, but only one at a time.Note the subtle differences: a rule with only an
ltelement executes when the attribute value becomes less thenvalue(the becoming less is the trigger). A rule with both altand adxelement executes when the attribute is less thanvalueafter the change, regardless of the previous value (lastupdatedis the trigger). For example: a rule withstatus lt 2executes when setting status from 2 to 1, but not when setting it from 0 to 1 or 1 to 0. A rule withstatus lt 2, lastupdated dxexecutes when setting status from 2 to 1, from 0 to 1, from 1 to 0, and even from 1 to 1. Likewise, a rule withdark eq trueexecutes whenlightlevelchanges from abovetholddarkto belowtholddark(becomingdarkis the trigger), but not when it changes from one value belowtholddarkto another value belowtholddark.The conditions of all rules linked to a trigger are to be evaluated before any action is executed. In particular the change of
lastupdatedthat caused adxorddxtrigger should evaluate to false for the changes caused by rule actions (see the Hue tap toggle example).Hope this helps - it’s not easy to explain.
I agree and suggest to adapt the behaviour of the fire then last condition becomes true. The sooner the better to not cause side effects when implementing it too late.
Great examples! The Hue Tap toggle makes it more clear to me.
When the rules are evaluated after event fired, at the beginning a snapshot of current state, of only related resources will be made. Evaluation will be made only with this snapshot. This (should) support rules like the Hue Tap toggle.
To properly support operators
lt,gtand similar, the resource item events will extended with previous value before set/change.Is it? This is may be not the best example, but if I PUT state
opentwice to aCLIPOpenClosesensor the lastupdated changes while state/open doesn’t.Hue allows
dxon other attributes. A rule I used for ‘arriving home after sunset’ with a CLIPPresence sensor. It is executed when presence becomes true:Web sockets are my main reason for switching to deCONZ 👍. I understand your point of view, however the Scene API is also quite different compared to Hue api. I’m also a user of your homebridge plugin. It is not my intention to give you extra work with this proposal. Although, recalling scenes using a HomeKit switch in homebridge-hue would be nice 😉 Would result in one API call, instead of one for every light in a HomeKit scene.
This is tricky, the Hue API doesn’t distinguish between triggers and conditions, unlike e.g. HomeKit. When a condition changes value, it might be a trigger (when the other conditions hold). When it doesn’t change value, it’s just a condition (regardless whether it’s refreshed).
Best give some examples.
On the Hue bridge a rule with
fires only when
state.flagchanges totrue, i.e. when PUTting{"flag": true}whilestate.flagisfalse. It does not fire when when PUTting{"flag": true}whilestate.flagwas alreadytrue. For that, you’d use:which fires when
state.lastupdatedchanges whilestate.flagistrue.state.flagis updated beforestate.lastupdated, so this rules fires whenever PUTting{"flag": true}, regardless of the (previous) value ofstate.flag. Same forddxvariants.In other words: including a condition for
state.lastupdatedforces any update (“refresh”) of the state to be (considered as) a trigger.As rule with
fires:
state.daylightchanges tofalse) whilestate.flagistrue; orstate.flagchanges totruewhilestate.daylightisfalse, i.e. when PUTting{"flag": true}whilestate.flagisfalseandstate.daylightisfalse.A rule with
fires when PUTting
{"flag": true}whilestate.daylightisfalse, irrespective of the (previous) value ofstate.flag, but not on sunset.A rule with
Fires:
state.flagistrue; orstate.flagbecomestruebetween 7:00 and 23:00, i.e. when PUTting{"flag": true}whilestate.flagisfalseandconfig.localtimeis between T07:00:00 and T23:00:00.In other words: only the first time in a range is (considered as) a trigger.
A rule with
fires when
state.lightlevelchanges from 13000 to 11000, but also when it changes from 11000 to 10000. A rule withfires only when
state.lightleveldrops below 12000 (assuming that’s the value ofconfig.tholddark).