Passing values to a UI Script

Hello,

Using the UI, I have created a Script based on the Javascript language (application/javascript;version=ECMAScript-2021) that calls a webservice API. This works just fine when I press the test button, but now I want to move to the real use case which is this:

  • Call this script from multiple rules, sending a string parameter to be used by the JS code.

Reading this message, I understand that I actually created a special kind of rule, and that it can be called from other rules. The message also says this:

It’s even possible to pass stuff to the called rule

I tried looking for examples as to how to do that, but I could not find any obvious one among the many rule definitions that I have found.

As a pseudo code, what I would like to write is this:

If itemXX changed to OPEN then call NotificationSender(ItemXX.name + " is now opened!");

Any pointers, suggestions are mot welcome.

For reference this is the version I’m using:

runtimeInfo:
  version: 3.2.0
  buildString: Release Build
locale: en-FR
systemInfo:
  javaVersion: 11.0.13
  javaVendor: Oracle Corporation
  osName: Linux
  osVersion: 5.4.164-1-MANJARO
  osArchitecture: amd64

Just to be clear, any rule can be called from another rule.

See Some JS Scripting UI Rules Examples - #14 by rlkoshak which shows an example of calling a rule from another rule.

However, it’s also perfectly reasonable to use a String Item to trigger your other rule. Another approach is to use a library function.

Ah! That’s what I was missing, how to pass a value and how to extract it from the context. Thanks, that’s really nice of you to add to the documentation, even if it’s harder to find it this way.

I figured that one too, but I very much prefer to have everything in the same interface.

Ah, yes indeed. But what about fast changes? I mean, if that String item gets its value changed twice in a very short time, will the trigger be called twice, even if a bit delayed?

Much of what I’ve written as tutorials will be added to some sort of documentation. I’m still in learning mode and not yet ready to start writing docs which will codify “best practices”.

Yes. If the rule is already running, subsequent triggers will queue up and be run through the rule in sequence.

This should be the same behavior whether you call the rule from a rule, or command a String Item. Only one instance of a given rule can run at a given time.

Thanks, that clarifies the situation. But I have another related question coming up from the way I wrote the JS code:

const item = items.getItem('StringItem');

if (item.state == 'ON')
  DoSomething;

What is the value of item.state with the following timeline?

Clearly, the two rules “run” are queued, which is fine. But will item.state still be ON when read by the first run of the rule script? Or will it already be set to OFF?
I have a feeling it’s the later but can’t be sure.

Reading around I see that there is an always declared object called event that contains information about what triggered the rule.
But I could not find documentation about the properties it contains, maybe there is the state value as it has changed to trigger the rule inside those.

The Item state is ‘live’, you will read whatever it is at that moment.

If you want to capture the state as of when the rule begins, you can do so in a local variable.

For a rule triggered by command or change of the Item in question, the system provides an ‘event’ object already capturing this.

You’re thinking of important stuff here - dealing with fast-changing happenings in openHABs event-driven world require careful understanding of the mechanisms behind. :wink:

Thanks, that confirms my feeling about how things works in openHAB

That’s the one I found about reading around here. But I can’t find a definitive list of what properties it offers, especially the name of the one that contains the new “State” that triggered the rule.

Well, it’s becoming a habit from my job, where I develop heavily multithreaded applications with dozens of events coming up in under a millisecond.

I think that depends on the rules language and the support library you use, I can’t help for JS

Well, there is a somewhat outdated documentation here: Event Object Attributes — openHAB Helper Libraries documentation

It allowed me to see that there itemState when the state has changed, but contrary to item.state, it’s directly the object so one has to use toString() to get the same result.

In the end, here is what I did:

  • Manually create an item, of String type, called MessageToSend
  • Write a rule that reacts to a change of state on that item:
configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: MessageToSend
    type: core.ItemStateChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript;version=ECMAScript-2021
      script: >-
        (function(){
          const item = items.getItem('MessageToSend');
          if (item)
          {
            // if we were not triggered by an event, use the current item state
            let message = '';
            if (event)
            {
              if (event.itemState)
                message = event.itemState.toString();
            }
            else
            {
              message = item.state;
            }

            // Do not send empty messages
            if (message && message !== '')
            {
              const data = {
                msg: message
              }

              actions.HTTP.sendHttpPostRequest('https://remote.server.com/notify', 'application/json', JSON.stringify(data));

              // Reset state to empty string to allow sending the same message twice
              // if it is required by whoever changed MessageToSend
              item.sendCommand('');
            }
          }
        })();
    type: script.ScriptAction
  • And finally, in all rules that need to send that notification, I use sendCommand as well:
configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: DoorContactA4C13816386E5EF9_ContactPortal1
      state: OPEN
      previousState: CLOSED
    type: core.ItemStateChangeTrigger
conditions:
  - inputs: {}
    id: "3"
    configuration:
      itemName: AlarmActivated
      state: ON
      operator: =
    type: core.ItemStateCondition
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript;version=ECMAScript-2021
      script: |-
        (function(){
          const messageItem = items.getItem('MessageToSend');
          const eventItem = items.getItem(event.itemName);
          
          messageItem.sendCommand(eventItem.label + ' is opened!');
        }
        )();
    type: script.ScriptAction

