From 3816377944711f16c4d3d594e613cce4b6ec3ffa Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Thu, 17 Aug 2023 10:01:35 -0600 Subject: [PATCH 01/16] Warn deprecated, don't test deprecated, use time.toZDT() Signed-off-by: Richard Koshak --- tests/timeUtilsTests | 113 ++++--------------------------------------- timeUtils.js | 13 +++-- 2 files changed, 18 insertions(+), 108 deletions(-) diff --git a/tests/timeUtilsTests b/tests/timeUtilsTests index 427a794..193af71 100755 --- a/tests/timeUtilsTests +++ b/tests/timeUtilsTests @@ -1,122 +1,27 @@ -var { time } = require('openhab'); var { timeUtils, testUtils } = require('openhab_rules_tools'); var logger = log('rules_tools.timeUtils tests'); logger.info('Starting timeUtils tests'); -// Test 1: parseDuration -//var dur = timeUtils.parseDuration('1d 2h 3m 10s'); -//var standard = time.Duration.ofDays(1).plusHours(2).plusMinutes(3).plusSeconds(10); -//testUtils.assert((dur.compareTo(standard) == 0), 'Duration was not parsed correctly!'); - -// Test 2: duration to date time -//var dt = timeUtils.durationToDateTime(dur); -//standard = time.ZonedDateTime.now().plus(dur); -//var delta = time.Duration.between(dt, standard); -//testUtils.assert((delta.compareTo(time.Duration.ofSeconds(1)) <= 0), -// 'Library DT is more than one second off of expected DT'); - -// Test 3: isISO8601 +// Test 1: isISO8601 testUtils.assert(timeUtils.isISO8601('2020-11-06T13:03:00-07:00'), 'isISO8601 failed to identify a valid string'); testUtils.assert(!timeUtils.isISO8601('2020-11-06 13:03:00'), 'isISO8601 failed to identity an invalid string'); -// Test 4: Already joda-js ZonedDatetime -//dt = time.ZonedDateTime.now(); -//testUtils.assert((dt === timeUtils.toDateTime(dt)), -// 'joda-js ZonedDateTime was not left alone!'); - -// Test 5: Convert Java ZonedDateTime to joda-js ZonedDateTime -//var JZDT = Java.type('java.time.ZonedDateTime'); -//var jnow = JZDT.now(); -//var jodanow = timeUtils.toDateTime(jnow); -//testUtils.assert((jodanow instanceof time.ZonedDateTime -// && jnow.toInstant().toEpochMilli() == jodanow.toInstant().toEpochMilli()), -// 'Failed to convert Java ZonedDateTime to joda-js ZonedDateTime'); - -// Test 6: ISO8601 String Parsing not working -//standard = time.ZonedDateTime.parse('2021-01-07T13:50:01-07:00', time.DateTimeFormatter.ISO_LOCAL_DATE_TIME); -//testUtils.assert((standard.compareTo(timeUtils.toDateTime('2021-01-07T13:50:01')) == 0), -// 'Failed to parse ISO8601 String'); - -var millisDur = time.Duration.ofMillis(10); - -// Test 7: Duration String - -//testUtils.assert(isClose(time.ZonedDateTime.now().plusMinutes(10), timeUtils.toDateTime('10m'), millisDur), -// 'Failed to convert a duration string to a datetime'); - -// Test 8: Milliseconds -//testUtils.assert(isClose(time.ZonedDateTime.now().plus('2000', time.ChronoUnit.MILLIS), timeUtils.toDateTime(2000), millisDur), -// 'Failed to convert milliseconds to datetime'); - -// Test 9: DateTimeType -//var DateTimeType = Java.type('org.openhab.core.library.types.DateTimeType'); -//var dtt = new DateTimeType(); -//standard = dtt.getZonedDateTime(); -//dt = timeUtils.toDateTime(dtt); -//testUtils.assert((dt === standard), -// 'Failed to convert DateTimeType'); - -// Test 10: DecimalType -//var DecimalType = Java.type('org.openhab.core.library.types.DecimalType'); -//testUtils.assert(isClose(time.ZonedDateTime.now().plus('3000', time.ChronoUnit.MILLIS), timeUtils.toDateTime(new DecimalType(3000)), millisDur), -// 'Failed to convert DecimalType to datetime'); - -// Test 11: PercentType -//var PercentType = Java.type('org.openhab.core.library.types.PercentType'); -//testUtils.assert(isClose(time.ZonedDateTime.now().plusSeconds(50), timeUtils.toDateTime(new PercentType(50)), millisDur), -// 'Failed to convert PercentType to datetime'); - -// Test 12: QuantityType -//var QuantityType = Java.type('org.openhab.core.library.types.QuantityType'); -//testUtils.assert(isClose(time.ZonedDateTime.now().plusSeconds(5), timeUtils.toDateTime(new QuantityType('5 s')), millisDur), -// 'Failed to convert QuantityType to datetime'); -//testUtils.assert(!timeUtils.toDateTime(new QuantityType('68 °F')), -// 'Failed to return null for unsupported QuantityType'); - -// Test 13: toToday -//dt = dt.minusDays(1); -//testUtils.assert(timeUtils.toToday(dt).dayOfMonth() == time.ZonedDateTime.now().dayOfMonth()); - -// Test 14: is24Hr -//testUtils.assert(timeUtils.is24Hr('01:02'), 'Failed to match 01:02'); -//testUtils.assert(timeUtils.is24Hr('13:14'), 'Failed to match 13:14'); -//testUtils.assert(!timeUtils.is24Hr('1:02 pm'), 'Matched 1:02 pm'); - -// Test 15: is12Hr -//testUtils.assert(timeUtils.is12Hr('1:02 am'), 'Failed to match 1:02 am'); -//testUtils.assert(timeUtils.is12Hr('11:22 pm'), 'Failed to match 11:22 pm'); -//testUtils.assert(timeUtils.is12Hr('02:03 A.M.'), 'Failed to match 02:03 A.M.'); -//testUtils.assert(timeUtils.is12Hr('03:04 P.M.'), 'Failed to match 03:04 P.M.'); -//testUtils.assert(!timeUtils.is12Hr('13:00')), 'Matched 13:00'; -//testUtils.assert(!timeUtils.is12Hr('13:00 am'), 'Matched 13:00 am'); - -// Test 16: Convert HH:MM to ZonedDateTime -//dt = timeUtils.toDateTime('13:12'); -//testUtils.assert(dt.hour() == 13 && dt.minute() == 12, 'Failed to convert 24 hour time'); -//dt = timeUtils.toDateTime('1:12 pm'); -//testUtils.assert(dt.hour() == 13 && dt.minute() == 12, 'Failed to convert 12 hour time'); - -// Test 17: toTomorrow +// Test 2: toTomorrow dt = time.ZonedDateTime.now(); testUtils.assert(timeUtils.toTomorrow(dt).dayOfMonth() == time.ZonedDateTime.now().plusDays(1).dayOfMonth(), 'Failed to move dt to tomorrow'); -// Test 18: toYesterday +// Test 3: toYesterday testUtils.assert(timeUtils.toYesterday(dt).dayOfMonth() == time.ZonedDateTime.now().minusDays(1).dayOfMonth(), 'Failed to move dt to yesterday'); -// Test 19: betweenTimes -//var startTime = time.ZonedDateTime.now().minusHours(1); -//var endTime = time.ZonedDateTime.now().plusHours(1); -//testUtils.assert(timeUtils.betweenTimes(startTime, endTime), 'Failed to detect between times'); - -//startTime = time.ZonedDateTime.now().minusHours(2); -//endTime = time.ZonedDateTime.now().minusHours(1); -//testUtils.assert(!timeUtils.betweenTimes(startTime, endTime), 'Outside the time range but detected inside'); +// Test 4: is24Hr +testUtils.assert(timeUtils.is24Hr('13:00')); +testUtils.assert(!timeUtils.is24Hr('awert')); -//startTime = time.ZonedDateTime.now().minusHours(1); -//endTime = time.ZonedDateTime.now().plusDays(1); -//testUtils.assert(timeUtils.betweenTimes(startTime, endTime), 'Failed to detect between times end past midnight'); +// Test 5: is12Hr +testUtils.assert(timeUtils.is12Hr('1:00 pm')); +testUtils.assert(!timeUtils.is12Hr('sdfa')); logger.info("timeUtils tests are done"); \ No newline at end of file diff --git a/timeUtils.js b/timeUtils.js index 31ecdee..9a4720d 100755 --- a/timeUtils.js +++ b/timeUtils.js @@ -25,6 +25,7 @@ const QuantityType = Java.type('org.openhab.core.library.types.QuantityType'); * @returns {time.Duration} null if the string is not parsable */ const parseDuration = (durationStr) => { + console.warn('timeUtils.parseDuration() is deprecated. Use time.Duration.parse() instead.'); var regex = new RegExp(/[\d]+[d|h|m|s|z]/gi); var numMatches = 0; var part = null; @@ -55,6 +56,7 @@ const parseDuration = (durationStr) => { * @returns {time.ZonedDateTime} the duration added to now, null if not a usable duration */ const durationToDateTime = (duration) => { + console.warn('timeUtils.durationToDateTime() is deprecated, use time.toZDT() instead.'); if (duration instanceof time.Duration) { return time.ZonedDateTime.now().plus(duration); } @@ -108,6 +110,7 @@ const is12Hr = (dtStr) => { * @returns {time.ZonedDateTime} null if it cannot be converted */ const toDateTime = (when) => { + console.warn('timeUtils.toDateTime() is deprecated. Use time.toZDT() instead.'); var dt = null; if (!when) { @@ -183,6 +186,7 @@ const toDateTime = (when) => { * @returns time.ZonedDateTime with today's date */ const toToday = (when) => { + console.warn('timeUtils.toToday() is deprecated. Use time.toZDT() instead.'); var now = time.ZonedDateTime.now(); var dt = toDateTime(when); return dt.withYear(now.year()) @@ -197,8 +201,8 @@ const toToday = (when) => { * @returns time.ZonedDateTime with tomorrow's date */ const toTomorrow = (when) => { - var tomorrow = time.ZonedDateTime.now().plusDays(1); - var dt = toDateTime(when); + var tomorrow = time.toZDT('P1D'); + var dt = time.toZDT(when); return dt.withYear(tomorrow.year()) .withMonth(tomorrow.month()) .withDayOfMonth(tomorrow.dayOfMonth()); @@ -211,8 +215,8 @@ const toTomorrow = (when) => { * @returns time.ZonedDateTime with yesterday's date */ const toYesterday = (when) => { - var yesterday = time.ZonedDateTime.now().minusDays(1); - var dt = toDateTime(when); + var yesterday = time.toZDT('P-1D'); + var dt = time.toZDT(when); return dt.withYear(yesterday.year()) .withMonth(yesterday.month()) .withDayOfMonth(yesterday.dayOfMonth()); @@ -229,6 +233,7 @@ const toYesterday = (when) => { * @returns {boolean} true if now is between the times (ignoring dates) of the passed in start and end */ const betweenTimes = (start, end, test = time.ZonedDateTime.now()) => { + console.warn('timeUtils.betweenTimes() is deprecated. Use time.ZonedDateTime.isBetweenTimes() instead.'); var startTime = toDateTime(start); var endTime = toDateTime(end); const testTime = toDateTime(test); From 421978a45828c297ff09869253f49144d01d0ac5 Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Thu, 17 Aug 2023 11:13:18 -0600 Subject: [PATCH 02/16] TimerMgr updates Signed-off-by: Richard Koshak --- deferred.js | 4 +- index.js | 6 +- tests/timerMgrTests | 187 +++++++++++++++++++++++++------------------- timerMgr.js | 17 +++- 4 files changed, 127 insertions(+), 87 deletions(-) diff --git a/deferred.js b/deferred.js index 15970e7..fd8e10c 100755 --- a/deferred.js +++ b/deferred.js @@ -1,4 +1,4 @@ -const { timerMgr } = require('openhab_rules_tools'); +const { TimerMgr } = require('openhab_rules_tools'); const { time, items } = require('openhab'); /** @@ -10,7 +10,7 @@ class Deferred { * Constructor */ constructor() { - this.timers = new timerMgr.TimerMgr(); + this.timers = TimerMgr(); } /** diff --git a/index.js b/index.js index a4d5345..ddc0a62 100755 --- a/index.js +++ b/index.js @@ -1,6 +1,10 @@ module.exports = { get timeUtils() { return require('./timeUtils.js') }, - get timerMgr() { return require('./timerMgr.js') }, + get timerMgr() { + console.warn('require TimerMgr instead of timerMgr and use TimerMgr() instead of new timerMgr.TimerMgr().'); + return require('./timerMgr.js') + }, + get TimerMgr() { return require('./timerMgr.js').getTimerMgr; }, get loopingTimer() { return require('./loopingTimer.js') }, get rateLimit() { return require('./rateLimit.js') }, get testUtils() { return require('./testUtils.js') }, diff --git a/tests/timerMgrTests b/tests/timerMgrTests index 9de93db..e35266d 100755 --- a/tests/timerMgrTests +++ b/tests/timerMgrTests @@ -1,105 +1,132 @@ -var { timerMgr } = require('openhab_rules_tools'); +var { time } = require('openhab'); +var { TimerMgr } = require('openhab_rules_tools'); -var tm = new timerMgr.TimerMgr(); +var logger = log('rules_tools.TimerMgr Tests'); -console.info('Starting timerMgr Tests...'); +var tm = TimerMgr(); + +var EXPIRED = '_expired'; +var FLAPPING = '_flapping'; + +var test = (test, key, exists, expired, flapping) => { + var ht = tm.hasTimer(key); + var expiredCalled = cache.private.get(key + EXPIRED) || false; + var flappingCalled = cache.private.get(key + FLAPPING) || false; + var rval = false; + logger.debug('{} Expected/Actual - Exists: {}/{}, Expired: {}/{} Flapping: {}/{}', test, exists, ht, expired, expiredCalled, flapping, flappingCalled); + if (ht != exists) logger.error('{} Timer exists {}: expected {}', test, key, ht, expired); + else if (expiredCalled != expired) logger.error('{}: Expired called on {} expected {}', test, key, expiredCalled, expired); + else if (flappingCalled != flapping) logger.error('{}: Flapping called on {} expected {}', test, key, flappingCalled, flapping); + else rval = true; + + return rval; +} + + +var initKeys = (root) => { + cache.private.put(root, null); + cache.private.put(root + EXPIRED, null); + cache.private.put(root + FLAPPING, null); +} + +var expiredCalled = (key) => { + return () => { + logger.debug('Called expired function for {}', key); + cache.private.put(key + EXPIRED, true); + } +} + +var flappingCalled = (key) => { + return () => { + logger.debug('Called flapping function for {}', key); + cache.private.put(key + FLAPPING, true); + } +} + +logger.info('Starting TimerMgr Tests'); + +var now = time.toZDT(); var keys = []; for (i = 1; i <= 11; i++) { - keys.push('timerMgr_Test_' + i); + const key = ruleUID + '_test' + i; + keys.push(key); + initKeys(key); } -keys.forEach((key) => cache.private.put(key, false)); // Test 1: Timer created -tm.check(keys[0], 100, () => {}); +tm.check(keys[0], 1000, expiredCalled(keys[0]), flappingCalled(keys[0])); -// If timerMgr has a timer stored for the given key, test passes -if (tm.timers[keys[0]].timer) cache.private.put(keys[0], true); +actions.ScriptExecution.createTimer(now.plus(510, time.ChronoUnit.MILLIS), () => { + cache.private.put(keys[0], test(keys[0], keys[0], true, false, false)); + tm.check(keys[0], 1000, expiredCalled(keys[0]), true, flappingCalled(keys[0])); +}); -// Test 2: Is flapping -// Test 3: Expired not called -cache.private.put(keys[2], true); -tm.check(keys[1], 1000, () => { - // If expired func is called, test fails - cache.private.put(keys[2], false); +// Test 2: Flapping +actions.ScriptExecution.createTimer(now.plus(910, time.ChronoUnit.MILLIS), () => { + cache.private.put(keys[1], test(keys[1], keys[0], true, false, true)); }); -tm.check(keys[1], 1000, undefined, false, () => { - // If flappingFunc is called, test passes - cache.private.put(keys[1], true); + +// Test 3: Timer expired +actions.ScriptExecution.createTimer(now.plus(2110, time.ChronoUnit.MILLIS), () => { + cache.private.put(keys[2], test(keys[2], keys[0], false, true, true)); }); -// Test 4: Timer expired -tm.check(keys[3], 100, () => {}); -setTimeout(() => { - // If timer is removed from timerMgr, it expired and test passes - if (!tm.timers[keys[3]]) cache.private.put(keys[3], true); -}, 1100); - -// Test 5: No function timer runnning -// Not sure what this test should test -cache.private.put(keys[4], true); - -// Test 6: Expired without main function -tm.check(keys[5], 100); -setTimeout(() => { - // If timer was removed from timerMgr, it expired and test passes - if (!tm.timers[keys[5]]) cache.private.put(keys[5], true); -}, 1100); - -// Test 7: Timer gets rescheduled and both expired ... -// Test 8: and flapping are called -tm.check(keys[6], 1000, () => { - // If expired func is called, test passes - cache.private.put(keys[6], true); +// Test 4: No function timer runnning +tm.check(keys[3], 'PT1s', undefined, undefined, () => { + cache.private.put(keys[3] + FLAPPING, true); }); -// Can be called without func here because this is stored -tm.check(keys[6], 1000, undefined, true, () => { - // If flappingFunc is called, test passes - cache.private.put(keys[7], true); + +actions.ScriptExecution.createTimer(now.plus(500, time.ChronoUnit.MILLIS), () => { + cache.private.put(keys[3], test(keys[3], keys[3], true, false, false)); }); -// Test 9: Expired called -tm.check(keys[8], 100, () => { - // If expired func is called, test passed - cache.private.put(keys[8], true); +// Test 5: Expired without main function +actions.ScriptExecution.createTimer(now.plus(1050, time.ChronoUnit.MILLIS), () => { + cache.private.put(keys[4], test(keys[4], keys[3], false, false, false)); }); -// Test 10: Cancel timer -tm.check(keys[9], 100, () => {}); -tm.cancel(keys[9]); -setTimeout(() => { - // If timer was removed from timerMgr, it was cancelled and test passes - if (!tm.timers[keys[9]]) cache.private.put(keys[9], true); -}, 1500); - -// Test 11: After cancel, both expired ... -// Test 12: and flappingFunc weren't called -cache.private.put(keys[9], true); -cache.private.put(keys[10], true); -tm.check(keys[9], 100, () => { - // If expired func is called, test fails - cache.private.get(keys[9], false); -}, undefined, () => { - // If flappingFunc is called, test fails - cache.private.get(keys[10], false); +// Test 6: Timer gets rescheduled and both expired and flapping get called. +tm.check(keys[5], 'PT1.11s', expiredCalled(keys[5]), true, flappingCalled(keys[5])); +actions.ScriptExecution.createTimer(now.plus(525, time.ChronoUnit.MILLIS), () => { + cache.private.put(keys[5], test(keys[5], keys[5], true, false, false)); + tm.check(keys[5], 'PT1s', expiredCalled(keys[5]), true, flappingCalled(keys[5])); }); -tm.cancel(keys[9]); -// Test 12: Cancelling a non-existing timer (doesn't throw an exception) -tm.cancel('nonExistingTimer'); +// Test 7: See if flapping was called and timer is rescheduled +actions.ScriptExecution.createTimer(now.plus(1205, time.ChronoUnit.MILLIS), () => { + cache.private.put(keys[6], test(keys[6], keys[5], true, false, true)); +}); + +// Test 8: Expired called +actions.ScriptExecution.createTimer(now.plus(1710, time.ChronoUnit.MILLIS), () => { + cache.private.put(keys[7], test(keys[7], keys[5], false, true, true)); +}); + +// Test 9: CancelTimer +tm.check(keys[8], 750, expiredCalled(keys[8])); +actions.ScriptExecution.createTimer(now.plus(250, time.ChronoUnit.MILLIS), () => { + cache.private.put(keys[8], test(keys[8], keys[8], true, false, false)); + tm.cancel(keys[8]); +}); + +// Test 10: After cancel, flapping and expired wasn't called +actions.ScriptExecution.createTimer(now.plus(1010, time.ChronoUnit.MILLIS), () => { + cache.private.put(keys[9], test(keys[9], keys[8], false, false, false)); +}); + +// Test 11: Cancel a non-existing timer +tm.cancel(keys[10]); +cache.private.put(keys[10], test(keys[10], keys[10], false, false, false)); // Check results -setTimeout(() => { +actions.ScriptExecution.createTimer(now.plus(3000, time.ChronoUnit.MILLIS), () => { const passed = keys.map((key) => cache.private.get(key)).reduce((combine, value) => combine && value); - if (passed) { - console.info('timerMgr tests passed successfully!'); - } else { - let failed = []; - keys.forEach((key) => { - if (cache.private.get(key) !== true) failed.push(key); - }); - console.error('timerMgr tests have failed: ' + failed.join(', ')); + if (passed) logger.info('TimerMgr tests completed successfully!'); + else { + const failed = keys.filter((key) => cache.private.get(key)).join('\n'); + logger.error('TimerMgr tests have failed:\n{}', failed); } -}, 3000); +}); -console.info('All timerMgr tests have been created. Please wait about 3 seconds for the results.'); +logger.info('All timers have been created'); diff --git a/timerMgr.js b/timerMgr.js index 718414d..1cbac05 100755 --- a/timerMgr.js +++ b/timerMgr.js @@ -23,13 +23,13 @@ class TimerMgr { /** * Function to call when null was passed for the func or flappingFunc. */ - _noop() { + #noop() { // do nothing } /** * If there is no timer associated with key, create one to expire at when and - * call func (or _noop if func is null). + * call func (or #noop if func is null). * If there is a timer already associted with key, if reschedule is not * supplied or it's false cancel the timer. If reschedule is true, reschedule * the timer using when. @@ -73,7 +73,7 @@ class TimerMgr { this.timers[key] = { 'timer': timer, 'flapping': flappingFunc, - 'notFlapping': (func) ? func : this._noop + 'notFlapping': (func) ? func : this.#noop }; } } @@ -113,6 +113,15 @@ class TimerMgr { } } +/** + * The TimerMgr handles the book keeping to manage a bunch of timers identified + * with a unique key. + */ +function getTimerMgr () { + return new TimerMgr(); +} + module.exports = { - TimerMgr + TimerMgr, + getTimerMgr } From ee0b143bb2bdfa794ac51eb64e78c97fb18938dd Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Thu, 17 Aug 2023 11:20:51 -0600 Subject: [PATCH 03/16] Added comments, reimplemented sleep Signed-off-by: Richard Koshak --- testUtils.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/testUtils.js b/testUtils.js index 584409b..95bbb45 100755 --- a/testUtils.js +++ b/testUtils.js @@ -1,19 +1,24 @@ const { time } = require('openhab'); +/** + * If the condition is false, throw an exception with the message + * @param {boolean} condition if true, do nothing + * @param {string} message exception message if condition is false + */ const assert = (condition, message) => { if (!condition) { throw new Error(message || 'Assertion failed'); } } +/** + * Simple wrapper around javba.lang.Thread.sleep() + * + * @param {int} msec number of milliseconds to sleep + */ const sleep = (msec) => { - let curr = time.toZDT(); - const done = curr.plus(msec, time.ChronoUnit.MILLIS); - const timeout = time.toZDT('PT5s'); - while (curr.isBefore(done) && curr.isBefore(timeout)) { // busy wait - curr = time.toZDT(); - } - if (curr.isAfter(timeout)) console.error('sleep timed out!'); + var Thread = Java.type('java.lang.Thread'); + Thread.sleep(msec); } module.exports = { From 7425473d65778ebcafeed0ea1c637e2dbe80bc41 Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Thu, 17 Aug 2023 11:29:39 -0600 Subject: [PATCH 04/16] Added log warnings for deprecated functions Signed-off-by: Richard Koshak --- rulesUtils.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/rulesUtils.js b/rulesUtils.js index bb2f324..c96b7d5 100755 --- a/rulesUtils.js +++ b/rulesUtils.js @@ -20,6 +20,7 @@ const generateTriggers = (list, trigger, systemStarted) => { /** * Checks to see if the rule exists + * Only works for rules defined in the same file so it's utility is suspect * @param {String} uid UID for the rule to look for * @returns {Boolean} true when the rule exists */ @@ -28,10 +29,13 @@ const ruleExists = (uid) => { } /** + * DEPRECATED + * * Deletes the rule if it exists. Does not work in UI rules. * @param {String} uid UID for the rule to remove */ const removeRule = (uid) => { + console.warn('rulesUtils.removeRule() is deprecated, use rules.removeRule() instead.'); if(ruleExists(uid)) { ruleRegistry.remove(uid); return !ruleExists(uid); @@ -42,7 +46,7 @@ const removeRule = (uid) => { } /** - * If a rule with the given UID exists, it's deleted. Then a new rule is created + * If a rule with the given UID exists, it's deleted. Then a new rule is created * with the given properties. A DYNAMIC_RULE_TAG will be applied to the rule. * @param {string} uid unique identifier for the given rule * @param {string} ruleName name for the rule as it will appear in MainUI @@ -52,6 +56,7 @@ const removeRule = (uid) => { * @param {Array} [ts=[]] optional list of tags to apply to the rule */ const recreateRule = (uid, ruleName, ruleDescription, trigs, func, ts = []) => { + console.warn('rulesUtils.recreateRule() is known not to work in all cases'); console.log('Removing the rule if necessary'); // Remove the existing rule if it exists, only works for rules created in the same file if(ruleExists(uid)) if(removeRule(uid)) return null; @@ -84,7 +89,7 @@ const recreateRule = (uid, ruleName, ruleDescription, trigs, func, ts = []) => { /** * Creates a rule that is triggered when any Item with valid metadata in the passed in * namespace using the passed in trigger type. In addition to the passed in tags, - * a DYNAMIC_RULE_TAG will also be applied. + * a DYNAMIC_RULE_TAG will also be applied. * @param {string} namespace Item metadata namespace used to identify those Items to trigger * @param {function(itemName)} checkConfig function that returns true if the metadata on the Item is valid * @param {triggers.ItemStateChangeTrigger|triggers.ItemStateUpdateTrigger|triggers.ItemChangeTrigger} event the Item trigger type to create for each Item @@ -96,7 +101,7 @@ const recreateRule = (uid, ruleName, ruleDescription, trigs, func, ts = []) => { * @param {int} systemStarted optional when true a runlevel 40 trigger will be added to the rule. * @returns {HostRule} the created rule, or null if there was a problem */ -const createRuleWithMetadata = (namespace, checkConfig, trigger, ruleName, func, +const createRuleWithMetadata = (namespace, checkConfig, trigger, ruleName, func, description, uid, systemStarted, tags) => { // Get the Items let triggeringItems = utils.javaSetToJsArray(itemRegistry.getAll()) @@ -109,8 +114,8 @@ const createRuleWithMetadata = (namespace, checkConfig, trigger, ruleName, func, } /** - * Creates a rule that is triggered when any Item with a given tag trigger type. - * In addition to the passed in tags, a DYNAMIC_RULE_TAG will also be applied. + * Creates a rule that is triggered when any Item with a given tag trigger type. + * In addition to the passed in tags, a DYNAMIC_RULE_TAG will also be applied. * @param {string} tag Item tag used to identify those Items to trigger * @param {triggers.ItemStateChangeTrigger|triggers.ItemStateUpdateTrigger|triggers.ItemChangeTrigger} event the Item trigger type to create for each Item * @param {string} ruleName name of the rule generated @@ -130,6 +135,8 @@ const createRuleWithTags = (tag, trigger, ruleName, func, description, } /** + * DEPRECATED + * * Calls another rule based on UID or rule name. TODO remove when added to openhab-js * @param {string} nameOrUid Rule UID or name to run * @param {dict} argsDict optional dict of data to pass to the called rule @@ -137,6 +144,8 @@ const createRuleWithTags = (tag, trigger, ruleName, func, description, * @returns {boolean} true when the rule is found and successfully called */ const runRule = (nameOrUid, argsDict, cond = false) => { + console.warn('rulesUtils.runRule() is deprecated, use rules.runRule() instead'); + // If it's not a UID, try to find it by name if(!RuleManager.getStatusInfo(nameOrUid)) { const { ruleRegistry } = require('@runtime/RuleSupport'); From 9fad950af9984ca9ce47765cf3661be40aab72b5 Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Thu, 17 Aug 2023 11:39:10 -0600 Subject: [PATCH 05/16] Added factory for RateLimit Signed-off-by: Richard Koshak --- index.js | 6 +++++- rateLimit.js | 13 +++++++++++-- tests/rateLimitTests | 6 +++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index ddc0a62..1e53663 100755 --- a/index.js +++ b/index.js @@ -6,7 +6,11 @@ module.exports = { }, get TimerMgr() { return require('./timerMgr.js').getTimerMgr; }, get loopingTimer() { return require('./loopingTimer.js') }, - get rateLimit() { return require('./rateLimit.js') }, + get rateLimit() { + console.warn('require RateLimit instead of rateLimit and use RateLimit() instead of new rateLimit.RateLimit().'); + return require('./rateLimit.js') + }, + get RateLimit() { return require('./rateLimit.js').getRateLimit; }, get testUtils() { return require('./testUtils.js') }, get gatekeeper() { return require('./gatekeeper.js') }, get deferred() { return require('./deferred.js') }, diff --git a/rateLimit.js b/rateLimit.js index 83b71d7..779c363 100755 --- a/rateLimit.js +++ b/rateLimit.js @@ -26,6 +26,15 @@ class RateLimit { } } +/** + * The RateLimit class keeps track of when the last `run` was called and throws + * away subsequent calls to run that occur before the passed in `when`. + */ +function getRateLimit() { + return new RateLimit(); +} + module.exports = { - RateLimit -} \ No newline at end of file + RateLimit, + getRateLimit +} diff --git a/tests/rateLimitTests b/tests/rateLimitTests index f9b59cc..0e19ce4 100755 --- a/tests/rateLimitTests +++ b/tests/rateLimitTests @@ -1,4 +1,4 @@ -var { rateLimit, testUtils } = require('openhab_rules_tools'); +var { RateLimit, testUtils } = require('openhab_rules_tools'); var logger = log('rules_tools.RateLimit Tests'); @@ -6,7 +6,7 @@ var funcCalled = false; var test = () => funcCalled = true; -var rl = new rateLimit.RateLimit(); +var rl = RateLimit(); logger.info('Starting RateLimit tests'); @@ -33,4 +33,4 @@ funcCalled = false; rl.run(test, 'PT2s'); testUtils.assert(!funcCalled, 'Test 3: Second run function was called before wait time'); -logger.info('All tests have passed'); \ No newline at end of file +logger.info('All tests have passed'); From 0557c20ce64975b7c7c44295f16c0b14809193c4 Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Thu, 17 Aug 2023 11:50:21 -0600 Subject: [PATCH 06/16] Updated docs and rule templates based on changes already made Signed-off-by: Richard Koshak --- README.md | 2 +- rule-templates/debounce/debounce2.yaml | 10 +++++----- rule-templates/ephemToD/time_state_machine.yaml | 8 ++++---- rule-templates/thresholdAlert/newThresholdAlert.yaml | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 5f7ee50..55bbe0f 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ New to OH 3.4 release, a system wide cache has been added where variables can be On can pull, and if it doesn't exist instantiate a Object in one line inside your rule. ``` -var timers = cache.shared.get('timers', () new timerMgr.TimerMgr()); +var timers = cache.shared.get('timers', () TimerMgr()); ``` It is important to use unique keys across all your rules to avoid collisions. diff --git a/rule-templates/debounce/debounce2.yaml b/rule-templates/debounce/debounce2.yaml index 52e1bec..0acc0ef 100644 --- a/rule-templates/debounce/debounce2.yaml +++ b/rule-templates/debounce/debounce2.yaml @@ -63,16 +63,16 @@ actions: configuration: type: application/javascript script: > - // Version 0.2 + // Version 0.3 - var {timerMgr, helpers} = require('openhab_rules_tools'); + var {TimerMgr, helpers} = require('openhab_rules_tools'); console.loggerName = 'org.openhab.automation.rules_tools.Debounce'; //osgi.getService('org.apache.karaf.log.core.LogService').setLevel(console.loggerName, 'DEBUG'); - helpers.validateLibraries('4.1.0', '2.0.1'); + helpers.validateLibraries('4.1.0', '2.0.2'); var USAGE = "Debounce metadata should follow this format:\n" @@ -93,7 +93,7 @@ actions: */ var getConfig = (itemName) => { - const md = items[itemName].getMetadata()['{{namespace}}']; + const md = items[itemName].getMetadata()['{{namespace}}']; if(!md) { throw itemName + ' does not have {{namespace}} metadata!\n' + USAGE; } @@ -159,7 +159,7 @@ actions: console.debug('Debounce:', event.type, 'item:', event.itemName); // Initialize the timers, config and end debounce function - const timers = cache.private.get('timerMgr', () => new timerMgr.TimerMgr()); + const timers = cache.private.get('timerMgr', () => TimerMgr()); const cfg = getConfig(event.itemName); const endDebounce = endDebounceGenerator(event.itemName, event.itemState, cfg.proxy, cfg.command); diff --git a/rule-templates/ephemToD/time_state_machine.yaml b/rule-templates/ephemToD/time_state_machine.yaml index 697a197..e5da5f6 100644 --- a/rule-templates/ephemToD/time_state_machine.yaml +++ b/rule-templates/ephemToD/time_state_machine.yaml @@ -45,16 +45,16 @@ actions: configuration: type: application/javascript script: > - // Version 0.2 + // Version 0.3 - var {timerMgr, helpers} = require('openhab_rules_tools'); + var {TimerMgr, helpers} = require('openhab_rules_tools'); console.loggerName = 'org.openhab.automation.rules_tools.TimeStateMachine'; //osgi.getService('org.apache.karaf.log.core.LogService').setLevel(console.loggerName, 'DEBUG'); - helpers.validateLibraries('4.2.0', '2.0.1'); + helpers.validateLibraries('4.2.0', '2.0.2'); console.debug('Starting state machine in ten seconds...'); @@ -316,7 +316,7 @@ actions: }; - var timers = cache.private.get('timers', () => new timerMgr.TimerMgr()); + var timers = cache.private.get('timers', () => TimerMgr()); // Wait a minute after the last time the rule is triggered to make sure all Items are done changing (e.g. diff --git a/rule-templates/thresholdAlert/newThresholdAlert.yaml b/rule-templates/thresholdAlert/newThresholdAlert.yaml index 6a4cebe..d0e7068 100644 --- a/rule-templates/thresholdAlert/newThresholdAlert.yaml +++ b/rule-templates/thresholdAlert/newThresholdAlert.yaml @@ -129,9 +129,9 @@ actions: configuration: type: application/javascript script: >- - // Version 0.10 + // Version 0.12 - var {helpers, loopingTimer, gatekeeper, timeUtils, rateLimit} = + var {helpers, loopingTimer, gatekeeper, timeUtils, RateLimit} = require('openhab_rules_tools'); var {DecimalType, QuantityType, PercentType} = require('@runtime'); @@ -309,7 +309,7 @@ actions: console.debug('Calling ' + ruleID + ' with alertItem=' + record.name + ', alertState=' + state + ', isAlerting=' + isAlerting + ', and initial alert ' + isInitialAlert); - var rl = cache.private.get('rl', () => new rateLimit.RateLimit()); + var rl = cache.private.get('rl', () => RateLimit()); const throttle = () => { const gk = cache.private.get('gatekeeper', () => new gatekeeper.Gatekeeper()); From 857edbb3d7d769e246c060ec99ea9f213a553e3f Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Thu, 17 Aug 2023 11:50:49 -0600 Subject: [PATCH 07/16] Updated required version for OHRT Signed-off-by: Richard Koshak --- rule-templates/thresholdAlert/newThresholdAlert.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rule-templates/thresholdAlert/newThresholdAlert.yaml b/rule-templates/thresholdAlert/newThresholdAlert.yaml index d0e7068..acf0231 100644 --- a/rule-templates/thresholdAlert/newThresholdAlert.yaml +++ b/rule-templates/thresholdAlert/newThresholdAlert.yaml @@ -144,7 +144,7 @@ actions: //osgi.getService('org.apache.karaf.log.core.LogService').setLevel(console.loggerName, 'DEBUG'); - helpers.validateLibraries('4.1.0', '2.0.1'); + helpers.validateLibraries('4.1.0', '2.0.2'); console.debug('Starting threshold alert'); From 92504cd42f1ca44e125513b37122f47a10bfe953 Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Thu, 17 Aug 2023 12:11:16 -0600 Subject: [PATCH 08/16] Added factory for LoopingTimer Signed-off-by: Richard Koshak --- countdownTimer.js | 4 ++-- index.js | 6 +++++- loopingTimer.js | 11 ++++++++++- .../thresholdAlert/newThresholdAlert.yaml | 8 ++++---- tests/loopingTimerTests | 16 ++++++++-------- 5 files changed, 29 insertions(+), 16 deletions(-) diff --git a/countdownTimer.js b/countdownTimer.js index d13965a..0a01eea 100755 --- a/countdownTimer.js +++ b/countdownTimer.js @@ -1,4 +1,4 @@ -const { loopingTimer } = require('openhab_rules_tools'); +const { LoopingTimer } = require('openhab_rules_tools'); const { time, items } = require('openhab'); /** @@ -27,7 +27,7 @@ class CountdownTimer { // Start the countdown timer this.countItem = countItem; - this.countdownTimer = new loopingTimer.LoopingTimer(); + this.countdownTimer = LoopingTimer(); this.countdownTimer.loop(this._iterateGenerator(this), 0, name); // start now } diff --git a/index.js b/index.js index 1e53663..b4ed281 100755 --- a/index.js +++ b/index.js @@ -5,7 +5,11 @@ module.exports = { return require('./timerMgr.js') }, get TimerMgr() { return require('./timerMgr.js').getTimerMgr; }, - get loopingTimer() { return require('./loopingTimer.js') }, + get loopingTimer() { + console.warn('require LoopingTimer instead of loopingTimer and use LoopingTimer() instead of new loopingTimer.LoopingTimer().'); + return require('./loopingTimer.js') + }, + get LoopingTimer() { return require('./loopingTimer.js').getLoopingTimer; }, get rateLimit() { console.warn('require RateLimit instead of rateLimit and use RateLimit() instead of new rateLimit.RateLimit().'); return require('./rateLimit.js') diff --git a/loopingTimer.js b/loopingTimer.js index 25eb374..a9a8246 100755 --- a/loopingTimer.js +++ b/loopingTimer.js @@ -67,6 +67,15 @@ class LoopingTimer { } } +/** + * @returns a timer that resheduels itself until the passed in looping function + * return null + */ +function getLoopingTimer() { + return new LoopingTimer(); +} + module.exports = { - LoopingTimer + LoopingTimer, + getLoopingTimer } diff --git a/rule-templates/thresholdAlert/newThresholdAlert.yaml b/rule-templates/thresholdAlert/newThresholdAlert.yaml index acf0231..bf1cbe2 100644 --- a/rule-templates/thresholdAlert/newThresholdAlert.yaml +++ b/rule-templates/thresholdAlert/newThresholdAlert.yaml @@ -131,7 +131,7 @@ actions: script: >- // Version 0.12 - var {helpers, loopingTimer, gatekeeper, timeUtils, RateLimit} = + var {helpers, LoopingTimer, gatekeeper, timeUtils, RateLimit} = require('openhab_rules_tools'); var {DecimalType, QuantityType, PercentType} = require('@runtime'); @@ -407,7 +407,7 @@ actions: if(record.alertTimer === null && !record.isAlerting) { // Schedule the initAlertTimer first and it will run first console.debug('Calling the initial alert rule for ' + record.name); - record.initAlertTimer = new loopingTimer.LoopingTimer(); + record.initAlertTimer = LoopingTimer(); record.initAlertTimer.loop(() => { console.debug('Calling init alert rule for ' + record.name); callRule(state, record.alertRule, false, true, record); @@ -415,7 +415,7 @@ actions: }, generateAlertTime(time.toZDT(), record.dndStart, record.dndEnd)); // Create the alert timer console.debug('Creating looping alert timer for ' + record.name + ' at ' + timeout); - record.alertTimer = new loopingTimer.LoopingTimer(); + record.alertTimer = LoopingTimer(); record.alertTimer.loop(sendAlertGenerator(record), timeout); } // Reschedule the alert timer @@ -498,7 +498,7 @@ actions: if(record.endRule && record.alerted) { console.debug('Sending alert that ' + record.name + ' is no longer in the alerting state'); // Schedule a timer if in DND, otherwise run now - record.endAlertTimer = new loopingTimer.LoopingTimer(); + record.endAlertTimer = LoopingTimer(); record.endAlertTimer.loop(() => { console.debug('Calling end alert rule for ' + record.name); callRule(stateToValue(items[record.name].rawState), record.endRule, false, false, record); diff --git a/tests/loopingTimerTests b/tests/loopingTimerTests index b516ee7..00ab46b 100755 --- a/tests/loopingTimerTests +++ b/tests/loopingTimerTests @@ -1,12 +1,12 @@ var { time } = require('openhab'); -var { loopingTimer } = require('openhab_rules_tools'); +var { LoopingTimer } = require('openhab_rules_tools'); var logger = log('rules_tools.LoopingTimer Tests'); var ID = ruleUID + '_count'; var func = () => { - var count = cache.get(ID); + var count = cache.private.get(ID); logger.debug('Incrementing count {}', count); - cache.put(ID, ++count); + cache.private.put(ID, ++count); if (count < 5) return 'PT1s'; else { logger.debug('Reached a count of 5, exiting'); @@ -14,15 +14,15 @@ var func = () => { } } -var lt = new loopingTimer.LoopingTimer(); -cache.put(ID, 0); +var lt = LoopingTimer(); +cache.private.put(ID, 0); lt.loop(func, 'PT0s'); actions.ScriptExecution.createTimer(time.toZDT('PT6s'), () => { - if (cache.get(ID) != 5) { - logger.error('Count is not 5: {}', cache.get(ID)); + if (cache.private.get(ID) != 5) { + logger.error('Count is not 5: {}', cache.private.get(ID)); } else { logger.info('Looping timer tests completed successfully!'); } -}); \ No newline at end of file +}); From 54bda4679d960f9938030cdd6a3c37f46a629cff Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Thu, 17 Aug 2023 12:33:39 -0600 Subject: [PATCH 09/16] use const for constants Signed-off-by: Richard Koshak --- helpers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/helpers.js b/helpers.js index f610936..d4fbac3 100644 --- a/helpers.js +++ b/helpers.js @@ -42,10 +42,10 @@ const createTimer = (when, func, name, key) => { * @returns {boolean} true if validation passes, false if there's a problem **/ const checkGrpAndMetadata = (namespace, grp, validateFunc, usage) => { - let isGood = true; - let badItems = []; + const isGood = true; + const badItems = []; - // Get all the Item with NAMESPACE metadata + // Get all the Items with NAMESPACE metadata const allItems = items.getItems(); const filtered = allItems.filter(item => item.getMetadata(namespace)); From e45f7484a02535c5555537f443c88eead43b3887 Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Thu, 17 Aug 2023 14:34:42 -0600 Subject: [PATCH 10/16] gatekeeper updates Signed-off-by: Richard Koshak --- gatekeeper.js | 23 ++++++++--- index.js | 6 ++- .../thresholdAlert/newThresholdAlert.yaml | 4 +- tests/gatekeeperTests | 38 +++++++++---------- 4 files changed, 43 insertions(+), 28 deletions(-) diff --git a/gatekeeper.js b/gatekeeper.js index 15b1822..64df4c5 100755 --- a/gatekeeper.js +++ b/gatekeeper.js @@ -26,7 +26,7 @@ class Gatekeeper { * @parm {*} ctx pointer to the Gatekeeper OPbject. * @returns {function} function called by the timer to process the next command. */ - _procCommandGenerator(ctx) { + #procCommandGenerator(ctx) { return () => { // no more commands @@ -38,15 +38,15 @@ class Gatekeeper { else { const command = ctx.commands.pop(); const func = command[1]; - const before = time.ZonedDateTime.now(); + const before = time.toZDT(); func(); - const after = time.ZonedDateTime.now(); + const after = time.toZDT(); const delta = time.Duration.between(before, after); const pause = time.toZDT(command[0]); const triggerTime = pause.minus(delta); - ctx.timer = helpers.createTimer(triggerTime, ctx._procCommandGenerator(ctx), this.name, 'gatekeeper'); + ctx.timer = helpers.createTimer(triggerTime, ctx.#procCommandGenerator(ctx), this.name, 'gatekeeper'); } }; @@ -61,7 +61,7 @@ class Gatekeeper { addCommand(pause, command) { this.commands.add([pause, command]); if (this.timer === null || this.timer.hasTerminated()) { - this._procCommandGenerator(this)(); + this.#procCommandGenerator(this)(); } } @@ -76,6 +76,17 @@ class Gatekeeper { } } +/** + * The Gatekeeper will ensure that a certain amount of time passes between + * commands. + * @param {String} name optional and used in error messages + * @returns a new Gatekeeper instance + */ +function getGatekeeper(name) { + return new Gatekeeper(name); +} + module.exports = { - Gatekeeper + Gatekeeper, + getGatekeeper } diff --git a/index.js b/index.js index b4ed281..bc1c611 100755 --- a/index.js +++ b/index.js @@ -16,7 +16,11 @@ module.exports = { }, get RateLimit() { return require('./rateLimit.js').getRateLimit; }, get testUtils() { return require('./testUtils.js') }, - get gatekeeper() { return require('./gatekeeper.js') }, + get gatekeeper() { + console.warn('require Gatekeeper instead of gatekeeper and use Gatekeeper() instead of new gatekeeper.Gatekeeper().'); + return require('./gatekeeper.js'); + }, + get Gatekeeper() { return require('./gatekeeper.js').getGatekeeper; }, get deferred() { return require('./deferred.js') }, get countdownTimer() { return require('./countdownTimer.js') }, get groupUtils() { return require('./groupUtils.js') }, diff --git a/rule-templates/thresholdAlert/newThresholdAlert.yaml b/rule-templates/thresholdAlert/newThresholdAlert.yaml index bf1cbe2..be3b4ea 100644 --- a/rule-templates/thresholdAlert/newThresholdAlert.yaml +++ b/rule-templates/thresholdAlert/newThresholdAlert.yaml @@ -131,7 +131,7 @@ actions: script: >- // Version 0.12 - var {helpers, LoopingTimer, gatekeeper, timeUtils, RateLimit} = + var {helpers, LoopingTimer, Gatekeeper, timeUtils, RateLimit} = require('openhab_rules_tools'); var {DecimalType, QuantityType, PercentType} = require('@runtime'); @@ -312,7 +312,7 @@ actions: var rl = cache.private.get('rl', () => RateLimit()); const throttle = () => { - const gk = cache.private.get('gatekeeper', () => new gatekeeper.Gatekeeper()); + const gk = cache.private.get('gatekeeper', () => Gatekeeper()); const records = cache.private.get('records'); gk.addCommand(record.gatekeeper, () => { try { diff --git a/tests/gatekeeperTests b/tests/gatekeeperTests index ad6760a..a63040f 100755 --- a/tests/gatekeeperTests +++ b/tests/gatekeeperTests @@ -1,12 +1,12 @@ var { time } = require('openhab'); -var { gatekeeper } = require('openhab_rules_tools'); +var { Gatekeeper } = require('openhab_rules_tools'); var logger = log('rules_tools.Gatekeeper Tests'); var testFunGen = (test, num) => { logger.debug('generating function for {} run{}', test, num); return () => { logger.debug('{}: Test {} ran', test, num); - cache.put(test + '_run' + num, time.toZDT()); + cache.private.put(test + '_run' + num, time.toZDT()); }; } @@ -14,20 +14,20 @@ logger.info('Starting Gatekeeper tests'); var reset = (test) => { logger.debug('resetting {}'); - cache.put(test, null); + cache.private.put(test, null); logger.debug('resetting {}', test + '_start'); - cache.put(test + '_start', null); + cache.private.put(test + '_start', null); for (var i = 1; i <= 4; i++) { logger.debug('resetting {}', test + '_run' + 1) - cache.put(test + '_run' + i, null); + cache.private.put(test + '_run' + i, null); } } // Test 1: Scheduling -var gk1 = new gatekeeper.Gatekeeper(); +var gk1 = Gatekeeper(); var TEST1 = ruleUID + '_test1'; reset(TEST1); -cache.put(TEST1 + '_start', time.ZonedDateTime.now()); +cache.private.put(TEST1 + '_start', time.ZonedDateTime.now()); gk1.addCommand('PT1s', testFunGen(TEST1, 1)); gk1.addCommand('PT2s', testFunGen(TEST1, 2)); gk1.addCommand('PT3s', testFunGen(TEST1, 3)); @@ -35,11 +35,11 @@ gk1.addCommand(500, testFunGen(TEST1, 4)); actions.ScriptExecution.createTimer(time.toZDT('PT6.51s'), () => { var success = true; - const start = cache.get(TEST1 + '_start'); - const run1 = cache.get(TEST1 + '_run1'); - const run2 = cache.get(TEST1 + '_run2'); - const run3 = cache.get(TEST1 + '_run3'); - const run4 = cache.get(TEST1 + '_run4'); + const start = cache.private.get(TEST1 + '_start'); + const run1 = cache.private.get(TEST1 + '_run1'); + const run2 = cache.private.get(TEST1 + '_run2'); + const run3 = cache.private.get(TEST1 + '_run3'); + const run4 = cache.private.get(TEST1 + '_run4'); if (start === null) { logger.error('{} Failed to get starting timestamp', TEST1); success = false; @@ -89,7 +89,7 @@ actions.ScriptExecution.createTimer(time.toZDT('PT6.51s'), () => { }); // Test 2: cancelAll -var gk2 = new gatekeeper.Gatekeeper(); +var gk2 = Gatekeeper(); var TEST2 = ruleUID + '_test2' reset(TEST2); gk2.addCommand('PT1.5s', testFunGen(TEST2, 1)); @@ -99,10 +99,10 @@ gk2.addCommand(500, testFunGen(TEST2, 4)); actions.ScriptExecution.createTimer(time.ZonedDateTime.now().plus(2750, time.ChronoUnit.MILLIS), () => { var success = true; - const run1 = cache.get(TEST2 + '_run1'); - const run2 = cache.get(TEST2 + '_run2'); - const run3 = cache.get(TEST2 + '_run3'); - const run4 = cache.get(TEST2 + '_run4'); + const run1 = cache.private.get(TEST2 + '_run1'); + const run2 = cache.private.get(TEST2 + '_run2'); + const run3 = cache.private.get(TEST2 + '_run3'); + const run4 = cache.private.get(TEST2 + '_run4'); if (!run1) { logger.error('{} failed, run1 did not run', TEST2); @@ -124,8 +124,8 @@ actions.ScriptExecution.createTimer(time.ZonedDateTime.now().plus(2750, time.Chr gk2.cancelAll(); actions.ScriptExecution.createTimer(time.toZDT('PT4s'), () => { var success = true; - const run3 = cache.get(TEST2 + '_run3'); - const run4 = cache.get(TEST2 + '_run4'); + const run3 = cache.private.get(TEST2 + '_run3'); + const run4 = cache.private.get(TEST2 + '_run4'); if (run3) { logger.error('{} failed, run3 ran after being cancelled'); From 6c492223683ad8e03f341425b7e168abeaf6b8dc Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Thu, 17 Aug 2023 14:44:46 -0600 Subject: [PATCH 11/16] updates for deferred Signed-off-by: Richard Koshak --- deferred.js | 20 +++++++++++++++----- index.js | 6 +++++- tests/deferredTests | 26 +++++++++++++------------- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/deferred.js b/deferred.js index fd8e10c..8d2b0f6 100755 --- a/deferred.js +++ b/deferred.js @@ -19,7 +19,7 @@ class Deferred { * @param {string} value command or update to send * @param {boolean} isCommand when true, value will be sent as a command, otherwise posted as an update */ - _timerBodyGenerator(target, value, isCommand) { + #timerBodyGenerator(target, value, isCommand) { const item = items.getItem(target); return () => (isCommand) ? item.sendCommand(value) : item.postUpdate(value); } @@ -34,11 +34,11 @@ class Deferred { */ defer(target, value, when, isCommand) { const triggerTime = time.toZDT(when); - if (triggerTime.isBefore(time.ZonedDateTime.now())) { - triggerTime = time.ZonedDateTime.now(); + if (triggerTime.isBefore(time.toZDT())) { + triggerTime = time.toZDT(); } this.timers.cancel(target); - this.timers.check(target, triggerTime, this._timerBodyGenerator(target, value, isCommand, when), false); + this.timers.check(target, triggerTime, this.#timerBodyGenerator(target, value, isCommand, when), false); } /** @@ -57,6 +57,16 @@ class Deferred { } } +/** + * Deferred is a way to schedule a simple command sometime in the future. + * + * @returns a new instance of Deferred + */ +function getDeferred() { + return new Deferred(); +} + module.exports = { - Deferred + Deferred, + getDeferred } \ No newline at end of file diff --git a/index.js b/index.js index bc1c611..fb72ded 100755 --- a/index.js +++ b/index.js @@ -21,7 +21,11 @@ module.exports = { return require('./gatekeeper.js'); }, get Gatekeeper() { return require('./gatekeeper.js').getGatekeeper; }, - get deferred() { return require('./deferred.js') }, + get deferred() { + console.warn('require Deferred instead of deferred and use Deferred() instead of new deferred.Deferred().'); + return require('./deferred.js'); + }, + get Deferred() { return require('./deferred.js').getDeferred; }, get countdownTimer() { return require('./countdownTimer.js') }, get groupUtils() { return require('./groupUtils.js') }, get rulesUtils() { return require('./rulesUtils.js') }, diff --git a/tests/deferredTests b/tests/deferredTests index d63fc6a..9323cf6 100755 --- a/tests/deferredTests +++ b/tests/deferredTests @@ -1,8 +1,8 @@ var { time, items } = require('openhab'); -var { deferred } = require('openhab_rules_tools'); +var { Deferred } = require('openhab_rules_tools'); var logger = log('rules_tools.Deferred Tests'); -var deferred = new deferred.Deferred(); +var deferred = Deferred(); // Change this if you want to run the unit test var testItem = items.getItem('TestSwitch'); @@ -11,7 +11,7 @@ logger.info('Starting Deferred tests'); var reset = (testName) => { testItem.postUpdate('UNDEF'); - cache.put(testName, null); + cache.private.put(testName, null); } @@ -21,10 +21,10 @@ function test1() { reset(test1); deferred.defer(testItem.name, 'ON', 'PT1s'); actions.ScriptExecution.createTimer(time.toZDT(100), () => { - if (!testItem.isUninitialized) cache.put(test1, test1 + ': Deferred updated ' + testItem + ' too soon!'); + if (!testItem.isUninitialized) cache.private.put(test1, test1 + ': Deferred updated ' + testItem + ' too soon!'); }); actions.ScriptExecution.createTimer(time.toZDT(1200), () => { - if (testItem.state != 'ON' && !cache.get(test1)) cache.put(test1 + ': Deferred failed to update ' + testItem); + if (testItem.state != 'ON' && !cache.private.get(test1)) cache.privvate.put(test1 + ': Deferred failed to update ' + testItem); else test2(); }); @@ -36,14 +36,14 @@ function test2() { reset(test2); deferred.defer(testItem.name, 'OFF', 'PT1s'); actions.ScriptExecution.createTimer(time.toZDT(100), () => { - if (!testItem.isUninitialized) cache.put(test2, test2 + ': Deferred updated ' + testItem + ' too soon!'); + if (!testItem.isUninitialized) cache.private.put(test2, test2 + ': Deferred updated ' + testItem + ' too soon!'); else deferred.defer(testItem.name, 'ON', 'PT2s'); }); actions.ScriptExecution.createTimer(time.toZDT(1100), () => { - if (!testItem.isUninitialized && !cache.get(test2)) cache.put(test2, test2 + ': Failed to reschedule'); + if (!testItem.isUninitialized && !cache.private.get(test2)) cache.private.put(test2, test2 + ': Failed to reschedule'); }); actions.ScriptExecution.createTimer(time.toZDT(2300), () => { - if (testItem.state != 'ON' && !cache.get(test2)) cache.put(test2, test2 + ': Did not update after reschedule'); + if (testItem.state != 'ON' && !cache.private.get(test2)) cache.private.put(test2, test2 + ': Did not update after reschedule'); else test3(); }); } @@ -56,7 +56,7 @@ function test3() { deferred.cancel(testItem.name); }); actions.ScriptExecution.createTimer(time.toZDT(1200), () => { - if (!testItem.isUninitialized) cache.put(test3, test3 + ': Deferred was not cancelled'); + if (!testItem.isUninitialized) cache.private.put(test3, test3 + ': Deferred was not cancelled'); else test4(); }); } @@ -69,7 +69,7 @@ function test4() { deferred.cancelAll(); }); actions.ScriptExecution.createTimer(time.toZDT(1200), () => { - if (!testItem.isUninitialized) cache.put(test4, test4 + ': Cancel did not cancel all'); + if (!testItem.isUninitialized) cache.private.put(test4, test4 + ': Cancel did not cancel all'); }); } @@ -77,10 +77,10 @@ actions.ScriptExecution.createTimer(time.toZDT(7100), () => { let success = true; for (let i = 1; i <= 4; i++) { let id = ruleUID + '_test' + i; - if (cache.get(id)) { - logger.error(cache.get(id)); + if (cache.private.get(id)) { + logger.error(cache.private.get(id)); success = false; - cache.put(id, null); + cache.private.put(id, null); } } if (success) logger.info('Deferred tests completed successfully'); From 6995b2efc03baf20cc91bf4f1f9147e0233dce79 Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Thu, 17 Aug 2023 14:59:03 -0600 Subject: [PATCH 12/16] countdownTimer updates Signed-off-by: Richard Koshak --- countdownTimer.js | 26 +++++++++++++---- index.js | 6 +++- tests/countdownTimerTests | 60 ++++++++++++++++++++------------------- 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/countdownTimer.js b/countdownTimer.js index 0a01eea..a965874 100755 --- a/countdownTimer.js +++ b/countdownTimer.js @@ -28,7 +28,7 @@ class CountdownTimer { // Start the countdown timer this.countItem = countItem; this.countdownTimer = LoopingTimer(); - this.countdownTimer.loop(this._iterateGenerator(this), 0, name); // start now + this.countdownTimer.loop(this.#iterateGenerator(this), 0, name); // start now } /** @@ -36,7 +36,7 @@ class CountdownTimer { * time left is less than a second, 0 is the value posted. * @param {CountdownTimer} ctx Context to access the timer information from inside the countdown Timer's lambda */ - _updateItem(ctx) { + #updateItem(ctx) { let left = (ctx.timeLeft.compareTo(ctx.ONE_SEC) < 0) ? 0 : ctx.timeLeft.seconds(); items.getItem(ctx.countItem).postUpdate(left); } @@ -46,9 +46,9 @@ class CountdownTimer { * the time has run out, calling updateItem each time. * @param {CountdownTimer} ctx Context to access the timer information from inside the looping timer */ - _iterateGenerator(ctx) { + #iterateGenerator(ctx) { return () => { - ctx._updateItem(ctx); + ctx.#updateItem(ctx); if (!ctx.timeLeft.isZero()) { let sleepTime = (ctx.timeLeft.compareTo(ctx.ONE_SEC) < 0) ? ctx.timeLeft : ctx.ONE_SEC; ctx.timeLeft = ctx.timeLeft.minusDuration(sleepTime); @@ -73,12 +73,26 @@ class CountdownTimer { */ cancel() { this.timeLeft = time.Duration.ofSeconds(0); - this._updateItem(this); + this.#updateItem(this); this.countDownTimer?.cancel(); return this.timer.cancel(); } } +/** + * A countdown timer updates an Item with the number of seconds left in the timer + * once a second. + * @param {*} when time.toZDT compatible time or duration + * @param {function} func function to call at when + * @param {string} countItem name of the Item to update with the seconds remaining + * @param {string} [name] countdown name displayed in openHAB + * @returns a new CountdownTimer + */ +function getCountdownTimer(when, func, countItem, name) { + return new CountdownTimer(when, func, countItem, name); +} + module.exports = { - CountdownTimer + CountdownTimer, + getCountdownTimer } \ No newline at end of file diff --git a/index.js b/index.js index fb72ded..14bcf67 100755 --- a/index.js +++ b/index.js @@ -26,7 +26,11 @@ module.exports = { return require('./deferred.js'); }, get Deferred() { return require('./deferred.js').getDeferred; }, - get countdownTimer() { return require('./countdownTimer.js') }, + get countdownTimer() { + console.warn('require CountdownTimer instead of countdownTimer and use CountdownTimer() instead of new countdownTimer.CountdownTimer().'); + return require('./countdownTimer.js'); + }, + get CountdownTimer() { return require('./countdownTimer.js').getCountdownTimer; }, get groupUtils() { return require('./groupUtils.js') }, get rulesUtils() { return require('./rulesUtils.js') }, get helpers() { return require('./helpers.js') } diff --git a/tests/countdownTimerTests b/tests/countdownTimerTests index 2ba6bad..66d404c 100755 --- a/tests/countdownTimerTests +++ b/tests/countdownTimerTests @@ -1,20 +1,22 @@ var { time, items } = require('openhab'); -var { countdownTimer, testUtils } = require('openhab_rules_tools'); +var { CountdownTimer, testUtils } = require('openhab_rules_tools'); var logger = log('rules_tools.Countdown Timer Tests'); -cache.put(ruleUID, false); -var countItem = items.getItem('TestNumber'); +var testItem = 'TestNumber'; + +cache.private.put(ruleUID, false); +var countItem = items.getItem(testItem); function testFun() { logger.debug('Function called'); - cache.put(ruleUID, true); + cache.private.put(ruleUID, true); } function reset(testName) { countItem.postUpdate('UNDEF'); - cache.put(ruleUID, false); - cache.put(testName, null); + cache.private.put(ruleUID, false); + cache.private.put(testName, null); } // Test1: function is called @@ -22,9 +24,9 @@ function test1() { let test1 = ruleUID + '_test1'; reset(test1); logger.info('Running ' + test1); - let timer = new countdownTimer.CountdownTimer('PT2s', testFun, countItem.name); + let timer = CountdownTimer('PT2s', testFun, countItem.name); actions.ScriptExecution.createTimer(time.toZDT(2200), () => { - if (!cache.get(ruleUID)) cache.put(test1, test1 + ': Failed to call function ' + cache.get(ruleUID)); + if (!cache.private.get(ruleUID)) cache.private.put(test1, test1 + ': Failed to call function ' + cache.private.get(ruleUID)); else test2(); }); } @@ -34,10 +36,10 @@ function test2() { let test2 = ruleUID + '_test2'; logger.info('Running ' + test2); reset(test2); - let timer = new countdownTimer.CountdownTimer(2100, testFun, countItem.name); + let timer = CountdownTimer(2100, testFun, countItem.name); actions.ScriptExecution.createTimer(time.toZDT(2300), () => { - if (!cache.get(ruleUID)) cache.put(test2, test2 + ': Failed to call function'); - else if (!cache.get(test2)) test3(); + if (!cache.private.get(ruleUID)) cache.private.put(test2, test2 + ': Failed to call function'); + else if (!cache.private.get(test2)) test3(); }); } @@ -46,22 +48,22 @@ function test3() { let test3 = ruleUID + '_test3'; logger.info('Running ' + test3); reset(test3); - let timer = new countdownTimer.CountdownTimer('PT4s', testFun, countItem.name); + let timer = CountdownTimer('PT4s', testFun, countItem.name); actions.ScriptExecution.createTimer(time.toZDT(100), () => { - if (countItem.state != 4) cache.put(test3, test3 + ': Count Item is not 4 - ' + countItem.state); + if (countItem.state != 4) cache.private.put(test3, test3 + ': Count Item is not 4 - ' + countItem.state); }); actions.ScriptExecution.createTimer(time.toZDT(1100), () => { - if (countItem.state != 3 && !cache.get(test3)) cache.put(test3, test3 + ': Count Item is not 3 - ' + countItem.state); + if (countItem.state != 3 && !cache.private.get(test3)) cache.private.put(test3, test3 + ': Count Item is not 3 - ' + countItem.state); }); actions.ScriptExecution.createTimer(time.toZDT(2200), () => { - if (countItem.state != 2 && !cache.get(test3)) cache.put(test3, test3 + ': Count Item is not 2 - ' + countItem.state + ' ' + (countItem.state != 2)); + if (countItem.state != 2 && !cache.private.get(test3)) cache.private.put(test3, test3 + ': Count Item is not 2 - ' + countItem.state + ' ' + (countItem.state != 2)); }); actions.ScriptExecution.createTimer(time.toZDT(3200), () => { - if (countItem.state != 1 && !cache.get(test3)) cache.put(test3, test3 + ': Count Item is not 1 - ' + countItem.state); + if (countItem.state != 1 && !cache.private.get(test3)) cache.private.put(test3, test3 + ': Count Item is not 1 - ' + countItem.state); }); actions.ScriptExecution.createTimer(time.toZDT(4200), () => { - if (countItem.state != 0 && !cache.get(test3)) cache.put(test3, test3 + ': Count Item is not 0 - ' + countItem.state); - else if (!cache.get(test3)) test4(); + if (countItem.state != 0 && !cache.private.get(test3)) cache.private.put(test3, test3 + ': Count Item is not 0 - ' + countItem.state); + else if (!cache.private.get(test3)) test4(); }); } @@ -70,10 +72,10 @@ function test4() { let test4 = ruleUID + '_test4'; logger.info('Running ' + test4); reset(test4); - let timer = new countdownTimer.CountdownTimer('PT1s', testFun, countItem.name); + let timer = CountdownTimer('PT1s', testFun, countItem.name); actions.ScriptExecution.createTimer(time.toZDT(1100), () => { - if (!timer.hasTerminated()) cache.put(test4, test4 + ': hasTerminated() did not return correct value'); - else if (cache.get(test4)) test5(); + if (!timer.hasTerminated()) cache.private.put(test4, test4 + ': hasTerminated() did not return correct value'); + else if (cache.private.get(test4)) test5(); }); } @@ -82,15 +84,15 @@ function test5() { let test5 = ruleUID + '_test5'; logger.info('Running ' + test5); reset(test5); - let timer = new countdownTimer.CountdownTimer('PT2s', testFun, countItem.name); + let timer = CountdownTimer('PT2s', testFun, countItem.name); testUtils.sleep(100); let oldVal = countItem.state; - if (!countItem.state == '2') cache.put(test5, test5 + ': Count Item did not initialize to 2 - ' + countItem.state); + if (!countItem.state == '2') cache.private.put(test5, test5 + ': Count Item did not initialize to 2 - ' + countItem.state); logger.info('{} Cancelling timer', test5); timer.cancel(); actions.ScriptExecution.createTimer(time.toZDT(2100), () => { - if (!countItem.state == '0' && !cache.get(test5)) cache.put(test5, test5 + ': Cancelled timer did not reset count Item - ' + countItem.state); - if (cache.get(ruleUID) && !cache.get(test5)) cache.put(test5, test5 + ': Function was called despite being cancelled'); + if (!countItem.state == '0' && !cache.private.get(test5)) cache.private.put(test5, test5 + ': Cancelled timer did not reset count Item - ' + countItem.state); + if (cache.private.get(ruleUID) && !cache.private.get(test5)) cache.private.put(test5, test5 + ': Function was called despite being cancelled'); }) } @@ -99,13 +101,13 @@ actions.ScriptExecution.createTimer(time.toZDT(11000), () => { let success = true; for (let i = 0; i <= 5; i++) { let id = ruleUID + '_test' + i; - if (cache.get(id)) { - logger.error(cache.get(id)); + if (cache.private.get(id)) { + logger.error(cache.private.get(id)); success = false; - cache.put(id, null); + cache.private.put(id, null); } } if (success) logger.info('CountdownTimer tests completed successfully'); }); -test1(); \ No newline at end of file +test1(); From 1f758816a1b0304fb33fc50c6981dc404eb1249d Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Fri, 18 Aug 2023 11:37:04 -0600 Subject: [PATCH 13/16] Update debounce2.yaml Moved minimum OHRT version o 2.0.3 --- rule-templates/debounce/debounce2.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rule-templates/debounce/debounce2.yaml b/rule-templates/debounce/debounce2.yaml index 0acc0ef..14727b5 100644 --- a/rule-templates/debounce/debounce2.yaml +++ b/rule-templates/debounce/debounce2.yaml @@ -72,7 +72,7 @@ actions: //osgi.getService('org.apache.karaf.log.core.LogService').setLevel(console.loggerName, 'DEBUG'); - helpers.validateLibraries('4.1.0', '2.0.2'); + helpers.validateLibraries('4.1.0', '2.0.3'); var USAGE = "Debounce metadata should follow this format:\n" From 43e28997d57418fd869b3e29ca05c84988385d90 Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Fri, 18 Aug 2023 11:37:44 -0600 Subject: [PATCH 14/16] Update time_state_machine.yaml Moved minimum OHRT version to 2.0.3 --- rule-templates/ephemToD/time_state_machine.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rule-templates/ephemToD/time_state_machine.yaml b/rule-templates/ephemToD/time_state_machine.yaml index e5da5f6..ec90353 100644 --- a/rule-templates/ephemToD/time_state_machine.yaml +++ b/rule-templates/ephemToD/time_state_machine.yaml @@ -54,7 +54,7 @@ actions: //osgi.getService('org.apache.karaf.log.core.LogService').setLevel(console.loggerName, 'DEBUG'); - helpers.validateLibraries('4.2.0', '2.0.2'); + helpers.validateLibraries('4.2.0', '2.0.3'); console.debug('Starting state machine in ten seconds...'); From 87e7fd3906f24427f28d2b6b19c131aa32efa9a2 Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Fri, 18 Aug 2023 11:38:18 -0600 Subject: [PATCH 15/16] Update newThresholdAlert.yaml moved minimum OHRT version 2.0.3 --- rule-templates/thresholdAlert/newThresholdAlert.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rule-templates/thresholdAlert/newThresholdAlert.yaml b/rule-templates/thresholdAlert/newThresholdAlert.yaml index be3b4ea..6124979 100644 --- a/rule-templates/thresholdAlert/newThresholdAlert.yaml +++ b/rule-templates/thresholdAlert/newThresholdAlert.yaml @@ -144,7 +144,7 @@ actions: //osgi.getService('org.apache.karaf.log.core.LogService').setLevel(console.loggerName, 'DEBUG'); - helpers.validateLibraries('4.1.0', '2.0.2'); + helpers.validateLibraries('4.1.0', '2.0.3'); console.debug('Starting threshold alert'); From a3894f7af1df22bc6977a5e30f24aab45e8c5ae3 Mon Sep 17 00:00:00 2001 From: Richard Koshak Date: Fri, 18 Aug 2023 11:41:19 -0600 Subject: [PATCH 16/16] Update package.json bumping npm version number --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f61af6b..2a0a785 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openhab_rules_tools", - "version": "2.0.2", + "version": "2.0.3", "description": "Functions and classes to make writing openHAB rules in JS Scripting easier.", "private": false, "license": "EPL-2.0", @@ -21,6 +21,6 @@ "url": "https://github.com/rkoshak/openhab-rules-tools/issues" }, "devDependencies": { - "openhab": "^4.1.0" + "openhab": "^4.5.1" } -} \ No newline at end of file +}