Problem with Timer in an ECMA Script

Hello,

I have a problem with a rule which works in OH3 but in OH4 I have still problems and don’t find a solution. I guess it’s imple but no idea.

This is the rule

ZonedDateTime = Java.type(“java.time.ZonedDateTime”);
var TimerMinutes = items[“Timer_Badezimmer”];
this.timer = ScriptExecution.createTimer(ZonedDateTime.now().plusMinutes(TimerMinutes), turnOff);

I have an item “Timer_Badezimmer” type number which defined the time in minutes to set the heater off.

With OH 3 it works without problems, in OH4 it looks like the type was changed.

This is the OH log :

2023-10-20 12:07:03.381 [ERROR] [b.automation.script.javascript.stack] - Failed to execute script:
org.graalvm.polyglot.PolyglotException: TypeError: invokeMember (plusMinutes) on java.time.ZonedDateTime@1c4161f failed due to: Cannot convert ‘[object Object]’(language: JavaScript, type: d) to Java type ‘long’: Invalid or lossy primitive coercion.
at .:program(:24) ~[?:?]
at org.graalvm.polyglot.Context.eval(Context.java:399) ~[?:?]
at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:458) ~[?:?]

2023-10-20 12:07:03.398 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID ‘0e0284f2af’ failed: org.graalvm.polyglot.PolyglotException: TypeError: invokeMember (plusMinutes) on java.time.ZonedDateTime@1c4161f failed due to: Cannot convert ‘[object Object]’(language: JavaScript, type: d) to Java type ‘long’: Invalid or lossy primitive coercion.

many thanks
Chris

plusMinutes() expects a long Value, not an Item.
I don’t use JavaScript, but at least I would expect something like

ZonedDateTime = Java.type("java.time.ZonedDateTime");
var TimerMinutes = (items["Timer_Badezimmer"].getState as Number).intValue;
this.timer = ScriptExecution.createTimer(ZonedDateTime.now().plusMinutes(TimerMinutes), turnOff);

Many thanks for the reply.

Your hint doesn’t solve the problem, looks like as was remarked as wrong. I’m also no a Java guy so I have no clue what I can do that it works again. I’m not sure if it is a Java problem or a change in the definition of the items in OH4.

the item itself is defined as type number.

label: Timer Badezimmer
type: Number
category: number
groupNames:
  - gPersistanceReboot
groupType: None
function: null
tags: []

bests

As said, I don’t use JavaScript…
As it turns out, you have to write it like that:

