openhab-core icon indicating copy to clipboard operation
openhab-core copied to clipboard

Javascript based rules lack the ability for global variables

Open FSeidinger opened this issue 4 years ago • 19 comments

As already mentioned in the community under Experimental Next-Gen Rules Engine Documentation 1 of : Introduction, the OH3 rule engine lacks the ability to hold variables that have a persistent nature for ECMA Script rules. As also described in the article, the most common use case for such variables would be timer handles or stored state.

So not having the possibility to store the timer handles reduces the acceptance and usability of rules based on javascript significantly. In contrast, the rule DSL based rules had this feature for a long time and they still have.

To solve this issue I can think of two solutions:

  1. Introduction of a global context that gets injected into the script engine environment when the script is created or updated.
  2. Adding a shutdown hook to rules that can be implemented by the rule script to give the script the ability to do it's housekeeping before it gets destroyed.

FSeidinger avatar Jan 07 '21 21:01 FSeidinger

In contrast, the rule DSL based rules had this feature for a long time and they still have.

That's not true - there are no global variables and I am not convinced that global variables are a good concept in general. What DSL rules (from rule files) offer is a shared context between rules within the same file. Files hence create a natural boundary for them, especially as they are also loaded and refreshed together.

As you mainly seem to be concerned about Javascript rules: Isn't it already possible to have shared variables in Javascript files (by creating scripted rules)? This would then be similar to what exists for DSL rules (from files) and probably be the simplest solution.

If we really want to introduce global scopes in rules in general, we should imho not only do it in ScriptActions, but in ALL rule modules, independent of whether they are scripted or Java. But that would be a bigger change and it would break the current isolation, potentially introduce concurrency issues and increase the complexity.

kaikreuzer avatar Jan 09 '21 23:01 kaikreuzer

That's not true - there are no global variables and I am not convinced that global variables are a good concept in general. What DSL rules (from rule files) offer is a shared context between rules within the same file. Files hence create a natural boundary for them, especially as they are also loaded and refreshed together.

Dear Kai,

thanks for the clarification. Maybe I was not very precise with my words. But there is a mismatch in the behaviour of the contexts between the DSL scripts and the script engine scripts. When you declare variable in a Rule DSL file outside the rules itself, you get, what most of the users would call a 'global variable'. It is true, that it is only global in the context of the file, but it survives changes and edits to the rule and the action script. So, if I store e.g. a timer instance in such a variable, it will be there next time the rule runs, even if it was updated or edited.

This is different with an jsr223 action script on a rule. If you store a variable in the local context of the script, it survives for the next invocations of the script, but will be lost, if you edit or update the script/rule. So the stored, and still active timer instance is lost.

This shortcoming is described several times in the community forum and hinders users to switch from DSL scripts to script engine scripts. The community discussions addressing this topics e.g. are:

Experimental Next-Gen Rules Engine Documentation 1 of : Introduction Use of Timers within OH 3

As you mainly seem to be concerned about Javascript rules: Isn't it already possible to have shared variables in Javascript files (by creating scripted rules)? This would then be similar to what exists for DSL rules (from files) and probably be the simplest solution.

As I said, the behaviour is different. The shared variables get lost if the rule is updated or the script is edited. Or do you mean scripts that are not created with the UI but on the file system?

If we really want to introduce global scopes in rules in general, we should imho not only do it in ScriptActions, but in ALL rule modules, independent of whether they are scripted or Java. But that would be a bigger change and it would break the current isolation, potentially introduce concurrency issues and increase the complexity.

That is a very good point and should be discussed further. I've submitted a pull request to show how this could be done in a first version. The global scope introduced there covers all engines handled by the 'javax.script' package.

FSeidinger avatar Jan 10 '21 00:01 FSeidinger

You can test the behaviour for yourself. I've created a simple rule in the main UI with the following code:

