"event" is undefined in Script

  • Platform information:
    • Hardware: Old Intel box with lots of RAM
    • OS: Ubuntu. Linux 6.8.0-51.
    • OpenJDK Runtime Environment (build 17.0.15+6-Ubuntu-0ubuntu124.04)
      *JavaScript SCripting addon- latest off openhab. (listed as 4.3.5)
    • openHAB version: 4.3.5. Formerly 4.3.1- issue was the same there.

Everything here is done through the UI.

I’ve got a rule that triggers whenever a scene controller state is updated.
It then calls a script (ECMAScript 262 Edition 11).
And dies because it can’t get the state of the triggering item. I tracked it back to the item being updefined / null, and after reading lots of articles , more articles , and documentation.

The problem is that event is undefined.

I distilled the script down to a minimal example:

console.info(‘EventTestScript2 Triggered’);
console.info(event.thingUID + " was updated");
console.info("Event itemState is: ", event.itemState)
//console.info(event.itemState.toString() == “test”) //toString fails because unknown has no such property
console.info("ItemName is: ", event.itemName);
//console.info("Triggering Item’s state is: ", triggeringItem.state);
console.info(‘State of triggering item should have been displayed’)

When this gets triggered, the logs show:

20:56:04.571 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item ‘My_scene_controller_Scene_Number’ changed from 3.3 to 4.0
20:56:04.574 [INFO ] [enhab.automation.script.ui.EventTest2] - EventTestScript2 Triggered
20:56:04.575 [INFO ] [enhab.automation.script.ui.EventTest2] - undefined was updated
20:56:04.577 [INFO ] [enhab.automation.script.ui.EventTest2] - Event itemState is: undefined
20:56:04.579 [INFO ] [enhab.automation.script.ui.EventTest2] - ItemName is: undefined
20:56:04.580 [INFO ] [enhab.automation.script.ui.EventTest2] - State of triggering item should have been displayed

This topic seemed close, but it wasn’t an undefined problem and the code there is embedded in the rule, not in a script called by the rule.

The documentation doesn’t mention anything special needing to be done if it’s in a script.

What am I missing?

Thanks

If you use ECMAScript you can pass parameters to another rule:
data is the name that gets passed to the other rule and gridpower is the value that is passed.

//run the rule to send to wled sends parameters data and colour
var parameter = { 'data': gridpower,'colour': colour };
rules.runRule('wled_rule', parameter);

Rule wled below:

PAYLOAD="{\"seg\":[{\"col\":[[" + colour + "]],\"n\":\"" + data + "\"}]}"

items.getItem("Wled_matrix_Wled_text").sendCommand(PAYLOAD)

Basically the first rule runs a script called wled and wled gets the parameters.

As @ubeaut describes, you can pass data into the rule you call from the other rule.

However, nothing gets passed automatically. So unless you explicitly pass that event to the called rule, event won’t exist there.

However, this smells of an XY Problem. If you are using JS Scripting, which you are, there’s a limitation. No rule or script can be accessed from more than one thread at a time. There are all sorts of locks in place to prevent this from happening for a single rule. But there are no locks in place for rule called from other rules (note a Script is just a rule consisting of a single Script Action and no triggers or conditions).

The problem is there’s no way to prevent two separate rules from trying to call the same “script” at the same time. When that does happen, you’ll get a Multi-threaded exception in the log and the call will fail.

In short, UI Scripts are not a replacement for libraries. If you have shared code you want to be able to call from multiple different rules, you should use a personal library and not Scripts.

Hopefully sometime soon, rule templates will also be an option. I think the PR just needs to be reviewed.

So how does one pass the event into the first script, in the UI? I haven’t seen any way to include a parameter- all the UI provides is a checkbox.

Should the docs be updated from saying:
" Note that event object is only available when the UI based rule was triggered by an event and is not manually run!"
to:
“Note that event object is only available when the UI based rule was triggered by an event and the object is explicitly passed into the script- not if the script is manually run!”

You can’t. Basic UI rules are basic. Pretty much anything that isn’t super straight forward is going to require an inline script.

