Extending Blockly with new openHAB commands

Fair enough - well said: Here is the PR → https://github.com/openhab/openhab-webui/pull/1179

I am already working on timers which I will probably drop during the next week as I am progressing well at the moment.

Update 18th of Oct: Timers have been fully implemented

2 Likes

Hi Yannick, or anyone else who is pretty familiar with GIT

I think my github repo got into a wrong state. I am struggling to get it right. Can you have a look here: https://github.com/openhab/openhab-webui/pull/1179#issuecomment-945430821

On the topic of Nashorn / GraalVM
Just as sidenote and reference, here is a good guide for Migration from Nashorn to GraalVM: GraalVM

  • Which one are we currently running. Is it still Nashorn at the moment?

Looking at the blocks and the code generated I have a few comments.

  • One thing that I’ve never really liked about how timers work in Rules DSL is how the lambda that gets passed to the Timer to run is defined inline. There are reason why one would want to do that given how Rules DSL works. But looking at the blocks here I wonder if it makes sense to follow that. Is there a way for the Run This block to present a list of the locally defined functions? Then the body of the Timer could be defined in a separate block which provides a bit of encapsulation which might make the code a little easier to read and maintain.

    Definitely keep the option to define the code inline too. I just want to also have the option to define the code in a separate function also.

  • I don’t think the third line will work where MyTimer is defined, though apparently it’s working for you. When I first started messing with it though that exact construction wouldn’t work for me. The very first time the rule runs, this.MyTimer won’t even exist. When a variable doesn’t exist its undefined, not null. I think the test needs to be this.MyTimer = (this.MyTimer === undefined) ? null : this.MyTimer; That means if the variable doesn’t exist, set it to null, otherwise leave it alone.

  • When comparing to undefined or null use the identity operator (=== or !==).

  • In JavaScript null and undefined are considered falsy. So the test to see if the Timer is active could be reduced to

    if( !this.MyTimer || !this.MyTimer.isActive() ) {

    The or operator fails fast. As soon as one of the operands evaluates to true it returns true. So if this.MyTimer is null it will be treated as false and the negate operator ! will convert that to true. Only if the Timer exists will isActive() be called. I don’t know if there is a Blockly reason why the current more complex construction is used here. If possible though it would be good to condense the code when possible.

  • Be consistent in the use of ending semicolons. There are several lines that are missing them.

Everything else looks reasonable to me.

re 1: Regarding the anonymous block execution block: I guess the challenge would be here that the code that would have to be generated had to generate a function that had to named uniquely and not only that had to be put >outside< the code. This is probably really tough in blockly (technically the inner part block set is given to the outer part and >I< have to compose them together. The other option though is to define a function and then call that function within that block which works out of box and looks much cleaner (thanks for the idea - I wouldn’t have had this idea):

re 2: ok, I’ll change it to add “undefined” but I thing we need to say: “if it is different from null or undefined, keep it, otherwise set it to null”, i.e. the boolean inversion then is

this.MyTimer = ( this.MyTimer === undefined || this.myTimer ===null) ? null : this.MyTimer;

re 3: poor ol’ Java guys keep forgetting about the triple = … I will amend this

re 4:
That’s neat - the reason is me not blockly :wink: → So this

((this.MyTimer !== null) ? this.MyTimer.isActive() : false) == false

becomes this

( !this.MyTimer || !this.MyTimer.isActive() )

re 5: semicolons → thanks for noting that. I already had cleaned up some of them (and also change string concatenation to js string interpolation in the code) but I now did again a thorough screening of the code. I am in favour of eliminating them but note that some of the code is standard blockly (like the one with the string concatenation or variable assignment) where I cannot change that.

So, I have changed that now (Luckily I have a few days off, so I can work on that :slight_smile: ).

sendNotification
While I have you on the line: Do you happen to know how can use “sendNotification” in javascript?

  • I cannot use event.sendNotification because “event” is not available automatically. Maybe we can add it?
  • org.openhab.io.openhabcloud.NotificationAction as a class as mentioned in some thread doesn’t work either.

I already tried to find the DSL-Code but don’t find the source for that. Maybe someone can point me to that too?

This meets my intent just fine. It’s very easy to get lost in a huge chunk of blocks and this gives a nice way to separate them out.

By any chance, does it work with arguments?

If it’s different from null we absolutely fo not want to set it to null. If we do that we will lose the handle on the Timer created the last time the script was run so we won’t be able to reschedule or cancel.

On-the-other-hand, if it’s already null (which is what your code says counter to the sentence) why reset it to null?

Maybe we need to step back a moment on what this line is supposed to do.

this.myTimer = means create a variable named “this.myTimer” and set it to the result of the expression following the “=”, or if the variable does exist, set it to the result of the expression following the “=”.

No matter what, this.myTimer will exist as a variable after this line of code. Therefore only the very first time the rule runs after it’s loaded will `this.myTimer === undefined" be true.

Now the question is what does the expression evaluate to? I propose

( this.myTimer === undefined) ? null : this.myTimer

The very first time the script runs this.myTimer gets initialized to null. From that point on, this.myTimer gets set to what ever it is already assigned to. If it’s null, it will remain null. If it’s pointing to an active or even an expired timer, it will remain pointing to that same timer.

At a high level the expression is saying: if the variable doesn’t exist, initialize it to null, otherwise do nothing.

When I first started learning JavaScript many years ago I was told there was some reason why they should be used. It was presented as a best practice to keep them. I’ve since seen the contrary said so I don’t know any more.

My recommendation would be to do a quick bit of searching (or wait until tomorrow when I’ll have a chance) to find a JavaScript coding standards and follow what it recommends. Keep in mind that Nashorn is ECMAScript 5.1 and GraalVM is ECMAScript 2021 (I think) so take note in case there are different standards for the two and choose the newer standard where feasible.

In Nashorn it would be:

var NotificationAction = Java.type("org.openhab.io.openhabcloud.NotificationAction");
NotificationAction.sendNotification("....

I’ve successfully used that in the not too distant pass. I’ve always found OH notifications to be a bit delayed and unreliable so I switched to email.

I think that should work in GraalVM too, but there is a bug in GraalVM right now that is making the typing of openHAB core classes like that not work correctly. Maybe you are running into that?

Hi Rich,

function arguments

By any chance, does it work with arguments?

As you can see in the following post blockly has a rather uncommon approach of parameters of a function which is basically a “no” to your question as a function doesn’t support local variables.

https://oucc.uk/index.php?action=content&id=47

Initialization

If it’s different from null we absolutely fo not want to set it to null .

I agree which is why I did it that way:

this.MyTimer=(this.MyTimer === undefined || this.MyTimer === null) ? null : this.MyTimer

which is the same like

this.MyTimer=(this.MyTimer !=== undefined && this.MyTimer !=== null) ? this.MyTimer : null

→ If the timer is null or undefined then set it to null, otherwise keep the value. With an if-statement I could have omitted the the assignment of the original value but the ternary operator doesn’t allow that. However, I find the latter more concise in the code or would you prefer an if like so?

if (this.MyTimer === undefined) {
  this.myTimer = null
}

Maybe this more clearer even when it was three lines line or we just format it to be a one-liner

 if (this.MyTimer === undefined) { this.myTimer = null}

I think we should go for the if-one liner, shouldn’t we?

Notifications

This is the code I am generating:

var notificationAction = Java.type("org.openhab.io.openhabcloud.NotificationAction")

notificationAction.sendNotification('you@email.com','Message')

which results into

2021-10-19 08:59:39.734 [WARN ] [e.automation.internal.RuleEngineImpl] - Fail to execute action: 2
java.lang.RuntimeException: java.lang.ClassNotFoundException: org.openhab.io.openhabcloud.NotificationAction cannot be found by org.openhab.core.automation.module.script_3.2.0.M3
	at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:531) ~[jdk.scripting.nashorn:?]
	at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:456) ~[jdk.scripting.nashorn:?]
	at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:413) ~[jdk.scripting.nashorn:?]
	at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:409) ~[jdk.scripting.nashorn:?]
	at jdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:162) ~[jdk.scripting.nashorn:?]

which btw also tells me we are on Nashorn.

The code of the action could be found here. As you can see it is code within a bundle. Now that I know the issue I am able to ask the right question :wink: and therefore found the thread that already deals with it:

@5iver recommends using the openhab-scripters library.

var OPENHAB_CONF = Java.type('java.lang.System').getenv('OPENHAB_CONF')
load(OPENHAB_CONF + '/automation/lib/javascript/core/actions.js')
NotificationAction.sendNotification('mail@me.com','Test notification')

Apart from the fact that even though I have installed these libs and I then get an exception

org.eclipse.smarthome.model.script.actions.Exec cannot be found by org.openhab.core.automation.module.script_3.2.0.M3

I think this solution is not a solution because it references the openhab-scripters library which is not delivered by default with openhab and is far from easy to be installed.

@Kai @cweitkamp As you are the author of the original NotificationAction, do you have an idea or recommendation how we could solve that?

It’s that last part I’m objecting to. If it’s already null there is no reason to set it to null again. Really, the only time we need to do anything special is when it’s undefined. So there is no reason to test for both undefined and `null’. It’s redundant.

I’ve only used the ternary operator in the past but it the if statement looks a bit more concise in what it does. I’m not too concerned about it taking up three lines. Stylistically I’d prefer that to the one liner if.

One thing the ternary operator has going for it is it explicitly shows what’s going on, but as you note, it’s got a redundant assignment. So I think I prefer the if statement best of all. But it’s not a strong preference.

Personally I like it on three lines. It’s easier for me to read. But looking at style guides a one liner if statement seems to be OK. Though they tend to be without the curly brackets.

if(this.myTimer === undefined) this.myTimer = null;

Unfortunately, that library was never updated to OH 3. @CrazyIvan359 has forked the library and has about an 80% complete (as of last reporting IIRC) port of the JavaScript library to OH 3 at GitHub - CrazyIvan359/openhab-helper-libraries at js-rewrite. Notice the branch is “js-rewrite”.

However, neither openhab-scripters nor Michael’s fork of these Helper Libraries are actually a part of the openHAB project. Furthermore there is no way to install them easily like as an add-on. Cloning a git repo and copying files to the right location is not something we can nor should we expect the audience we are targeting with Blockly to be able to do. Consequently, unless and until we figure out how to package the Helper Libraries with the automation language add-on (which is discussed in the issue @ysc linked to) the Blockly code cannot rely on it.

I believe the current consensus is to use the GraalVM JS as the language backing Blockly so if a Helper Library were to be relied upon, it would be @jpg0’s Helper Library, not @CrazyIvan359’s. The concern is Nashorn is already deprecated and will go away completely when OH upgrades Java beyond Java 11. We want to minimize rework for developers and users when that happens so it makes sense to standardize around a non-deprecated language now.

However, looking at either Helper Library’s source code is a fantastic way to discover how to do stuff in these languages.

Just to avoid the obvious, you have installed the openHAB Cloud Connector add-on? I don’t think the notification actions exist without that add-on installed.

1 Like

Hi @stefan.hoehn,

the NotificationAction will be registered dynamically as ActionService once you install the openHAB Cloud Connector. You might try to import all classes from org.openhab.core.model.script.engine.action.ActionService and import them. Like this:

(function(context) {
    'use strict';
    var oh3_actions = find_services("org.openhab.core.model.script.engine.action.ActionService", null) || [];
    oh3_actions.forEach(function (item, index) {
        context[item.actionClass.simpleName] = item.actionClass.static;
    });
})(this);

I am not a JS-Rule expert but I am doing something similar with Jython.

// Edit: the find_services method can be found here:

FYI (and a bit of a spoiler alert) I have already spent a few hours on my idea/openHAB-related project of late, that is providing the means for users to extend the Blockly toolbox themselves with YAML definitions, similarly to the UI widgets (these definitions follow and same structure are stored in the JSON DB as “UI Components” in the same manner as the widgets).

Here’s an example:

image

This would make Blockly infinitely extensible, which regarding its appeal could be a game changer.
You could define or import blocks to tackle with domain-specific interests, import utility functions, and wouldn’t solely rely on what’s on offer in the built-in toolbox.

Because in fact the situation is pretty similar to the UI Widgets actually: no matter how many built-in widgets and options you add, you will never be “exhaustive”. There will always be another need that you didn’t anticipate and now if there’s no extensibility, the pressure is on you to deliver a solution.

So instead there is a way to define custom blocks that get added at runtime to the Blockly editor toolbox, so the users themselves are in control; they can reuse self-contained YAML definitions from others that include everything, share their own… including in the new Marketplace! Since these definitions just follow the UI Components structure, like widgets, they can be easily be offered on the marketplace as well.

The best part of it is that users could choose what to install to their toolbox based on their needs - you just browse the add-on store, see what’s available and install what you need and only that! A set of blocks to deal with GPS positions, another set to deal with date & time, or a block set to deal with specific hardware or services, you name it.

Note that not only new block types could be shared (that involves defining a way to derive JS code from declarative properties with placeholders, that part is not yet done), but also pre-assembled blocks of already-existing types - for instance check the Library > Randomize category on Blockly Demo: Toolbox

2021-10-19_16-48-23

In this example you can drag and drop a whole routine block, but contrary to the individual block types that you can define and generate the code of, it’s not a single block: the user in this case can import the block assembly as an example and inspiration, but alter the logic entirely.

So to be completely honest, while these screenshots are real, there is much to do (the code generation part will be the trickiest), and I’ve been figuring out things as a go to make the final spec in my head - but don’t worry it’s coming along nicely. :slight_smile:

As a conclusion, @stefan.hoehn don’t take all this all wrong, because I support your work and believe a comprehensive built-in “system library” of blocks is very much needed and there is no conflict, neither in my intent nor in my code; my current work doesn’t really overlaps with yours, and I believe I will merge your PR before I can submit this new code; but in the end I believe Blockly in OH can have a two-tier approach - a comprenhensive, well-tested built-in set of blocks (in the openHAB category and children); and the user/runtime-defined Library.

7 Likes

I guess I completely get it right and I think it is just another great idea and improvement for OpenHAB. I would also allow me to provide good samples, I guess, for all the blocks I am currently working on to have kind of a demo.

Back to my implementation:

I only now found out that there is a linting that should be run before pushing: npm run lint

This led to quite some (mostly formatting errors)

  • formatting: Is there a way similar to “mvn spotless:apply” in the core to automatically apply the formatting? (edit: I fixed them now manually)

  • Please have a look at the audiosink-component here . I have noticed that the following popup hadn’t been implemented (even the import file doesn’t exist). Taking it out did not have, afaik, any side effect but now lint complains. How can we solve that? What does the popup need to be implemented?

edit: for the time being I readded it, so the build runs through and can be reviewed but still we should for sure talk about that.

TIA,
Stefan

Finally I was able to provide the first drop of the new blockly blocks and the build has run through on the Pull Request, so it is available for reviewing, further testing and documentation (@Andrew_Rowe).

The Pull Request

  • has refactored and cleaned up the code
  • lots of code documentation
  • tooltips and help urls were added on the blocks to point to openHAB documentation pages
  • introduced lots of new blocks including voice, multimedia streaming, calendar (ephemeris), span timer based execution
  • the blocks have been put into openHAB subgroups

image

Here are the final subgroups

Items & Things

Timers and Delays

Ephemeris

Voice/Multimedia

Logging/Output

image

For example something like this can be easily done noew:

I hope that this be a leap in using openhab blocklies.

Stefan

PS: there are more ideas to come

  • variable persistence (lets you reuse when triggering rule multiple times)
  • http requests
  • notifications (see discussion above)
  • Execution of code (from /usr/bin/command) and scripts, though I am not sure if this is actually a good idea.

PPS: I finally found out who was the originally contributor which I based my work on even though I was’t able to get in touch with him. His name is Brian Homeyer and it is only fair to appreciate his ground work on that.

3 Likes

This is part of a bigger two part concern: Actions.

executeCommandLine is one of the core OH Actions similar to isWeekend or say. I think we should endeavor to support all the core Actions.

However, there are also binding provided Actions (e.g. publishMQTT). I don’t think there is an API end point nor any other way to get the list of those Actions though so I’m not sure how we can solve that problem.

I’ll have a go at testing this out probably tomorrow (I’ve a full day today). Thanks a bunch for picking this up and moving it forward. I’m very excited about the potential!

1 Like

Rich, I created some blocks that would generate the following code

logger.info('Starting Loop')
logger.info(itemRegistry.getItem('plannedTime').members)
var theItem_list = itemRegistry.getItem('plannedTime').members;
logger.info(theItem_list)
logger.info(theItem_list.length)
for (var theItem_index in theItem_list) {
  theItem = theItem_list[theItem_index];
  logger.info(theItem)
}

Both logs in line 2 and 4 print out the following test items of my server

plannedTime_down_rs_az (Type=StringItem, State=NULL, Label=AZ Rollladen Schließzeit, Category=time, Groups=[plannedTime]), rollershutter1_time (Type=StringItem, State=NULL, Label=Rollershutter 1 Zeit, Category=, Tags=[Blinds], Groups=[plannedTime])]

line 5 prints 2 as the length

But the for-loop doesn’t print out anything which confuses me. What am I missing?

The best I can guess is the for loop isn’t executing so I’d focus on that. One thing to be careful of is that theItem_list is a Java List<Item>, not a JavaScript array, I would not expect JavaScript operations like in to work with a Java List<Item>.

Now that I’m not trying to run out the door I can post an example.

I tend to stick to Java ways when working with Java Objects which is one of my controversial positions. Given that the purpose of rules is to interact with OH and OH is Java and requires Java Objects most of the time I’m not a big fan of converting stuff to JavaScript only to have to convert it back again.

A native Java way I think to do the for loop would be something like:

var forEach = Array.prototype.forEach;
forEach.call(theItem_list, function(theItem) {
  logger.info(theItem);
});

I found this on a StackOverflow posting and cannot say whether it works or not but it’s worth a try.

Using the Java Stream API it would be something like:

theItem_list.stream()
            .forEach(function(theItem) {
              logger.info(theItem);
            });

Note, the order is not always guaranteed with stream operations because they are optimized for parallel processing.

To use pure JavaScript you need to convert the Java List<Item> to a JavaScript Array which I think can be done using

var theItem_array = Java.from(theItem_list);

“You’re simply the best, better than all the rest” :musical_note:

That exactly did the trick!

… works as expected. The is a proof-of-concept and I am sure you know where I am heading to:

  • adding a filter
  • accessing individual attributes of the item.

Which then will look like this:

Do you know by chance how I would access for example the groups attribute from plannedTime_down_rs_az

plannedTime_down_rs_az (Type=StringItem, State=NULL, Label=AZ Rollladen Schließzeit, Category=time, Groups=[plannedTime])

with respect to

var theItem_list = Java.from(itemRegistry.getItem('plannedTime').members);
for (var theItem_index in theItem_list) {
  theItem = theItem_list[theItem_index];
  logger.info(theItem)
  logger.info(theItem.getGroups)  // doesn't work
  logger.info(theItem.Groups) // doesn't work
}

OK, here’s one of the things I don’t like about Rules DSL.

Xtend and therefore Rules DSL provides some features we call “syntatic sugar”. These are little shortcuts that let you use short hand for certain things.

In this case, let’s say have a method on an Object named getFoo() that returns a foo.

Rules DSL has to bits of syntactic sugar that can come into play.

  1. When calling a method that doesn’t take arguments, Rules DSL lets you omit the parens. So MyObject.getFoo() and MyObject.getFoo are equivalent.

  2. When one has a getter method (named “getX()” where X is the thing you are getting) you can call that method as if you were accessing the value directly. For example MyObject.getFoo() and MyObject.foo are the same.

JavaScript supports none of that. If it’s a method you have to call it as a method. JavaScript only allows MyObject.getFoo().

So in this case you need theItem.getGroups().

As you proceed, I suspect you’ll find more stuff like this you’ll need to negotiate. Using the openHAB JavaDocs will be invaluable. In this case, GenericItem (openHAB Core 4.2.0-SNAPSHOT API) is a great place to start to find what’s available on an Item.

getGroups()
doesn’t work either :frowning:

However, with looking now at the GenericItem (thanks for pointing to it - I actually have the core source even cloned) It shows the toString()-method is kind of hinting to the wrong method name for the getter which is actually

theItem.getGroupNames()

which finally works. So I have to map the name to the right function.

So this will be available soon as well which definitely opens many more possibilities with blockly as it allows iterations over a number of items:

Edit: I played around with it even more and it is actually pretty cool what we can do now:

Of course you could now check the State of the item and turn it on if it is off or vice versa.

Here are the items of the group firstFloor - I intentionally created the 4th one by not categorizing as a light:

LightLeft (Type=SwitchItem, State=NULL, Label=Light Left, Category=light, Tags=[Point], Groups=[firstFloor])
LightRight (Type=SwitchItem, State=NULL, Label=Light Right, Category=colorlight, Tags=[Point], Groups=[firstFloor])
MainLight (Type=SwitchItem, State=NULL, Label=Main Light, Category=light, Tags=[Point], Groups=[firstFloor])
SocketSwitch (Type=SwitchItem, State=NULL, Label=Socket Switch, Category=poweroutlet, Tags=[Point], Groups=[firstFloor])

Note how I used the “create text with” block. Only by chance I noticed that it actually has a very clever implementation that applies a .toString() method or in case of several string a .join(). This this is neat trick which allows to scan a collection whether it contains any string. Of course this could be improved here by making the comparison case-insensitive which I left out here for brevity.

  1. I am wondering if we should a block that allows us to retrieve the state-description as well?

StateDescription [minimum=null, maximum=null, step=null, pattern=null, readOnly=false, channelStateOptions=[StateOption [value=ON, label=EIN], StateOption [value=OFF, label=AUS]]]

  1. Do you by chance know how to access the displayState as it is not part of the GenericItem so I am not currently able to support this in the block?

On another topic,

@ysc asked me to put all timers into a grouping object named “timers”. I implemented it as follows. Do you mind taking a look if this is the right way of implementing it and making it safe?

would result into

var scriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution")
var zonedDateTime = Java.type("java.time.ZonedDateTime")
var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID)