triggers: []
conditions: []
actions:
  - inputs: {}
    id: "3"
    configuration:
      type: application/javascript
      script: >
        var logger =
        Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.core.model.script.Test");

        var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");

        var ZonedDateTime = Java.type("java.time.ZonedDateTime");


        logger.info("Scriped invoked");


        if (this.timer === undefined) {
          this.timer = ScriptExecution.createTimer(ZonedDateTime.now().plusSeconds(60), function() {
            logger.info("Timer fired");
          });
          
          logger.info("Timer {} created", this.timer);
        } else {
          if (this.timer.isActive()) {
            this.timer.cancel();
            logger.info("Timer {} cancelled", this.timer);
          } else {
            logger.info("Timer {} is inactive", this.timer);
          }
        }
    type: script.ScriptAction

Then press the run button several times. The log looks like this:

2021-01-10 02:05:02.109 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked
2021-01-10 02:05:02.119 [INFO ] [org.openhab.core.model.script.Test  ] - Timer org.openhab.core.model.script.internal.actions.TimerImpl@2f959178 created
2021-01-10 02:05:08.174 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked
2021-01-10 02:05:08.175 [INFO ] [org.openhab.core.model.script.Test  ] - Timer org.openhab.core.model.script.internal.actions.TimerImpl@2f959178 cancelled
2021-01-10 02:05:11.445 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked
2021-01-10 02:05:11.446 [INFO ] [org.openhab.core.model.script.Test  ] - Timer org.openhab.core.model.script.internal.actions.TimerImpl@2f959178 is inactive
2021-01-10 02:05:16.597 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked
2021-01-10 02:05:16.598 [INFO ] [org.openhab.core.model.script.Test  ] - Timer org.openhab.core.model.script.internal.actions.TimerImpl@2f959178 is inactive
2021-01-10 02:05:21.133 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked
2021-01-10 02:05:21.134 [INFO ] [org.openhab.core.model.script.Test  ] - Timer org.openhab.core.model.script.internal.actions.TimerImpl@2f959178 is inactive
2021-01-10 02:05:30.173 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked
2021-01-10 02:05:30.174 [INFO ] [org.openhab.core.model.script.Test  ] - Timer org.openhab.core.model.script.internal.actions.TimerImpl@2f959178 is inactive
2021-01-10 02:05:41.988 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked

Then press the save button in the UI and the run button several times more. The logs show, that the timer variable got lost:

2021-01-10 02:05:02.109 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked
2021-01-10 02:05:02.119 [INFO ] [org.openhab.core.model.script.Test  ] - Timer org.openhab.core.model.script.internal.actions.TimerImpl@2f959178 created
2021-01-10 02:05:08.174 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked
2021-01-10 02:05:08.175 [INFO ] [org.openhab.core.model.script.Test  ] - Timer org.openhab.core.model.script.internal.actions.TimerImpl@2f959178 cancelled
2021-01-10 02:05:11.445 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked
2021-01-10 02:05:11.446 [INFO ] [org.openhab.core.model.script.Test  ] - Timer org.openhab.core.model.script.internal.actions.TimerImpl@2f959178 is inactive
2021-01-10 02:05:16.597 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked
2021-01-10 02:05:16.598 [INFO ] [org.openhab.core.model.script.Test  ] - Timer org.openhab.core.model.script.internal.actions.TimerImpl@2f959178 is inactive
2021-01-10 02:05:21.133 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked
2021-01-10 02:05:21.134 [INFO ] [org.openhab.core.model.script.Test  ] - Timer org.openhab.core.model.script.internal.actions.TimerImpl@2f959178 is inactive
2021-01-10 02:05:30.173 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked
2021-01-10 02:05:30.174 [INFO ] [org.openhab.core.model.script.Test  ] - Timer org.openhab.core.model.script.internal.actions.TimerImpl@2f959178 is inactive
2021-01-10 02:05:41.988 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked
2021-01-10 02:05:41.997 [INFO ] [org.openhab.core.model.script.Test  ] - Timer org.openhab.core.model.script.internal.actions.TimerImpl@356a7731 created
2021-01-10 02:05:46.110 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked
2021-01-10 02:05:46.112 [INFO ] [org.openhab.core.model.script.Test  ] - Timer org.openhab.core.model.script.internal.actions.TimerImpl@356a7731 cancelled
2021-01-10 02:05:48.981 [INFO ] [org.openhab.core.model.script.Test  ] - Scriped invoked
2021-01-10 02:05:48.982 [INFO ] [org.openhab.core.model.script.Test  ] - Timer org.openhab.core.model.script.internal.actions.TimerImpl@356a7731 is inactive