ZonedDateTime = Java.type("java.time.ZonedDateTime");
var TimerMinutes = (items["Timer_Badezimmer"].numericState;
this.timer = ScriptExecution.createTimer(ZonedDateTime.now().plusMinutes(TimerMinutes), turnOff);

I don’t know if you will need to use an additional .int to get the integer value.

Which JS Scripting add-on are you using?

The OH 3 rule is definitely Nashorn ECMAScript 5.1 and it will not work in GraalVM ECMAScript 2022+ as written. They are different add-ons and mostly not compatible with each other without some extra work on your part which, ultimately, is work better spent rewriting your rules to take advantage of the helper library that comes with the newer add-on.

That will work but the more canonical way to write it would be:

var TimerMins = 'PT'+items['Timer_Badezimmer'].state+'M'; // create a duration string
cache.private.put('timer', ScriptExecution.createTimer(time.toZDT(TimerMins), turnOff);

Or, if Timer_Badezimmer were a Number:Time instead of just a Number:

cache.private.put('timer', ScriptExecution.createTimer(time.toZDT(items['Timer_Badezimmer']), turnOff);

To pull the timer from the cache use:

var timer = cache.private.get('timer');

In the new add-on with the helper library:

  • there is almost never the need to import anything from Java, just about everything has been wrapped with pure JavaScript classes; all your interactions with OH should use the library instead of the raw Java Objects

  • time.toZDT() will intelligently handle almost everything that can be converted to a ZonedDateTime, see the table in the docs for details

  • don’t use this to save variables from one run to the next. Use the cache. Use cache.private if it’s limited to this one Script Action and use cache.shared if you need to share the value across multiple script actions. Note that Timers stored in the cache will be cancelled automatically if the Script becomes unloaded. No more orphaned Timers going off and throwing an exception because you edited the rule.

  • if you use openHABian, openhab_rules_tools is installed by default or can be installed through openhabian-config. This library has a bunch of utilities to make creation of and management of Timers easier. For example, assuming all turnOff does is send an OFF command to an Item at the given time you could use:

var { Deferred } = require('openhab_rules_tools');
Deferred().defer('NameOfLightItem', 'OFF', 'PT'+items.Timer_Badezimmer.state+'M', true);
1 Like

Many thanks for the detail explanation. I use GraalVM ECMAScript 2022+.

I have installed today the “openhab_rules_tools” now and will make some tries but today I’m still busy but will check tomorrow more in detail and come back.

bests
Chris

Hello,

now I test it - first, the solution from Udo works. So the priority is not there anymore.

But it would be more comfortable if the second provided solution of Rich will work for me. I setup a test rule and add the lines like posted.

I defined a new item with number:time () and add

cache.private.put('timer2', ScriptExecution.createTimer(time.toZDT(items['Timer_Badezimmer2']), turnOff));

But I got an error

2023-10-22 09:27:00.335 [ERROR] [b.automation.script.javascript.stack] - Failed to execute script:

org.graalvm.polyglot.PolyglotException: Error: “Timer_Badezimmer2 (Type=NumberItem, State=1 s, Label=Timer-Badezimmer 2, Category=)” is an unsupported type for conversion to time.ZonedDateTime

Here I see two problems - first I got an error message about conversion. Not sure but it could be an result that the item includes now seconds and not minutes and the kind of it is part of the state (“1 s”)

Another question : If I put the command on the private stack, will it be executed straight away or does it have to be triggered explicitly?

Last but not least, I have add the add_on library - must I add an automatisation to handle it ?

Sorry for this amount of questions but I think it could help some other too to understand it a little bit more.

many thanks for your patience :slight_smile:
Chris

I have always struggled with the timers. It always seems so much code to do something so simple :grinning:
I just put the timer value in minutes in the script rather than use an item.

This is a script I am using to execute a script on the server and I am using the latest scripting on OH4. Works for me.

var zmtimer=5

if (cache.private.exists('zmalarm') === false || cache.private.get('zmalarm').hasTerminated()) {
       actions.Exec.executeCommandLine(time.Duration.ofSeconds(20), "/usr/local/sbin/zoneminder-alarm.sh","on");
       console.log("Zoneminder alarm ON: ");

  cache.private.put('zmalarm', actions.ScriptExecution.createTimer('zmalarm', time.ZonedDateTime.now().plusMinutes(zmtimer), function () {
       actions.Exec.executeCommandLine(time.Duration.ofSeconds(20), "/usr/local/sbin/zoneminder-alarm.sh","off");
       cache.private.remove('zmalarm');
       console.log("Zoneminder alarm OFF: ");    
  }));
} else {
       cache.private.get('zmalarm').reschedule(time.ZonedDateTime.now().plusMinutes(zmtimer));
       console.log("Zoneminder alarm script running reschedule ");
};


Thanks Greg,

for me the part to “test if exist” and to reschedule is interesting. But the basic idea is to have a items which can set flexible in the GUI. Here I have a problem with the conversion of the item to the Java function. If I use a number it works for me in all variants.

bests,
Chris

Then this line might work:

var zmtimer=items.getItem("Timer_Badezimmer2").numericState

Yes, you are right, this works.

@rlkoshak I tried you way and go a little bit forward, but not at all.

I create a test event and use it like described here

    var logger          = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID); 
    var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");  
    var ZonedDateTime   = Java.type("java.time.ZonedDateTime");  
    var TimerMinutes    = 2
    logger.info("Badezimmer Timer für "+TimerMinutes+" Minuten gestartet");


    logger.info("Badezimmer Timer trace 2");

    function turnOff() {
      events.sendCommand("SetPointTemperature", 13);
      logger.info("Badezimmer Timer nach "+TimerMinutes+" Minuten beendet");
      events.postUpdate("Alexa_Sprachausgabe","Badezimmer heizen beendet");
      events.sendCommand("Alexa_Sprachausgabe_Say",ON);
    }

    logger.info("Badezimmer Timer trace 3");

    cache.private.put('timer2', ScriptExecution.createTimer(time.toZDT(items['Timer_Badezimmer'].numericState), turnOff));

    logger.info("Badezimmer Timer trace 4");

Result

2023-10-22 12:53:18.043 [INFO ] [org.openhab.rule.cb3c4659b8 ] - Badezimmer Timer für 2 Minuten gestartet

2023-10-22 12:53:18.046 [INFO ] [org.openhab.rule.cb3c4659b8 ] - Badezimmer Timer trace 2

2023-10-22 12:53:18.048 [INFO ] [org.openhab.rule.cb3c4659b8 ] - Badezimmer Timer trace 3

2023-10-22 12:53:18.043 [ERROR] [b.automation.script.javascript.stack] - Failed to execute script:

org.graalvm.polyglot.PolyglotException: ReferenceError: “scriptExtension” is not defined

a short time later

2023-10-22 12:53:18.086 [INFO ] [org.openhab.rule.cb3c4659b8 ] - Badezimmer Timer trace 4

2023-10-22 12:53:18.185 [WARN ] [ore.internal.scheduler.SchedulerImpl] - Scheduled job ‘’ failed and stopped

org.graalvm.polyglot.PolyglotException: ReferenceError: “events” is not defined

The second message I could understand if I only run the script but I add it into an event and it runs only if the test sw is set to on.

Is the openhab_tool needed in this case ? must I load the library ?

bests
Christian

You might be running into a bug in the helper library depending on which version you are running. I know there was a bug with DateTime Items but maybe the same problem exists for Number Items. time.toZDT(items['Timer_Badezimmer2'].quantityState) should work around that bug.

The bug was that it doesn’t recognize the Item that’s passed to it as an Item, covers it to a string and of course the toString of an Item doesn’t make sense as a date time or time duration.

I don’t understand the question. There is no “stack”. at all. Do you mean the private cache? That’s just a place to store data. You don’t need to do anything special.

A slightly terser option could be

var zmtimer = 'PT5M'
...
    cache.private.put('zmalarm', actions.ScriptExecution.createTimer('zmalarm', time.(zmtimer), function () {

The whole purpose of time.toZDT() is to make it so you don’t have to mess with plusMinutes this and minusSeconds that.

That error indicates a typo. “ScriptExecution”. Case matters.

There is no such thing as events in GraalVM JS. That’s a Nashorn thing.

`

    var TimerMinutes    = 2
    console.info("Badezimmer Timer für "+TimerMinutes+" Minuten gestartet");

    console.info("Badezimmer Timer trace 2");

    function turnOff() {
      items.SetPointTemperatire.sendCommand("13");
      console.info("Badezimmer Timer nach "+TimerMinutes+" Minuten beendet");
      items.getItem('Alexa_Sprachausgabe').postUpdate("Badezimmer heizen beendet");
      items["Alexa_Sprachausgabe_Say"].sendCommand("ON");
    }

    console.info("Badezimmer Timer trace 3");

    cache.private.put('timer2', ScriptExecution.createTimer(time.toZDT(items['Timer_Badezimmer'].quantityState), turnOff));

    console.info("Badezimmer Timer trace 4");

I’m assuming Timer_Badezimmer is a Number:Time. Note how you don’t Java.type anything. The above shows three different ways to get an Item.

Hi Rich,

many thanks for your detail explanation. In all what you assumed you are right.

I register that the defaul Unit is seconds so I add the Unit “min” to the Item and it works now.

For me - I’m not really have knowledge about Java and the different versions - it’s hard if such a change comes up with a release change. But I guess that all who have made thinkings about it that there is a good reason to do it :slight_smile: So in result I’m very happy that member like you helps us to have a progress and understand more about the platform.

Many thanks to all other who have give me hints what I can do and how to solve it. Udo’s way was easiest way and it works too.

What I learned again is that there are a lot of ways to do the same things :slight_smile:

Thanks to all again and best regards,
Chris

It’s not Java, it’s JavaScript. And it’s not a “release change”. It’s a wholly new automation add-on. It’s like going from Rules DSL to jRuby.

You don’t have to use the new JS Scripting. You can use the old Nashorn JavaScript. It’s a separate add-on though and it’s mainly there for legacy support. It should be considered deprecated. It only supports a version of the JS language that is nearly a decade old and there is no built in heloper library. It’ll take you roughly 50% more lines of code to do the same thing.

When rewriting your rules to use the new add-on, make sure you have a tab open with the docs for the new add-on. Every line of the rule should go through the following process:

  1. Look at the relevant part of the docs for the line
  2. Is there a new way to do the same thing? If not move to the next line.
  3. Use the docs to figure out how to do it the new way.

For example:

var logger          = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID); 

Looking at JavaScript Scripting - Automation | openHAB we see a different way to create a logger and looking at JavaScript Scripting - Automation | openHAB we see we don’t need to create the logger in the first place.

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

Looking at JavaScript Scripting - Automation | openHAB we see that it’s already imported as part of actions.

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

Looking at JavaScript Scripting - Automation | openHAB we see not only do we not need to import it but there are a bunch of additions and utilities.

var TimerMinutes    = 2

That’s pretty basic but looking at the examples shows similar syntax.

  events.sendCommand("SetPointTemperature", 13);

Looking at JavaScript Scripting - Automation | openHAB shows that we get the Item from items and sendCommand on that Item Object instead of using events.

And so on.

1 Like

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.