if(this.timers === undefined){
	 this.timers = new Object()
}
if(this.timers.MyTimer1 === undefined){
	 this.timers.MyTimer1 = null
}
if(this.timers.MyTimer2 === undefined){
	 this.timers.MyTimer2 = null
}

if (this.timers.MyTimer1 === null) {
	this.timers.MyTimer1 = scriptExecution.createTimer(zonedDateTime.now().plusSeconds(2), function(){
	  logger.error('abc')
})} else {
		this.timers.MyTimer1.reschedule(zonedDateTime.now().plusSeconds(2))
}
if (this.timers.MyTimer2 === null) {
	this.timers.MyTimer2 = scriptExecution.createTimer(zonedDateTime.now().plusSeconds(2), function(){
	  logger.error('abc')
})} else {
		this.timers.MyTimer2.reschedule(zonedDateTime.now().plusSeconds(2))
}
  1. If you build it someone will find a use for it I’m sure. But I wouldn’t implement that as a first priority thing to do.

  2. displayState is, as far as I know, only something that exists in the UI. But it’s not exactly clear to me if that is actually the case because StateDescription gets used all over the place in core beyond just UI stuff so maybe the equivalent of displayState is available some other way.

That approach looks reasonable and straight forward for the case where the timer name is hard coded. Is there a way to dynamically set the name of the Timer? I’m thinking of the use case where one potentially has one timer per triggering Item. In that case, the name of the Timer needs to be somehow derived from the Item’s name.