FSeidinger avatar Jan 10 '21 01:01 FSeidinger

Whilst I can understand the desire for some kind of persistence between script changes, I don't agree that this should be the default like you are suggesting in the above comment.

I believe that in the common case the opposite is more desirable; that you get a clean state when the script is run. If there may be left-over global variables it is very likely to result in extremely hard-to-debug problems later. It is also likely to break any 3rd party scripts which may not expect these variables to be predefined.

I am sympathetic to the need for shared or persistent state though. The simple case of static state can be solved with referencing a shared file. For dynamic state I would suggest an explicit cache object which can have objects put into or taken out of. I did implement this as a script extension at one point, but ultimately found that I could get away without it (and typically ended up with cleaner code).

jpg0 avatar Jan 10 '21 09:01 jpg0

Whilst I can understand the desire for some kind of persistence between script changes, I don't agree that this should be the default like you are suggesting in the above comment.

It is not the default per se. The developers of the javax.script API already took care of this with introducing the two scopes ScriptContext.ENGINE_SCOPE and ScriptContext.GLOBAL_SCOPE. As long as you put your variables into the engine scope, nothing changes, because the engine scope takes precedence over the global scope when resolving variables.

You can therefore decide as a user, which scope your variable go in. My suggested pull request does exactly that and introduces the global scope under the globalScope property.

I believe that in the common case the opposite is more desirable; that you get a clean state when the script is run. If there may be left-over global variables it is very likely to result in extremely hard-to-debug problems later. It is also likely to break any 3rd party scripts which may not expect these variables to be predefined.

With this I agree and will not see to break this. But it should be possible if the user chooses so. And this is why I came up with the topic. In the community forums you can see that there is reservation against the jsr223 engines because they behave different in this sense to the DSL script engine. And my whole point is about user experience here.

But the current behaviour produces exactly such leftovers. The timer handles of future scheduled timers are lost and cannot be cancelled or rescheduled anymore. But they will executed if they are due, no matter if you wanted that or not.

We could also try out to drop then the 'quasi global' behaviour of the DSL rules engine to have the same user experience and then we can take feedback from the community.

I am sympathetic to the need for shared or persistent state though. The simple case of static state can be solved with referencing a shared file.

And so are the community users as well. But again. For the same user experience you have to do the extra step of creating shared scripts just to get the shared state. This is not necessary with the DSL rules and adds additional complexity on users trying to migrate from DSL rules to jsr232 rules.

For dynamic state I would suggest an explicit cache object which can have objects put into or taken out of. I did implement this as a script extension at one point, but ultimately found that I could get away without it (and typically ended up with cleaner code).

If you talk of cache, then you mean an extra service holding, well, a global context under user control? This would be a similar solution than the one I suggested with introducing an additional global context.

In the end I'm glad, that there is such a fruitful discussion about the topic and will be thrilled to reach a conclusion about this. Happy sunday to you all :-)

FSeidinger avatar Jan 10 '21 12:01 FSeidinger

I am by no means an expert on this, nor have I figured out how to do it from JavaScript, but isn't this what ScriptExtension is for? At least for Jython there is an example in the Helper Libraries for setting and retrieving values from a custom ScriptExtension and supposedly this would work to share variables between rules written in different languages (except Rules DSL which doesn't have direct access to ScriptExtension as far as I know.

