WARNING: Users who have this template already installed for versions of OH 5.1 and before, after removing the old template and installing this new one, open your rule(s), click on the code tab, and change the ruleTemplate property to ohrt-driveTime before regenerating the rule.
Problem Statement
Frequently one wants to do something when one or more Items exceed or fall below a certain threshold, matches a given state for a given amount of time, or fails to change state (or update) for a certain amount of time. Put simply: “When an Item remains in a given state for a certain amount of time, do something.”
For example:
-
When the temperature remains above 75 °F for five minutes, turn on the air conditioner, turn it off when the temp reaches or falls below 70 °F.
-
When motion is detected turn on a light and then turn it off five minutes after the last motion is detected.
-
Send an alert when a door or window is left open for more than an hour.
-
Set a sensor Item to UNDEF if it doesn’t receive an update for fifteen minutes.
This rule template requires the openhab_rules_tools library to be installed. See openHAB Rules Tools Announcements for details. You can install it from openhabian-config and there is a rule template which may work for you to install it. See the announcements for all the ways to install this library.
See Threshold Alert and Open Reminder [4.0.0.0;5.1.9.9] - #2 by rlkoshak for some configuration examples with explanation. This template can do a lot of different things depending on the parameters configured because the use case is really common.
What the Rule Does
This rule is triggered by changes to the members of a Group. The Group should have members that are all of the same type of Item. At this time it does not support a mix of compatible Item types (e.g. Switches, Dimmers and Color Items) except in the case where it’s looking for NULL or UNDEF as the alerting state.
When the rule triggers, it compares the state of the Item with a threshold state using a user selected comparison.
- If the comparison evaluates to
true(orfalseif invert is enabled), an optional user defined rule is called. - After a user defined delay an optional alert rule is called.
- When the Item leaves this alerting state, the rule will optionally call an end alert rule, but only if the threshold alert rule was called.
- Any call to any rule that occurs during a user defined Do Not Disturb (DND) period are delayed to the end of that period. Only the most recent call gets made.
- Finally, an optional reminder period can be defined to call the alert rule repeatedly while the Item continues to meet the threshold comparison. These reminders also follow the do not disturb period.
The initial alert rule, alert rule, and end alert rule will receive these parameters passed to them when called:
| Variable | Purpose |
|---|---|
alertItem |
name of the Item that generated the alert |
alertState |
current state of the Item |
isAlerting |
boolean, when true the Item meets the threshold comparison, when false the Item exited the threshold state |
isInitialAlert |
boolean, when true the Item just now met the threshold comparison, when `false, the Item has been in this state for awhile. |
threshItems |
JavaScript Array of JavaScript Item Objects whose state meet the alerting criteria |
threshItemLabels |
JavaScript Array of the Item labels whose state meet the alerting criteria |
nullItems |
JavaScript Array of JavaScript Item Objects whose state is NULL or UNDEF |
nullItemLabels |
JavaScript Array of the Item labels whose state is NULL or UNDEF |
To access these attributes in your called rule varies from language to language. Blockly has a context block. JS Scripting ECMAScript 2021 injects them as variables (e.g. alertItem) but it’s better to reference them using ctx.alertItem. Nashorn JS Scripting (and other JSR223 rules engines) makes them available via context.getAttribute('alertItem');. Rules DSL does not support passed in values like this.
Note, the arrays of JS Items are left for backwards compatability and because they will continue to work in some circumstances. However, they gor through the new javaify and jsify process and may not work in all use cases. If it breaks for you, pull the Items from the registry using the names array.
Rule Properties
| Property | Format | Required | Default | Purpose |
|---|---|---|---|---|
| Triggering Group | Group Item | X | Changes to it’s members cause the rule to run. | |
| Threshold State | String | X | The state to compare the members of Triggering Group to using the Comparison Operator. If it’s parsable as a number, a number comparison will be made. If it’s a number with units, a Quantity comparison will be made. UnDefType, NULL and UNDEF are all normalized to UnDefType and treated as the same state. In all other cases a string comparison will be made. |
|
| Comparison Operator | X | The comparison operator to use (e.g. ==) |
||
| Invert Comparison | Boolean | false |
Invert the result of the comparison. | |
| Reschedule | Boolean | false |
When true, if an alert event occurs when an alerting timer already exists, the timer will be rescheduled instead of ignoring the new event. The different is whether the alert occurs based on the first alerting event or the most recent alerting event. |
|
| Hysteresis | String | '' |
Optional parameter which, when populated, will wait until the Item’s state changes at least that amount from the threshold before disabling the alert. Only applied for states which are Numbers or QuantityTypes. If the comparison is >, the Item will need to drop at least this amount below the threshold before alerting ends, and vice versa for <. If == the value needs to move above or below the threshold by this amount. It doesn’t make sense to use with !=. |
|
| Alert Delay | ISO8601 duration string (e.g. PT15M) | '' |
How long to wait after the Item meets the threshold comparison before calling the Alert Rule. Leave blank to call the rule immediately. | |
| Reminder Period | ISO8601 duration string | '' |
How long to wait after the first call to the alert rule before calling it again as a reminder. The rule will continue to be recalled by this period until the Item no longer meets the threshold comparison. | |
| Metadata Namesapce | String | thresholdAlert |
An optional metadata namespace where a per Item override for Alert Delan and Reminder Period can be defined (see below). | |
| Alert Rule | Rule UID | X | Rule called when the Item meets the threshold comparison and repeatedly thereafter if Reminder Period is defined. | |
| End Alert Rule | Rule UID | '' |
Rule called when the Item first exits the threshold comparison. This can be the same as Alert Rule or Initial Alert Rule. | |
| Initial Alert Rule | Rule UID | '' |
Rule called when the Item first meets the threshold comparison. This can be the same as Alert Rule or the End Alert Rule. | |
| Do Not Disturb Start Time | HH:MM |
'' |
The start time for the optional DND. If this is after the End DND time, the period will span midnight. | |
| Do Not Disturb End Time | HH:MM |
'' |
The end time for the optional DND. The most recent call to either Alert Rule or End Alert Rule will be delated until this time. | |
| Gatekeeper Delay | Number | 0 |
The minimum amount of time between calls to the alert and end alert rules before the next call will be allowed to occur. If you experience “Multi-threaded” exceptions, increase this time until they stop. The time is in milliseconds. If using a DND period, set this to something between 500 and 1000 depending on how long your alerting rule takes to run. | |
| Rate Limit | ISO8601 Duration String | '' |
The minimum amount of time between calls to the alert rule before the next call will be allowed to occur. Unlike Gatekeeper Delay, the rate limit will simply drop any events that occur rather than queueing them up and working them off in sequence. If used with Gatekeeper Delay, Rate Limit should be significantly longer. |
Any or all of the above properties except for Triggering Group and Metadata Namespace can be defined at the Item level using Item metadata with the addition of one extra parameter. Instead of threshold, a thresholdItem can be defined which will cause the state of that Item to be used as the threshold instead of the statically defined threshold. In the UI a full set of metadata should look like the following
value: " "
config:
thresholdItem: OfficeHumidity_Setpoint
operator: <
invert: false
reschedule: false
hysteresis: 4 %
alertDelay: PT1M
remPeriod: PT15M
alertRuleID: new_humidity_proc
endRuleID: new_humidity_proc
initAlertRuleID: new_humidity_proc
dndStart: 22:01
dndEnd: 08:02
gatekeeperDelay: 10
rateLimit: PT30M
All of these metadata are optional. If not present, the rule’s setting will be used for that Item. These should be used to override a default when one or two Items need one or two different parameters (e.g. one door needs to alert at five minutes when left open instead of one hour).
dndStart and dndEnd must both be present or both be absent and represent a time using either 24 hour (e.g. 22:00) or 12 hour (e.g. 10:00 PM) format.
gatekeeperDelay, when present, must be a positive integer.
Running the rule manually will cause the rule to verify the configuration of the rule parameters and the Item metadata configurations. Watch the logs for warnings and errors.
Requirements
Language: JS Scripting ECMAScript 2021
Dependencies:
- A script to call when an Item meets the threshold
- A Group containing all the Items to trigger this rule. All members of the Group must be the same type.
- openhab-js 5.21.0+
- openhab_rules_tools 2.2.1+
TODO:
- DateTime comparisons (i.e. a DateTime Item’s state is too long ago)
- Support using separate DateTime Items for DND periods (so we can use Astro based times)
- Option to operate at the semantic model equipment level (alert is called on Equipment, not point Items)
- Option to ignore events during DND instead of moving them to the end of DND
Changelog
Version 1.9
- migrated to the new YAML rule template format
Version 1.8
- Save the alerting status of Items to their metadata and restore the alerting status on a restart. To clear the metadata from the Items run the rule manually to perform a config test.
Verison 1.7
- Cannot rely on the helper library version number to know if the event is wrapped in JS or not. So I test to see whether newState or receivedState are undefined (if both are the rule was triggered with the JS wrapper. I test already to catch the case where neither are defined becuase it wasn’t an Item changed or updated event.
- When the event is wrapped with JS, even manual rule triggers now have an event so I need to test the eventName as well as the event no existing for manual triggers.
Version 1.6
- Adjusted to not log warning about use of deprecated properties on the event Object in openhab-js versions 5.14.0+ (i.e. OH 5.1 and later).
Version 1.5
- Fixed a temporary problem introduced by openhab-js 5.11.3 which I think has since been fixed. If the state of an Item is not a Quantity and one calles
quantityState, it should returnnullbut for this version of the library it throws and exception. This broke theinit()function which validates the rule configuration. This version provides a fix so that no problem should occur if the exception is thrown or the function returnsnull.
Version 1.4
- Fixed regression which breaks comparisons to UnDefType.
- removed stray newline
Version 1.3
- Fixed an issue where the alert timer uses what ever logger name that was used the last time the rule was run. Since the Item name is part of the logger name this creates logs that look like they are from a different Item.
- Fixed a potential issue with the alert timer generator. The record Object was passed into the generator but if it’s passed by reference, the generator will use what ever record was last used by the rule. I don’t actually think this was a real error becuase then the rule probably wouldn’t work at all, but pulling the record from the cache from inside the timer is a better idea anyway.
Version 1.2
- oops, there is no v1.2
Version 1.1
- fixed a minor bug that occurs when an alert occurs between the DND time and the rule fails to reschedule the alert to the end of the DND time.
Version 1.0
- promoted to version 1.0
- fixed a bug with the end alert looping timer being recreated even though it already exists, resulting in many looping timers building up during the DND period for each Item You would only see this if you have a DND period defined and an Item alerts and then ends alerting during the DND period.
Version 0.14
- Fixed misspelling in log statement
- Call the initialAlertRule the first time entering the alert state
- Cancel timers before setting them to
null - Loop through the values of the records, not the keys, when looking for timers to cancel
Version 0.13
- Do not pass raw ZonedDateTime Objects to create timers. That’s failing for some reason.
Version 0.12
- adjusting for changes to OHRT updates.
Version 0.11
- Various fixes
| Function | Problem | Fix |
|---|---|---|
stateToValue |
The JS function isNaN returns true for empty string causing problems when hysteresis was not set. |
Added if clause to handle empty string explicitly. |
isAlertingState |
The ternary operation in the log statement was not working, showing the wrong value in the logs. | Calculate the return value and save to a variable instead of recalculating it in the log statement. |
notAlerting |
Testing the wrong timer in record before cancelling initAlertTimer |
Fixed if statement |
refreshRecord |
It’s not clear which value is being sent to stateToValue for conversion. |
Added log statements before calls to stateToValue indicating what is being converted (state, threshold, hyst). |
procEvent |
state is already converted to a value before this function is called |
Use state instead of converting it again. |
procEvent |
record[name] should only be initialized once |
Don’t reinitialize if one of the timers or alerted do not exist. Treat those cases as an error. |
procEvent |
Code simplicity | Let notAlerting handle the case where we are no longer alerting but there is no endAlert rule to call. |
refreshRecord |
Missing newline after “Init Alert Rule ID” in log statement. | |
Body |
Missing case for ItemStateUpdatedEvent |
Added case for ItemStateUpdateEvent to handle cases where the default trigger is changed to received update from changed. |
Body |
It’s not always clear when the rule is triggered manually | Change debug log statement to info level when manually triggered. |
Version 0.10
- Handles the new to OH 4.0.0 M2 and later ItemStateUpdatedEvent
Version 0.9
- give each Item it’s own logger to make tracing logs easier
- schedule initAlertTimer to the end of DND like the alertTimer and endAlertTimer
Version 0.8
- normalize
UNDEFandNULLtoUnDefTypeso both are treated as the same - added an initial alert rule called when the Item first enters the alerting state to make motion sensor light type use cases easier to implement
Version 0.7
- refresh the Item’s settings inside the looping timer so reminders always run with the latest Item metadata settings
- cancel any running timers in
int()
Version 0.6
- added a
rescheduleparameter which causes the rule to call the alert rule alert duration after the most recent alerting event instead of after the first alerting event. This implements alerting when an Item doesn’t change for too long a time, mostion sensor timers, etc..
Version 0.5
- added ability to define another Item whose state will be used as the threshold (
thresholdItem) - any and all rule properties can be overridden through Item metadata
- todo: update the docs above to cover the metadata and throughly test the config checking
Version 0.4
- added rate limiting option
Version 0.3
- catch and log exception if the alerting rule no longer exists or is disabled
- pass a list of all the Items and their labels that are null/undef and those that are alerting to have parity with the old Threshold Alert rule to ease migration.
Version 0.2
- added support for hysteresis
Version 0.1
- initial release
Sponsorship
If you want to send a tip my way or sponsor my work you can through Sponsor @rkoshak on GitHub or PayPal. It won’t change what I contribute to OH but it might keep me in coffee or let me buy hardware to test out new things.