That’s a great first contribution opportunity. There’s a link at the bottom of the page called “edit this page on GitHub” which will take you straight to where you need to be to create a PR. See Contributing to documentation for some screen shots of what that looks like.

This is what I’d begun to suspect.

So what’s the difference between a “Basic UI rule” and a “UI Rule” or “UI Script”. I don’t recall seeing any distinctions in the documentation.

Rules overview (parts of a rule and some discussion on how to create them in various ways):

Basic UI Rule:

UI Rule (aka Managed Rule):

Script:

Ok. I’d interpreted that rules doc as “simple rules” and “Complex rules”, not that they were structurally different.

Moving it into an inline script (Interestingly, the only places “inline script” appears in the docs talks about blockley) got the event stuff working. Some additional debugging into the guts of my real script and now it’s working.

Now it’s go look at making it into a library, and dusting off GitHub info to look at the doc suggestions.

Thanks for the help.

BTW- the ultimate objective was a way to handle 4 scene controllers in the garage. It just looks up the scene state in a dictionary to know which of 15 switches to toggle.

I even put notes in my rules:

//NOTE: You can oly test this with an event. You cannot just run it from here as there is no event

:grinning_face:

Look at Scenes. You can set the states you want all the switches to be in and call that from a rule or rules. Note that the multithreaded issue does not exist here so it’s possible to have more than one rule call the same scene without risk of collisions.

I’d found that already. The problem was even when it was triggered by an event, the event object didn’t get passed into the secondary script.

I’ll go through that again, but my understanding is scenes are what comes after this particular code- they’d replace what is currently my “toggle” function. This is more for an easy way to manage which scene to call from a scene controller that can do up 35 different scenes. “What scene did the user want?”. Eventually some will be toggles, some will be more complex scenes like “turn off all the lights in the garage, and the dust collector, and the air compressor.”

Rather than having 16 rules, or one nasty complex rule full of if/else, it’s using a lookup table instead of a case statement. By using a dict, I’ll be able to reuse it elsewhere if I want once I turn it into a function- just pass in the dictionary. I just installed the next batch of controllers this weekend so I don’t have them in a group yet, but I believe I’ll be able to put them into a group and then since they all work the same I can handle the entire group with one dict+function.

The Scenes would replace your dict. You’d just call the Scene and all the commands defined in the scene will go out to the Items. The commands can be what ever the Items involved require: ON/OFF, numbers, Strings, etc.

For example:

YAML version:

items:
  FrontPorchLight: ON
  PatioLights_Switch: ON
  FamilyRoomOutlet: ON
  PeanutPlug4_Switch: ON
  FrontRoomOutlet: ON
  FamilyRoomLampShelves: ON
triggers: []
conditions: []

This only shows Switches because that’s all I use in my Scenes right now. But, for example, if I wanted to change the thermostat setpoint as part of the scene I’d add it and use a number.

Thermostat_HeatingSetpoint: 72 °F

A Color Item would take an HSV.

MyColorLight: 200,45,50

Then I just call the scene from another rule.

YAML version of the rule:

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: TimeOfDay
      state: AFTERNOON
    type: core.ItemStateChangeTrigger
conditions: []
actions:
  - id: "2"
    configuration:
      ruleUIDs:
        - lights_afternoon
    type: core.RunRuleAction

Notice how it’s a simple rule. It just calls the scene when TimeOfDay changes to AFTERNOON.

That’s what Scenes are for. It’s your lookup table. And once they are in the Scenes you don’t need any code to call them beyond calling the rule.

And you can cheat and add triggers and conditions to the Scenes manually on the code tab if you don’t want a separate rule to call them. You just won’t get the nice GUI to build them. The actual YAML code for my Scene above is

items:
  FrontPorchLight: ON
  PatioLights_Switch: ON
  FamilyRoomOutlet: ON
  PeanutPlug4_Switch: ON
  FrontRoomOutlet: ON
  FamilyRoomLampShelves: ON
triggers:
  - id: "1"
    configuration:
      itemName: TimeOfDay
      state: AFTERNOON
    type: core.ItemStateChangeTrigger
conditions: []