https://github.com/openhab-scripters/openhab-helper-libraries/blob/master/Script%20Examples/Python/script_extension_example.py

I've not spent any time yet to figure out how to port that to JavaScript but this should be a way to share variables between rules.

But the current behaviour produces exactly such leftovers. The timer handles of future scheduled timers are lost and cannot be cancelled or rescheduled anymore. But they will executed if they are due, no matter if you wanted that or not.

I think I agree with Kai. Rules DSL and JavaScript are behaving in exactly the same way in this regard. If you define your rules in a .js file, you can define "global" variables in that file and share them between all the Rules defined in that file just like you can in .rules files.

However, if you define the Rules through the UI, and variables will only exist in that specific Script Condition or Script Action. You can save that variable so it persists between runs of the rule (e.g. this.myTimer = (this.myTimer === undefined) ? null : this.myTimer;). You just can't share it with other rules or Script Actions. In fact, in this respect, JavaScript is behaving better than Rules DSL because I'm not aware of a similar way to do this in Rules DSL in Script Actions and Script Conditions. I haven't tried yet though so it might be possible.

tl;dr I think this is solving the wrong problem. The issue isn't that .js files and .rules files work different in regard to "global" variables. The problem is Rules DSL Script Actions and Script Conditions do not have an obvious way to define and reuse a variable across multiple runs.

rkoshak avatar Jan 11 '21 16:01 rkoshak

@FSeidinger if you are creating a specific variable to hold the state (globalScope) then I have no problem with you suggestion in principal. I do agree with @rkoshak that it should be via a script extension though. This allows it to be used across all languages. It's incredibly easy to write, much simpler than in the code in your suggested pull request (and doesn't mix the logic up with the script loading code itself).

jpg0 avatar Jan 16 '21 22:01 jpg0

But the current behaviour produces exactly such leftovers. The timer handles of future scheduled timers are lost and cannot be cancelled or rescheduled anymore. But they will executed if they are due, no matter if you wanted that or not.

I think I agree with Kai. Rules DSL and JavaScript are behaving in exactly the same way in this regard. If you define your rules in a .js file, you can define "global" variables in that file and share them between all the Rules defined in that file just like you can in .rules files.

However, if you define the Rules through the UI, and variables will only exist in that specific Script Condition or Script Action. You can save that variable so it persists between runs of the rule (e.g. this.myTimer = (this.myTimer === undefined) ? null : this.myTimer;). You just can't share it with other rules or Script Actions. In fact, in this respect, JavaScript is behaving better than Rules DSL because I'm not aware of a similar way to do this in Rules DSL in Script Actions and Script Conditions. I haven't tried yet though so it might be possible.

I realize now, that I made a hidden assumption here and I want to share it with you. My feeling was that OH3 is all about improving the user experience and bring down the mental load to get things (of users) done. For that reason, my impression was, that you all put a lot of effort into the new main UI especially to define scripts and rules. So the current state, in my feeling is, that you can just click and configure your trigger conditions and action scripts for a rule. You also gave the users the opportunity to define the action script parts with Blockly to further lower the burden of programming the scripts. The best outcome I can see here is, that you do not need to install/program/maintain text files any more.

And my issue tries to address the problem perceived from that point of view. So yes, I talk of the behaviour of action scripts of a rule administered via UI compared the a rule script manually maintained on the file system.

tl;dr I think this is solving the wrong problem. The issue isn't that .js files and .rules files work different in regard to "global" variables. The problem is Rules DSL Script Actions and Script Conditions do not have an obvious way to define and reuse a variable across multiple runs.

That is a much better description of my issue. I mean exactly that. There is no obvious way via UI to store a variable to be used at least across multiple runs. My extension to that would be that the variable is preserved even it the rule was changed or the action script edited. Because this is simply what you do when you are in the developing or testing phase of a rule.

