This is a collection of blocks that allow Blockly users to leverage many of the capabilities of the openhab_rules_tools library, a JS Scripting library that provides a number of classes and utilities that add advanced capabilities to timers and provides utilities to help make managing timers easier, among other things.
This library requires openhab_rules_tools to be installed. This can be installed from openhabian-config or by running the command npm install openhab_rules_tools
from the $OH_CONF/automation/js
folder.
When you use a block from this library, the versions of the openhab-rules-tools library and the openhab-js libraries are checked and an exception thrown if the library is not compatible with the installed versions.
ToDo:
- groupUtils, this one may not be feasible
- updates to OHRT so the above work with the standard Blockly blocks (e.g. reschedule a CountdownTimer)
Blocks
Countdown Timer
This block instantiates a CountdownTimer to run at the given time. The timer is stored in the given cache under the given name, and if there is an error in the timer, the error will be reported under that name also. Every second, the passed in countdown Item is updated with the remaining amount of time left until the Timer function is run. If the Item is a Number Item, the number of seconds left until the Timer runs is posted to that Item. If itâs a String Item, the amount of time left in HH:MM:SS format is posted to the Item.
The timer created by this block can be used with the standard âcancelâ and âhasTerminatedâ blocks in the âTimers and Delaysâ category (support for the rest will be added at a future date).
The above example will schedule a timer to log âTimer expiredâ after ten seconds. TestNumber will be updated to 10 to start and every second it will be updated with 9, 8, and so on until the timer expires.
Looping Timer
Creates a timer to run after the given time and reschedules itself to run again after the same delay as long as the passed in condition evaluates to false.
The timer created by this block can be used with the standard âcancelâ and âhasTerminatedâ blocks in the âTimers and Delaysâ category (support for the rest will be added at a future date).
The above example will log âlooping timer 1 Xâ every second five times where X counts from 0 to 4.
Deferred
This block lets you easily schedule a command or update to an Item at a future time. The same Deferred Object can be used to schedule commands/updates to multiple Items. There is no need to instantiate more than one.
Deferred commands/updates can be cancelled using the âcancel managed timerâ and âcancel all managed timersâ blocks (see below).
The above example is kind of contrived and doesnât actually do anything.
The first block schedules an OFF command to TestSwitch in five seconds.
The second block schedules an update to 10 to TestNumber in ten seconds.
The third block cancels the scheduled command to TestSwitch.
The fourth block cancels all the scheduled commands and updates on âdnâ.
Gatekeeper
Gatekeeper is a class that allows one to space out how quickly some code can run after the last block of code ran. For example, if you have a technology that cannot handle a blast of a bunch of commands all at once, Gatekeeper can be used to space out the commands so there is some space between them.
What makes it distinct from Rate Limit is it queues up the calls and works them off in order with the given time between them. This makes it useful for other cases, such creating a sequence of timed events like one would use to control irrigation.
Gatekeeper works with the âcancel all managed timersâ block (see below).
In the above example, IrrigationZone1 will be commanded ON. After five minutes itâs commanded OFF and IrrigationZone2 is commanded to ON. After ten minutes itâs commanded to OFF and IrrigationZone3 is commanded to ON. After two minutes itâs commanded to OFF. Passing a time of 0 seconds to Gatekeeper will allow subsequent calls to run immediately.
Rate Limit
This is just like Gatekeeper except that if itâs too soon after the last call to Rate Limit, the call is completely dropped instead of being queued up and run. This can be useful to debounce a noisy sensor. Calls to this block cannot be cancelled.
In the above example you will see âRateLimit 1â and âRateLimit 3â in the logs but not âRateLimit 2â because it happens before the 2 seconds are up.
TimerMgr
TimerMgr manages one or more Timerâs entire life cycle through a simple interface. It is most useful when one has one rule that needs to manage multiple timers, one for each Item that can trigger the rule.
Property | Purpose |
---|---|
private/shared | which cache the TimerMgr is stored in |
TimerMgr Name | used as the key to pull the TimerMgr from the cache |
âkeyâ | used as the key for the specific timer being managed |
0 | duration of the timer, must be a number |
seconds/minutes/hours | the unit of the duration |
reschedule if exists | when checked, if there is already a timer by the name âTimerMgr Nameâ, reschedule it, otherwise cancel it |
run at scheduled time | optional, the code to run when the timer expires |
run if already exists | optional, the code to run if a timer by the name âkeyâ already exists |
Timers created with this block work with the âcancel managed timerâ and âcancel all managed timersâ blocks (see bleow).
For example:
The above will log âCreating timerâ and then create a timer named âfooâ to run after five seconds. If the timer already exists it will be rescheduled for five seconds from now and âflappingâ will be logged.
The next block will log âTimer foo exists falseâ because there is no âfooâ timer scheduled. If the âbarâ were changed to âtrueâ it would log âTimer foo exists trueâ.
The next block waits one second. Then âReschedule timerâ is logged.
The next block behaves differently from the first because the timer already exists. You will see âflappingâ in the log because the timer already exists and then the timer is rescheduled for five seconds from now. (Note: typically youâd have the one block which gets called with subsequent calls to a rule).
If we stopped there, after five seconds, it would log out âtimer expiredâ.
If we continue you see how to cancel all timers in a timer manager and how to cancel an individual timer managed by a timer manager.
cancel managed timer
The standard cancel timer block under Timers and Delays will work for Countdown Timer but for managed timers both the manager (e.g. TimerMgr or Deferred) in addition to the âkeyâ for the Timer itself needs to be passed. See examples above.
cancel all managed timers
This will cancel all the managed timers that are part of a TimerMgr or Deferred manager. See examples above.
timer exists
Returns true if a timer by the given âkeyâ exists in the given TimerMgr. See example above.
to today/tomorrow/yesterday
Change the date portion of the passed in Date Time block to yesterday, today, or tomorrow.
The above example creates a date time four days into the past and logs it. It then logs the results of to today, to tomorrow, and to yesterday and the logs will show that the date changes accordingly.
Changelog
Version 0.5
- Fixed problem where only static numbers can be used for the timer duration. Now a variable, the âget numericState from itemâ or any other block/combination of blocs that eventually evaluate to a number can be used.
- Fixed âUtilityâ typos.
- Next version that includes updates to the OHRT library will become 1.0
Version 0.4 (first published version, upgrade status to beta as itâs ready for testing)
- a better first example created for Gatekeeper and the image that represents the library
- implemented looping timer
- fixed all that was broken on 0.3 (there was a lot, sorry about that)
- made better use of utilities to make the generated code shorter and easier to read and maintain
- added examples for all block types
- all blocks have been tested
Version 0.3
- cancel block for managed timers (Deferred, TimerMgr)
- cancel all block for managed timers
- timeUtils
- version checking
Version 0.2
- fixed bug in implementation of Countdown Timer
- added Deferred, Gatekeeper, Rate Limit, and TimerMgr
Version 0.1
- initial release with preliminary implementation of CountdownTimer
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.
Resources
uid: ohrt
tags:
- marketplace:153371
props:
parameters: []
parameterGroups: []
timestamp: Feb 3, 2024, 11:54:54 AM
component: BlockLibrary
config:
name: openHAB Rules Tools
slots:
blocks:
- component: BlockType
config:
args0:
- check: Number
name: DURATION
type: input_value
- name: UNIT
options:
- - seconds
- S
- - minutes
- M
- - hours
- H
type: field_dropdown
- name: CACHE
options:
- - private
- private
- - shared
- shared
type: field_dropdown
- check: String
name: TIMER_NAME
type: input_value
- name: COUNTDOWN_ITEM
type: input_value
- name: TIMER_FUNC
type: input_statement
colour: 0
helpUrl: https://github.com/rkoshak/openhab-rules-tools
inputsInline: true
message0: after %1 %2 with %3 %4 using countdown item %5 %6
nextStatement: ""
previousStatement: ""
tooltip: Creates a countdown timer stored in the selected cache and the supplied countdown Item to count down the seconds until the timer runs.
type: ohrt_countdown_timer
slots:
code:
- component: BlockCodeTemplate
config:
template: >
if(!cache.{{field:CACHE}}.exists({{input:TIMER_NAME}}) || cache.{{field:CACHE}}.get({{input:TIMER_NAME}}).hasTerminated()) {
cache.{{field:CACHE}}.put({{input:TIMER_NAME}},
{{utility:OHRT}}.CountdownTimer(
'PT' + {{input:DURATION}} + '{{field:UNIT}}',
() => {
{{statements:TIMER_FUNC}}
cache.{{field:CACHE}}.put({{input:TIMER_NAME}}, null);
},
{{input:COUNTDOWN_ITEM}},
{{input:TIMER_NAME}}));
} // TODO: optionally reschedule
toolbox:
- component: PresetInput
config:
fields:
NUM: 0
name: DURATION
shadow: true
type: math_number
- component: PresetInput
config:
fields:
TEXT: MyTimer
name: TIMER_NAME
shadow: true
type: text
- component: PresetInput
config:
name: COUNTDOWN_ITEM
shadow: true
type: oh_item
- component: BlockType
config:
args0:
- name: CACHE
options:
- - private
- private
- - shared
- shared
type: field_dropdown
- cherck: String
name: TIMER_NAME
type: input_value
- check: Number
name: DURATION
type: input_value
- name: UNIT
options:
- - seconds
- S
- - minutes
- M
- - hours
- H
type: field_dropdown
- type: input_dummy
- name: TIMER_FUNC
type: input_statement
- check: Boolean
name: CONDITION
type: input_value
colour: 0
helpUrl: https://github.com/rkoshak/openhab-rules-tools
inputsInline: true
message0: create a looping timer %1 %2 to run every %3 %4 %5 with %6 until %7
nextStatement: ""
previousStatement: ""
tooltip: Creates a looping timer that will run the code periodically until the condition becomes true.
type: ohrt_looping_timer
slots:
code:
- component: BlockCodeTemplate
config:
template: >
{{utility:getLoopingTimer}}(cache.{{field:CACHE}}, {{input:TIMER_NAME}}).loop(() => {
{{statements:TIMER_FUNC}}
return ({{input:CONDITION}}) ? null : 'PT' + {{input:DURATION}} + '{{field:UNIT}}';
},'PT' + {{input:DURATION}} + '{{field:UNIT}}', {{input:TIMER_NAME}});
toolbox:
- component: PresetInput
config:
fields:
TEXT: MyTimer
name: TIMER_NAME
shadow: true
type: text
- component: PresetInput
config:
fields:
NUM: 0
name: DURATION
shadow: true
type: math_number
- component: PresetInput
config:
name: CONDITION
type: logic_boolean
- component: BlockType
config:
args0:
- check: Number
name: DURATION
type: input_value
- name: UNIT
options:
- - seconds
- S
- - minutes
- M
- - hours
- H
type: field_dropdown
- name: IS_COMMAND
options:
- - command
- "true"
- - update
- "false"
type: field_dropdown
- name: ITEM
type: input_value
- check: String
name: VALUE
type: input_value
- name: CACHE
options:
- - private
- private
- - shared
- shared
type: field_dropdown
- check: String
name: TIMER_NAME
type: input_value
colour: 0
helpUrl: https://github.com/rkoshak/openhab-rules-tools
inputsInline: true
message0: after %1 %2 %3 item %4 to %5 with %6 %7
nextStatement: ""
previousStatement: ""
tooltip: Creates a deferred Object which can be used to schedule a command or update to an Item.
type: ohrt_deferred
slots:
code:
- component: BlockCodeTemplate
config:
template: >
{{utility:getDeferred}}(cache.{{field:CACHE}}, {{input:TIMER_NAME}}).defer({{input:ITEM}}, {{input:VALUE}}, 'PT' + {{input:DURATION}} + '{{field:UNIT}}', {{field:IS_COMMAND}});
toolbox:
- component: PresetInput
config:
fields:
NUM: 0
name: DURATION
shadow: true
type: math_number
- component: PresetInput
config:
fields:
TEXT: ""
name: VALUE
shadow: true
type: text
- component: PresetInput
config:
name: ITEM
shadow: true
type: oh_item
- component: PresetInput
config:
fields:
TEXT: true
name: VALUE
shadow: true
type: text
- component: PresetInput
config:
fields:
TEXT: Deferred Name
name: TIMER_NAME
shadow: true
type: text
- component: BlockType
config:
args0:
- check: Number
name: DURATION
type: input_value
- name: UNIT
options:
- - seconds
- S
- - minutes
- M
- - hours
- H
type: field_dropdown
- name: CACHE
options:
- - private
- private
- - shared
- shared
type: field_dropdown
- check: String
name: TIMER_NAME
type: input_value
- name: TIMER_FUNC
type: input_statement
colour: 0
helpUrl: https://github.com/rkoshak/openhab-rules-tools
inputsInline: true
message0: run this and prevent another run for %1 %2 using gatekeeper %3 %4 %5
nextStatement: ""
previousStatement: ""
tooltip: Creates a gatekeeper Object if it does not already exist to prevent code from running too quickly after the last run. Gatekeeper queues subsequent calls and works them off in order with the specified delays between them.
type: ohrt_gatekeeper
slots:
code:
- component: BlockCodeTemplate
config:
template: >
{{utility:getGatekeeper}}(cache.{{field:CACHE}}, {{input:TIMER_NAME}}).addCommand('PT' + {{input:DURATION}} + '{{field:UNIT}}', () => {
{{statements:TIMER_FUNC}}
});
toolbox:
- component: PresetInput
config:
fields:
NUM: 0
name: DURATION
shadow: true
type: math_number
- component: PresetInput
config:
fields:
TEXT: Gatekeeper Name
name: TIMER_NAME
shadow: true
type: text
- component: BlockType
config:
args0:
- check: Number
name: DURATION
type: input_value
- name: UNIT
options:
- - seconds
- S
- - minutes
- M
- - hours
- H
type: field_dropdown
- name: CACHE
options:
- - private
- private
- - shared
- shared
type: field_dropdown
- check: String
name: TIMER_NAME
type: input_value
- name: TIMER_FUNC
type: input_statement
colour: 0
helpUrl: https://github.com/rkoshak/openhab-rules-tools
inputsInline: true
message0: run this and prevent another run for %1 %2 using rate limit %3 %4 %5
nextStatement: ""
previousStatement: ""
tooltip: Creates a rate limit Object if it does not already exist to prevent code from running too quickly after the last run. Rate limit drops calls that happen within the duration.
type: ohrt_rate_limit
slots:
code:
- component: BlockCodeTemplate
config:
template: >
{{utility:getRateLimit}}(cache.{{field:CACHE}}, {{input:TIMER_NAME}}).run(function() {
{{statements:TIMER_FUNC}}
}, 'PT' + {{input:DURATION}} + '{{field:UNIT}}');
toolbox:
- component: PresetInput
config:
fields:
NUM: 0
name: DURATION
shadow: true
type: math_number
- component: PresetInput
config:
fields:
TEXT: RateLimit Name
name: TIMER_NAME
shadow: true
type: text
- component: BlockType
config:
args0:
- name: CACHE
options:
- - private
- private
- - shared
- shared
type: field_dropdown
- check: String
name: TIMER_MGR_NAME
type: input_value
- check: String
name: TIMER_KEY
type: input_value
- check: Number
name: DURATION
type: input_value
- name: UNIT
options:
- - seconds
- S
- - minutes
- M
- - hours
- H
type: field_dropdown
- checked: true
name: RESCHDULE
type: field_checkbox
- name: TIMER_FUNC
type: input_statement
- name: FLAPPING_FUNC
type: input_statement
- type: input_dummy
colour: 0
helpUrl: https://github.com/rkoshak/openhab-rules-tools
inputsInline: true
message0: using timer manager %1 %2 check %3 to run after %4 %5 reschdule if exists %6 %9 run at scheduled time %7 run if already exists %8
nextStatement: ""
previousStatement: ""
tooltip: Manages the life cycle of multiple timers.
type: ohrt_timerMgr
slots:
code:
- component: BlockCodeTemplate
config:
template: >
{{utility:getTimerMgr}}(cache.{{field:CACHE}}, {{input:TIMER_MGR_NAME}}).check({{input:TIMER_KEY}}, 'PT' + {{input:DURATION}} + '{{field:UNIT}}',
function() { {{statements:TIMER_FUNC}} },'{{field:RESCHDULE}}' == 'TRUE',
function() { {{statements:FLAPPING_FUNC}} }, {{input:TIMER_KEY}});
toolbox:
- component: PresetInput
config:
fields:
TEXT: TimerMgr Name
name: TIMER_MGR_NAME
shadow: true
type: text
- component: PresetInput
config:
fields:
TEXT: key
name: TIMER_KEY
shadow: true
type: text
- component: PresetInput
config:
fields:
NUM: 0
name: DURATION
shadow: true
type: math_number
- component: BlockType
config:
args0:
- name: TIMER_KEY
type: input_value
- name: CACHE
options:
- - private
- private
- - shared
- shared
type: field_dropdown
- name: TIMER_MGR_NAME
type: input_value
colour: 0
helpUrl: https://github.com/rkoshak/openhab-rules-tools
inputsInline: true
message0: cancel managed timer %1 from %2 %3
nextStatement: ""
previousStatement: ""
tooltip: Cancels a managed timer from TimerMgr or Deferred.
type: ohrt_cancel_managed
slots:
code:
- component: BlockCodeTemplate
config:
template: >
if(cache.{{field:CACHE}}.exists({{input:TIMER_MGR_NAME}})) {
cache.{{field:CACHE}}.get({{input:TIMER_MGR_NAME}}).cancel({{input:TIMER_KEY}});
}
toolbox:
- component: PresetInput
config:
fields:
TEXT: TimerMgr Name
name: TIMER_MGR_NAME
shadow: true
type: text
- component: PresetInput
config:
fields:
TEXT: key
name: TIMER_KEY
shadow: true
type: text
- component: BlockType
config:
args0:
- name: CACHE
options:
- - private
- private
- - shared
- shared
type: field_dropdown
- name: TIMER_MGR_NAME
type: input_value
colour: 0
helpUrl: https://github.com/rkoshak/openhab-rules-tools
inputsInline: true
message0: cancel all managed timers from %1 %2
nextStatement: ""
previousStatement: ""
tooltip: Cancels all managed timers from Deferred, Gatekeeper, or TimerMg.
type: ohrt_cancel_all_managed
slots:
code:
- component: BlockCodeTemplate
config:
template: >
if(cache.{{field:CACHE}}.exists({{input:TIMER_MGR_NAME}})) {
cache.{{field:CACHE}}.get({{input:TIMER_MGR_NAME}}).cancelAll();
}
toolbox:
- component: PresetInput
config:
fields:
TEXT: Timer Mgr Name
name: TIMER_MGR_NAME
shadow: true
type: text
- component: PresetInput
config:
fields:
TEXT: key
name: TIMER_KEY
shadow: true
type: text
- component: BlockType
config:
args0:
- name: TIMER_KEY
type: input_value
- name: CACHE
options:
- - private
- private
- - shared
- shared
type: field_dropdown
- name: TIMER_MGR_NAME
type: input_value
colour: 210
helpUrl: https://github.com/rkoshak/openhab-rules-tools
inputsInline: true
message0: "%1 exists in %2 %3"
output: Boolean
tooltip: Returns true if a managed timer exists in indicated manager.
type: ohrt_has_managed_timer
slots:
code:
- component: BlockCodeTemplate
config:
template: >
cache.{{field:CACHE}}.exists({{input:TIMER_MGR_NAME}}) && cache.{{field:CACHE}}.get({{input:TIMER_MGR_NAME}}).hasTimer({{input:TIMER_KEY}})
toolbox:
- component: PresetInput
config:
fields:
TEXT: Timer Mgr Name
name: TIMER_MGR_NAME
shadow: true
type: text
- component: PresetInput
config:
fields:
TEXT: key
name: TIMER_KEY
shadow: true
type: text
- component: BlockType
config:
args0:
- name: ZDT
type: input_value
- name: DAY
options:
- - today
- toToday
- - tomorrow
- toTomorrow
- - yesterday
- toYesterday
type: field_dropdown
colour: 105
helpUrl: https://github.com/rkoshak/openhab-rules-tools
inputsInline: true
message0: change the date on %1 to %2
output: oh_zdt_now
tooltip: Changes the date of the date time block to tomorrow's date.
type: ohrt_to_tomorrow
slots:
code:
- component: BlockCodeTemplate
config:
template: >
{{utility:OHRT}}.timeUtils.{{field:DAY}}({{input:ZDT}})
toolbox:
- component: PresetInput
config:
name: ZDT
shadow: true
type: oh_zdt_now
- component: BlockType
config:
args0:
- name: VAR_NAME
type: field_variable
variable: item
- name: GROUP
type: input_value
- name: FILTER
type: input_statement
- name: MAP
type: input_statement
colour: 270
helperUrl: https://github.com/rkoshak/openhab-rules-tools
inputsInline: true
message0: create list for each %1 in %2 with filter %3 and map %4
output: lists_create_with
tooltip: Reduces a Group's members to a list of values (e.g. names).
type: ohrt_members_to_mapped_list
slots:
code:
- component: BlockCodeTemplate
config:
template: >
{{utility:OHRT}}.membersToMappedList({{input:GROUP}}, ({{field:item}})
toolbox:
- component: PresetField
config:
name: VAR_NAME
setEnabled: false
- component: PresetInput
config:
name: GROUP
shadow: true
type: oh_item
utilities:
- component: UtilityFunction
config:
code: >
var {{name}} = (() => {
const ohrt = require('openhab_rules_tools');
ohrt.helpers.validateLibraries('4.1.0', '2.0.3');
return ohrt;
})();
name: OHRT
- component: UtilityFunction
config:
code: >
function {{name}}(cache, name, condition, create) {
condition = (condition === undefined || condition === null) ? ((manager) => false) : condition;
let manager = cache.get(name);
if(manager === null || condition(manager)) {
manager = create();
cache.put(name, manager);
}
return manager;
}
name: getOrInstantiate
- component: UtilityFunction
config:
code: >
function {{name}}(cache, name) {
return {{getOrInstantiate}}(cache, name, (timer) => timer.hasTerminated(), () => {{OHRT}}.LoopingTimer());
}
name: getLoopingTimer
- component: UtilityFunction
config:
code: >
function {{name}}(cache, name) {
return {{getOrInstantiate}}(cache, name, null, () => {{OHRT}}.Deferred());
}
name: getDeferred
- component: UtilityFunction
config:
code: >
function {{name}}(cache, name) {
return {{getOrInstantiate}}(cache, name, null, () => {{OHRT}}.Gatekeeper(name));
}
name: getGatekeeper
- component: UtilityFunction
config:
code: >
function {{name}}(cache, name) {
return {{getOrInstantiate}}(cache, name, null, () => {{OHRT}}.RateLimit());
}
name: getRateLimit
- component: UtilityFunction
config:
code: >
function {{name}}(cache, name) {
return {{getOrInstantiate}}(cache, name, null, () => {{OHRT}}.TimerMgr());
}
name: getTimerMgr