One may notice the weird (function(){ })(); construct but as I discovered in other messages, this is required to limit the scope of declared variables (const, let, var) to the current script. Else they are global and stay there for every invocation. This would trigger a “variable already declared” error, preventing the script from running a second time.

Thanks for your answers and suggestions, I hope this discussion helps others along the way.

Note that for now at least, this will cause problems in the UI. let and const cannot be used at the global level of a UI Script because the script gets reused on every run. The script will run fine the first time and then fail saying that item already exists or you cannot assign to a const or something like that.

It’s a known problem but the solution is not straight forward and will require changes in core. So for now you’ll have to use var or, as you did below, wrap your whole script in an immediately called anonymous function.

There is some weirdness about event right now with some differences between file based rules and UI based rules that I don’t fully understand yet. The file based rules event seems to be different from JSR223.

The most comprehensive docs I’ve seen for what event might contain (it depends on how the rule was triggered, and for manually, system, and cron triggered rules there isn’t even an event Object at all) is located in the old Helper Libraries docs which you already found.

But you can also experiment and see for yourself what’s there. Add console.log(event); to your rule and it will log out all the properties of the Object.

Updates too.

event.itemState, at least in UI rules. In file based rules I think the names match the Rules DSL implicit variable names.

Here is a place where differences between UI rules and file based rules have a gap. One of the biggest problems users have in OH is dealing with type, and in particular dealing with the fact that some types will be Java and other types will be native to the language and sometimes those types are compatible and other times they are not.

In an attempt to solve this openhab-js does what it can to abstract and hide all the Java stuff so that you only have to worry about pure JS types in your scripts. So items.getItem('MyItem'); returns a JavaScript Item Object that wraps the raw Java Item Object and presents it’s fields and returns it’s values as pure JavaScript types. For example, items.getItem('MyGroup').members will return a JavaScript Array of JavaScript Item Objects.

However there are a few places where there are gaps. The event Object is one of those places. In the UI, that’s injected into the rule by openHAB core before openhab-js would have a change to wrap it and abstract it so you can get pure JavaScript. So event.itemState is going to be the Java State Object.

Note that you can get at the Java state Object using items.getItem('MyItem').rawState which is particularly useful when dealing with QuantityTypes and stuff like that. The original developer of openhab-js decided that it was easier to just deal with strings all the time than mess with all these Java Classes so .state returns the toString() of the Item’s state (except for QuantityTypes where it strips off the units before returning the string :frowning: ).

If you change the rule to trigger on updates I think you can skip this which will prevent the rule from triggering again on this reset. This is an unbound Item so it’s not going to be updated periodically on a polling period or something like that.

Alternatively you can add a Condition to the rule to prevent it from running when the message is blank. The rule would still trigger but the script action wouldn’t be run if the condition is false.

1 Like

It’s not the reset that I’m trying to workaround, but rather a situation like this:

  1. Another rule changes StringItem from whatever it was to Some message
  2. The change is detected and the notification is sent
  3. Yet another rule sends a command to StringItem with the same value that it had before: Some message

In that case, I could not find a way to trigger the notification rule again. Hence my “reset” to an empty string that you have singled out.
But that is not perfect: if steps 2 and 3 are reversed because the second change occurred very rapidly, the notification will not be sent.

Well, being able to calling a rule from another rule would solve this, but the code for this should really be much simpler, especially as I may have to duplicate it in various places.

In the end, as much as I’d like to avoid it, using a library function would be much easier and less convoluted to use.

Trigger on received update. That will trigger the rule even if the Item’s state didn’t change.

There are three types of events.

  • commands: an event indicating that the “device” linked to the Item was asked to do something; commands may or may not result in a change or an update
  • changed: an event indicating that the Item has changed state, a changed event will also have a corresponding update event
  • updated: an event indicating that the Item has been “touched” whether or not the Item changed state.

So when you sendCommand to the MessageToSend Item there is a little service called “autoupdate” which sees that command and updates the Item.

The sequence of events for a command resulting in a change would be:

  1. command
  2. changed
  3. updated

2 and 3 occur at the same time.

If the Item doesn’t change as a result of the command you get

  1. command
  2. updated

Thus, in either case, a received update rule will trigger even when it didn’t change state. If you change your rule trigger to received update, you don’t need to reset the Item. It will trigger even when the Item is commanded to the same state.

It’s 4 lines of code which you can put into a library. That’s what libraries are for.

You are definitely right, I don’t know why I over complicated things.

That’s right, but if I go down the library path, I might as well create a “SendNotification” method that receives the message directly instead of going through dummy items and rules.

In the end, with a “item updated” trigger, I don’t need to reset to an empty string, and I don’t get the concurrency issue that I mentioned. So all is good for me now!

Thanks a lot for the help along the way

Oh, and just for completeness, you could also trigger the rule on receivedCommand and use event.itemCommand. Then it doesn’t even matter what the Item is doing. It’s just a channel to trigger the rule.

2 Likes

In a situation like this I use command and receivedCommand for the action - this fits the openHAB paradigm that commands are “do something” instructions.
In OH2 that took care of concurrency issues too, although it’s less relevant now.

The Item state is essentially irrelevant then. Disable autoupdate and it is completley disassociated. Use it for some other purpose altogether, like "OK"if it worked, or “last message timestamp”,whatever.