@FSeidinger if you are creating a specific variable to hold the state (globalScope) then I have no problem with you suggestion in principal. I do agree with @rkoshak that it should be via a script extension though. This allows it to be used across all languages. It's incredibly easy to write, much simpler than in the code in your suggested pull request (and doesn't mix the logic up with the script loading code itself).

I'm by no means think, that my solution is the only or best one to address this issue. And up to this point I got a good picture about your reservations against my proposal. And after that important phase I for myself see the need of two things to start:

  1. A clear decision, if this is a real issue and must be solved by means of OH3
  2. If you decide that this is an issue worth the effort of extending OH3, let's talk about alternative approaches and their pros and cons.

Kind regards,

Frank

P.S. If you decide to solve this issue by means of OH3, I volunteer to work on a solution and submit the needed pull requests

FSeidinger avatar Jan 17 '21 10:01 FSeidinger

It's incredibly easy to write

@jpg0 A quick example would be really really really appreciated. I tried to figure it out from the Python Helper Libraries but ran out of time and haven't come back. If you know how to create and populate a ScriptExtension from Nashorn off the top of your head that would fill a gap in my knowledge and I'll create a tutorial on the forum and add it to the getting started docs.

There is no obvious way via UI to store a variable to be used at least across multiple runs.

In JavaScript it's pretty simple:

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

That sets the variable this.myTimer to null if it's never been defined (i.e. undefined) or keeps what ever value it was set to the last run of the script.

I assume Python and Groovy have something similar that could be used. If they don't, then ScriptExtensions is probably the way to go. Rules DSL has no way to do this as far as I know. And that is why I say this is solving the wrong problem. JavaScript already handled preserving a variable across runs of the script. Rules DSL doesn't (as far as I know).

My extension to that would be that the variable is preserved even it the rule was changed or the action script edited.

I don't think that is ever going to be possible unless ScriptExtensions are used. And even there if OH restarts I suspect the ScriptExtensions will be reset too.

The advantage of ScriptExtensions are:

  • it's already available, no changes are required
  • it works across all the supported languages (except Rules DSL?)
  • it is separate from the Rules so reloading a file or changing a rule won't wipe out the values

A clear decision, if this is a real issue and must be solved by means of OH3

I've vote for yes, preserving variables between runs of a rule is something that must be solved for all the supported languages. I think getting knowledge out there about how to access and use ScriptExtensions will be the way to go for this one.

If you decide that this is an issue worth the effort of extending OH3, let's talk about alternative approaches and their pros and cons.

I'd say let's give ScriptExtensions a shot and see if it addresses the problem. If so then I'd say lets make what ever updates are necessary so it can be done in Rules DSL Scripts as well as the other languages.

That's just my opinion though. The maintainer's opinions will carry more weight.

rkoshak avatar Jan 20 '21 18:01 rkoshak

A quick example would be really really really appreciated.

@rkoshak when I said that "It's incredibly easy to write" I actually meant in Java, not Javascript. It's a single Java class that supplies a single script extension. If you want to do this in JS, it is still possible, but needs supporting JS to handle the osgi bits - so moves out of the "easy" realm unfortunately. If you do want to do this, then you need to essentially do the same as in Java: create a class which implements ScriptExtensionProvider then register it in the osgi runtime. I do have code which does this (for example I have a generated sitemap), but it's for ES6 so wouldn't work directly. The osgi bits are here: https://github.com/jpg0/oh-config/blob/master/automation/lib/javascript/personal/node_modules/sitemap/singletons.js. Happy to help if you want to try to get it working in ES5 though.

jpg0 avatar Jan 27 '21 10:01 jpg0

Thanks, I'll take a look. If I can just get a look at which Java classes from OH I need to interact with and in what ways I can muddle my way through. I've looked at the Python examples in the Helper Library and frankly got lost figuring out what's Python and what's openHAB.