Agreed. What I don’t see in the docs or your example is a scene handling:
-when button 1 is pressed once, call scene J.
-when button 1 is pressed twice, call scene K. (toggle North outlet)
-when button 2 is pressed once, call scene L
-when button 2 is pressed twice call scene M
-when button 2 is pressed 3 times, call scene N
.
.
.
-when button 5 is held, call scene Z. (toggle all the lights)
This list could be 35 entries long with the <a href=Zooz 800 Series Z-Wave Long Range Scene Controller Switch ZEN32 800LR - The Smartest House> Scene Controller

As you said “just call the scene from another rule”. ← that’s where the dict comes in.

To handle the currently defined 16 scenes, I’d need 16 rules (or one rule with a lot of conditionals, or a lot of conditionals in my scene code), calling a bunch of scenes.

Unless there’s something I’ve missed- which is entirely possible. I’ve used OpenHab for quite a while but only simple things like turning on/off the christmas lights with some random variation- so there’s a lot I haven’t played with.

Or is your concern about the multithread concerns of handling complex stuff in rules, which you pointed out scenes avoid? That concern is a great point I was unaware of, and is in the future enhancement pile along with handling scenes instead of just toggling items.

// use states as keys , items as values (for now- later we might add scenes as values) ItemDict = {'1.0': null, '1.1': null, '1.2': null, '1.3': 'outlet1', '1.4': 'outlet2', '1.5': null, '1.6': null, '2.0': null, '2.1': null, '2.2': null, '2.3': 'outlet3', '2.4': 'Outlet4', '2.5': null, '2.6': null, '3': null, '3.1': null, '3.2': 'Switch 2', '3.3': 'outlet5', '3.4': null, '3.5': null, '3.6': null, '4.0': 'Relay1', '4.1': null, '4.2': null, '4.3': null, '4.4': 'switch4', '4.5': 'outlet6', '4.6': null, '5': null, '5.1': null, '5.2': null, '5.3': null, '5.4': null, '5.5': null, '5.6': null};

if (event.itemName == null) { // not strictly necessary - added when debugging, kept for robustness
} else {
SceneControllerState_string = items.getItem(event.itemName).state; // will this break when the controllers are in a group? triggeringItem.state didn’t get passed in currently, which is why we dig the state out of the item.
}
TargetItemName = ItemDict[SceneControllerState_string];

if (TargetItemName != null) {
TargetItem=items.getItem(TargetItemName);
ToggleItem(TargetItem); // add stuff here in the future to handle scenes
}

Regards

The scene doesn’t really cover that part. The scenes are what to do, not when to do it. For the when to do it you need to set the rule triggers.

There are ways to be clever to have one rule without a bunch of conditionals.

Yes, scenes do not use JS Scripting so there is no multi-threaded problems like there is with multiple rules calling the same JS Scripting Script (i.e. a rule defined under Settings → Scripts).

What I don’t know is how these events come across. Are they Item updates? Event Channel triggers? Something else? Because of that lack I can’t really give you a concrete example to go off of.

But using scenes there are the following general options. In all these cases each scene (i.e. the commands to send to each Item in the scene) is defined in Settings → Scenes.

  1. Create one Rule (Settings → Rules) per Scene. Here is where you define the “when button 1 is pressed once” which is the trigger if this rule. Without more details I cannot tell you what that trigger needs to be. The action is a simple “call rule” with the proper scene as the rule to call. This is the most straight forward, but it results in the largest number of rules with at least two per scene.

  2. Instead of creating a separate rule to control when the scene triggers, manually add the triggers to the Scene from the code tab of the scene. To figure out the proper syntax, create a Rule (Settings → Rules), add an example trigger, and then copy the YAML for that trigger from the code tab. Paste/edit it in the code tab for the corresponding Scene. This approach avoids needing 2 rules per Scene, but it requires manually messing with the code of the Scenes. This is the approach I use.

  3. Create one rule (Settings → Rules) which is triggered by all the button press events. Inside the rule, use what triggered it to create the UID of the Scene to call. But for this to work, when you create your Scenes, you need to create them with a UID that can be calculated from what ever triggered the rule. This is very similar to Design Pattern: Associated Items only we are calculating the UID of the Scene instead of the name of an Item.