I’m a little uncomfortable about automatically rescheduling the Timer if it’s already running. That is not reflected in the blocks at all and that only covers one use case. The other use cases could be to cancel the timer instead of rescheduling it or simply to do nothing. I think Blockly needs to handle all three cases.

  1. If you build it someone will find a use for it I’m sure. But I wouldn’t implement that as a first priority thing to do.

Already fully implemented :slight_smile:

  1. displayState is, as far as I know, only something that exists in the UI

Ok, then let’s forget about it.

  1. Is there a way to dynamically set the name of the Timer?

This would be something like this where you would create a timer based on the items name

However, this would result into nonsense code:

I may even have to prevent for the time being that a variable can be put into that place…
This could maybe be solved by using timers as an associative array and not creating property fields for that which makes the implementation even more complex …!?

  1. Regarding the rescheduling
if (this.timers.MyTimer2 === null) {
	this.timers.MyTimer2 = scriptExecution.createTimer(zonedDateTime.now().plusSeconds(2), function(){
	  logger.error('abc')
})} else {
		this.timers.MyTimer2.reschedule(zonedDateTime.now().plusSeconds(2))
}

The idea wasn’t originally from but I think it actually makes sense:

  • Let’s say you trigger the rule for the first time. Then the code makes sure the timer is created.
  • Now you trigger the rule for the second time. You most like do not want the timer to be recreated but rather only rescheduled.

I am with you that this might not be directly obvious, though if we didn’t do this, I think it would become much worse in that every retriggering of the rule would create a new timer which I think would be worse, wouldn’t it?
An alternative could be that we would have to then cancel the original timer and by assigning the new timer it should then free up the reference of the old one and be garbage collected.