rkoshak avatar Jan 27 '21 20:01 rkoshak

You don't actually need to interact with any classes, you just need to register a class with OSGi that implements ScriptExtensionProvider. OSGi will ensure that the rest works. The primary class that does this for default bits is: https://github.com/openhab/openhab-core/blob/main/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/defaultscope/DefaultScriptScopeProvider.java - you can see that this class is self-contained and called by others rather than having to interact with OH itself.

jpg0 avatar Jan 28 '21 00:01 jpg0

After some time has passed, I want to start discussions about possible solutions. Currently I see the following approaches to solve or improve this issue:

  1. Create and register a script extension that gives access to a named scope, like globalScope as suggested by @jpg0.
  2. Create a named scope, like globalScope and inject it into the ScriptContext.GLOBAL_SCOPE context as suggested by me.
  3. Introduce a TimerRegistry which holds created timers and which can be accessed by scripts.
  4. Use a StorageService implementation for storing user created objects, like timers. Actually I see multiple approaches here: a. Use the already existing JsonStorageService b. Use an in-memory implementation of a StorageService

Please feel free to add other possible solutions. I will edit this post accordingly. For further discussion I've also started a pros and cons list. Feel free to add comments. I will edit this post accordingly.

Approach Pros Cons
1 - Script extension :heavy_plus_sign: Easy to implement
:heavy_plus_sign: Does not oppose a security risk
:heavy_plus_sign: Does not introduce or break OH 3.0 concepts
:heavy_minus_sign: Needs a second thought on housekeeping/lifecycle
2 - Global scope :heavy_plus_sign: Moderate to implement
:heavy_plus_sign: Introduces an explicit concept for user controlled variables with JSR 223 script engines
:heavy_minus_sign: Could impose a stability or security problem
:heavy_minus_sign: Needs a second thought on housekeeping/lifecycle
3 - Timer registry :heavy_plus_sign: Moderate to implement
:heavy_plus_sign: Gives timers the same life cycle than other platform objects, like items, things, etc.
:heavy_minus_sign: Needs a second thought on housekeeping/lifecycle
4a - JSON storage :heavy_plus_sign: Already usable by scripts with OSGI lookup and can be improved with a script extension :heavy_minus_sign: Does not work with timers, because GSON is not able to serialize the timer implementation
4b - In memory storage :heavy_plus_sign: Easy to implement either MAP backed or moving the VolatileStorageService from test code to production code
:heavy_plus_sign: Works well with all kinds of objects
:heavy_plus_sign: User can have as many contexts as he likes
:heavy_minus_sign: Needs a second thought on housekeeping/lifecycle

FSeidinger avatar Jan 29 '21 15:01 FSeidinger

Thanks for the summary, @FSeidinger. From my pov, I would rule out option 4, since the Storage Service is really meant for persisting entities to disk and not as an in memory db. This simply does not fit here. Wrt option 3: This would introduce "Timer" as its own (important) concept. My impression was that this issue is about any kind of variable where Timers are only one example. So this option does not sound generic enough to me.

Leaves us with 1 and 2. I'm personally no expert in JSR223 and have to admit that I haven't really figured out yet how ScriptContext.GLOBAL_SCOPE exactly behaves and how it fits into our usage of scripts in openHAB. My tendency would hence be option 1 as this is a dedicated concept in the openHAB rule engine script support. But I'm happy to leave details of that discussion to @jpg0 and you.

In general, I'd like to reiterate that I think that global variables are rather a thing to avoid. While it gives users a lot of flexibility, it also easily leads to spaghetti code and strong coupling. So I wonder whether we really want/need global variables or if the main issue is the "reuse a variable across multiple runs" (while still being local to the script). In this case, we might need to look into other directions for an appropriate solution.

kaikreuzer avatar Mar 14 '21 22:03 kaikreuzer