Creating the scenes is easy and straight forward. But because I don’t know how these button press events come through I can only wave my hands at what the triggers would be. Because these are button presses I would assume they are Channel events and not Items. If that is case I’d need to know what event.event looks like to craft the rule trigger.


Based on your code though it looks like it’s an Item. If that’s the case, option 1 is pretty straight forward. The Rule trigger will be an Item Updated trigger for with Item SceneControllerState and state 1.3 (for example). The action will be a rule action to call the corresponding scene.

It would look like this only this is using my Item names instead of yours. The UI only lets you select existing Items and I don’t want to create a bunch of example Items for these screenshots. You’d select your SceneControllerState Item and put what ever you are using as the key in your dict.

Then add a simple rule action to call the corresponding scene.

The YAML for the final rule looks like this:

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: TimeOfDay
      state: AFTERNOON
    type: core.ItemStateUpdateTrigger
conditions: []
actions:
  - id: "2"
    configuration:
      ruleUIDs:
        - lights_afternoon
    type: core.RunRuleAction

Repeat for each scene.

One advantage of this approach is it’s easy to add triggers from other sources (e.g. a presence simulation rule, Astro sunrise/sunset events, motion events, etc.). It’s also easy to add conditions (e.g. don’t allow the rule to run when you are not home or it’s between 22:00 and 06:00).


Option 2 will use the YAML I pasted in above. Copy the triggers section. Then navigate to the Scene. Open the code tab and replace the triggers: [] line with what you’ve copied.

Before:

items:
  FrontPorchLight: ON
  PatioLights_Switch: ON
  FamilyRoomOutlet: ON
  PeanutPlug4_Switch: ON
  FrontRoomOutlet: ON
  FamilyRoomLampShelves: ON
triggers: []
conditions: []

After:

items:
  FrontPorchLight: ON
  PatioLights_Switch: ON
  FamilyRoomOutlet: ON
  PeanutPlug4_Switch: ON
  FrontRoomOutlet: ON
  FamilyRoomLampShelves: ON
triggers:
  - id: "1"
    configuration:
      itemName: TimeOfDay
      state: AFTERNOON
    type: core.ItemStateUpdateTrigger
conditions: []

Repeat for the rest of your scenes, only change the “state” part of the trigger to the state SceneControllerState should update to in order to cause that scene to run.

Obviously you can add additional triggers and add conditions like described above, but you’ll have to look up/experiment to get the right YAML for each.


For option 3, you’d careful set the UIDs of your Scenes to correspond with state that should trigger it. It looks like it follows the pattern “Number”.“Number”. so, for example, we’d use as the UID for the scene that corresponds to 1.3 might be something like “garage1_3”. If that’s the case your JS rule would trigger on any update to SceneControllerState and the code would be something like:

var sceneID = "garage"+event.itemState.replace('.', '_');
rules.runRule(sceneID, {}, true);

The logic gets much more complicated if you have more triggers though and if you have conditions you’ll want to put those on the Scene as described in option 2.


Some notes about your code as written:

Please use code fences for code and logs:

```
code goes here
```

The current approach can handle only one Item per scene. Your stated your goal is to handle multiple Items per scene. You can do that in your map using an array for each key instead of just a String. Then you need to loop through the array and command each Item. But you also need to add what to command the Item to, so the array needs to be an array of arrays.

var ItemDict = { '1.0': [ ['outlet1', 'ON'], ['outlet2, 'OFF']'], '1.1'...
...
targetItemTuples.forEach( i => items[i[0]].sendCommand(i[1]))

You should define variables using var, let, or const. However, unless you are between { } only var can be used.

var ItemDict = ...

Because SceneControllerState_string is defined between { }, it only exists between the { } meaning it won’t exist later on when you try to use it outside of the else clause.

Per the docs, it’s event.itemName. The state is event.itemState so you don’t really need to pull it from the Item itself and in fact it’s better not to as the Item may have already changed state.

But I hope you can see that in the long run, even option 1 above with 32 separate but very simple rules will be easier to maintain than one really complicated rule with a dit of arrays or arrays and all sorts of logic to define the scenes.