I don't have an opinion on option 4 so defer to Kai's assessment. I agree that option 3 is too limited, however that approach would open up the possibility of showing scheduled timers on the Schedule in MainUI some day which would be kind of nice. And frankly I've always thought that the way we use timers now is a bit awkward and a registry might address that. So I wouldn't abandon the idea entirely.

But, while timers are the most common use case, they are not the sole use case. While I tend to agree that global variables in general are to be avoided, we can't ignore the fact that there are tons and tons of existing OH rules code posted out there in multiple languages that rely upon variables that are global to a given rules text file. With UI rules there is no file so to make these sorts of rules work in the UI either they have to be completely reworked to eliminate the global variables (ideal but unrealistic for a good portion of OH users), truly global variables would need to be supported (2), or some sort of global data store to keep them in and access them from (1).

Over all I'm more in favor of 1 as that seems to be a bit less disruptive over all. It also potentially solves another potential problem of orphaned timers. Right now if a rule has created a timer, becomes edited or disabled and therefore reloaded, that timer will become orphaned. When the timer finally does go off an error will appear in the logs. If we could put that timer into a ScriptExtension then the timer's handle will still be there and we can cancel it as the first step with the rule runs. It's not as clean as scriptLoaded and scriptUnloaded but it'd work in UI rules.

So I wonder whether we really want/need global variables or if the main issue is the "reuse a variable across multiple runs" (while still being local to the script). In this case, we might need to look into other directions for an appropriate solution.

I don't think just preserving variables from one run to the next is sufficient. Consider the following. Let's say I create a timer in a Script Action. As long as this timer has not completed, the rule doesn't need to run again even if the triggering event occurs. The natural thing to do would be to add a Script Condition to check for the existence of the timer. But that's not possible because the variable that holds the timer only exists in the Script Action. It's not the end of the world to put the test for the timer in the Script Action, but that limits things. For example, what if I have a Script Action and a sendCommand to an Item and a call to trigger another rule? I could only prevent the Script Action from running while the timer exists, not all three unless I had a way to add the test to the Script Condition. Are there work arounds? Yes, one can use a Switch Item as a lock, but the latency in updating an Item makes that not as attractive of a solution.

At least with JavaScript we have a way to preserve a variable between runs of a Script already. I don't know however if the same approach works for any of the other languages as it depends on the ability to test whether a variable is already defined and only if it is not to define it. I don't know if Python or Groovy supports that and am pretty sure Rules DSL does not.

rkoshak avatar Mar 15 '21 22:03 rkoshak

I don't think just preserving variables from one run to the next is sufficient.

I can't imagine I'm the only person using "old style DSL file 'globals'" to share objects between rules? e.g. a Timer that can be set up by one rule, and referred to or manipulated by others or a hashmap "array" of properties used by several rules

These kind of uses do not lend themselves to use of an Item or its metadata as the "sharing" method. Being forced to write one megarule for common data, instead of being able to divide functionality, seems like a step backwards.

Rossko57 avatar May 01 '21 13:05 Rossko57

@Rossko57 But that is already possible with JSR-223 rules using JavaScript (or any other language). You just can't do that with UI defined rules. Textual rules can share variables between rules in the same file (like the DSL rules can).

Regarding the timers or cleanup: In textual rules you can define a scriptUnloaded method/function that is called when the script file is unloaded. Timers can be cancelled in that method/function or other things cleaned up. There is also a scriptLoaded that can be used to do initialization. This is no solution for UI based rules, though.

I would also vote for (1).

J-N-K avatar Sep 03 '21 17:09 J-N-K

This issue has been mentioned on openHAB Community. There might be relevant details there:

https://community.openhab.org/t/global-variables-in-ui-rules/127672/3

openhab-bot avatar Oct 18 '21 19:10 openhab-bot

This issue has been mentioned on openHAB Community. There might be relevant details there:

https://community.openhab.org/t/extending-blockly-with-new-openhab-commands/127169/96

openhab-bot avatar Nov 01 '21 13:11 openhab-bot